赞
踩
本章内容包括:
在本章中,我们将介绍编写资源管理器需要了解的内容。
资源管理器只是具有某些定义明确的特征的程序。该程序在不同的操作系统上被称为不同的事物,有人称它们为“设备驱动程序”,“ I / O管理器”,“文件系统”,“驱动程序”,“设备”,等等。但是,在所有情况下,该程序(我们将其简称为资源管理器)的目标都是提供某些服务的抽象视图。
另外,由于Neutrino是符合POSIX的操作系统,因此可以证明抽象是基于POSIX规范的。
在不为所动之前,让我们看几个例子,看看它们如何“抽象”一些“服务”。我们将看一看实际的硬件(串行端口)和更抽象的东西(文件系统)。
在典型的系统上,程序通常存在某种方式来从串行RS-232样式的硬件接口发送输出和接收输入。该硬件接口由一堆硬件设备,包括一个中的UART(û niversal 甲同步ř eceiver Ť变送器),其知道如何在CPU的并行数据流转换为串行数据流,反之亦然芯片。
在这种情况下,串行资源管理器提供的“服务”是程序在串行端口上发送和接收字符的能力。
我们说发生了“抽象”,因为客户端程序(最终使用该服务的程序)不知道(也不关心)UART芯片及其实现的细节。客户程序只知道发送一些字符应该调用fprintf()函数,接收一些字符应该调用fgets()函数。注意,我们使用了标准的POSIX函数调用来与串行端口交互。
作为资源管理器的另一个示例,让我们检查文件系统。它由许多协作模块组成:文件系统本身,块I / O驱动程序和磁盘驱动程序。
这里提供的“服务”是程序在某种介质上读写字符的能力。发生的“抽象”与上面的串行端口示例相同–客户端程序仍可以使用完全相同的函数调用(例如fprintf()和fgets() 函数)与存储介质而不是存储介质进行交互。串行端口。实际上,客户端确实不知道或不需要知道与之交互的资源管理器。
正如我们在上面的示例中看到的那样,资源管理器灵活性的关键在于,可以使用标准的POSIX函数调用来访问资源管理器的所有功能-在与串行通讯时,我们没有使用“特殊”功能港口。但是,如果你有什么需要做一些“特殊”的东西非常 装置的具体情况?例如,在串行端口上设置波特率是一项非常特定于串行端口资源管理器的操作,而对于文件系统资源管理器则完全没有意义。同样,通过lseek()设置文件位置在文件系统中很有用,但在串行端口中则毫无意义。POSIX为此选择的解决方案很简单。一些函数,例如 lseek(),只需在不支持它们的设备上返回错误代码。然后是称为devctl()的 “ 包罗万象 ”设备控制功能,该功能允许在POSIX框架内提供特定于设备的功能。不了解特定 *devctl()命令的设备只会返回一个错误,就像不了解lseek()*命令的设备一样。
由于我们已经将lseek()和devctl()称为两个常用命令,因此值得注意的是,资源管理器几乎支持所有文件描述符(或FILE *
流)函数调用。
这自然使我们得出一个结论,即资源管理器将几乎只处理基于文件描述符的函数调用。由于Neutrino是消息传递操作系统,因此可以将POSIX函数转换为消息,然后将其发送给资源管理器。正是这种“从POSIX函数传递消息的功能”翻译技巧使我们能够将客户端与资源管理器分离。资源管理器要做的就是处理某些定义明确的消息。客户端所需要做的就是生成资源管理器希望接收和处理的相同定义明确的消息。
我们已经看到了客户期望的暗示。它期望使用标准POSIX函数的基于文件描述符的接口。
不过,实际上,“幕后”还有更多事情要做。
例如,客户端实际上如何连接到适当的资源管理器?对于联合文件系统(多个文件系统负责同一个“命名空间”)会发生什么情况?如何处理目录?
客户端要做的第一件事是调用open() 以获取文件描述符。(请注意,如果客户端调用了更高级别的函数fopen(),则同样的讨论适用— fopen()最终将调用open())。
在open()的C库实现内部,构造了一条消息,并将其发送到流程管理器(procnto
)组件。流程管理器负责维护有关路径名空间的信息。该信息由一个树结构组成,该树结构包含路径名和节点描述符,进程ID,通道ID和句柄关联:
Neutrino的名称空间。
请注意,在上图中以及下面的描述中,我已将名称fs-qnx4 用作实现QNX 4文件系统的资源管理器的名称-实际上,这有点复杂,因为文件系统驱动程序基于a捆绑在一起的一系列DLL。因此,实际上没有可执行文件称为fs-qnx4 ;我们只是将其用作文件系统组件的占位符。 | |
---|---|
假设客户端调用open():
fd = open ("/dev/ser1", O_WRONLY);
在客户端的*open()*的C库实现中,将构造一条消息并将其发送到流程管理器。此消息指出:“我要打开/dev/ser1
;我应该和谁说话?”
名字解析的第一阶段。
流程管理器接收到请求,并查看其树结构以查看是否存在匹配项(现在假设我们需要一个完全匹配项)。当然,路径名“ /dev/ser1
”与请求匹配,并且流程管理器能够答复客户端:“我找到了/dev/ser1
。正在由节点描述符0,进程ID 44,通道ID 1,句柄1处理。向您发送您的请求!”
记住,我们仍然在客户端的*open()*代码中!
因此,*open()函数会创建另一条消息,并连接到指定的节点描述符(0,表示我们的节点),进程ID(44),通道ID(1),并将句柄塞入消息本身。该消息实际上是“连接”消息,它是客户端的open()消息。*库用于建立与资源管理器的连接(下图中的步骤3)。资源管理器收到连接消息后,将对其进行查看并执行验证。例如,您可能试图打开一个实现只读文件系统的资源管理器,以进行写操作,在这种情况下,您将获得一个错误(在此情况下为EROFS)。但是,在我们的示例中,串行端口资源管理器查看该请求(我们指定了O_WRONLY;对于串行端口完全合法),并以EOK进行回复(下图中的步骤4)。
_IO_CONNECT消息。
最后,客户端的*open()*返回带有有效文件描述符的客户端。
确实,此文件描述符是我们刚刚用来向资源管理器发送连接消息的连接ID!如果资源管理器没有 给我们一个EOK,我们将把这个错误传递回客户端(通过errno和open()的-1返回)。(值得注意的是,进程管理器可以响应一个名称解析请求返回一个以上资源管理器的节点ID,进程ID和通道ID 。在这种情况下,客户端将依次尝试其中的每个,直到成功为止,返回的错误不是ENOSYS,ENOENT或EROFS,否则客户端将耗尽列表,在这种情况下,*open()*失败。我们稍后将讨论“ before”和“ after”标志时将对此进行进一步讨论。)
现在,我们了解了找到特定资源管理器的基本步骤,我们需要解决“如何找到流程管理器开始”之谜。其实,这很容易。根据定义,进程管理器的节点描述符为0(表示此节点),进程ID为1,通道ID为1。因此,ND / PID / CHID三元组0/1/1始终标识进程管理器。
我们上面使用的示例是串行端口资源管理器的示例。我们还提出了一个假设:“现在假设我们需要完全匹配。” 这个假设只有一半是正确的-我们将在本章中讨论的所有路径名匹配都必须完全匹配该路径名的一个组成部分,但不一定必须匹配 整个路径名。我们会尽快解决。
假设我有执行此操作的代码:
fp = fopen ("/etc/passwd", "r");
回想一下fopen()最终会调用open(),因此我们让*open()*询问路径名/etc/passwd
。但是图中没有一个:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hzz23dza-1607647576680)(…/…/…/…/…/…/…/…/pic/image-20200725143953378.png)]
Neutrino的名称空间。
但是,我们确实注意到,它fs-qnx4
已将ND / PID / CHID的关联注册在路径名“ /
。” 处。尽管未在图中显示,但已将fs-qnx4
自己注册为目录资源管理器 -它告诉流程管理器它将负责“ /
” 及以下内容。这是其他“设备”资源管理器(例如,串行端口资源管理器)所没有做的。通过设置“目录”标志,fs-qnx4
由于请求/etc/passwd
的第一部分是“ /
”(匹配组件),因此能够处理对“ ”的请求!
如果我们尝试执行以下操作怎么办?
fd = open ("/dev/ser1/9600.8.1.n", O_WRONLY);
好吧,由于串行端口资源管理器没有设置目录标志,因此进程管理器将查看它并说“不,对不起,路径名/dev/ser1
不是目录。我将不得不拒绝此请求。” 此刻请求就此失败了–进程管理器甚至没有返回*open()*函数应尝试的ND / PID / CHID /句柄。
显然,正如我在上面的open()调用参数选择中所暗示的那样,允许某些“传统”驱动程序以“常规”名称之外的其他参数打开可能是一个聪明的主意。但是,这里的经验法则是:“如果您可以在设计审查会议上摆脱它,那就把自己踢出去。” 我的一些学生在听完我的话后说:“但是我是设计审查委员会!” 我通常会回答:“您被赋予了枪支。用脚拍自己的脚。:-) ” | |
---|---|
仔细看看我们一直在使用的图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D8d6GGjq-1607647576682)(…/…/…/…/…/…/…/…/pic/image-20200725144004533.png)]
Neutrino的名称空间。
请注意如何既 fs-qnx4
与进程管理器已经登记自己作为负责“ /
”?很好,不用担心。实际上,有时候这是个好主意。让我们考虑一种这样的情况。
假设您的网络连接速度很慢,并且已在其上安装了网络文件系统。您会注意到您经常使用某些文件,并希望它们以某种方式神奇地“缓存”在您的系统上,但是可惜,网络文件系统的设计者没有为您提供这样做的方法。因此,您可以编写一个fs-cache
位于网络文件系统顶部的缓存文件系统(称为)。从客户的角度看,这是它的外观:
覆盖的文件系统。
这两种fs-nfs
(网络文件系统)和您的缓存文件系统(fs-cache
)已经注册了自己的前缀相同,即/nfs
“” 正如我们上面提到的,在Neutrino下,这是正常的,合法的行为。
假设系统刚刚启动,并且您的缓存文件系统还没有任何内容。假设某个客户端程序尝试打开文件/nfs/home/rk/abc.txt
。您的缓存文件系统位于网络文件系统的“前面”(稍后,当我们讨论资源管理器的实现时,我将向您展示如何执行此操作)。
此时,客户端的*open()*代码执行通常的步骤:
/nfs/home/rk/abc.txt
?”fs-cache
,然后交谈fs-nfs
。”注意这里,流程管理器返回了两组ND / PID / CHID /句柄。一为fs-cache
一fs-nfs
。这很关键。
现在,客户端的*open()*继续:
fs-cache
:“ /nfs/home/rk/abc.txt
请打开文件以供阅读。”fs-cache
:“抱歉,我从未听说过此文件。”此时,就资源管理器而言,客户端的*open()*函数很不走运fs-cache
。该文件不存在!但是,open()函数知道它得到了两个 ND / PID / CHID /处理元组的列表,因此接下来尝试第二个:
fs-nfs
:“ /nfs/home/rk/abc.txt
请打开文件以供阅读。”fs-nfs
:“当然,没问题!”现在,open()函数具有EOK(“没问题”),它返回文件描述符。然后,客户端与资源管理器执行所有进一步的交互fs-nfs
。
我们唯一解析为资源管理器的时间是在*open()*调用期间。这意味着一旦成功打开特定的资源管理器,我们将继续对所有文件描述符调用使用该资源管理器。 | |
---|---|
那么我们的fs-cache
缓存文件系统如何发挥作用?好吧,最终,我们假设用户已完成文件的读取(他们已将其加载到文本编辑器中)。现在他们想把它写出来。发生相同的一组步骤,但有趣的是:
/nfs/home/rk/abc.txt
?”fs-cache
,然后交谈fs-nfs
。”fs-cache
:“ 请打开文件/nfs/home/rk/abc.txt
进行写入。”fs-cache
:“当然,没问题。”请注意,这次,在第3步中,我们打开了文件以进行写入,而不是像以前一样进行读取。因此,这次fs-cache
允许操作(在步骤4中)也就不足为奇了。
更有趣的是,观察下一次我们下次阅读文件时会发生什么:
/nfs/home/rk/abc.txt
?”fs-cache
,然后交谈fs-nfs
。”fs-cache
:“ 请打开文件/nfs/home/rk/abc.txt
以供阅读。”fs-cache
:“当然,没问题。”确实,这次缓存文件系统处理了读取请求(在步骤4中)!
现在,我们省略了一些细节,但是这些对于理解基本概念并不重要。显然,缓存文件系统将需要某种方式通过网络将数据发送到“真实”存储介质。它还应该具有某种方法来验证在文件返回文件内容给客户端之前,没有其他人修改过该文件(这样客户端就不会获取过时的数据)。缓存文件系统可以通过 将第一次读取时的网络文件系统中的数据加载到其缓存中来处理第一次读取请求。等等。
我们已经完成了客户端的工作。以下是要记住的关键点:
FILE *
流)(例如read(), lseek(),fgets())。让我们从资源管理器的角度来看事情。基本上,资源管理器需要告诉流程管理器它将对路径名空间的特定部分负责(它需要注册 自己)。然后,资源管理器需要从客户端接收消息并进行处理。显然,事情并不是那么简单。
让我们快速浏览一下资源管理器提供的功能,然后再看详细信息。
资源管理器需要告诉流程管理器,一个或多个路径名现在在其权限范围内 -有效地,该特定资源管理器已准备好处理这些路径名的客户端请求。
串行端口资源管理器可以处理(比方说)四个串行端口。在这种情况下,它会注册四种不同的路径名与进程管理器:/dev/ser1
,/dev/ser2
,/dev/ser3
,和/dev/ser4
。这样做的影响是,流程管理器的路径名树中现在有四个不同的条目,每个串行端口一个。四个条目还不错。但是,如果串行端口资源管理器处理了其中一个带有256个端口的精美多端口卡,该怎么办?注册256个单独的路径名(即/dev/ser1
通过/dev/ser256
)将在流程管理器的路径名树中产生256个不同的条目!流程管理器并未针对搜索此树进行优化。它假定树中只有几个条目,而不是数百个。
通常,您不应在每个级别离散地注册多个路径名,这是因为执行了线性搜索。256端口注册肯定不止于此。在这种情况下,多端口串行资源管理器应该做的是注册一个目录样式的路径名,例如/dev/multiport
。这仅占流程管理器的路径名树中的一个条目。客户端打开串行端口时,假设端口57:
fp = fopen ("/dev/multiport/57", "w");
进程管理器将其解析为多端口串行资源管理器的ND / PID / CHID /句柄;由资源管理器决定其余路径名(在我们的示例中为“ 57”)是否有效。在此示例中,假设变量路径包含安装点之后的其余路径名,这意味着资源管理器可以以非常简单的方式进行检查:
devnum = atoi (path);
if ((devnum <= 0) || (devnum >= 256)) {
// bad device number specified
} else {
// good device number specified
}
这种搜索肯定比流程管理器可以做的任何事情都要快,因为根据设计,流程管理器必须比我们的资源管理器具有更多的通用性。
一旦注册了一个或多个路径名,就应该准备好接收来自客户端的消息。可以通过MsgReceive()函数调用以“常规”方式完成。资源管理器处理的定义好的消息类型少于30种。为了简化讨论和实施,将它们分为两类:
连接消息
始终包含路径名;这些要么是一次性消息,要么它们为其他I / O消息建立了上下文。
I / O消息
始终基于连接消息;这些执行进一步的工作。
连接消息始终包含路径名。在 整个讨论过程中一直使用的open()函数是生成连接消息的函数的完美示例。在这种情况下,连接消息的处理程序将为其他I / O消息建立上下文。(毕竟,我们希望在完成open()之后 执行诸如read()之类的事情)。
“一次性”连接消息的一个示例是由于rename()函数调用而生成的消息。没有进一步的“上下文”被建立-资源管理器中的处理程序应将指定文件的名称更改为新名称,仅此而已。
仅在连接消息之后才需要I / O消息,并且该I / O消息是指由该连接消息创建的上下文。正如上面在连接消息讨论中所提到的,*open()后跟read()*是一个完美的例子。
除了连接和I / O消息外,资源管理器还可以接收(和处理)“其他”消息。由于它们不是适当的“资源管理器”消息,因此我们将其讨论推迟到稍后。
在深入探讨围绕资源管理器的所有问题之前,我们必须熟悉QSS的资源管理器库。请注意,此“库”实际上由几个不同的部分组成:
尽管您当然可以 “从头开始”编写资源管理器(就像在QNX 4世界中所做的那样),但这要比花在麻烦上要麻烦得多。
为了向您展示库方法的实用性,以下是“ /dev/null
” 的单线程版本的来源:
/*
* resmgr1.c
*
* /dev/null using the resource manager library
*/
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <sys/iofunc.h>
#include <sys/dispatch.h>
int
main (int argc, char **argv)
{
dispatch_t *dpp;
resmgr_attr_t resmgr_attr;
dispatch_context_t *ctp;
resmgr_connect_funcs_t connect_func;
resmgr_io_funcs_t io_func;
iofunc_attr_t attr;
// create the dispatch structure if ((dpp = dispatch_create ()) == NULL) { perror ("Unable to dispatch_create\n"); exit (EXIT_FAILURE); } // initialize the various data structures memset (&resmgr_attr, 0, sizeof (resmgr_attr)); resmgr_attr.nparts_max = 1; resmgr_attr.msg_max_size = 2048; // bind default functions into the outcall tables iofunc_func_init (_RESMGR_CONNECT_NFUNCS, &connect_func, _RESMGR_IO_NFUNCS, &io_func); iofunc_attr_init (&attr, S_IFNAM | 0666, 0, 0); // establish a name in the pathname space if (resmgr_attach (dpp, &resmgr_attr, "/dev/mynull", _FTYPE_ANY, 0, &connect_func, &io_func, &attr) == -1) { perror ("Unable to resmgr_attach\n"); exit (EXIT_FAILURE); } ctp = dispatch_context_alloc (dpp); // wait here forever, handling messages while (1) { if ((ctp = dispatch_block (ctp)) == NULL) { perror ("Unable to dispatch_block\n"); exit (EXIT_FAILURE); } dispatch_handler (ctp); }
}
你有它!通过/dev/null
几个函数调用实现的完整资源管理器!
如果您要从头开始编写此代码,并使其支持该代码执行的所有功能(例如stat() 起作用,chown()和chmod()起作用等等),那么您会发现有数百种如果不是几千行的C代码。
通过对库的介绍,我们(简要地)看一下调用在/dev/null
资源管理器中的作用。
创建一个调度结构;这将用于阻止消息接收。
初始化设备使用的属性结构。稍后我们将更深入地讨论属性结构,但到目前为止,简短的故事是每个设备名称都有一个属性,并且它们包含有关特定设备的信息。
初始化两个数据结构cfuncs和ifuncs,它们分别包含指向connect和I / O函数的指针。您可能会争辩说,此调用具有最大的“魔力”,因为这是用于处理所有消息的实际“工人”例程绑定到数据结构中的地方。实际上,我们没有看到任何代码来处理连接消息,也没有看到由客户端*read()或stat()函数等产生的I / O消息。这是因为该库正在为我们提供这些函数的默认POSIX版本,并且是iofunc_func_init()*函数将这些相同的默认处理函数绑定到两个提供的表中。
创建资源管理器将用于接收消息的渠道,并与流程管理器对话以告知我们我们将负责“” /dev/null
。尽管有很多参数,但稍后我们将详细介绍它们。现在,需要特别注意的是这是调度句柄(dpp),路径名(字符串/dev/null
)以及connect(cfuncs)和I / O(ifuncs)消息处理程序都绑定在一起的地方。
分配调度内部上下文块。它包含与正在处理的消息有关的信息。
这是调度层的阻塞调用;在这里我们等待来自客户的消息。
消息从客户端到达后,将调用此函数进行处理。
您已经看到您的代码负责提供主要的消息接收循环:
while (1) {
// wait here for a message
if ((ctp = dispatch_block (ctp)) == NULL) {
perror ("Unable to dispatch_block\n");
exit (EXIT_FAILURE);
}
// handle the message
dispatch_handler (ctp);
}
这非常方便,因为它使您可以在接收函数上放置断点,并在操作过程中拦截消息(可能带有调试器)。
该库在dispatch_handler()函数内部实现了“魔术” 功能,因为通过我们前面提到的connect和I / O函数表可以对消息进行分析和处理。
实际上,该库由两个协作层组成:一个提供“原始”资源管理器功能的基础层,一个提供POSIX帮助程序和默认功能的POSIX层。我们将简要定义两层,然后在下面的 “资源管理器结构”中,选择详细信息。
最底层包含名称以*resmgr _ *()*开头的函数。此类功能与使资源管理器工作的机制有关。
我将简要介绍可用的功能以及在何处使用它们。然后,我将请您参考QSS的文档以获取有关这些功能的更多详细信息。
基本层功能包括:
resmgr_msgreadv()和 resmgr_msgread()
使用消息传递从客户端的地址空间读取数据。
resmgr_msgwritev()和 resmgr_msgwrite()
使用消息传递将数据写入客户端的地址空间。
通过连接功能关联上下文,以便以后可以由I / O功能使用。
创建一个通道,将路径名,调度句柄,连接函数,I / O函数和其他参数关联在一起。向过程管理器发送消息以注册路径名。
与*resmgr_attach()*相反;解除路径名和资源管理器的绑定。
将脉冲代码与功能关联。由于库实现了消息接收循环,因此这是“获得控制”以处理脉冲的便捷方式。
将脉冲代码与该功能分离。
除了上面列出的功能之外,还有许多函数处理调度接口。
上面列表中值得特别提及的一个函数是resmgr_open_bind()。当连接消息到达时(通常是由于客户端调用open()或fopen()的结果),它将关联某种形式的上下文数据,以便在处理I / O消息时此数据块就在周围。为什么我们在/dev/null
处理程序中看不到这个?因为POSIX层的默认函数为我们调用了此函数。如果我们自己处理所有消息,则肯定会调用此函数。
所述resmgr_open_bind()函数不仅建立用于进一步的I / O消息的上下文块,但也初始化由资源管理器库本身使用的其他数据结构。 | |
---|---|
上面列表中的其余功能有些直观-我们将推迟使用它们的讨论。
QSS的资源管理器库提供的第二层是POSIX层。与基础层一样,您可以在不使用资源管理器的情况下编写代码,但这将需要大量工作!在详细讨论POSIX层功能之前,我们需要查看一些基本层数据结构,来自客户端的消息以及资源管理器的总体结构和职责。
现在,我们已经介绍了基础知识—客户如何看待世界,资源管理器如何看待世界以及库中两个协作层的概述,是时候集中精力讨论细节了。
在本节中,我们将研究以下主题:
请记住以下“大图”,其中几乎包含与资源管理器相关的所有内容:
资源管理器的架构-全局。
我们需要了解的第一件事是用于控制库操作的数据结构:
库内部使用的一种数据结构:
稍后,我们将看到与POSIX层库一起使用的OCB,属性结构和安装结构数据类型。
resmgr_attr_t
控制结构控制结构(类型resmgr_attr_t
)传递给resmgr_attach()函数,该函数将资源管理器的路径放入常规路径名空间中,并将该路径上的请求绑定到调度句柄。
控制结构(来自<sys/dispatch.h>
)具有以下内容:
typedef struct _resmgr_attr {
unsigned flags;
unsigned nparts_max;
unsigned msg_max_size;
int (*other_func) (resmgr_context_t *ctp, void *msg);
} resmgr_attr_t;
通常,应避免使用此成员。该成员(如果为非NULL)表示一个例程,当库无法识别消息时,该例程将与资源管理器库收到的当前消息一起调用。尽管您可以使用它来实现“私有”或“自定义”消息,但不鼓励这种做法(使用_IO_DEVCTL或_IO_MSG处理程序,请参见下文)。如果您希望处理传入的脉冲,我建议您改用 pulse_attach()函数。
您应该将此成员的值保留为NULL。
这两个参数用于控制消息传递区域的各种大小。
所述nparts_max参数控制动态分配的大小IOV在所述资源管理器的库上下文块构件(类型的resmgr_context_t
,见下文)。如果您从某些处理函数返回的IOV不只一部分,则通常需要调整此成员。请注意,它对传入消息没有影响-仅用于传出 消息。
该msg_max_size参数控制资源管理库应该多少缓冲空间留作接收缓冲区的消息。资源管理器库将将此值设置为至少与它将接收的最大消息的标头一样大。这样可以确保在调用处理程序函数时,将传递消息的整个标头。但是请注意,即使msg_max_size参数“足够大” ,也不保证超出当前头的数据(如果有的话) 存在于缓冲区中。例如,当使用Qnet通过网络传输消息时。(有关缓冲区大小的更多详细信息,请参见下面的“ 内部上下文块”。)resmgr_context_t
此参数将其他信息提供给资源管理器库。为了我们的目的,我们只传递0。您可以在Neutrino库参考中的resmgr_attach()函数下阅读其他值 。
resmgr_connect_funcs_t
连接表当资源管理器库收到消息时,它将查看消息的类型,并查看它是否可以执行任何操作。在基础层中,有两个影响此行为的表。该resmgr_connect_funcs_t
表包含一个连接消息处理程序列表,而该resmgr_io_funcs_t
表包含一个类似的I / O消息处理程序列表。我们将在下面看到I / O版本。
当需要填写connect和I / O表时,我们建议您使用iofunc_func_init()函数通过POSIX层默认处理程序例程加载表。然后,如果您需要重写特定消息处理程序的某些功能,则只需分配自己的处理程序功能,而不是POSIX默认例程即可。我们将在“添加您自己的功能”部分中看到这一点。现在,让我们看一下connect函数表本身(来自<sys/resmgr.h>
):
typedef struct _resmgr_connect_funcs {
unsigned nfuncs;
int (*open)
(ctp, io_open_t *msg, handle, void *extra);
int (*unlink)
(ctp, io_unlink_t *msg, handle, void *reserved);
int (*rename)
(ctp, io_rename_t *msg, handle, io_rename_extra_t *extra);
int (*mknod)
(ctp, io_mknod_t *msg, handle, void *reserved);
int (*readlink)
(ctp, io_readlink_t *msg, handle, void *reserved);
int (*link)
(ctp, io_link_t *msg, handle, io_link_extra_t *extra);
int (*unblock)
(ctp, io_pulse_t *msg, handle, void *reserved);
int (*mount)
(ctp, io_mount_t *msg, handle, io_mount_extra_t *extra);
} resmgr_connect_funcs_t;
请注意,我通过省略resmgr_context_t *
第一个成员的类型(ctp)和RESMGR_HANDLE_T *
第三个成员的类型(handle)缩短了原型。例如,开放的完整原型 实际上是:
int (*open) (resmgr_context_t *ctp,
io_open_t *msg,
RESMGR_HANDLE_T *handle,
void *extra);
结构的第一个成员(nfuncs)指示结构的大小(包含多少个成员)。在上述结构中,它应包含数值“8”为有8个成员(打开通到安装)。该成员主要是为了使QSS升级该库而对您的代码没有任何不良影响。例如,假设您已将值编译为8,然后QSS将该库升级为9。由于该成员的值只有8,因此库可以对自己说:“啊!当我们只有8个函数,而现在只有9个函数时,便对该库的用户进行了编译。我将为第九个函数提供一个有用的默认值。” 有一个明显的常数<sys/resmgr.h>
名为_RESMGR_CONNECT_NFUNCS的具有当前编号。如果手动填写连接函数表,请使用此常量(尽管 最好使用iofunc_func_init())。
注意,所有函数原型都共享一种通用格式。第一个参数ctp是指向resmgr_context_t
结构的指针。这是资源管理器库使用的内部上下文块,您应将其视为只读(一个字段除外,我们将返回到该字段)。
第二个参数始终是消息的指针。由于表中的函数可以处理不同类型的消息,因此原型与每个函数将处理的消息类型相匹配。
第三个参数是RESMGR_HANDLE_T
称为句柄的结构-用于标识此消息所针对的设备。我们稍后将在查看属性结构时看到这一点。
最后,对于需要一些额外数据的功能,最后一个参数是“保留”或“额外”参数。在讨论处理函数时,我们将适当地显示额外的参数。
resmgr_io_funcs_t
I / O表I / O表在本质上与上面显示的连接功能表非常相似。来自<sys/resmgr.h>
:
typedef struct _resmgr_io_funcs { unsigned nfuncs; int (*read) (ctp, io_read_t *msg, ocb); int (*write) (ctp, io_write_t *msg, ocb); int (*close_ocb) (ctp, void *reserved, ocb); int (*stat) (ctp, io_stat_t *msg, ocb); int (*notify) (ctp, io_notify_t *msg, ocb); int (*devctl) (ctp, io_devctl_t *msg, ocb); int (*unblock) (ctp, io_pulse_t *msg, ocb); int (*pathconf) (ctp, io_pathconf_t *msg, ocb); int (*lseek) (ctp, io_lseek_t *msg, ocb); int (*chmod) (ctp, io_chmod_t *msg, ocb); int (*chown) (ctp, io_chown_t *msg, ocb); int (*utime) (ctp, io_utime_t *msg, ocb); int (*openfd) (ctp, io_openfd_t *msg, ocb); int (*fdinfo) (ctp, io_fdinfo_t *msg, ocb); int (*lock) (ctp, io_lock_t *msg, ocb); int (*space) (ctp, io_space_t *msg, ocb); int (*shutdown) (ctp, io_shutdown_t *msg, ocb); int (*mmap) (ctp, io_mmap_t *msg, ocb); int (*msg) (ctp, io_msg_t *msg, ocb); int (*dup) (ctp, io_dup_t *msg, ocb); int (*close_dup) (ctp, io_close_t *msg, ocb); int (*lock_ocb) (ctp, void *reserved, ocb); int (*unlock_ocb) (ctp, void *reserved, ocb); int (*sync) (ctp, io_sync_t *msg, ocb); int (*power) (ctp, io_power_t *msg, ocb); } resmgr_io_funcs_t;
对于此结构,我也删除了ctp成员(resmgr_context_t *
)和最后一个成员(ocb,type RESMGR_OCB_T *
)的类型,从而缩短了原型。例如,完整的读取原型实际上是:
int (*read) (resmgr_context_t *ctp,
io_read_t *msg,
RESMGR_OCB_T *ocb);
结构的第一个成员(nfuncs)指示结构的大小(包含多少个成员)。用于初始化的适当清单常量是_RESMGR_IO_NFUNCS。
请注意,I / O表中的参数列表也很规则。就像在连接表处理程序中一样,第一个参数是ctp,第二个参数是msg。
但是,第三个参数是不同的。这是一个ocb,代表“开放上下文块”。它包含由连接消息处理程序绑定的上下文(例如,作为客户端的*open()*调用的结果),并且可供I / O函数使用。
如上所述,当需要填写两个表时,我们建议您使用iofunc_func_init()函数通过POSIX层默认处理程序例程加载表。然后,如果您需要重写特定消息处理程序的某些功能,则只需分配自己的处理程序功能,而不是POSIX默认例程即可。我们将在“添加您自己的功能”部分中看到这一点。
resmgr_context_t
内部上下文块最后,库的最低层使用一种数据结构来跟踪其需要了解的信息。您应将此数据结构的内容视为“只读”(iov成员除外)。
这是数据结构(来自<sys/resmgr.h>
):
typedef struct _resmgr_context {
int rcvid;
struct _msg_info info;
resmgr_iomsgs_t *msg;
dispatch_t *dpp;
int id;
unsigned msg_max_size;
int status;
int offset;
int size;
iov_t iov [1];
} resmgr_context_t;
与其他数据结构示例一样,我已经删除了保留字段。
让我们看一下内容:
rcvid
来自资源管理器库的*MsgReceivev()*函数调用的接收ID 。指明您应该回复谁(如果您要自己回复)。
信息
包含资源管理器库的接收循环中*MsgReceivev()*返回的信息结构。对于获取有关客户端的信息很有用,包括节点描述符,进程ID,线程ID等。有关更多详细信息,请参见MsgReceivev()的文档。
味精
指向所有可能的消息类型的并集的指针。这对您不是很有用,因为您的每个处理程序函数都会通过适当的联合成员作为其第二个参数传递。
dpp
指向您开始传入的调度结构的指针。同样,对您不是很有用,但对资源管理器库显然很有用。
ID
此消息旨在用于的安装点的标识符。完成resmgr_attach()时,它返回一个小的整数ID。此ID是id成员的值。请注意,您很可能永远不会自己使用此参数,而是会依赖*io_open()*处理函数中传递给您的属性结构。
msg_max_size
这包含msg_max_size这是在作为通过msg_max_size的构件resmgr_attr_t
(给予resmgr_attach() ,使得功能)尺寸,偏移量,和msg_max_size 都包含在一个方便的结构/位置。
状态
这是您的处理程序函数放置操作结果的地方。请注意,您应始终使用宏*RESMGR_STATUS编写此字段。例如,如果你从一个处理连接消息*的open()* ,和你是一个只读的资源管理器,但客户想打开你写,你会返回一个EROFS *错误号*通过(通常)* RESMGR_STATUS (ctp,EROFS)
。
抵消
客户端消息缓冲区中的当前字节数。当与带有合并消息的resmgr_msgreadv()一起使用时,仅与基础层库相关(请参阅下文)。
尺寸
这告诉您在传递给处理程序函数的消息区域中有多少字节有效。此数字很重要,因为它指示是否需要从客户端读取更多的数据(例如,如果资源管理器基础库未读取所有客户端的数据),或者是否需要分配存储空间来答复客户端。客户端(例如,回复客户端的*read()*请求)。
媒介
I / O向量表,如果返回数据,则可以在其中写入返回值。例如,当客户端调用read()并调用您的读取处理代码时,您可能需要返回数据。可以在iov数组中设置此数据,然后您的读取处理代码可以返回一些内容,_RESMGR_NPARTS (2)
以指示(在此示例中)这两者iov [0]
并iov [1]
包含要返回给客户端的数据。注意,iov成员被定义为仅具有一个元素。但是,您还会注意到,它在结构的结尾很方便。当您设置控件结构的nparts_max成员时,您将定义iov数组中元素的实际数量以上(在“ resmgr_attr_t
控制结构”部分中)。
既然我们已经了解了数据结构,我们就可以讨论您要提供的各部分之间的交互作用,以使资源管理器实际执行 某些操作。
我们来看一下:
如您在上面的/dev/null
示例中看到的,您要做的第一件事是向流程管理器注册您选择的“挂载点”。这是通过具有以下原型的resmgr_attach()完成的:
int
resmgr_attach (void *dpp,
resmgr_attr_t *resmgr_attr,
const char *path,
enum _file_type file_type,
unsigned flags,
const resmgr_connect_funcs_t *connect_funcs,
const resmgr_io_funcs_t *io_funcs,
RESMGR_HANDLE_T *handle);
让我们按顺序检查这些参数,看看它们的用途。
dpp
调度句柄。这使调度界面可以管理资源管理器收到的消息。
resmgr_attr
如上所述,控制资源管理器的特征。
路径
您正在注册的安装点。如果您要注册一个离散的挂载点(例如,使用/dev/null
或/dev/ser1
),则该挂载点必须与客户端完全匹配,并且没有其他路径名组件超出挂载点。如果要注册目录挂载点(例如,将网络文件系统挂载为的情况/nfs
),则匹配也必须完全相同,并具有允许超出挂载点的路径名的附加功能;它们被传递给除去挂载点的连接函数(例如,路径名将/nfs/etc/passwd
与网络文件系统资源管理器匹配,并且将etc/passwd
作为其余的路径名获得)。
文件类型
资源管理器的类。见下文。
标志
其他标志,用于控制资源管理器的行为。这些标志定义如下。
connect_funcs和io_funcs
这些只是您希望绑定到安装点的连接功能和I / O功能的列表。
处理
这是一个“可扩展”数据结构(也称为 “属性结构”),用于标识正在安装的资源。例如,对于串行端口,您可以通过添加有关串行端口的基地址,波特率等信息来扩展标准的POSIX层属性结构。请注意,它不必是属性结构-如果您将提供自己的“开放”处理程序,然后可以选择以任何希望的方式解释此字段。仅当您使用默认的iofunc_open_default()处理程序作为“打开”处理程序时,此字段才必须是属性结构。
所述标志构件可以包含任何以下标志(或恒定0,如果没有指定)的:
_RESMGR_FLAG_BEFORE或_RESMGR_FLAG_AFTER
这些标志指示您的资源管理器希望放置在(分别)具有相同安装点的其他资源管理器之前或之后。这两个标志对于联合(覆盖)的文件系统很有用。我们将很快讨论这些标志的交互作用。
_RESMGR_FLAG_DIR
此标志表明您的资源管理器正在接管指定的安装点及以下的安装点-与离散地显示的资源管理器相反,它实际上是资源管理器的文件系统样式。
_RESMGR_FLAG_OPAQUE
如果设置,则防止解析到安装点以下的任何其他管理器( 路径管理器除外)。这有效地消除了路径上的并集。
_RESMGR_FLAG_FTYPEONLY
这确保了只有具有相同FTYPE_ 作为请求FILE_TYPE传递给resmgr_attach()*相匹配。
_RESMGR_FLAG_FTYPEALL
此标志时资源管理器要赶上所有的客户端请求,即使是那些具有不同FTYPE_ 规格比一个传递给resmgr_attach()在FILE_TYPE*说法。只能与FTYPE_ALL注册文件类型一起使用。
_RESMGR_FLAG_SELF
允许该资源管理器自言自语。这确实是一个“别在家里尝试,孩子们”的标志,因为允许资源管理器自言自语可能会破坏发送层次结构并导致死锁(如“消息传递”一章中所述)。
您可以多次调用resmgr_attach()来挂载不同的挂载点。您也可以从connect或I / O函数中调用resmgr_attach() -这是一种简洁的功能,使您可以即时“创建”设备。
确定挂载点并想要创建挂载点时,您需要告诉流程管理器该资源管理器是否只能处理任何人的请求,或者是否仅限于处理识别其连接消息的客户端的请求带有特殊标签。例如,考虑POSIX消息队列(mqueue
)驱动程序。它不会允许(当然也不知道该如何处理)来自任何旧客户端的“常规” open()消息。它将仅允许来自使用POSIX mq_open(),mq_receive()等函数调用的客户端的消息。为了防止进程管理器从甚至允许定期请求在到达mqueue
资源管理器,mqueue
指定_FTYPE_MQUEUE为FILE_TYPE 参数。这意味着,当客户端向流程管理器请求名称解析时,除非客户端已指定要与自己标识为_FTYPE_MQUEUE的资源管理器对话,否则流程管理器甚至不会在搜索过程中考虑使用资源管理器。 。
除非你正在做一些非常特别的,你会使用FILE_TYPE的FTYPE_ANY,这意味着你的资源管理器准备好处理请求任何人。有关_FTYPE 清单常量的完整列表,请参阅*<sys/ftype.h>
。
关于“ before”和“ after”标志,事情变得更加有趣了。您只能指定这些标志之一或常数0。
让我们看看它是如何工作的。按照表中指定的顺序启动了许多资源管理器。我们还看到了为flags成员传递的标志。观察他们的位置:
mg | 旗 | 订购 |
---|---|---|
1个 | _RESMGR_FLAG_BEFORE | 1个 |
2 | _RESMGR_FLAG_AFTER | 一二 |
3 | 0 | 1 3 2 |
4 | _RESMGR_FLAG_BEFORE | 1 4 3 2 |
5 | _RESMGR_FLAG_AFTER | 1,4,3,5,2 |
6 | 0 | 1,4,6,3,5,2 |
如您所见,第一个实际指定标志的资源管理器始终以该位置结束。(从表中,资源管理器1是第一个指定“ before”标志的;无论谁注册,资源管理器1始终是列表中的第一个。同样,资源管理器2也是第一个指定“ before”标志的。 ;同样,无论其他人注册,它总是最后一个。)如果未指定标志,则它实际上充当“中间”标志。当资源管理器3从零标志开始时,它被放到中间。与“之前”和“之后”标志一样,对所有“中间”资源管理器都有优先顺序,从而将较新的资源管理器放置在其他现有的“中间”资源管理器之前。
但是,实际上,很少有您实际上要装载多个资源管理器的情况,甚至更少的情况下,您要在同一装载点装载两个以上的资源管理器。这是一个设计技巧:公开在资源管理器的命令行中设置标志的功能,以便资源管理器的最终用户能够指定例如**-b**
使用“ before”标志以及**-a**
使用“之后”标志,未指定命令行选项以指示应将零作为标志传递。
请记住,这个讨论适用只到资源管理器安装使用相同的挂载点。安装“ /nfs
的”与“前”标志和“ /disk2
”与“后”标志将具有在彼此没有影响; 只有当你是再安装另一 “ /nfs
”或“ /disk2
”将这些标志(和规则)来发挥作用。
最后,resmgr_attach()函数在成功时返回一个小的整数句柄(对于失败,则返回-1)。然后,可以随后使用此句柄从流程管理器的内部路径名表中分离路径名。
在设计您的第一个资源管理器时,您很可能希望采用增量设计方法。编写成千上万行代码而仅仅遇到一个基本的误解,然后不得不做出决定是否尝试混淆(或者,我是说“修复”)所有这些代码,或者将其废弃的丑陋决定,可能会非常令人沮丧。白手起家。
推荐的使事情运行的方法是使用iofunc_func_init() POSIX层默认的初始化函数来用POSIX层默认函数填充connect和I / O表。这意味着您可以像上面一样,通过一些函数调用从字面上编写资源管理器的初始版本。
您首先要实现哪种功能实际上取决于您要编写哪种资源管理器。如果它是资源管理器的文件系统类型,而您要接管一个安装点及其下的所有内容,则最好从io_open()函数开始。另一方面,如果它是执行“传统” I / O操作的离散资源管理器(即,您主要通过read()和write()之类的客户端调用来访问它),那么最好的起点是io_read() 和/或io_write() 函数。第三种可能性是它是离散显示的资源管理器,它不执行传统的I / O操作,而是依靠*devctl()或ioctl()*客户端调用将执行其大部分功能。在这种情况下,您将从io_devctl()函数开始。
无论您从哪里开始,都需要确保以预期的方式调用函数。POSIX层默认功能的真正酷之处在于可以将它们直接放置在connect或I / O功能表中。这意味着,如果您只是想获得控制权,请执行一个*printf()*来说“我在io_open中!”,然后“做所有应做的事”,那么您将很轻松。这是接管io_open()函数的资源管理器的一部分:
// forward reference
int io_open (resmgr_context_t *, io_open_t *,
RESMGR_HANDLE_T *, void *);
int
main ()
{
// everything as before, in the /dev/null example
// except after this line:
iofunc_func_init (_RESMGR_CONNECT_NFUNCS, &cfuncs,
_RESMGR_IO_NFUNCS, &ifuncs);
// add the following to gain control:
cfuncs.open = io_open;
假设您已正确编写了io_open()函数调用的原型,如代码示例中所示,您可以在自己的内部使用默认值!
int
io_open (resmgr_context_t *ctp, io_open_t *msg,
RESMGR_HANDLE_T *handle, void *extra)
{
printf ("I'm here in the io_open!\n");
return (iofunc_open_default (ctp, msg, handle, extra));
}
以这种方式,您仍在使用默认的POSIX层iofunc_open_default()处理程序,但是您还获得了执行printf()的控制权。
显然,您可以对io_read(),io_write()和io_devctl()函数以及任何其他具有POSIX层默认功能的函数执行此操作。实际上,这是一个非常好的主意,因为它向您表明客户端确实正在按预期方式致电您的资源管理器。
正如我们在上面的客户端和资源管理器概述部分中提到的那样,资源管理器的一般流程从客户端的open()开始。这被转换为连接消息,并最终被资源管理器的io_open()调用连接函数接收。
这真的很关键,因为io_open()调用函数是资源管理器的“关守”。如果该消息导致网守使请求失败,则您将不会收到任何I / O请求,因为客户端从未获得有效的文件描述符。相反,如果该消息被网守所接受,则客户端现在具有有效的文件描述符,并且您应该期望获得I / O消息。
但是io_open()调用函数起着更大的作用。它不仅负责验证客户端是否可以打开特定资源,还负责:
前两个操作是通过基础层函数resmgr_open_bind()执行的;属性结构的绑定是通过简单的分配完成的。
一旦io_open() 外呼函数被调用,它的画面出来。客户端可能会或可能不会发送I / O消息,但是无论如何,最终将使用与close()函数相对应的消息最终终止“会话” 。请注意,如果客户端遭受意外死亡(例如,被SIGSEGV击中,或者客户端正在崩溃的节点崩溃),则操作系统将合成close()消息,以便资源管理器可以清除。因此,你保证得到一个接近() 消息!
您可能已经注意到了一个有趣的观点。chown()的客户端原型为 :
int
chown (const char *path,
uid_t owner,
gid_t group);
请记住,连接消息始终包含路径名,或者是一次性消息,或者为其他I / O消息建立上下文。
那么,为什么客户端的chown()函数没有连接消息?事实上,为什么是有一个I / O 消息?!?客户的原型中肯定没有隐含的文件描述符!
答案是:“让您的生活更简单!”
想象一下,如果诸如chown(),chmod(),stat()之类的函数以及其他函数需要资源管理器查找路径名,然后执行某种工作。(顺便说一下,这就是在QNX 4中实现的方式。)常见的问题是:
无论如何,在Neutrino下发生的事情是客户端构造了一个合并消息 —实际上只是一个包含多个资源管理器消息的单个消息。如果没有合并消息,我们可以使用以下内容模拟 chown():
int chown (const char *path, uid_t owner, gid_t group) { int fd, sts;
if ((fd = open (path, O_RDWR)) == -1) { return (-1); } sts = fchown (fd, owner, group); close (fd); return (sts);
}
其中fchown()是*chown()的基于文件描述符的版本。这里的问题是我们现在正在发出三个函数调用(和三个独立的消息传递事务),并在客户端产生了open()和close()*的开销。
通过合并消息,在Neutrino下,看起来像这样的单个消息是由客户端的*chown()*库调用直接构造的:
合并消息。
消息分为两部分,连接部分(类似于客户端的*open()会生成的部分)和I / O部分(与fchown()生成的消息等效)。没有等效的close(),*因为我们在选择连接消息时暗含了这一点。我们使用了_IO_CONNECT_COMBINE_CLOSE消息,该消息有效地指出“打开此路径名,使用获取的文件描述符来处理消息的其余部分,当结束时遇到错误或遇到错误,请关闭文件描述符。”
您编写的资源管理器不知道客户端称为chown()或客户端执行了不同的open(),之后是fchown(),然后是 close()。基本层库都将其隐藏。
事实证明,这种合并消息的概念并不仅仅是为了节省带宽(如上面的*chown()*情况)有用。这对于确保原子完成操作也至关重要。
假设客户端进程具有两个或多个线程和一个文件描述符。客户端中的一个线程执行lseek(),然后执行 read()。一切都如我们所料。如果客户端中的另一个线程在相同的文件描述符上执行相同的操作,则会遇到问题。由于lseek()和 read()函数彼此不了解,因此第一个线程可能会执行lseek(),然后被第二个线程抢占。第二个线程在放弃CPU之前先执行其lseek(),然后执行其read()。问题在于,由于两个线程共享相同的线程 文件描述符,第一个线程的*lseek()偏移现在在错误的位置-它位于第二个线程的read()函数给定的位置!这也是跨进程dup()*的文件描述符的问题,更不用说网络了。
一个明显的解决方案是将*lseek()和read()*函数放在互斥锁中-当第一个线程获得互斥锁时,我们现在知道它具有对文件描述符的独占访问权。第二个线程必须等待,直到它可以获取互斥体,然后才能混乱文件描述符的位置。
不幸的是,如果有人忘记为每个文件描述符操作获取一个互斥锁,则这种“不受保护的”访问可能会导致线程读取或写入数据到错误的位置。
让我们看一下C库调用readblock()(来自<unistd.h>
):
int
readblock (int fd,
size_t blksize,
unsigned block,
int numblks,
void *buff);
(writeblock()函数与此类似。)
您可以想象readblock()的一个相当“简单”的实现:
int
readblock (int fd, size_t blksize, unsigned block,
int numblks, void *buff)
{
lseek (fd, blksize * block, SEEK_SET); // get to the block
read (fd, buff, blksize * numblks);
}
显然,此实现在多线程环境中没有用。我们至少必须在呼叫周围放置一个互斥锁:
int
readblock (int fd, size_t blksize, unsigned block,
int numblks, void *buff)
{
pthread_mutex_lock (&block_mutex);
lseek (fd, blksize * block, SEEK_SET); // get to the block
read (fd, buff, blksize * numblks);
pthread_mutex_unlock (&block_mutex);
}
(我们假设互斥量已经初始化。)
该代码仍然容易受到“未保护”的访问;如果进程中的其他线程在文件描述符上执行了一个简单的非互斥的lseek(),则说明存在一个错误。
解决方案是使用合并消息,正如我们在上面针对chown()函数所讨论的那样。在这种情况下,C库实现的读出数据块()看跌期权都在lseek的() ,并在*阅读()*操作成一个单一的消息,并将其关闭,以资源管理器:
** readblock()*函数的合并消息。*
之所以可行,是因为消息传递是原子的。从客户端的角度来看,要么整个消息都已发送到资源管理器,要么什么都没有。因此,中间的“不受保护”的lseek()无关紧要-当资源管理器接收到readblock()操作时,只需一次即可完成。(显然,损坏将是不受保护的lseek(),因为在*readblock()之后,文件描述符的偏移量与原始lseek()*放置的位置不同。)
但是资源管理器呢?如何确保一次 处理整个*readblock()*操作?在讨论每个消息组件执行的操作时,我们将很快看到这一点。
存在三种与POSIX层支持例程有关的数据结构。注意,就基础层而言,您可以使用所需的任何数据结构。这是POSIX层,要求您遵循特定的内容和布局。POSIX层提供的好处非常值得这个微小的约束。稍后我们将看到,您也可以将自己的内容添加到结构中。
下图中说明了这三种数据结构,显示了一些使用资源管理器的客户端恰好体现了两个设备:
数据结构-全局。
数据结构为:
iofunc_ocb_t
— OCB结构
包含每个文件描述符的信息
iofunc_attr_t
—属性结构
包含每个设备的信息
iofunc_mount_t
—安装结构
包含每个安装点的信息
当我们谈论I / O和连接表时,您看到了OCB和属性结构-在I / O表中,OCB结构是最后一个传递的参数。属性结构作为连接表函数(第三个参数)中的句柄传递。装载结构通常是全局结构,并“手动”绑定到属性结构(在为资源管理器提供的初始化代码中)。
iofunc_ocb_t
OCB结构OCB结构包含基于每个文件描述符的信息。这意味着当客户端执行open()调用并返回文件描述符(与错误指示相对)时,资源管理器将创建一个OCB并将其与客户端关联。只要客户端打开文件描述符,此OCB就会存在。实际上,OCB和文件描述符是一对匹配的。每当客户端调用I / O函数时,资源管理器库将自动关联OCB,并将其与消息一起传递给I / O函数表条目指定的I / O函数。这就是为什么所有I / O函数都将ocb参数传递给它们的原因。最后,客户端将关闭文件描述符(通过close()),这将导致资源管理器将OCB与文件描述符和客户端分离。请注意,客户端的dup()函数只是增加引用计数。在这种情况下,仅当引用计数达到零时(即,当相同数量的close()被称为open()和dup()时),OCB才与文件描述符和客户端分离 。
您可能会怀疑,OCB包含每个打开或每个文件描述符都重要的内容。以下是内容(来自<sys/iofunc.h>
):
typedef struct _iofunc_ocb {
IOFUNC_ATTR_T *attr;
int32_t ioflag;
SEE_BELOW!!! offset;
uint16_t sflag;
uint16_t flags;
} iofunc_ocb_t;
现在忽略有关偏移量字段的注释;在讨论之后,我们将立即返回。
该iofunc_ocb_t
成员是:
属性
指向与此OCB相关的属性结构的指针。您将在I / O函数中看到一个常见的编码习惯,即“” ocb->attr
,用于访问属性结构的成员。
旗
开放模式;如何打开此资源(例如,只读)。打开模式(传递给客户端的open())与ioflag值对应,如下所示:打开模式 ioflag值O_RDONLY _IO_FLAG_RD O_RDWR _IO_FLAG_RD | _IO_FLAG_WR O_WRONLY _IO_FLAG_WR
抵消
当前的*lseek()*偏移到该资源中。
旗
<share.h>
客户端的sopen()函数调用使用的共享标志(请参见参考)。这些是标志SH_COMPAT,SH_DENYRW,SH_DENYWR,SH_DENYRD和SH_DENYNO。
标志
系统标志。当前支持的两个标志是IOFUNC_OCB_PRIVILEGED和IOFUNC_OCB_MMAP,IOFUNC_OCB_PRIVILEGED指示特权进程是否发出导致该OCB的连接消息,而IOFUNC_OCB_MMAP指示客户端上的mmap()是否正在使用此OCB 。目前没有定义其他标志。您可以将IOFUNC_OCB_FLAGS_PRIVATE定义的位用于自己的专用标志。
如果您希望将其他数据与“普通” OCB一起存储,请放心,您可以“扩展” OCB。我们将在“高级主题”部分中对此进行讨论。
该偏移字段,至少可以说,有意思。看看<sys/iofunc.h>
,看看它是如何实现的。根据您设置的预处理器标志,您可能会获得偏移量区域的六种(!)可能布局之一。但是不必太担心实现-取决于考虑是否要支持64位偏移量,实际上只有两种情况需要考虑:
出于我们的目的,除非我们专门谈论32位和64位,否则我们将假定所有偏移量均为类型的64位off_t
,并且平台知道如何处理64位数量。
iofunc_attr_t
属性结构OCB是按打开或每个文件描述符的结构,而属性结构是按设备的数据结构。您已经看到标准iofunc_ocb_t
OCB具有一个称为attr的成员,该 成员 指向属性结构。这样做是为了使OCB可以访问有关该设备的信息。让我们看一下属性结构(来自<sys/iofunc.h>
):
typedef struct _iofunc_attr { IOFUNC_MOUNT_T *mount; uint32_t flags; int32_t lock_tid; uint16_t lock_count; uint16_t count; uint16_t rcount; uint16_t wcount; uint16_t rlocks; uint16_t wlocks; struct _iofunc_mmap_list *mmap_list; struct _iofunc_lock_list *lock_list; void *list; uint32_t list_size; SEE_BELOW!!! nbytes; SEE_BELOW!!! inode; uid_t uid; gid_t gid; time_t mtime; time_t atime; time_t ctime; mode_t mode; nlink_t nlink; dev_t rdev; } iofunc_attr_t;
的为nbytes和索引节点都具有相同的组的#ifdef
条件语句作为偏移 的OCB的构件(参照“的奇怪的事例偏移构件” 上文)。
请注意,属性结构的某些字段仅对POSIX帮助程序例程有用。
让我们分别看一下这些字段:
安装
指向可选iofunc_mount_t
安装结构的指针。使用此方法的方式与使用从OCB指向属性结构的指针的方式相同,不同之处在于,此值可以为NULL,在这种情况下,将使用默认的安装结构(请参见 下面的“ iofunc_mount_t
安装结构”)。如前所述,安装结构通常“手工”绑定到您为资源管理器初始化提供的代码中的属性结构中。
标志
包含描述其他属性结构字段状态的标志。我们将在稍后讨论这些。
lock_tid
为了防止同步问题,使用相同属性结构的多个线程将互斥。该lock_tid包含当前具有的属性结构锁定线程的线程ID。
lock_count
指示尝试使用此属性结构的线程数。零值表示该结构已解锁。一个或多个值表示一个或多个线程正在使用该结构。
计数
指示由于任何原因使此属性结构打开的OCB的数量。例如,如果一个客户端打开了一个可供读取的OCB,另一个客户端打开了另一个可供读取/写入的OCB,并且两个OCB都指向该属性结构,则count的值为2,表示两个客户端拥有此资源。打开。
计数
数读者。在为count给出的示例中,rcount也将具有值2,因为两个客户端都具有可供读取的资源。
count
数作家。在为count给出的示例中,wcount的值为1,因为只有一个客户端开放此资源以供写入。
lock
指示在特定资源上具有读取锁定的OCB的数量。如果为零,则表示没有读锁,但可能有写锁。
lock
与rlock相同,但用于写锁。
mmap_list
由POSIX iofunc_mmap_default()内部使用。
lock_list
由POSIX iofunc_lock_default()内部使用。
清单
保留以备将来使用。
list_size
列表保留的区域大小。
字节数
资源大小,以字节为单位。例如,如果此资源描述了一个特定的文件,并且该文件的大小为7756字节,则nbytes成员将包含数字7756。
索引节点
包含文件或资源序列号,每个安装点必须是唯一的。该索引节点不应该是零,因为零传统指示文件,该文件未在使用。
uid
此资源所有者的用户标识。
id
此资源所有者的组ID。
时光
文件修改时间,每当处理客户端*write()*时更新或至少无效。
一次
每当客户端的*read()*返回的字节数大于零时,文件访问时间就会更新或至少无效。
时间
每当处理客户端write(),*chown()或chmod()*时,文件更改时间即更新或至少无效 。
模式
文件的模式。这些是来自的标准S_ *值 <sys/stat.h>
(例如S_IFCHR)或八进制表示形式(例如0664),用于指示所有者和组的读/写许可,以及对其他所有者的只读许可。
链接
客户端的*stat()*函数调用返回的文件链接数。
开发者
对于字符特殊设备,此字段由主要和次要设备代码组成(最低有效位为10位次要字符;接下来的6位为主要设备号)。对于其他类型的设备,包含设备号。(有关更多讨论,请参见下面的“设备号,索引节点和我们的朋友rdev的 ”)。
与OCB一样,您可以使用自己的数据扩展“常规”属性结构。请参阅“高级主题”部分。
iofunc_mount_t
安装结构装载结构包含多个属性结构之间共有的信息。
以下是安装结构的内容(来自<sys/iofunc.h>
):
typedef struct _iofunc_mount {
uint32_t flags;
uint32_t conf;
dev_t dev;
int32_t blocksize;
iofunc_funcs_t *funcs;
} iofunc_mount_t;
该标志成员只包含一个标志,IOFUNC_MOUNT_32BIT。此标志指示OCB 中的偏移量以及属性结构中的nbytes和inode为32位。请注意,您可以使用常量IOFUNC_MOUNT_FLAGS_PRIVATE中的任何位在flags中定义自己的标志。
该CONF成员包含以下标志:
IOFUNC_PC_CHOWN_RESTRICTED
指示文件系统是否以“ chown
-restricted”方式运行,意味着是否仅root
允许chown
文件使用。
IOFUNC_PC_NO_TRUNC
指示文件系统没有截断名称。
IOFUNC_PC_SYNC_IO
指示文件系统支持同步I / O操作。
IOFUNC_PC_LINK_DIR
表示允许目录的链接/取消链接。
该开发成员包含设备号,并在下面描述的“设备号,i节点,和我们的朋友rdev的。”
的块大小描述了字节设备的本地的块大小。例如,在典型的旋转介质存储系统上,该值为512。
最后,funcs指针指向一个结构(来自<sys/iofunc.h>
):
typedef struct _iofunc_funcs {
unsigned nfuncs;
IOFUNC_OCB_T *(*ocb_calloc)
(resmgr_context_t *ctp,
IOFUNC_ATTR_T *attr);
void (*ocb_free)
(IOFUNC_OCB_T *ocb);
} iofunc_funcs_t;
与connect和I / O函数表一样,nfuncs 成员应填充表的当前大小。为此,请使用常量_IOFUNC_NFUNCS。
的ocb_calloc和ocb_free函数指针可以填充到呼叫的函数的地址,每当一个OCB将被分配或释放。我们稍后将讨论扩展OCB时为什么要使用这些功能。
安装结构包含一个名为dev的成员。属性结构包含两个成员:inode和rdev。让我们通过检查传统的基于磁盘的文件系统来了解它们之间的关系。文件系统安装在块设备(即整个磁盘)上。该块设备可能被称为/dev/hd0
(系统中的第一个硬盘)。在该磁盘上,可能有许多分区,例如/dev/hd0t77
(该特定设备上的第一个QNX文件系统分区)。最后,在该分区内,可能有任意数量的文件,其中一个可能是/hd/spud.txt
。
该开发(或“设备号”)的成员,包含了许多所独有的,这种资源管理器注册的节点。所述RDEV构件是dev的 根设备的数目。最后,索引节点是文件序列号。(请注意,您可以通过调用rsrcdbmgr_devno_attach()获得主要和次要设备编号;有关更多详细信息,请参见《中微子库参考》。您只能使用64个主要设备,每个主要设备只能使用1024个次要设备。)
让我们将其与磁盘示例相关联。下表显示了一些示例编号;桌子之后,我们将看看这些数字的来源以及它们之间的关系。
设备 | 开发者 | 索引节点 | 开发者 |
---|---|---|---|
/dev/hd0 | 6 | 2 | 1个 |
/dev/hd0t77 | 1个 | 12 | 77 |
/hd/spud.txt | 77 | 47343 | 不适用 |
对于原始块设备,/dev/hd0
进程管理器分配了dev和inode值(上表中的6和2)。资源管理器在启动时为设备选择了唯一的rdev值(为1)。
为分区,/dev/hd0t77
中,dev的 值来从原始块设备的RDEV号(1)。资源管理器将索引节点选择为唯一编号(在rdev中)。这是12的来源。最后,资源管理器也选择了rdev编号-在这种情况下,资源管理器的编写者选择了77,因为它与分区类型相对应。
最后,对于文件/hd/spud.txt
中,开发值(77),来自该分区的rdev的 价值。该索引节点是由资源管理器中选择(在文件的情况下,号码选择对应于文件的一些内部表示-它不会不管它是什么,只要它不是零,它内部的独特rdev)。这是47343的来源。对于文件,rdev字段没有意义。
并非所有的出站都与客户端消息相对应-有些是由内核合成的,有些是由库合成的。
我已将本节分为以下几部分:
每个处理程序函数都会传递一个内部上下文块(ctp参数),除了iov成员之外,应将其视为“只读” 。如上文“ resmgr_context_t
内部上下文块 ”中所述,此上下文块包含一些感兴趣的项目。”此外,每个函数都会传递一个指向消息的指针(在msg 参数中)。您将广泛使用此消息指针,因为它包含客户端的C库调用已放置在其中供您使用的参数。
您提供的函数必须返回一个值(所有函数都原型化为return in int
)。这些值是从以下列表中选择的:
_RESMGR_NOREPLY
指示到资源管理器库,它应该不 执行MsgReplyv() -前提是,你要么自己进行它在你的处理函数,或者你打算以后做了一段时间。
_RESMGR_NPARTS (*n*)
资源管理器库执行MsgReplyv()(IOV位于中)时,应返回n个部分的IOV 。您的函数负责填写ctp结构的 iov成员,然后返回具有正确数量零件的_RESMGR_NPARTS。 ctp的iov成员是动态分配的,因此它必须足够大,以容纳要写入iov成员的数组元素的数量!有关设置nparts_max成员的信息,请参见上面的“ 控制结构” 部分。ctp -> iov
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n1S1VuXx-1607647576688)(…/…/…/…/…/…/…/…/pointing.gif)]resmgr_attr_t
_RESMGR_DEFAULT
这将指示资源管理库进行低级别的默认功能(这是不一样的*iofunc ***默认设置()*函数!)你会很少会使用这个返回值。通常,它使资源管理器库将ENOSYS的错误号返回给客户端,这表明不支持该功能。
一个错误号值
向资源管理器库指示应使用此值作为错误 参数调用MsgError()。这通常会使客户端函数(例如open())返回-1,并将 客户端的errno设置为返回值。
_RESMGR_ERRNO (*errno*)
(不建议使用)此返回值已用于“包装”错误号作为消息的返回值。例如,如果客户端发出对只读设备的*open()请求,则返回错误值EROFS是适当的。由于不建议使用此函数,因此您可以直接返回错误号,而不必用RESMGR_ERRNO宏return (EROFS);
将其包装(例如,*而不是比较麻烦的`return( RESMGR_ERRNO(EROFS));“。)
_RESMGR_PTR (*ctp*, *addr*, *len*)
这是一个方便的宏,它接受上下文指针ctp,并填充其第一个IOV元素,以len指定的长度指向addr指定的地址,然后将其返回等效给库。如果从函数返回单部分IOV,通常会使用此方法。_RESMGR_NPARTS (1)
当我们查看readblock()时(在“ 合并消息 ”中),我们看到了合并消息的客户端。客户端能够原子地构造包含多个资源管理器“子消息”的消息-在示例中,这些消息分别对应于单独的函数lseek()和read()。从客户端的角度来看,这两个(或多个)函数至少是原子发送的(并且由于消息传递的性质,资源管理器会自动接收)。我们尚未讨论的是如何确保原子地处理消息 。
该讨论不仅适用于合并消息,而且还适用于资源管理器库接收到的所有 I / O消息(关闭消息除外,稍后将再次介绍)。
资源管理器库要做的第一件事就是锁定与接收到的消息所使用的资源相对应的属性结构。然后,它处理传入消息中的一个或多个子消息。最后,它解锁属性结构。 | |
---|---|
这样可以确保对传入消息进行原子处理,因为资源管理器中的其他线程(当然,在多线程资源管理器的情况下)无法在线程忙于使用它时“跳入”并修改资源。如果没有锁定,则两个客户端线程都可能发出它们认为是原子合并消息的消息(例如lseek()和read())。由于资源管理器中可能有两个不同的线程在运行并处理消息,因此两个资源管理器线程可能会相互抢占,而*lseek()*组件可能会相互干扰。通过锁定和解锁,可以避免这种情况,因为访问资源的每条消息都会自动完成。
锁定和解锁资源由默认的辅助函数(iofunc_lock_ocb_default()和iofunc_unlock_ocb_default())处理,这些函数位于I / O表中的lock_ocb和unlock_ocb 位置。当然,如果要在此锁定和解锁阶段执行进一步的操作,则可以覆盖这些功能。
注意,资源被解锁之前的io_close()函数被调用。这是必需的,因为io_close()函数将释放OCB,这将有效地使用于访问属性结构(存储锁的位置)的指针无效!还要注意,所有connect函数都不会执行此锁定,因为传递给它们的句柄不必是 属性结构(并且锁存储在属性结构中)。
但是,在深入研究单个消息之前,值得指出的是,connect函数都具有相同的消息结构(稍作重新排列,请参见<sys/iomsg.h>
原始内容):
struct _io_connect { // Internal use uint16_t type; uint16_t subtype; uint32_t file_type; uint16_t reply_max; uint16_t entry_max; uint32_t key; uint32_t handle; uint32_t ioflag; uint32_t mode; uint16_t sflag; uint16_t access; uint16_t zero; uint8_t eflag;
// End-user parameters uint16_t path_len; uint8_t extra_type; uint16_t extra_len; char path [1];
};
您会注意到,我已将struct _io_connect
结构分为两个区域,“内部使用”部分和“最终用户参数”部分。
第一部分包括资源管理器库用于以下目的的字段:
为简单起见,建议您在所有 connect函数中始终使用辅助函数(*iofunc *** default()*的辅助函数)。这些将返回通过/失败指示,然后,您可以使用connect函数中的“最终用户参数”成员。
成员的后半部分直接关系到connect函数的实现:
path_len和路径
是操作数的路径名(及其长度)(即,您正在操作的路径名)。
extra_type和extra_len
与connect函数相关的其他参数(例如,路径名)。
为了了解如何将路径成员用作“您正在操作的路径名”,让我们研究一下rename()函数。该函数采用两个路径名。“原始”路径名和“新”路径名。原始路径名是在path中传递的,因为它正在处理(正在更改名称的文件名)。新的路径名是该操作的参数。您将看到传递给connect函数的额外参数方便地包含一个指向操作参数的指针—在本例中为新的路径名。(实施明智的,新的路径存储刚刚过去在原来的路径名路径 指针,并考虑到对齐方式,但是您无需为此做任何事情- 额外的参数可以方便地为您提供正确的指针。)
本节按字母顺序列出了您可以填写的connect和I / O函数入口点(传递给resmgr_attach()的两个表)。请记住,如果仅调用iofunc_func_init(),则所有这些条目都将使用适当的默认值进行填充;您只希望在处理特定邮件时才修改该特定条目。在下面的“示例”部分,您将看到一些常用功能的示例。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ITgZnEr7-1607647576689)(…/…/…/…/…/…/…/…/pointing.gif)] | 乍一看似乎令人困惑,但请注意,实际上有两个未 阻塞的调用:一个是connect函数,一个是I / O函数。这是对的; 它的反映*,当*疏通发生。客户端发送连接消息后内核立即取消阻止客户端时,将使用unblock功能的connect版本。当客户端发送I / O消息后内核立即取消阻止客户端时,将使用unblock功能的I / O版本。 |
---|---|
为了不使客户的C库调用(例如,open())与进入该特定插槽的资源管理器连接输出混淆,我们给所有函数指定了io_
前缀“ ”。例如,open connect outcall插槽的功能描述将在io_open()下。
int io_chmod (resmgr_context_t **ctp*, io_chmod_t **msg*, RESMGR_OCB_T **ocb*)
分类:I / O功能
默认处理程序:iofunc_chmod_default()
辅助函数:iofunc_chmod()
讯息:_IO_CHMOD
数据结构:
struct _io_chmod {
uint16_t type;
uint16_t combine_len;
mode_t mode;
};
typedef union {
struct _io_chmod i;
} io_chmod_t;
说明:负责将传递的ocb标识的资源的模式更改为模式消息成员指定的值。
返回:通过辅助宏_RESMGR_STATUS的状态。
int io_chown (resmgr_context_t **ctp*, io_chown_t **msg*, RESMGR_OCB_T **ocb*)
分类:I / O功能
默认处理程序:iofunc_chown_default()
辅助函数:iofunc_chown()
讯息:_IO_CHOWN
数据结构:
struct _io_chown {
uint16_t type;
uint16_t combine_len;
int32_t gid;
int32_t uid;
};
typedef union {
struct _io_chown i;
} io_chown_t;
描述:负责将传递的ocb标识的资源的用户ID和组ID字段分别更改为uid 和gid。请注意, 应该检查安装结构标志IOFUNC_PC_CHOWN_RESTRICTED和OCB 标志字段,以确定文件系统是否允许非用户执行chown()root
。
返回:通过辅助宏_RESMGR_STATUS的状态。
int io_close_dup (resmgr_context_t **ctp*, io_close_t **msg*, RESMGR_OCB_T **ocb*)
分类:I / O功能
默认处理程序:iofunc_close_dup_default()
辅助函数:iofunc_close_dup()
讯息:_IO_CLOSE_DUP
数据结构:
struct _io_close {
uint16_t type;
uint16_t combine_len;
};
typedef union {
struct _io_close i;
} io_close_t;
描述:这是客户端的close()或fclose()函数调用的实际函数处理程序。请注意,您几乎永远不会接管该功能。您将其保留为I / O表中的iofunc_close_dup_default()。这是因为基础层会跟踪针对特定OCB发出的open(),dup()和close()消息的数量,然后 在最后一条**close()消息出现时,将合成io_close_ocb()调用(请参见下文)已收到特定的OCB。请注意,接收ID存在于 ctp->rcvid
不一定与传递给io_open()的那些匹配 。但是,可以确保至少有一个接收ID与*io_open()函数的接收ID匹配。“额外”接收ID是dup()*类型功能(可能是内部)的结果。
返回:通过辅助宏_RESMGR_STATUS的状态。
int io_close_ocb (resmgr_context_t **ctp*, void **reserved*, RESMGR_OCB_T **ocb*)
分类:I / O功能(由库综合)
默认处理程序:iofunc_close_ocb_default()
辅助功能:无
客户端功能:无-由库综合
消息:无-由库综合
数据结构:
// synthesized by library
struct _io_close {
uint16_t type;
uint16_t combine_len;
};
typedef union {
struct _io_close i;
} io_close_t;
说明:这是当接收到特定OCB 的最后一个*close()*时,由基层库合成的函数。在此位置,您可以执行任何最终清理,然后销毁OCB。请注意,其中存在的接收ID ctp->rcvid
为零,因为此函数是由库综合的,不一定与任何特定消息相对应。
返回:通过辅助宏_RESMGR_STATUS的状态。
int io_devctl (resmgr_context_t **ctp*, io_devctl_t **msg*, RESMGR_OCB_T **ocb*)
分类:I / O
默认处理程序:iofunc_devctl_default()
辅助函数:iofunc_devctl()
讯息:_IO_DEVCTL
数据结构:
struct _io_devctl {
uint16_t type;
uint16_t combine_len;
int32_t dcmd;
int32_t nbytes;
int32_t zero;
};
struct _io_devctl_reply {
uint32_t zero;
int32_t ret_val;
int32_t nbytes;
int32_t zero2;
};
typedef union {
struct _io_devctl i;
struct _io_devctl_reply o;
} io_devctl_t;
说明:执行作为从客户端的通过的装置I / O操作devctl()中的dcmd。客户端将一个方向编码为dcmd的高两位,指示*devctl()*如何传输数据(“ to”字段引用_POSIX_DEVDIR_TO位;“ from”字段引用_POSIX_DEVDIR_FROM位):
到现场 | 从现场 | 含义 |
---|---|---|
0 | 0 | 没有数据传输 |
0 | 1个 | 从驱动程序转移到客户端 |
1个 | 0 | 从客户端转移到驱动程序 |
1个 | 1个 | 双向转移 |
在不进行数据传输的情况下,希望驱动程序仅执行dcmd中给出的命令。在进行数据传输的情况下,希望驱动程序使用辅助函数resmgr_msgreadv()和resmgr_msgwritev()从客户端和/或向客户端传输数据。客户端以nbytes成员指示传输的大小;驱动程序将传出结构的nbytes成员设置为传输的字节数。
请注意,输入和输出数据结构使用零填充,以便它们彼此对齐。这意味着隐式数据区域在输入和输出结构中的相同地址处开始。
如果使用帮助程序例程iofunc_devctl(),请注意在无法使用devctl()消息执行任何操作的情况下,它将返回常量_RESMGR_DEFAULT 。该返回值用于将合法的errno返回值与“无法识别的命令”返回值分离。收到_RESMGR_DEFAULT后,基础层库将以ENOSYS 的错误响应,客户端的*devctl()*库函数会将其转换为ENOTTY(这是“正确的” POSIX值)。
根据操作来检查打开模式取决于您自己;客户端的devctl() 库或资源管理器库中的任何地方都不会进行检查。例如,可以以只读方式打开资源管理器,然后向其发出*devctl()*告诉其“格式化硬盘”(这是非常“写”的操作)。在继续进行操作之前,先验证打开模式是明智的。
请注意,您可以使用的dcmd值范围是有限的(QSS保留了0x0000至0x0FFF)。其他值可能正在使用中。浏览名称为的包含文件<sys/dcmd_*.h>
。
返回值:通过帮助程序宏_RESMGR_STATUS和答复缓冲区的状态(如果需要,还包含答复数据)。
例如,请看下面的“一个简单的*io_devctl()*示例”。
int io_dup (resmgr_context_t **ctp*, io_dup_t **msg*, RESMGR_OCB_T **ocb*)
分类:I / O
默认处理程序:NULL —由基础层处理
辅助功能:无
客户端功能:dup(), dup2(),fcntl(),fork(), spawn *(),vfork()
讯息:_IO_DUP
数据结构:
struct _io_dup {
uint16_t type;
uint16_t combine_len;
struct _msg_info info;
uint32_t reserved;
uint32_t key;
};
typedef union {
struct _io_dup i;
} io_dup_t;
说明:这是*dup()*消息处理程序。与 io_close_dup()一样,您自己也不太可能处理该消息。相反,基础层库将对其进行处理。
返回:通过辅助宏_RESMGR_STATUS的状态。
int io_fdinfo (resmgr_context_t **ctp*, io_fdinfo_t **msg*, RESMGR_OCB_T **ocb*)
分类:I / O
默认处理程序:iofunc_fdinfo_default()
辅助函数:iofunc_fdinfo()
客户端功能:iofdinfo()
讯息:_IO_FDINFO
数据结构:
struct _io_fdinfo {
uint16_t type;
uint16_t combine_len;
uint32_t flags;
int32_t path_len;
uint32_t reserved;
};
struct _io_fdinfo_reply {
uint32_t zero [2];
struct _fdinfo info;
};
typedef union {
struct _io_fdinfo i;
struct _io_fdinfo_reply o;
} io_fdinfo_t;
Description(说明):此功能用于允许客户端直接检索与文件描述符关联的属性和路径名的信息。使用客户端功能iofdinfo()。路径字符串隐式遵循struct _io_fdinfo_reply
数据结构。对于离散显示的路径名资源管理器,使用默认功能就足够了。
返回值:通过辅助宏_IO_SET_FDINFO_LEN设置要返回的路径字符串的长度。
int io_link (resmgr_context_t **ctp*, io_link_t **msg*, RESMGR_HANDLE_T **handle*, io_link_extra_t **extra*)
分类:连接
默认处理程序:无
辅助函数:iofunc_link()
客户端功能:link()
消息:_IO_CONNECT,其子类型为_IO_CONNECT_LINK
数据结构:
struct _io_connect {
// internal fields (as described above)
uint16_t path_len;
uint8_t extra_type;
uint16_t extra_len;
char path [1];
};
struct _io_connect_link_reply {
uint32_t reserved1 [2];
uint8_t eflag;
uint8_t reserved2 [3];
uint32_t umask;
uint16_t nentries;
uint16_t path_len;
};
typedef union {
struct _io_connect connect;
struct _io_connect_link_reply link_reply;
} io_link_t;
typedef union _io_link_extra {
struct _msg_info info;
void *ocb;
char path [1];
struct _io_resmgr_link_extra resmgr;
} io_link_extra_t;
说明:使用msg的路径成员中给定的名称创建一个新链接,该链接指向extra的路径成员所指定的现有路径名 (传递给您的函数)。为了方便起见,extra的ocb成员 包含一个指向现有路径名的OCB的指针。
返回:通过辅助宏_RESMGR_STATUS的状态。
int io_lock (resmgr_context_t **ctp*, io_lock_t **msg*, RESMGR_OCB_T **ocb*)
分类:I / O
默认处理程序:iofunc_lock_default()
辅助函数:iofunc_lock()
讯息:_IO_LOCK
数据结构:
struct _io_lock {
uint16_t type;
uint16_t combine_len;
uint32_t subtype;
int32_t nbytes;
};
struct _io_lock_reply {
uint32_t zero [3];
};
typedef union {
struct _io_lock i;
struct _io_lock_reply o;
} io_lock_t;
说明:这为设备提供了基于范围的咨询文件锁定。对于大多数资源管理器而言,默认功能很可能已足够。
返回:通过辅助宏_RESMGR_STATUS的状态。
int io_lock_ocb (resmgr_context_t **ctp*, void **reserved*, RESMGR_OCB_T **ocb*)
分类:I / O(由库综合)
默认处理程序:iofunc_lock_ocb_default()
辅助功能:无
客户端功能:全部
消息:无-由库综合
数据结构:无
说明:此函数负责锁定OCB指向的属性结构。这样做是为了确保一次仅一个线程同时在OCB和相应的属性结构上运行。锁定(和相应的解锁)功能由资源管理器库在完成消息处理之前和之后进行综合。有关更多详细信息,请参见上面的“合并消息”部分。您几乎永远不会自己使用此电话。而是使用POSIX层默认功能。
返回:通过辅助宏_RESMGR_STATUS的状态。
int io_lseek (resmgr_context_t **ctp*, io_lseek_t **msg*, RESMGR_OCB_T **ocb*)
分类:I / O
默认处理程序:iofunc_lseek_default()
辅助函数:iofunc_lseek()
客户端函数:lseek(),fseek(),rewinddir()
讯息:_IO_LSEEK
数据结构:
struct _io_lseek {
uint16_t type;
uint16_t combine_len;
short whence;
uint16_t zero;
uint64_t offset;
};
typedef union {
struct _io_lseek i;
uint64_t o;
} io_lseek_t;
说明:处理客户端的lseek()函数。请注意,处理目录的资源管理器还需要解释_IO_LSEEK消息以进行目录操作。在那里和偏移参数从客户传递lseek的()函数。该程序应调整OCB的 偏移解释后的参数那里 和偏移从消息参数,而应返回新的偏移或错误。
返回:通过辅助宏_RESMGR_STATUS的状态,以及可选的(如果没有错误,并且如果不是合并消息的一部分)当前的偏移量。
int io_mknod (resmgr_context_t **ctp*, io_mknod_t **msg*, RESMGR_HANDLE_T **handle*, void **reserved*)
分类:连接
默认处理程序:无
辅助函数:iofunc_mknod()
客户端函数:mknod(),mkdir(),mkfifo()
消息:_IO_CONNECT,子类型_IO_CONNECT_MKNOD
数据结构:
struct _io_connect {
// internal fields (as described above)
uint16_t path_len;
uint8_t extra_type;
uint16_t extra_len;
char path [1];
};
struct _io_connect_link_reply {
uint32_t reserved1 [2];
uint8_t eflag;
uint8_t reserved2 [3];
uint32_t umask;
uint16_t nentries;
uint16_t path_len;
};
typedef union {
struct _io_connect connect;
struct _io_connect_link_reply link_reply;
} io_mknod_t;
说明:创建一个新的文件系统入口点。该消息被发送到创建一个文件,由命名为路径构件,利用在编码文件类型模式构件(来自的“内部字段”的一部分struct _io_connect
的结构,未示出)。
实际上,这仅用于mkfifo(),*mkdir()和mknod()*客户端函数。
返回:通过辅助宏_RESMGR_STATUS的状态。
int io_mmap (resmgr_context_t **ctp*, io_mmap_t **msg*, RESMGR_OCB_T **ocb*)
分类:I / O
默认处理程序:iofunc_mmap_default()
辅助函数:iofunc_mmap()
客户端函数:mmap(), munmap(),mmap_device_io(),mmap_device_memory()
讯息:_IO_MMAP
数据结构:
struct _io_mmap {
uint16_t type;
uint16_t combine_len;
uint32_t prot;
uint64_t offset;
struct _msg_info info;
uint32_t zero [6];
};
struct _io_mmap_reply {
uint32_t zero;
uint32_t flags;
uint64_t offset;
int32_t coid;
int32_t fd;
};
typedef union {
struct _io_mmap i;
struct _io_mmap_reply o;
} io_mmap_t;
说明:允许进程管理器从您的资源管理器中*mmap()文件。通常,除非您特别希望禁用此功能(例如,串行端口驱动程序可以选择返回ENOSYS,因为它不这样做),否则您不应该自己编写此函数(使用iofunc_func_init()*提供的默认值—默认处理程序)。支持此操作是有意义的)。
只有流程管理器将调用此资源管理器功能。
请注意,流程管理器调用此函数的副作用是将创建OCB(即,将调用iofunc_ocb_calloc()),但这不会对正确实现的资源管理器产生任何影响。
返回:通过辅助宏_RESMGR_STATUS的状态。
int io_mount (resmgr_context_t **ctp*, io_mount_t **msg*, RESMGR_HANDLE_T **handle*, io_mount_extra_t **extra*)
分类:连接
默认处理程序:无
辅助功能:无
消息:具有_IO_CONNECT_MOUNT子类型的_IO_CONNECT。
数据结构:
struct _io_connect {
// internal fields (as described above)
uint16_t path_len;
uint8_t extra_type;
uint16_t extra_len;
char path [1];
};
struct _io_connect_link_reply {
uint32_t reserved1 [2];
uint8_t eflag;
uint8_t reserved2 [3];
uint32_t umask;
uint16_t nentries;
uint16_t path_len;
};
typedef union {
struct _io_connect connect;
struct _io_connect_link_reply link_reply;
} io_mount_t;
描述:每当*mount()*或 umount()客户端函数向资源管理器发送消息时,都会调用此函数。有关io_mount处理程序的更多信息,请参见“ 编写资源管理器”中的“处理其他消息”一章中的“ 处理mount() ”。
返回:通过辅助宏_IO_SET_CONNECT_RET返回状态。
int io_msg (resmgr_context_t **ctp*, io_msg_t **msg*, RESMGR_OCB_T **ocb*)
分类:I / O
默认处理程序:无。
辅助功能:无。
客户端功能:无-手动组装并通过MsgSend()发送
讯息:_IO_MSG
数据结构:
struct _io_msg {
uint16_t type;
uint16_t combine_len;
uint16_t mgrid;
uint16_t subtype;
};
typedef union {
struct _io_msg i;
} io_msg_t;
说明:_IO_MSG接口是ioctl() / devctl()主题的更通用但更不便于移植的变体。该MGRID是用来识别特定的经理-你不应该对那些不符合你的经理ID的请求的行动。该亚型有效的是,在客户端希望执行该命令。任何隐式传输的数据都遵循输入结构。返回给客户端的数据将单独发送,其状态通过_RESMGR_STATUS返回。您可以从QSS获取“经理ID”。
返回:通过辅助宏_RESMGR_STATUS的状态。
int io_notify (resmgr_context_t **ctp*, io_notify_t **msg*, RESMGR_OCB_T **ocb*)
分类:I / O
默认处理程序:无
辅助函数:iofunc_notify(),iofunc_notify_remove(),iofunc_notify_trigger()
客户端功能:select(),ionotify()
讯息:_IO_NOTIFY
数据结构:
struct _io_notify {
uint16_t type;
uint16_t combine_len;
int32_t action;
int32_t flags;
struct sigevent event;
};
struct _io_notify_reply {
uint32_t zero;
uint32_t flags;
};
typedef union {
struct _io_notify i;
struct _io_notify_reply o;
} io_notify_t;
说明:该处理程序负责安装,轮询或删除通知处理程序。该动作和标志 确定的那种通知操作和条件; 该事件是一个struct sigevent
定义了通知事件(如果有的话),客户端希望与用信号发送的结构。您可以使用MsgDeliverEvent()或iofunc_notify_trigger()函数将事件传递给客户端。
返回:通过帮助宏_RESMGR_STATUS的状态;这些标志通过消息回复返回。
int io_open (resmgr_context_t **ctp*, io_open_t **msg*, RESMGR_HANDLE_T **handle*, void **extra*)
分类:连接
默认处理程序:iofunc_open_default()
辅助函数:iofunc_open(),iofunc_ocb_attach()
客户端函数:open(), fopen(),sopen()(及其他)
消息:具有_IO_CONNECT_COMBINE,_IO_CONNECT_COMBINE_CLOSE或_IO_CONNECT_OPEN子类型之一的_IO_CONNECT。
数据结构:
struct _io_connect {
// internal fields (as described above)
uint16_t path_len;
uint8_t extra_type;
uint16_t extra_len;
char path [1];
};
struct _io_connect_link_reply {
uint32_t reserved1 [2];
uint8_t eflag;
uint8_t reserved2 [3];
uint32_t umask;
uint16_t nentries;
uint16_t path_len;
};
typedef union {
struct _io_connect connect;
struct _io_connect_link_reply link_reply;
} io_open_t;
说明:这是资源管理器的主要入口点。它检查客户端是否确实具有打开文件的适当权限,将OCB绑定到内部库结构(通过resmgr_bind_ocb()或iofunc_ocb_attach()),然后返回errno。请注意,并非所有输入和输出结构成员都与此功能相关。
返回:通过辅助宏_IO_SET_CONNECT_RET返回状态。
int io_openfd (resmgr_context_t **ctp*, io_openfd_t **msg*, RESMGR_OCB_T **ocb*)
分类:I / O
默认处理程序:iofunc_openfd_default()
辅助函数:iofunc_openfd()
客户端功能:openfd()
讯息:_IO_OPENFD
数据结构:
struct _io_openfd {
uint16_t type;
uint16_t combine_len;
uint32_t ioflag;
uint16_t sflag;
uint16_t reserved1;
struct _msg_info info;
uint32_t reserved2;
uint32_t key;
};
typedef union {
struct _io_openfd i;
} io_openfd_t;
说明:该函数类似于为io_open()提供的处理程序,除了传递一个已经打开的文件描述符而不是路径名(通过在函数调用中传递ocb)。
返回:通过辅助宏_RESMGR_STATUS的状态。
int io_pathconf (resmgr_context_t **ctp*, io_pathconf_t **msg*, RESMGR_OCB_T **ocb*)
分类:I / O
默认处理程序:iofunc_pathconf_default()
辅助函数:iofunc_pathconf()
客户端函数:fpathconf(),pathconf()
讯息:_IO_PATHCONF
数据结构:
struct _io_pathconf {
uint16_t type;
uint16_t combine_len;
short name;
uint16_t zero;
};
typedef union {
struct _io_pathconf i;
} io_pathconf_t;
说明:此消息的处理程序负责返回与此OCB关联的资源的可配置参数名称的值。使用默认功能,并为设备的名称成员添加其他大小写。
返回:通过帮助宏_IO_SET_PATHCONF_VALUE的状态以及通过消息答复的数据。
int io_power (resmgr_context_t **ctp*, io_power_t **msg*, RESMGR_OCB_T **ocb*)
此功能由QSS保留,以备将来使用。您应该使用iofunc_func_init()初始化I / O表,而不要修改该条目。
int io_read (resmgr_context_t **ctp*, io_read_t **msg*, RESMGR_OCB_T **ocb*)
分类:I / O
默认处理程序:iofunc_read_default()
辅助函数:iofunc_read_verify()
讯息:_IO_READ
数据结构:
struct _io_read {
uint16_t type;
uint16_t combine_len;
int32_t nbytes;
uint32_t xtype;
};
typedef union {
struct _io_read i;
} io_read_t;
描述:负责从资源中读取数据。客户端在nbytes输入成员中指定准备读取的字节数。您返回数据,在OCB中提前偏移量,并更新适当的时间字段。
请注意,xtype成员可以指定每个读取消息的覆盖标志。应该对此进行检查。如果不支持任何扩展的覆盖标志,则应返回EINVAL。我们将在下面的io_read()和io_write()示例中看到名为_IO_XTYPE_OFFSET的一个特别重要(且棘手的)替代标志的处理。
还要注意,_IO_READ消息不仅到达常规文件,而且到达目录的内容。您必须确保struct dirent
在目录大小写中返回整数个成员。有关返回目录条目的更多信息,请参阅“ 返回目录条目 ”下“高级主题”部分中的示例。”
应该调用辅助函数iofunc_read_verify()来确定文件是在与读取兼容的模式下打开的。另外,应该调用iofunc_sync_verify()函数以验证是否需要将数据同步到介质。(对于read(),这意味着返回的数据可以保证是媒体上的。)
返回:通过辅助宏_IO_SET_READ_NBYTES读取的字节数或状态,以及通过消息答复的数据本身。
有关仅返回数据的示例,请参见下面的“一个简单的io_read() 示例”。有关返回数据和目录条目的更复杂的示例,请查看“ 返回目录条目 ”下的“高级主题”部分。”
int io_readlink (resmgr_context_t **ctp*, io_readlink_t **msg*, RESMGR_HANDLE_T **handle*, void **reserved*)
分类:连接
默认处理程序:无
辅助函数:iofunc_readlink()
客户端功能:readlink()
消息:_IO_CONNECT,其子类型为_IO_CONNECT_READLINK
数据结构:
struct _io_connect {
// internal fields (as described above)
uint16_t path_len;
uint8_t extra_type;
uint16_t extra_len;
char path [1];
};
struct _io_connect_link_reply {
uint32_t reserved1 [2];
uint8_t eflag;
uint8_t reserved2 [3];
uint32_t umask;
uint16_t nentries;
uint16_t path_len;
};
typedef union {
struct _io_connect connect;
struct _io_connect_link_reply link_reply;
} io_open_t;
描述:负责读取输入结构的路径成员所指定的符号链接的内容。返回的字节是符号链接的内容。返回的状态是回复中的字节数。仅对符号链接应返回有效的返回值。所有其他访问均应返回错误代码。
返回:通过帮助宏_RESMGR_STATUS的状态以及通过消息回复的数据。
int io_rename (resmgr_context_t **ctp*, io_rename_t **msg*, RESMGR_HANDLE_T **handle*, io_rename_extra_t **extra*)
分类:连接
默认处理程序:无
辅助函数:iofunc_rename()
客户端功能:rename()
消息:_IO_CONNECT,其子类型为_IO_CONNECT_RENAME
数据结构:
struct _io_connect {
// internal fields (as described above)
uint16_t path_len;
uint8_t extra_type;
uint16_t extra_len;
char path [1];
};
struct _io_connect_link_reply {
uint32_t reserved1 [2];
uint8_t eflag;
uint8_t reserved2 [3];
uint32_t umask;
uint16_t nentries;
uint16_t path_len;
};
typedef union _io_rename_extra {
char path [1];
} io_rename_extra_t;
typedef union {
struct _io_connect connect;
struct _io_connect_link_reply link_reply;
} io_rename_t;
说明:给定路径中的新名称和传递的额外参数的路径成员中的原始名称,执行重命名操作。实施注意事项:原始名称的 路径名(而不是OCB)是专门为处理硬链接到另一个文件的文件的重命名而指定的。如果提供了OCB,则无法区分硬链接文件的两个(或更多)版本。
仅使用同一文件系统(同一设备)上的两个文件名调用此函数。因此,无需检查是否需要返回EXDEV的情况。如果您不想自己执行rename(),这不会阻止您返回EXDEV (例如,从一个目录到另一个目录进行重命名操作可能非常复杂)。在返回EXDEV的情况下,shell实用程序mv
将执行cp
后跟rm
(C库函数rename()不会执行此操作,它只会返回EXDEV 的错误号)。
同样,在调用此函数之前,所有符号链接都将在适用的情况下得到解决,并且传递的路径名将是绝对的,并且植根于该资源管理器负责的文件系统中。
返回:通过辅助宏_RESMGR_STATUS的状态。
int io_shutdown (resmgr_context_t **ctp*, io_shutdown_t **msg*, RESMGR_OCB_T **ocb*)
此功能由QSS保留,以备将来使用。您应该使用iofunc_func_init()初始化I / O表,而不要修改该条目。
int io_space (resmgr_context_t **ctp*, io_space_t **msg*, RESMGR_OCB_T **ocb*)
分类:I / O
默认处理程序:无
客户端功能:chsize(),fcntl(),ftruncate(),ltrunc()
讯息:_IO_SPACE
数据结构:
struct _io_space {
uint16_t type;
uint16_t combine_len;
uint16_t subtype;
short whence;
uint64_t start;
uint64_t len;
};
typedef union {
struct _io_space i;
uint64_t o;
} io_space_t;
说明:这用于分配或释放资源占用的空间。的子类型参数指示是否分配(如果设置为F_ALLOCSP)或释放(如果设置为F_FREESP)的存储空间。wherece和start的组合给出了分配或释放开始的位置;成员len表示操作的大小。
返回:通过辅助宏_RESMGR_STATUS的字节数(资源大小)。
int io_stat (resmgr_context_t **ctp*, io_stat_t **msg*, RESMGR_OCB_T **ocb*)
分类:I / O
默认处理程序:iofunc_stat_default()
辅助函数:iofunc_stat()
讯息:_IO_STAT
数据结构:
struct _io_stat {
uint16_t type;
uint16_t combine_len;
uint32_t zero;
};
typedef union {
struct _io_stat i;
struct stat o;
} io_stat_t;
描述:处理消息,该消息请求有关与传递的OCB关联的资源的信息。请注意,属性结构包含满足stat()请求所需的所有信息;辅助函数iofunc_stat()struct stat
根据属性结构填充结构。同样,helper函数将存储的dev / rdev成员从单个节点的角度修改为唯一的(对于通过网络对文件执行*stat()*调用很有用)。几乎没有理由为此函数编写自己的处理程序。
返回:通过帮助宏_RESMGR_STATUS和struct stat
通过消息答复的状态。
int io_sync (resmgr_context_t **ctp*, io_sync_t **msg*, RESMGR_OCB_T **ocb*)
分类:I / O
默认处理程序:iofunc_sync_default()
辅助函数:iofunc_sync_verify(),iofunc_sync()
客户端函数:fsync(),fdatasync()
讯息:_IO_SYNC
数据结构:
struct _io_sync {
uint16_t type;
uint16_t combine_len;
uint32_t flag;
};
typedef union {
struct _io_sync i;
} io_sync_t;
说明:这是刷新命令的入口点。帮助函数 iofunc_sync()从输入消息传递给标志成员,并返回以下值之一,这些值指示资源管理器必须执行的操作:
请注意,只有在您同意通过设置安装结构标志来提供同步服务时,才会发生此出局。
返回值:通过辅助宏_RESMGR_STATUS返回状态。
int io_unblock (resmgr_context_t **ctp*, io_pulse_t **msg*, RESMGR_HANDLE_T **handle*, void **reserved*)
分类:Connect(由内核综合,由库综合)
默认处理程序:无
辅助函数:iofunc_unblock()
客户端功能:无-由于信号或超时导致的内核操作
消息:无-由库综合
数据结构:(请参阅io_unblock()的 I / O版本,下一个)
描述:这是unblock outcall的connect消息版本,由于客户端在connect message阶段尝试进行unblock的尝试,因此由内核脉冲库综合了库。有关更多详细信息,请参见*io_unblock()*的I / O版本。
返回:通过辅助宏_RESMGR_STATUS的状态。
请参阅“消息传递”一章中标题为“使用_NTO_MI_UNBLOCK_REQ”的部分,以详细了解解锁策略。
int io_unblock (resmgr_context_t **ctp*, io_pulse_t **msg*, RESMGR_OCB_T **ocb*)
分类:I / O(由内核综合,由库综合)
默认处理程序:iofunc_unblock_default()
辅助函数:iofunc_unblock()
客户端功能:无-由于信号或超时导致的内核操作
消息:无-由库综合
数据结构:指向消息结构的指针被中断
描述:这是解除阻塞调用的I / O消息版本,由于客户端在I / O消息阶段尝试解除阻塞而导致内核脉冲,由库合成。连接消息阶段*io_unblock()*处理程序基本相同(请参见上一节)。
两种解除阻塞处理程序(连接和I / O)的共同点是客户端希望解除阻塞的特性,但受资源管理器的支配。资源管理器必须回复客户端的消息,以解除对客户端的阻止。(当我们查看ChannelCreate()标志,尤其是_NTO_CHF_UNBLOCK标志时,在“ 消息传递”一章中对此进行了讨论)。
返回:通过辅助宏_RESMGR_STATUS的状态。
请参阅“消息传递”一章中标题为“使用_NTO_MI_UNBLOCK_REQ”的部分,以详细了解解锁策略。
int io_unlink (resmgr_context_t **ctp*, io_unlink_t **msg*, RESMGR_HANDLE_T **handle*, void **reserved*)
分类:连接
默认处理程序:无
辅助函数:iofunc_unlink()
客户端功能:unlink()
消息:_IO_CONNECT,其子类型为_IO_CONNECT_UNLINK
数据结构:
struct _io_connect {
// internal fields (as described above)
uint16_t path_len;
uint8_t extra_type;
uint16_t extra_len;
char path [1];
};
struct _io_connect_link_reply {
uint32_t reserved1 [2];
uint8_t eflag;
uint8_t reserved2 [3];
uint32_t umask;
uint16_t nentries;
uint16_t path_len;
};
typedef union {
struct _io_connect connect;
struct _io_connect_link_reply link_reply;
} io_unlink_t;
描述:负责取消链接其路径名在输入消息结构的路径成员中传递的文件。
返回:通过辅助宏_RESMGR_STATUS的状态。
int io_unlock_ocb (resmgr_context_t **ctp*, void **reserved*, RESMGR_OCB_T **ocb*)
分类:I / O(由库综合)
默认处理程序:iofunc_unlock_ocb_default()
辅助功能:无
客户端功能:全部
消息:无-由库综合
数据结构:无
说明:上面的io_lock_ocb()的逆函数。也就是说,它负责解锁OCB指向的属性结构。此操作将释放属性结构,以便资源管理器中的其他线程可以对其进行操作。有关更多详细信息,请参见上面的“合并消息”部分。
返回:通过辅助宏_RESMGR_STATUS的状态。
int io_utime (resmgr_context_t **ctp*, io_utime_t **msg*, RESMGR_OCB_T **ocb*)
分类:I / O
默认处理程序:iofunc_utime_default()
辅助函数:iofunc_utime()
客户端函数:utime()
讯息:_IO_UTIME
数据结构:
struct _io_utime {
uint16_t type;
uint16_t combine_len;
int32_t cur_flag;
struct utimbuf times;
};
typedef union {
struct _io_utime i;
} io_utime_t;
说明:将访问和修改时间更改为“现在”(如果为零)或指定的值。请注意,根据POSIX规则,可能需要此消息处理程序来修改属性结构中的IOFUNC_ATTR_ *标志。您几乎永远不会自己使用此outcall,而将使用POSIX层帮助程序功能。
返回:通过辅助宏_RESMGR_STATUS的状态。
int io_write (resmgr_context_t **ctp*, io_write_t **msg*, RESMGR_OCB_T **ocb*)
分类:I / O
默认处理程序:iofunc_write_default()
讯息:_IO_WRITE
数据结构:
struct _io_write {
uint16_t type;
uint16_t combine_len;
int32_t nbytes;
uint32_t xtype;
};
typedef union {
struct _io_write i;
} io_write_t;
说明:此消息处理程序负责获取客户端写入资源管理器的数据。它传递了客户端尝试在nbytes成员中写入的字节数;数据隐式地遵循输入数据结构(除非xtype 重写为_IO_XTYPE_OFFSET;请参见下面的“简单的io_write() 示例”!)实现将需要使用resmgr_msgreadv()从客户端重新读取消息的数据部分。或同等水平。返回状态是实际写入的字节数或errno。
请注意,应该调用辅助函数iofunc_write_verify()来确定文件是在与写入兼容的模式下打开的。另外,应该调用*iofunc_sync_verify()*函数以验证是否需要将数据同步到介质。
返回:通过辅助宏_IO_SET_WRITE_NBYTES返回状态。
例如,请看下面的“一个简单的*io_write()*示例”。
现在,我将向您展示一些“菜谱”示例,您可以将其剪切并粘贴到代码中,以用作项目的基础。这些都不是完整的资源管理器-你需要添加线程池和调度“骨架”下示,并确保您的I / O功能的版本被放置到I / O功能表*后,*你已经完成了iofunc_func_init(),以覆盖默认值!
我将从许多简单的示例开始,这些示例显示了各种资源管理器消息处理程序的基本功能:
然后,在高级主题部分,我们将看一个*io_read()*返回目录条目。
以下内容可用作具有多个线程的资源管理器的模板。(在上面讨论资源管理器时,我们已经在“ 资源管理器库 ”中看到了可用于单线程资源管理器的模板/dev/null
)。
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <sys/iofunc.h>
#include <sys/dispatch.h>
static resmgr_connect_funcs_t connect_func;
static resmgr_io_funcs_t io_func;
static iofunc_attr_t attr;
main (int argc, char **argv)
{
thread_pool_attr_t pool_attr;
thread_pool_t *tpp;
dispatch_t *dpp;
resmgr_attr_t resmgr_attr;
resmgr_context_t *ctp;
int id;
if ((dpp = dispatch_create ()) == NULL) { fprintf (stderr, "%s: Unable to allocate dispatch context.\n", argv [0]); return (EXIT_FAILURE); } memset (&pool_attr, 0, sizeof (pool_attr)); pool_attr.handle = dpp; pool_attr.context_alloc = dispatch_context_alloc; pool_attr.block_func = dispatch_block; pool_attr.handler_func = dispatch_handler; pool_attr.context_free = dispatch_context_free; // 1) set up the number of threads that you want pool_attr.lo_water = 2; pool_attr.hi_water = 4; pool_attr.increment = 1; pool_attr.maximum = 50; if ((tpp = thread_pool_create (&pool_attr, POOL_FLAG_EXIT_SELF)) == NULL) { fprintf (stderr, "%s: Unable to initialize thread pool.\n", argv [0]); return (EXIT_FAILURE); } iofunc_func_init (_RESMGR_CONNECT_NFUNCS, &connect_func, _RESMGR_IO_NFUNCS, &io_func); iofunc_attr_init (&attr, S_IFNAM | 0777, 0, 0); // 2) override functions in "connect_func" and "io_func" as // required here memset (&resmgr_attr, 0, sizeof (resmgr_attr)); resmgr_attr.nparts_max = 1; resmgr_attr.msg_max_size = 2048; // 3) replace "/dev/whatever" with your device name if ((id = resmgr_attach (dpp, &resmgr_attr, "/dev/whatever", _FTYPE_ANY, 0, &connect_func, &io_func, &attr)) == -1) { fprintf (stderr, "%s: Unable to attach name.\n", argv [0]); return (EXIT_FAILURE); } // Never returns thread_pool_start (tpp);
}
有关调度接口(即dispatch_create()函数)的更多信息,请参见Neutrino库参考中的文档。
在这里,您将使用线程池功能创建线程池,这些线程池将能够为资源管理器中的消息提供服务。通常,我建议您从单线程资源管理器开始,就像我们/dev/null
上面提到的示例那样。一旦你的基本功能运行,可以再增加线程。您将按照“线程与进程”一章中的描述,修改pool_attr结构的lo_water,hi_water,增量和maximum成员,在该章中我们将讨论线程池功能。
在这里,你会添加任何功能,你要提供。这些是我们刚刚讨论的结果(例如io_read(),*io_devctl()等)。例如,要为_IO_READ消息添加自己的处理程序,该处理程序指向由您提供的名为my_io_read()*的函数,则需要添加以下代码行:
io_func.io_read = my_io_read;
这将覆盖iofunc_func_init()使用指向函数*my_io_read()*的指针放入表中的POSIX层默认函数。
您可能不希望您的资源管理器叫/dev/whatever
,所以您应该选择一个适当的名称。请注意,在这里resmgr_attach()函数将属性结构(attr参数)绑定到名称上-如果希望由资源管理器处理多个设备,则可以使用不同的属性结构多次调用resmgr_attach()。(这样您就可以在运行时区分不同的注册名称)。
为了说明您的资源管理器如何将数据返回给客户端,请考虑一个始终返回常量字符串的简单资源管理器 "Hello, world!\n"
。即使在这种非常简单的情况下,也涉及许多问题:
在我们的例子中,资源管理器返回一个14字节的固定字符串-确实有那么多数据可用。这与包含所讨论字符串的磁盘上的只读文件相同。唯一真正的不同是,该“文件”通过以下语句在我们的C程序中维护:
char *data_string = "Hello, world!\n";
另一方面,客户端可以发出 任何大小的read()请求-客户端可以要求一个字节,14个字节或更多。这对您将要提供的*io_read()*功能的影响是,您必须能够使客户端请求的数据大小与可用数据大小匹配。
处理客户端数据区域大小注意事项的方式的自然结果是处理固定字符串上的文件结尾(EOF)的特殊情况。一旦客户端读取了最后的“ \n
”字符,客户端进一步尝试读取更多数据应返回EOF。
“数据区域大小注意事项”和“处理EOF情况”方案都需要在传递给io_read() 函数的OCB中维护上下文,尤其是偏移成员。
最后一项考虑:从资源管理器读取数据时,需要更新POSIX 访问时间(atime)变量。这样,客户端stat() 函数将显示确实有人访问过该设备。
这是解决以上所有问题的代码。在下面的讨论中,我们将逐步介绍它:
/*
* io_read1.c
*/
#include <stdio.h>
#include <errno.h>
#include <sys/neutrino.h>
#include <sys/iofunc.h>
// our data string
char *data_string = “Hello, world!\n”;
int
io_read (resmgr_context_t *ctp, io_read_t *msg, iofunc_ocb_t *ocb)
{
int sts;
int nbytes;
int nleft;
int off;
int xtype;
struct _xtype_offset *xoffset;
// 1) verify that the device is opened for read if ((sts = iofunc_read_verify (ctp, msg, ocb, NULL)) != EOK) { return (sts); } // 2) check for and handle an XTYPE override xtype = msg -> i.xtype & _IO_XTYPE_MASK; if (xtype == _IO_XTYPE_OFFSET) { xoffset = (struct _xtype_offset *) (&msg -> i + 1); off = xoffset -> offset; } else if (xtype == _IO_XTYPE_NONE) { off = ocb -> offset; } else { // unknown, fail it return (ENOSYS); } // 3) how many bytes are left? nleft = ocb -> attr -> nbytes - off; // 4) how many bytes can we return to the client? nbytes = min (nleft, msg -> i.nbytes); // 5) if returning data, write it to client if (nbytes) { MsgReply (ctp -> rcvid, nbytes, data_string + off, nbytes); // 6) set up POSIX stat() "atime" data ocb -> attr -> flags |= IOFUNC_ATTR_ATIME | IOFUNC_ATTR_DIRTY_TIME; // 7) advance the lseek() index by the number of bytes // read if not _IO_XTYPE_OFFSET if (xtype == _IO_XTYPE_NONE) { ocb -> offset += nbytes; } } else { // 8) not returning data, just unblock client MsgReply (ctp -> rcvid, EOK, NULL, 0); } // 9) indicate we already did the MsgReply to the library return (_RESMGR_NOREPLY);
}
在这里,我们确保客户端的open() 调用实际上已经指定要打开该设备以进行读取。如果客户端打开该设备仅用于写入,然后尝试对其进行读取,则将其视为错误。在这种情况下,辅助函数iofunc_read_verify()将返回EBADF,而不是EOK,因此我们将该值返回给库,然后将其传递给客户端。
在这里,我们检查了客户端是否指定了*xtype-override-*每个消息的覆盖(例如,因为在非阻塞模式下打开设备时,这为此请求指定了我们想要的阻塞行为) 。请注意,“ xtype”替代的阻塞方面可以通过iofunc_read_verify()函数的最后一个参数来指出-因为我们在说明一个非常简单的示例,我们只是传入了NULL表示我们不在乎此方面。
但是,更重要的是查看如何处理特定的“ xtype”修饰符。一个有趣的是_IO_XTYPE_OFFSET改性剂,其中,如果存在,则指示从客户端传递该消息包含的偏移和该读操作不修改该文件描述符的“当前文件位置”(这是所使用的函数PREAD (),例如)。如果_IO_XTYPE_OFFSET修饰符不存在,则读取操作可以继续进行,并修改“当前文件位置”。我们使用变量xtype来存储在消息中收到的“ xtype”,而变量off代表我们在处理过程中应该使用的当前偏移量。在步骤7中,您将在下面看到_IO_XTYPE_OFFSET修饰符的一些其他处理。
如果存在与_IO_XTYPE_OFFSET不同的“ xtype覆盖”(而不是_IO_XTYPE_NONE的无操作之一),则我们将使用ENOSYS来使请求失败。这只是意味着我们不知道如何处理它,因此我们将错误返回给客户端。
要计算实际上可以返回给客户端的字节数,我们执行步骤3和4,计算出设备上有多少字节可用(通过从设备中ocb -> attr -> nbytes
减去设备的总大小并减去当前偏移量得出)。一旦知道剩下多少字节,就取那个数目和客户端指定他们希望读取的字节数中的较小者。例如,我们可能还剩下七个字节,而客户端只想读取两个字节。在这种情况下,我们只能将两个字节返回给客户端。或者,如果客户端需要4096个字节,但只剩下7个字节,则只能返回7个字节。
现在我们已经计算了要返回给客户端的字节数,我们需要根据是否要返回数据来做不同的事情。如果我们要返回数据,则在步骤5中进行检查后,我们将数据与客户端进行回复。注意,我们使用data_string + off
返回的数据以正确的偏移量开始(off是基于xtype覆盖计算的)。还要注意MsgReply()的第二个参数-它被记录为status 参数,但是在这种情况下,我们使用它来返回字节数。这是因为客户端的read()函数的实现知道其MsgSendv()的返回值(即状态顺便说一下,MsgReply()的参数 是已读取的字节数。这是一个常见的约定。
由于我们要从设备返回数据,因此我们知道设备已被访问。我们 在属性结构的标志成员中设置IOFUNC_ATTR_ATIME和IOFUNC_ATTR_DIRTY_TIME位。这提醒io_stat()函数访问时间无效,应在回复之前从系统时钟中获取访问时间。如果确实需要,我们可以将当前时间填充到属性结构的atime成员中,并清除IOFUNC_ATTR_DIRTY_TIME标志。但这不是很有效,因为我们期望从客户端获得的更多*read()请求比stat()*请求更多。但是,您的使用模式可能另有规定。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g1G9Yjtz-1607647576690)(…/…/…/…/…/…/…/…/pointing.gif)] | 因此,这时候客户是否看到当它终于做呼叫统计() ?资源管理器库提供的iofunc_stat_default()函数将查看属性结构的flags成员,以查看时间是否有效(atime,ctime和mtime字段)。如果不是(例如,在我们的*io_read()被调用返回的数据之后的情况),那么iofunc_stat_default()函数将使用当前时间更新时间。正如您所期望的,时间的真实值还会在close()*上更新。 |
---|---|
现在,仅当我们不处理IO_XTYPE_OFFSET覆盖修饰符时,才将lseek()偏移量增加返回给客户端的字节数*。*这样可以确保在非 IO_XTYPE_OFFSET的情况下,如果客户端调用lseek()获取当前位置,或者(更重要的是)当客户端调用read()获取接下来的几个字节时,将设置资源的偏移量到正确的值。在_IO_XTYPE_OFFSET覆盖的情况下,我们仅保留ocb 版本的偏移量。
将步骤6与该步骤进行对比。在这里,我们只取消阻止客户端,我们不执行任何其他功能。还请注意,没有为*MsgReply()*指定任何数据区域,因为我们没有返回数据。
最后,在步骤9中,无论是否将数据返回给客户端,我们都会执行通用的处理。由于我们已经通过MsgReply()解除了对客户端的限制,我们当然不希望资源管理器库为我们这样做,因此我们告诉我们已经通过返回_RESMGR_NOREPLY来做到这一点。
您会从“消息传递”一章中回想起,我们讨论了其他一些消息传递函数-MsgWrite(),MsgWritev()和MsgReplyv()。我在这里再次提及它们的原因是因为您的io_read()功能可能处于使用这些功能的最佳位置。在上面显示的简单示例中,我们从一个内存位置返回了一个连续的字节数组。在现实世界中,您可能需要从已分配的各种缓冲区中返回多个数据。一个典型的例子是环形缓冲区,可以在串行设备驱动程序中找到它。部分数据可能在缓冲区的末尾附近,而其余数据则“包装”到缓冲区的顶部。在这种情况下,您需要对MsgReplyv()使用两部分的IOV返回两个部分。IOV的第一部分将包含数据底部的地址(和长度),而IOV的第二部分将包含数据顶部的地址(和长度)。或者,如果数据要分批到达,则可以选择使用*MsgWrite()或MsgWritev()在数据到达时将其放入客户端的地址空间,然后指定最终的MsgReply()或MsgReplyv()来解除阻止客户端。正如我们在上面看到的,不需要使用MsgReply()*函数实际 传输数据-您可以使用它来简单地解除阻止客户端。
所述IO_READ()的例子是相当简单的; 让我们看一下io_write()。*io_write()要克服的主要障碍是访问数据。由于资源管理器库从客户端读取了消息的一小部分,因此客户端发送的数据内容(紧接在_IO_WRITE标头之后)可能仅部分到达了io_write()*函数。为了说明这一点,请考虑客户端写入1兆字节-资源管理器库仅读取数据的标头和几个字节。其余兆字节的数据仍可在客户端使用-资源管理器可以随意访问它。
实际上有两种情况需要考虑:
但是,真正的设计决定是“尝试保存已经存在的数据的内核副本有多少麻烦?” 答案是,这不值得。有许多的原因:
我认为前两点是不言而喻的。第三点值得澄清。假设客户向我们发送了大量数据,而我们 确实决定尝试保存已到达的部分数据是个好主意。不幸的是,这部分很小。这意味着我们不能将大块全部作为一个连续的字节数组处理,而必须将其作为一小部分加上其余部分来处理。有效地,我们必须对一小部分进行“特殊处理”,这可能会影响处理数据的代码的整体效率。这可能会导致头痛,所以请不要这样做!
因此,真正的答案是简单地将数据重新读入您准备好的缓冲区。在我们简单的*io_write()*示例中,我每次都将使用 *malloc()缓冲区,将数据读入缓冲区,然后通过free()*释放缓冲区。诚然,肯定有更有效的分配和管理缓冲区的方法!
*io_write()示例中引入的另一个问题是_IO_XTYPE_OFFSET修饰符(以及相关数据;其处理方式与io_read()*示例略有不同)。
这是代码:
/*
* io_write1.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/neutrino.h>
#include <sys/iofunc.h>
void
process_data (int offet, void *buffer, int nbytes)
{
// do something with the data
}
int
io_write (resmgr_context_t *ctp, io_write_t *msg,
iofunc_ocb_t *ocb)
{
int sts;
int nbytes;
int off;
int start_data_offset;
int xtype;
char *buffer;
struct _xtype_offset *xoffset;
// verify that the device is opened for write if ((sts = iofunc_write_verify (ctp, msg, ocb, NULL)) != EOK) { return (sts); } // 1) check for and handle an XTYPE override xtype = msg -> i.xtype & _IO_XTYPE_MASK; if (xtype == _IO_XTYPE_OFFSET) { xoffset = (struct _xtype_offset *) (&msg -> i + 1); start_data_offset = sizeof (msg -> i) + sizeof (*xoffset); off = xoffset -> offset; } else if (xtype == _IO_XTYPE_NONE) { off = ocb -> offset; start_data_offset = sizeof (msg -> i); } else { // unknown, fail it return (ENOSYS); } // 2) allocate a buffer big enough for the data nbytes = msg -> i.nbytes; if ((buffer = malloc (nbytes)) == NULL) { return (ENOMEM); } // 3) (re-)read the data from the client if (resmgr_msgread (ctp, buffer, nbytes, start_data_offset) == -1) { free (buffer); return (errno); } // 4) do something with the data process_data (off, buffer, nbytes); // 5) free the buffer free (buffer); // 6) set up the number of bytes for the client's "write" // function to return _IO_SET_WRITE_NBYTES (ctp, nbytes); // 7) if any data written, update POSIX structures and OCB offset if (nbytes) { ocb -> attr -> flags |= IOFUNC_ATTR_MTIME | IOFUNC_ATTR_DIRTY_TIME; if (xtype == _IO_XTYPE_NONE) { ocb -> offset += nbytes; } } // 8) tell the resource manager library to do the reply, and that it // was okay return (EOK);
}
如您所见,执行的一些初始操作与*io_read()*示例中进行的操作相同— iofunc_write_verify()与iofunc_read_verify()函数类似,并且xtype覆盖检查相同。
在这里,我们对“ xtype覆盖”执行了与io_read()示例相同的处理,不同之处在于偏移量未存储为传入消息结构的一部分。之所以不存储它,是因为一种常见的做法是使用传入消息结构的大小来确定从客户端传输的实际数据的起点。我们要尽力确保在xtype处理代码中数据开头的偏移量(doffset)是正确的。
在这里,我们为数据分配了足够大的缓冲区。客户端正在写入的字节数 在msg联合的nbytes成员中显示给我们。客户端的C库在*write()例程中自动将其填充。请注意,如果我们没有足够的内存来处理malloc()*请求,则会将错误号ENOMEM返回给客户端-有效地,我们会将返回码传递给客户端,以使其知道为什么其请求未被处理。 t完成。
在这里,我们使用辅助函数resmgr_msgread()从客户端将整个数据内容直接读取到新分配的缓冲区中。在大多数情况下,我们可能只使用了MsgRead(),但是在此消息是“组合消息”的一部分的情况下,*resmgr_msgread()为我们执行适当的“魔术”(有关更多信息,请参见“组合消息”部分)。 (为什么需要这样做。)resmgr_msgread()的参数非常简单;我们为其提供内部上下文指针(ctp),要将数据放入其中的缓冲区(buffer)以及希望读取的字节数(nbytes消息msg联盟的成员)。最后一个参数是当前消息中我们在步骤1中计算出的偏移量。该偏移量有效地跳过了客户端的C库实现的write()*放置在其中的标头信息,并直接进行数据处理。这实际上带来了两个有趣的观点:
在这里,您可以对数据进行任何所需的操作-我刚刚调用了一个名为process_data()的虚函数,并向其传递了缓冲区和大小。
这一步至关重要!忘记这样做很容易,并且会导致“内存泄漏”。请注意,如果在步骤3中出现故障,我们还应如何小心释放内存。
我们正在使用宏*_IO_SET_WRITE_NBYTES()*(请参阅Neutrino库参考中的iofunc_write_verify()条目)来存储已写入的字节数,然后将其作为客户端的返回值传递回客户端。write()。重要的是要注意您应该返回 实际的字节数!客户依赖于此。
现在,我们对stat(),lseek()和进一步的write()函数进行类似的内务处理,就像对io_read()例程所做的一样(同样,仅在不为_IO_XTYPE_OFFSET 的情况下,才修改ocb中的偏移量消息类型)。但是,由于我们正在写入设备,因此我们使用IOFUNC_ATTR_MTIME常量而不是IOFUNC_ATTR_ATIME常量。MTIME标志表示“修改”时间,对资源的*write()*当然可以对其进行“修改”。
最后一步很简单:我们返回常量EOK,该常量告诉资源管理器库它应该回复客户端。这样就结束了我们的处理。资源管理器将在回复中使用通过*_IO_SET_WRITE_NBYTES()宏保存的字节数,客户端将取消阻塞;客户端的C库write()*函数将返回由我们的设备写入的字节数。
客户端的devctl()调用正式定义为:
#include <sys/types.h>
#include <unistd.h>
#include <devctl.h>
int
devctl (int fd,
int dcmd,
void *dev_data_ptr,
size_t nbytes,
int *dev_info_ptr);
在研究事物的资源管理器方面之前,我们应该首先了解此功能。所述*devctl()函数是用于“带外”或“对照”操作。例如,您可能正在将数据写入声卡(声卡应将其转换为模拟音频的实际数字音频样本),并且可能决定需要将通道数从1(单声道)更改为2(立体声),或从CD标准(44.1 kHz)到DAT标准(48 kHz)的采样率。该devctl()函数是合适的方式来做到这一点。编写资源管理器时,您可能会发现根本不需要任何devctl()支持,并且只需通过标准read()和write()函数。另一方面,您可能会发现需要将devctl()*调用与 read()和write()调用混合使用,或者实际上您的设备仅使用devctl()函数,而不 使用read()或write() )。
该*devctl()*函数有这些参数:
fd
您要将devctl()发送到的资源管理器的文件描述符。
dcmd
命令本身-包含两个方向的值和30个命令的值的组合(请参见下面的讨论)。
dev_data_ptr
指向可以发送到,从接收到或从两者接收到的数据区域的指针。
字节数
dev_data_ptr数据区域的大小。
dev_info_ptr
可以由资源管理器设置的附加信息变量。
dcmd中的高两位编码数据传输的方向(如果有)。有关详细信息,请参见“ I / O参考”部分中的说明(在io_devctl()下)。
当资源管理器收到_IO_DEVCTL消息时,它由*io_devctl()*函数处理。这是一个非常简单的示例,我们假设该示例用于设置上面讨论的音频设备的声道数和采样率:
/*
* io_devctl1.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/neutrino.h>
#include <sys/iofunc.h>
#define DCMD_AUDIO_SET_CHANNEL_MONO 1
#define DCMD_AUDIO_SET_CHANNEL_STEREO 2
#define DCMD_AUDIO_SET_SAMPLE_RATE_CD 3
#define DCMD_AUDIO_SET_SAMPLE_RATE_DAT 4
int
io_devctl (resmgr_context_t *ctp, io_devctl_t *msg,
iofunc_ocb_t *ocb)
{
int sts;
// 1) see if it's a standard POSIX-supported devctl() if ((sts = iofunc_devctl_default (ctp, msg, ocb)) != _RESMGR_DEFAULT) { return (sts); } // 2) see which command it was, and act on it switch (msg -> i.dcmd) { case DCMD_AUDIO_SET_CHANNEL_MONO: audio_set_nchannels (1); break; case DCMD_AUDIO_SET_CHANNEL_STEREO: audio_set_nchannels (2); break; case DCMD_AUDIO_SET_SAMPLE_RATE_CD: audio_set_samplerate (44100); break; case DCMD_AUDIO_SET_SAMPLE_RATE_DAT: audio_set_samplerate (48000); break; // 3) in case it's a command that we don't recognize, fail it default: return (ENOSYS); } // 4) tell the client it worked memset (&msg -> o, 0, sizeof (msg -> o)); SETIOV (ctp -> iov, &msg -> o, sizeof (msg -> o)); return (_RESMGR_NPARTS (1));
}
在第一步中,我们再次看到辅助函数的使用,这次是iofunc_devctl_default(),该函数用于对devctl()函数执行所有默认处理。如果您不提供自己的io_devctl(),而只是让iofunc_func_init()初始化I / O并为您连接函数表,则将调用iofunc_devctl_default()函数。我们将其包含在io_devctl()函数中,因为我们希望它为我们处理所有常规POSIX devctl() 情况。我们检查返回值;如果不是_RESMGR_DEFAULT,则表示iofunc_devctl_default() 函数“处理”了请求,因此我们仅将其返回值作为我们的返回值传递。
如果常量_RESMGR_DEFAULT 是返回值,那么我们知道helper函数没有处理该请求,我们应该检查它是否是我们的请求之一。
该检查是通过switch/case
语句在步骤2中完成的。我们只是比较客户端代码将填充到devctl()的第二个参数中的dcmd值, 以查看是否存在匹配项。请注意,我们调用虚拟函数audio_set_nchannels()和audio_set_samplerate()来完成客户端的实际“工作”。这里应该提到的一个重要注意事项是,我们特别避免接触devctl()的数据区域方面—您可能会想,“如果我想将采样率设置为任意数字n怎么办?那?” 这将在下面的下一个*io_devctl()*示例中得到回答。
这一步只是好的防御性编程。我们返回ENOSYS的错误代码来告诉客户端我们不理解他们的请求。
最后,我们清除返回结构并设置一个由一部分组成的IOV指向它。然后,我们将一个值返回到由宏_RESMGR_NPARTS()编码的资源管理器库,告诉我们正在返回一个IOV部件。然后将其返回给客户端。我们也可以使用 _RESMGR_PTR()宏:
// instead of this
// 4) tell the client it worked
memset (&msg -> o, 0, sizeof (msg -> o));
SETIOV (ctp -> iov, &msg -> o, sizeof (msg -> o));
return (_RESMGR_NPARTS (1));
// we could have done this
// 4) tell the client it worked
memset (&msg -> o, 0, sizeof (msg -> o));
return (_RESMGR_PTR (ctp, &msg -> o, sizeof (msg -> o)));
我们在此处清除返回结构(而不是在*io_read()或io_write()示例中)的原因是,在这种情况下,返回结构具有实际内容!(在io_read()情况下,返回的唯一数据是数据本身和读取的字节数-没有“返回数据结构”,在io_write()*情况下,返回的唯一数据是写入的字节数)
在上面的上一个*io_devctl()示例中,我们提出了如何设置任意采样率的问题。显然,创建大量DCMD_AUDIO_SET_SAMPLE_RATE_ 常量不是一个好的解决方案-我们会很快用完dcmd成员中的可用位。
在客户端,我们将使用dev_data_ptr指针指向采样率,我们将简单地将其作为整数传递。因此, nbytes成员将只是整数的字节数(在32位计算机上为4)。我们假设为此定义了常量DCMD_AUDIO_SET_SAMPLE_RATE。
另外,我们希望能够读取当前的采样率。如上所述,我们还将使用dev_data_ptr和nbytes,但方向相反-资源管理器会将数据返回到dev_data_ptr指向的内存位置(对于nbytes),而不是从该内存位置获取数据。假设为此定义了常量DCMD_AUDIO_GET_SAMPLE_RATE。
让我们看看资源管理器的io_devctl()中发生了什么,如下所示(我们不会讨论上一个示例中已经讨论过的事情):
/*
* io_devctl2.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <devctl.h>
#include <sys/neutrino.h>
#include <sys/iofunc.h>
#define DCMD_AUDIO_SET_SAMPLE_RATE 1
#define DCMD_AUDIO_GET_SAMPLE_RATE 2
int
io_devctl (resmgr_context_t *ctp, io_devctl_t *msg,
iofunc_ocb_t *ocb)
{
int sts;
void *data;
int nbytes;
if ((sts = iofunc_devctl_default (ctp, msg, ocb)) != _RESMGR_DEFAULT) { return (sts); } // 1) assign a pointer to the data area of the message data = _DEVCTL_DATA (msg); // 2) preset the number of bytes that we'll return to zero nbytes = 0; // check for all commands; we'll just show the ones we're // interested in here switch (msg -> i.dcmd) { // 3) process the SET command case DCMD_AUDIO_SET_SAMPLE_RATE: audio_set_samplerate (* (int *) data); break; // 4) process the GET command case DCMD_AUDIO_GET_SAMPLE_RATE: * (int *) data = audio_get_samplerate (); nbytes = sizeof (int); break; } // 5) return data (if any) to the client memset (&msg -> o, 0, sizeof (msg -> o)); msg -> o.nbytes = nbytes; SETIOV (ctp -> iov, &msg -> o, sizeof (msg -> o) + nbytes); return (_RESMGR_NPARTS (1));
}
在声明中,我们声明了一个void *
称为data的数据,它将用作指向数据区域的通用指针。如果参考上面的io_devctl()描述,您将看到数据结构由输入和输出头结构的并集组成,而数据区域隐式地位于该头之后。在步骤1中,*_DEVCTL_DATA()*宏(请参见Neutrino库参考中的iofunc_devctl()条目)返回指向该数据区域的指针。
在这里,我们需要指出要返回给客户端的字节数。为方便起见,在进行任何处理之前,我已将nbytes变量设置为零-这样,我不必在每个switch/case
语句中将其显式设置为零。
现在使用“ set”命令。我们调用虚拟函数audio_set_samplerate(),然后将通过解引用数据指针(我们“诱骗”成为整数指针)获得的采样率传递给它。好吧,我们没有欺骗它,我们这是一种关键机制,因为这是我们根据命令“解释”数据区域(客户端的dev_data_ptr)的方式。在更复杂的情况下,您可能会将其类型转换为大型结构,而不仅仅是简单的整数。显然,客户端和资源管理器对结构的定义必须相同-定义结构的最佳位置因此位于.h
包含DCMD_ *命令代码常量的文件中。
对于第4步中的“ get”命令,处理非常相似(使用类型转换),只是这次我们将写入数据结构而不是从中读取数据。请注意,我们还将nbytes变量设置为与我们要返回给客户端的字节数相对应。对于更复杂的数据访问,您将返回数据区域的大小(即,如果是结构,则将返回结构的大小)。
最后,要将数据返回给客户端,我们需要注意,客户端需要一个标头结构,以及返回数据(如果有)立即遵循该标头结构。因此,在此步骤中,我们将标头结构清除为零,并将字节数(nbytes 成员)设置为要返回的字节数(回想一下,我们已将其预先初始化为零)。然后,我们使用指向标头的指针设置一个由一部分组成的IOV,并将标头的大小扩展为返回的字节数。最后,我们只是简单地告诉资源管理器库,我们正在向客户端返回一份IOV。
回顾上面的io_write()示例中有关标题后面的数据区域的讨论。概括地说,我们声明了标头后面的字节可能是完整的,也可能是不完整的(即标头可能是完整的,也可能不是从客户端读取的),具体取决于资源管理器库读取了多少数据。然后,我们继续讨论尝试“保存”消息传递并“重用”数据区域效率低下。但是,devctl()的情况略有不同,尤其是在要传输的数据量很小的情况下(例如在我们的示例中)。在这些情况下,数据很有可能具有实际上已被读取到数据区域,因此重新读取数据确实是浪费。有一种简单的方法来告诉您有多少空间:ctp的大小成员包含从msg参数开始可用于您的字节数。通过从ctp的size成员中减去消息缓冲区的大小来计算超出可用消息缓冲区末尾的数据区域的大小:
data_area_size = ctp -> size - sizeof (*msg);
请注意,当您将数据返回给客户端时,此大小同样有效(如DCMD_AUDIO_GET_SAMPLE_RATE命令中所示)。
对于大于分配的区域的任何内容,您将想要执行与上面的*io_write()*示例相同的处理,以从客户端获取数据,并且您将希望分配一个缓冲区以用于返回数据给客户。
既然我们已经介绍了资源管理器的“基础知识”,那么现在该看一些更复杂的方面了:
在某些情况下,您可能会发现需要扩展OCB。这是相对轻松的事情。扩展OCB的常见用途是添加您希望在每次打开时维护的额外标志。一个这样的标志可以与*io_unblock()*处理函数一起使用,以缓存内核的_NTO_MI_UNBLOCK_REQ标志的值。(有关 更多详细信息,请参见“使用_NTO_MI_UNBLOCK_REQ”下的“消息传递”一章。)
要扩展OCB,您需要提供两个功能。一种分配(并初始化)新的OCB,另一种释放它。然后,您需要将这两个函数绑定到mount结构中。(是的,这确实意味着,仅出于此目的,您将需要一个安装结构。)最后,您需要定义自己的OCB typedef,以便代码原型都是正确的。
首先让我们看一下OCB typedef,然后看一下如何重写这些函数:
#define IOFUNC_OCB_T struct my_ocb
#include <sys/iofunc.h>
这告诉包含的文件<sys/iofunc.h>
清单常量IOFUNC_OCB_T现在指向您新的和改进的OCB结构。
这是非常要记住的是,“正常”的OCB重要的必须出现在您的扩展OCB的第一项!这是因为POSIX帮助程序库传递了一个指向正常OCB的指针-它不知道您的扩展OCB,因此指针位置的第一个数据元素必须是正常OCB。 | |
---|---|
这是我们扩展的OCB:
typedef struct my_ocb
{
iofunc_ocb_t normal_ocb;
int my_extra_flags;
…
} my_ocb_t;
最后,以下代码说明了如何在安装结构中覆盖分配和释放函数:
// declare
iofunc_mount_t mount;
iofunc_funcs_t mount_funcs;
// set up the mount functions structure
// with our allocate/deallocate functions
// _IOFUNC_NFUNCS is from the .h file
mount_funcs.nfuncs = _IOFUNC_NFUNCS;
// your new OCB allocator
mount_funcs.ocb_calloc = my_ocb_calloc;
// your new OCB deallocator
mount_funcs.ocb_free = my_ocb_free;
// set up the mount structure
memset (&mount, 0, sizeof (mount));
然后,您要做的就是将安装函数绑定到安装结构,并将安装结构绑定到属性结构:
…
mount.funcs = &mount_funcs;
attr.mount = &mount;
所述my_ocb_calloc()和my_ocb_free() 函数是负责分配和初始化一个扩展OCB和用于分别释放的OCB,。它们的原型为:
IOFUNC_OCB_T *
my_ocb_calloc (resmgr_context_t *ctp, IOFUNC_ATTR_T *attr);
void
my_ocb_free (IOFUNC_OCB_T *ocb);
这意味着*my_ocb_calloc()函数将同时传递内部资源管理器上下文和属性结构。该函数负责返回初始化的OCB。该my_ocb_free()*函数会得到的OCB和负责释放存储它。
重要的是要意识到OCB可以由普通的io_open()处理函数以外的函数来分配-例如,内存管理器可以分配OCB。这样做的影响是您的OCB分配函数必须能够使用attr 参数初始化OCB 。 | |
---|---|
这两个功能有两个有趣的用途(与扩展OCB无关):
在这种情况下,您可以简单地“绑定”到分配器/解除分配器并监视OCB的使用(例如,您可能希望限制在任何给定时间未处理的OCB的总数)。如果您不接管*io_open()*调用,但仍然需要拦截OCB的创建(可能是删除),那么这可能是个好主意。
覆盖库的内置OCB分配器/取消分配器的另一个用途是,您可能希望将OCB保留在空闲列表中,而不是在库的 *calloc()和free()*函数中。如果您要以较高的速率分配和取消分配OCB,则可能会更有效。
在需要存储其他设备信息的情况下,您可能希望扩展属性结构。由于属性结构是基于“每个设备”关联的,这意味着引用该设备的所有OCB都可以访问您存储的所有额外信息(因为OCB包含指向该属性结构的指针)。通常,诸如串行波特率之类的东西都存储在扩展属性结构中。
扩展属性结构比处理扩展的OCB要简单得多,这仅仅是因为属性结构无论如何都是由代码分配和释放的。
您必须执行与上述扩展的OCB相同的“技巧”,以“新”属性结构覆盖头文件:
#define IOFUNC_ATTR_T struct my_attr
#include <sys/iofunc.h>
接下来,您实际上定义了扩展属性结构的内容。请注意,扩展属性结构必须将“普通”属性结构封装为最开始的元素,就像我们对扩展OCB所做的一样(出于相同的原因)。
到目前为止,我们避免谈论资源管理器中的阻塞。我们假定您将提供一个调用函数(例如*io_read()的处理程序),并且该数据将立即可用。如果您需要阻塞等待数据怎么办?例如,在串行端口上执行read()*可能需要阻塞直到字符到达。显然,我们无法预测这将花费多长时间。
资源管理器中的阻塞基于我们在“消息传递”一章中讨论的相同原则—毕竟,资源管理器实际上是处理某些定义明确的消息的服务器。当与客户端的read()请求相对应的消息到达时,它以接收ID到达,并且客户端被阻止。如果资源管理器有可用的数据,它将简单地返回数据,就像我们在上面的各个示例中已经看到的那样。但是,如果数据不可用,资源管理器将需要保持客户端处于阻塞状态(如果客户端确实为该操作指定了阻塞行为)以继续处理其他消息。这的真正含义是(从资源管理器中)从客户端接收消息的线程不应阻塞,等待数据。如果确实阻塞了,您可以想象这最终会耗尽资源管理器中的大量线程,每个线程都在等待来自某个设备的某些数据。
正确的解决方案是将与客户端消息一起到达的接收ID存储到某个地方的队列中,并从处理程序中返回特殊常量_RESMGR_NOREPLY。这告诉资源管理器库该消息的处理已完成,但是不应解除对客户端的阻止。
一段时间后,当数据到达时,您将检索正在等待消息的客户端的接收ID,并构造一个包含数据的回复消息。最后,您将回复客户。
您还可以将此概念扩展为在服务器内实现超时,就像我们在“时钟,计时器和时常获得踢动”一章(在“服务器维护的超时”部分中)中所做的一样。总而言之,经过一段时间后,客户端的请求被视为“超时”,服务器以某种形式的失败消息回复了已存储的接收ID。
在上面的io_read()函数的示例中,我们看到了如何返回数据。如*io_read()*函数的描述(在“ Connect和I / O函数的字母列表”中)所述,*io_read()*函数也可能返回目录条目。由于这不是每个人都想做的事情,因此我在这里进行讨论。
首先,让我们看看为什么和何时要返回目录条目,而不是返回io_read()的原始数据。
如果您在路径名空间中离散地显示条目,并且这些条目没有用_RESMGR_FLAG_DIR标记,则您不必在io_read()中返回目录条目。如果您从“文件系统”的角度考虑这一点,那么您实际上就是在创建“文件”类型的对象。另一方面,如果您确实指定_RESMGR_FLAG_DIR,那么您正在创建“目录”类型的对象。除了您,其他人都不知道该目录的内容,因此您必须是提供此数据的人。这就是为什么要从io_read()处理程序返回目录条目的原因。
一般来说,返回目录条目就像返回原始数据一样,除了:
struct dirent
条目。struct dirent
条目。第一点意味着您不能返回例如七个半 struct dirent
条目。如果这些结构中有八个不适合分配的空间,则必须仅返回七个。
第二点很明显。之所以在这里提到它,是因为struct dirent
与“普通” *io_read()*的“原始数据”方法相比,填写可能有些棘手。
struct dirent
结构和朋友让我们看一下struct dirent
结构,因为这是在读取目录的情况下*io_read()*函数返回的数据结构。我们还将快速查看处理目录条目的客户端调用,因为该struct dirent
结构存在一些有趣的关系。
为了使客户端能够使用目录,客户端使用函数 closeir(),opendir(),readdir(),rewinddir(),seekdir()和telldir()。
注意与“普通”文件类型函数的相似性(以及资源管理器消息的共同性):
如果我们暂时假设将自动为我们处理*opendir()和closedir()*函数,那么我们可以仅关注_IO_READ和_IO_LSEEK消息以及相关函数。
_IO_LSEEK消息和相关功能用于在文件中“查找”(或“移动”)。它在目录内执行完全相同的操作;您可以移至“第一个”目录条目(通过显式赋予seekdir()偏移量或通过调用rewinddir())或任何任意条目(通过使用seekdir()),或者可以在目录中找到当前位置。目录条目列表(通过使用telldir())。
但是,具有目录的“技巧”是,查找偏移量完全取决于您定义和管理。这意味着您可以决定将目录条目偏移量称为“ 0”,“ 1”,“ 2”,依此类推,或者也可以将其称为“ 0”,“ 64”,“ 128”,依此类推。唯一重要的是,偏移量必须在*io_lseek()处理函数以及io_read()*处理函数中都一致。
在下面的示例中,我们假设使用的是简单的“ 0”,“ 1”,“ 2”…方法。(如果这些数字对应于某种媒介上的偏移量,则可以使用“ 0”,“ 64”,“ 128” …方法。)
因此,现在剩下的就是“简单地”填写 struct dirent
目录的“内容”。以下是struct dirent
(来自<dirent.h>
)的外观:
struct dirent {
ino_t d_ino;
off_t d_offset;
uint16_t d_reclen;
uint16_t d_namelen;
char d_name [1];
};
以下是各种成员的简要说明:
d_ino
“ inode” —不能为零的唯一安装点序列号(传统上为零表示对应于该inode的条目为free / empty)。
d_offset
我们上面刚刚讨论的目录中的偏移量。在我们的示例中,这将是一个简单的数字,例如“ 0”,“ 1”,“ 2”等。在某些文件系统中,这是下一个目录的偏移量。
d_reclen
整个struct dirent
字段的大小以及其中可能放置的任何扩展名。尺寸包括所需的任何对齐填充物。
d_namelen
d_name字段中的字符数,不包括 NUL终止符。
d_name
该目录条目的名称,必须以NUL终止。
返回struct dirent
条目时,传递回客户端的返回码是返回的字节数。
在此示例中,我们将创建一个称为/dev/atoz
目录资源管理器的资源管理器。这将体现在“文件” /dev/atoz/a
通过对dev/atoz/z
,与 cat
任何返回对应的文件名的大写字母的文件。这是一个示例命令行会话,可让您大致了解其工作原理:
# cd /dev # ls atoz null ptyp2 socket ttyp0 ttyp3 enet0 ptyp0 ptyp3 text ttyp1 zero mem ptyp1 shmem tty ttyp2 # ls -ld atoz dr-xr-xr-x 1 root 0 26 Sep 05 07:59 atoz # cd atoz # ls a e i m q u y b f j n r v z c g k o s w d h l p t x # ls -l e -r--r--r-- 1 root 0 1 Sep 05 07:59 e # cat m M# cat q Q#
上面的示例说明了目录atoz
显示在/dev
目录中,并且您可以ls
对目录本身进行操作,也可以对目录进行操作cd
。该 /dev/atoz
目录的大小为“ 26”,这是我们在代码中选择的数字。进入atoz
目录后,再执行一次即可ls
显示内容- a
通过中的文件z
。做一个ls
特定文件,比方说e
,表示该文件是由所有(可读 -r--r--r--
部分)并且大小为一个字节。最后,做一些随机的 cat
的表示文件确实具有指定的内容。(请注意,由于文件仅包含一个字节,因此在打印字符后没有换行符,这就是为什么提示与输出显示在同一行。)
现在我们已经了解了这些特征,下面让我们看一下由以下功能组成的代码:
main()和声明
主功能; 这是我们初始化所有内容并启动资源管理器运行的地方。
_IO_CONNECT消息的处理程序例程。
_IO_READ消息的处理程序例程。
这两个例程执行*my_read()*函数的实际工作。
实用程序功能处理struct dirent
结构。
请注意,虽然此处的代码分为几个简短的文本部分,但您可以atoz.c
在示例程序附录中找到的完整版本。
呈现的代码的第一部分是main()函数和一些声明。有一个便利宏ALIGN(),由dirent_fill()和dirent_size()函数用于对齐。
在此示例中,atoz_attrs数组包含用于“文件”的属性结构。我们声明NUM_ENTS个数组成员,因为我们有NUM_ENTS(26)个文件“ a
”到“” z
。用于目录本身(即/dev/atoz
目录)的属性结构在main()中声明, 并简称为attr。注意两种类型的属性结构的填充方式不同:
文件属性结构
标记为常规文件(S_IFREG常量),访问模式为0444(意味着每个人都具有读访问权限,没有人具有写访问权限)。大小为“ 1”-文件仅包含一个字节,即对应于文件名的大写字母。这些单独文件的索引节点编号为“ 1”至“ 26”(包括0和25),将它们编号为“ 0”至“ 25”会更方便,但保留“ 0”。
目录属性结构
标记为目录文件(S_IFDIR常量),访问模式为0555(意味着每个人都具有读取和寻求访问权限,没有人具有写访问权限)。大小为“ 26”-这只是根据目录中条目数选择的数字。索引节点为“ 27”-已知其他任何属性结构均未使用的数字。
注意我们如何只覆盖connect_func结构的open成员和io_func结构的read成员 。我们已经让所有其他人使用POSIX默认值。
最后,请注意我们如何/dev/atoz
使用 resmgr_attach()创建名称。最重要的是,我们使用了标志_RESMGR_FLAG_DIR,它告诉流程管理器它可以解析此安装点及其以下的请求。
/*
* atoz.c
*
* /dev/atoz using the resource manager library
*/
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <errno.h>
#include <dirent.h>
#include <limits.h>
#include <sys/iofunc.h>
#include <sys/dispatch.h>
#define ALIGN(x) (((x) + 3) & ~3)
#define NUM_ENTS 26
static iofunc_attr_t atoz_attrs [NUM_ENTS];
int
main (int argc, char **argv)
{
dispatch_t *dpp;
resmgr_attr_t resmgr_attr;
dispatch_context_t *ctp;
resmgr_connect_funcs_t connect_func;
resmgr_io_funcs_t io_func;
iofunc_attr_t attr;
int i;
// create the dispatch structure if ((dpp = dispatch_create ()) == NULL) { perror ("Unable to dispatch_create\n"); exit (EXIT_FAILURE); } // initialize the various data structures memset (&resmgr_attr, 0, sizeof (resmgr_attr)); resmgr_attr.nparts_max = 1; resmgr_attr.msg_max_size = 2048; // bind default functions into the outcall tables iofunc_func_init (_RESMGR_CONNECT_NFUNCS, &connect_func, _RESMGR_IO_NFUNCS, &io_func); // create and initialize the attributes structure // for the directory. Inodes 1-26 are reserved for the // files 'a' through 'z'. The number of bytes is 26 // because that's how many entries there are. iofunc_attr_init (&attr, S_IFDIR | 0555, 0, 0); attr.inode = NUM_ENTS + 1; attr.nbytes = NUM_ENTS; // and for the "a" through "z" names for (i = 0; i < NUM_ENTS; i++) { iofunc_attr_init (&atoz_attrs [i], S_IFREG | 0444, 0, 0); atoz_attrs [i].inode = i + 1; atoz_attrs [i].nbytes = 1; } // add our functions; we're interested only in // io_open and io_read connect_func.open = my_open; io_func.read = my_read; // establish a name in the pathname space if (resmgr_attach (dpp, &resmgr_attr, "/dev/atoz", _FTYPE_ANY, _RESMGR_FLAG_DIR, &connect_func, &io_func, &attr) == -1) { perror ("Unable to resmgr_attach\n"); exit (EXIT_FAILURE); } // allocate a context ctp = dispatch_context_alloc (dpp); // wait here forever, handling messages while (1) { if ((ctp = dispatch_block (ctp)) == NULL) { perror ("Unable to dispatch_block\n"); exit (EXIT_FAILURE); } dispatch_handler (ctp); } // you'll never get here return (EXIT_SUCCESS);
}
尽管my_open()很短,但它有许多关键点。注意,我们如何仅基于路径名长度来确定要打开的资源是“文件”还是“目录”。我们可以执行此“技巧”,因为我们知道此资源管理器中除主目录外没有其他目录。如果要在安装点下有多个目录,则必须对msg结构的路径成员 进行更复杂的分析。对于我们的简单示例,如果路径名中没有任何内容,那么我们知道它是目录。另外,请注意,路径名验证的检查非常简化:我们只是进行比较以确保只有一个字符传递给我们,并且该字符位于“ ”到“a``z
“ 包括的。同样,对于更复杂的资源管理器,您将负责解析已注册挂载点之后的名称。
现在,最重要的功能!请注意,我们如何使用POSIX层的默认功能为我们完成所有工作!该iofunc_open_default()函数通常安装在连接功能表在同一地点,我们的新my_open()函数现在占用。这意味着它采用相同的参数集!我们所要做的就是决定哪些 属性,我们希望能与银监默认功能是将创建结合的结构:要么是目录中的一个(在这种情况下,我们通过ATTR),或在26点不同的人之一的26不同的文件(在这种情况下,我们从atoz_attrs中传递适当的元素)。这很关键,因为您打开的处理程序 连接功能表中的插槽用作对资源管理器进行所有其他访问的网守。
static int my_open (resmgr_context_t *ctp, io_open_t *msg, iofunc_attr_t *attr, void *extra) { // an empty path means the directory, is that what we have? if (msg -> connect.path [0] == 0) { return (iofunc_open_default (ctp, msg, attr, extra));
// else check if it's a single char 'a' -> 'z' } else if (msg -> connect.path [1] == 0 && (msg -> connect.path [0] >= 'a' && msg -> connect.path [0] <= 'z')) { // yes, that means it's the file (/dev/atoz/[a-z]) return (iofunc_open_default (ctp, msg, atoz_attrs + msg -> connect.path [0] - 'a', extra)); } else { return (ENOENT); }
}
在my_read()函数中,要确定我们需要进行哪种处理,我们查看了属性结构的mode成员。如果*S_ISDIR()宏说它是一个目录,我们调用my_read_dir();如果S_ISREG()*宏说它是一个文件,我们称为my_read_file()。(有关这些宏的详细信息,请参见Neutrino库参考中有关stat()的条目。)请注意,如果我们不能确定它是什么,则返回EBADF;否则,将返回EBADF。向客户表明发生了一些不好的事情。
这里的代码对我们的特殊设备一无所知,也不在乎。它只是基于标准的知名数据做出决策。
static int my_read (resmgr_context_t *ctp, io_read_t *msg, iofunc_ocb_t *ocb) { int sts;
// use the helper function to decide if valid if ((sts = iofunc_read_verify (ctp, msg, ocb, NULL)) != EOK) { return (sts); } // decide if we should perform the "file" or "dir" read if (S_ISDIR (ocb -> attr -> mode)) { return (my_read_dir (ctp, msg, ocb)); } else if (S_ISREG (ocb -> attr -> mode)) { return (my_read_file (ctp, msg, ocb)); } else { return (EBADF); }
}
在my_read_dir()中,乐趣开始了。从高级的角度来看,我们分配了一个缓冲区,该缓冲区将保存此操作的结果(称为reply_msg)。然后,我们使用dp 沿输出缓冲区“遍历”,并沿过程填充struct dirent
条目。帮助程序例程dirent_size()用于确定我们在输出缓冲区中是否有足够的空间填充下一个条目;否则,请执行以下操作。帮助程序例程dirent_fill()用于执行填充。(请注意,这些例程不是资源管理器库的一部分;下面将对其进行讨论和记录。)
乍一看,此代码可能看起来效率很低。我们正在使用*sprintf()*将两个字节的文件名(文件名字符和NUL终止符)创建到_POSIX_PATH_MAX(256)字节长的缓冲区中。这样做是为了使代码尽可能通用。
最后,请注意,我们使用OCB的offset成员来指示我们struct dirent
在任何给定时间生成的特定文件名。这意味着每当我们返回数据时,我们还必须更新offset字段。
通过*MsgReply()*以“常规”方式将数据返回给客户端。请注意,*MsgReply()*的状态字段用于指示已发送到客户端的字节数。
static int my_read_dir (resmgr_context_t *ctp, io_read_t *msg, iofunc_ocb_t *ocb) { int nbytes; int nleft; struct dirent *dp; char *reply_msg; char fname [_POSIX_PATH_MAX];
// allocate a buffer for the reply reply_msg = calloc (1, msg -> i.nbytes); if (reply_msg == NULL) { return (ENOMEM); } // assign output buffer dp = (struct dirent *) reply_msg; // we have "nleft" bytes left nleft = msg -> i.nbytes; while (ocb -> offset < NUM_ENTS) { // create the filename sprintf (fname, "%c", ocb -> offset + 'a'); // see how big the result is nbytes = dirent_size (fname); // do we have room for it? if (nleft - nbytes >= 0) { // fill the dirent, and advance the dirent pointer dp = dirent_fill (dp, ocb -> offset + 1, ocb -> offset, fname); // move the OCB offset ocb -> offset++; // account for the bytes we just used up nleft -= nbytes; } else { // don't have any more room, stop break; } } // return info back to the client MsgReply (ctp -> rcvid, (char *) dp - reply_msg, reply_msg, (char *) dp - reply_msg); // release our buffer free (reply_msg); // tell resource manager library we already did the reply return (_RESMGR_NOREPLY);
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。