QNX系列:五、资源管理器(1)官方文档的翻译

本文介绍了资源管理器的概念及其在操作系统中的作用。重点讲述了资源管理器如何为客户端程序提供服务的抽象视图,包括串行端口和文件系统的示例。此外,还详细探讨了资源管理器的实现细节,如数据结构、消息处理程序以及与POSIX规范的整合。

文章目录

资源管理器

本章内容包括:

什么是资源管理器?

在本章中,我们将介绍编写资源管理器需要了解的内容。

资源管理器只是具有某些定义明确的特征的程序。该程序在不同的操作系统上被称为不同的事物,有人称它们为“设备驱动程序”,“ 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函数传递消息的功能”翻译技巧使我们能够将客户端与资源管理器分离。资源管理器要做的就是处理某些定义明确的消息。客户端所需要做的就是生成资源管理器希望接收和处理的相同定义明确的消息。


由于客户端和资源管理器之间的交互基于消息传递,因此使此“翻译层”尽可能薄是有意义的。例如,当客户端执行open()并获取文件描述符时,该文件描述符实际上就是连接ID!此连接ID(文件描述符)在客户端的C库函数(例如read())中使用,该函数在其中创建消息并将其发送到资源管理器。

client的观点

我们已经看到了客户期望的暗示。它期望使用标准POSIX函数的基于文件描述符的接口。

不过,实际上,“幕后”还有更多事情要做。

例如,客户端实际上如何连接到适当的资源管理器?对于联合文件系统(多个文件系统负责同一个“命名空间”)会发生什么情况?如何处理目录?

查找服务器

客户端要做的第一件事是调用open() 以获取文件描述符。(请注意,如果客户端调用了更高级别的函数fopen(),则同样的讨论适用— fopen()最终将调用open())。

open()的C库实现内部,构造了一条消息,并将其发送到流程管理器(procnto)组件。流程管理器负责维护有关路径名空间的信息。该信息由一个树结构组成,该树结构包含路径名和节点描述符,进程ID,通道ID和句柄关联:


20200724233030


Neutrino的名称空间。


请注意,在上图中以及下面的描述中,我已将名称fs-qnx4用作实现QNX 4文件系统的资源管理器的名称-实际上,这有点复杂,因为文件系统驱动程序基于a捆绑在一起的一系列DLL。因此,实际上没有可执行文件称为fs-qnx4;我们只是将其用作文件系统组件的占位符。

假设客户端调用open()

fd = open ("/dev/ser1", O_WRONLY);

在客户端的*open()*的C库实现中,将构造一条消息并将其发送到流程管理器。此消息指出:“我要打开/dev/ser1;我应该和谁说话?”


20200725143902


名字解析的第一阶段。

流程管理器接收到请求,并查看其树结构以查看是否存在匹配项(现在假设我们需要一个完全匹配项)。当然,路径名“ /dev/ser1”与请求匹配,并且流程管理器能够答复客户端:“我找到了/dev/ser1。正在由节点描述符0,进程ID 44,通道ID 1,句柄1处理。向您发送您的请求!”

记住,我们仍然在客户端的*open()*代码中!

因此,*open()函数会创建另一条消息,并连接到指定的节点描述符(0,表示我们的节点),进程ID(44),通道ID(1),并将句柄塞入消息本身。该消息实际上是“连接”消息,它是客户端的open()消息。*库用于建立与资源管理器的连接(下图中的步骤3)。资源管理器收到连接消息后,将对其进行查看并执行验证。例如,您可能试图打开一个实现只读文件系统的资源管理器,以进行写操作,在这种情况下,您将获得一个错误(在此情况下为EROFS)。但是,在我们的示例中,串行端口资源管理器查看该请求(我们指定了O_WRONLY;对于串行端口完全合法),并以EOK进行回复(下图中的步骤4)。


20200725143921


_IO_CONNECT消息。

最后,客户端的*open()*返回带有有效文件描述符的客户端。

确实,此文件描述符是我们刚刚用来向资源管理器发送连接消息的连接ID!如果资源管理器没有 给我们一个EOK,我们将把这个错误传递回客户端(通过errnoopen()的-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位于网络文件系统顶部的缓存文件系统(称为)。从客户的角度看,这是它的外观:


20200725144009


覆盖的文件系统。

这两种fs-nfs(网络文件系统)和您的缓存文件系统(fs-cache)已经注册了自己的前缀相同,即/nfs“” 正如我们上面提到的,在Neutrino下,这是正常的,合法的行为。

假设系统刚刚启动,并且您的缓存文件系统还没有任何内容。假设某个客户端程序尝试打开文件/nfs/home/rk/abc.txt。您的缓存文件系统位于网络文件系统的“前面”(稍后,当我们讨论资源管理器的实现时,我将向您展示如何执行此操作)。

此时,客户端的*open()*代码执行通常的步骤:

  1. 给流程经理的信息:“我应该和谁谈谈文件名/nfs/home/rk/abc.txt?”
  2. 流程经理的回复:“先交谈fs-cache,然后交谈fs-nfs。”

注意这里,流程管理器返回两组ND / PID / CHID /句柄。一为fs-cachefs-nfs。这很关键。

现在,客户端的*open()*继续:

  1. 消息发送至fs-cache:“ /nfs/home/rk/abc.txt请打开文件以供阅读。”
  2. 来自fs-cache:“抱歉,我从未听说过此文件。”

此时,就资源管理器而言,客户端的*open()*函数很不走运fs-cache。该文件不存在!但是,open()函数知道它得到了两个 ND / PID / CHID /处理元组的列表,因此接下来尝试第二个:

  1. 消息发送至fs-nfs:“ /nfs/home/rk/abc.txt请打开文件以供阅读。”
  2. 回复fs-nfs:“当然,没问题!”

现在,open()函数具有EOK(“没问题”),它返回文件描述符。然后,客户端与资源管理器执行所有进一步的交互fs-nfs


我们唯一解析为资源管理器的时间是在*open()*调用期间。这意味着一旦成功打开特定的资源管理器,我们将继续对所有文件描述符调用使用该资源管理器。

那么我们的fs-cache缓存文件系统如何发挥作用?好吧,最终,我们假设用户已完成文件的读取(他们已将其加载到文本编辑器中)。现在他们想把它写出来。发生相同的一组步骤,但有趣的是:

  1. 给流程经理的信息:“我应该和谁谈谈文件名/nfs/home/rk/abc.txt?”
  2. 流程经理的回复:“先交谈fs-cache,然后交谈fs-nfs。”
  3. 消息发送至fs-cache:“ 请打开文件/nfs/home/rk/abc.txt进行写入。”
  4. 回复fs-cache:“当然,没问题。”

请注意,这次,在第3步中,我们打开了文件以进行写入,而不是像以前一样进行读取。因此,这次fs-cache允许操作(在步骤4中)也就不足为奇了。

更有趣的是,观察一次我们下次阅读文件时会发生什么:

  1. 给流程经理的信息:“我应该和谁谈谈文件名/nfs/home/rk/abc.txt?”
  2. 流程经理的回复:“先交谈fs-cache,然后交谈fs-nfs。”
  3. 消息至fs-cache:“ 请打开文件/nfs/home/rk/abc.txt以供阅读。”
  4. 回复fs-cache:“当然,没问题。”

确实,这次缓存文件系统处理了读取请求(在步骤4中)!

现在,我们省略了一些细节,但是这些对于理解基本概念并不重要。显然,缓存文件系统将需要某种方式通过网络将数据发送到“真实”存储介质。它还应该具有某种方法来验证在文件返回文件内容给客户端之前,没有其他人修改过该文件(这样客户端就不会获取过时的数据)。缓存文件系统可以通过 将第一次读取时的网络文件系统中的数据加载到其缓存中处理第一次读取请求。等等。

client总结

我们已经完成了客户端的工作。以下是要记住的关键点:

  • 客户端通常通过open()(或fopen())触发与资源管理器的通信。
  • 一旦客户的请求已“解决”到特定的资源管理器,我们就永远不会更改资源管理器。
  • 客户端会话的所有其他消息均基于文件描述符(或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消息,并且该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资源管理器中的作用。

  • dispatch_create()

    创建一个调度结构;这将用于阻止消息接收。

  • iofunc_attr_init()

    初始化设备使用的属性结构。稍后我们将更深入地讨论属性结构,但到目前为止,简短的故事是每个设备名称都有一个属性,并且它们包含有关特定设备的信息。

  • iofunc_func_init()

    初始化两个数据结构cfuncsifuncs,它们分别包含指向connect和I / O函数的指针。您可能会争辩说,此调用具有最大的“魔力”,因为这是用于处理所有消息的实际“工人”例程绑定到数据结构中的地方。实际上,我们没有看到任何代码来处理连接消息,也没有看到由客户端*read()stat()函数等产生的I / O消息。这是因为该库正在为我们提供这些函数的默认POSIX版本,并且是iofunc_func_init()*函数将这些相同的默认处理函数绑定到两个提供的表中。

  • resmgr_attach()

    创建资源管理器将用于接收消息的渠道,并与流程管理器对话以告知我们我们将负责“” /dev/null。尽管有很多参数,但稍后我们将详细介绍它们。现在,需要特别注意的是这是调度句柄(dpp),路径名(字符串/dev/null)以及connect(cfuncs)和I / O(ifuncs)消息处理程序都绑定在一起的地方。

  • dispatch_context_alloc()

    分配调度内部上下文块。它包含与正在处理的消息有关的信息。

  • dispatch_block()

    这是调度层的阻塞调用;在这里我们等待来自客户的消息。

  • dispatch_handler()

    消息从客户端到达后,将调用此函数进行处理。

图书馆的幕后花絮

您已经看到您的代码负责提供主要的消息接收循环:

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_open_bind()。当连接消息到达时(通常是由于客户端调用open()fopen()的结果),它将关联某种形式的上下文数据,以便在处理I / O消息时此数据块就在周围。为什么我们在/dev/null处理程序中看不到这个?因为POSIX层的默认函数为我们调用了此函数。如果我们自己处理所有消息,则肯定会调用此函数。


所述resmgr_open_bind()函数不仅建立用于进一步的I / O消息的上下文块,但初始化由资源管理器库本身使用的其他数据结构。

上面列表中的其余功能有些直观-我们将推迟使用它们的讨论。

POSIX层

QSS的资源管理器库提供的第二层是POSIX层。与基础层一样,您可以在不使用资源管理器的情况下编写代码,但这将需要大量工作!在详细讨论POSIX层功能之前,我们需要查看一些基本层数据结构,来自客户端的消息以及资源管理器的总体结构和职责。

编写资源管理器

现在,我们已经介绍了基础知识—客户如何看待世界,资源管理器如何看待世界以及库中两个协作层的概述,是时候集中精力讨论细节了。

在本节中,我们将研究以下主题:

请记住以下“大图”,其中几乎包含与资源管理器相关的所有内容:


20200725144119


资源管理器的架构-全局。

数据结构

我们需要了解的第一件事是用于控制库操作的数据结构:

库内部使用的一种数据结构:

稍后,我们将看到与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;
other_func消息处理程序

通常,应避免使用此成员。该成员(如果为非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 控制结构”部分中)。

资源管理器结构

既然我们已经了解了数据结构,我们就可以讨论您要提供的各部分之间的交互作用,以使资源管理器实际执行 某些操作。

我们来看一下:

所述*resmgr_attach()*函数和它的参数

如您在上面/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_func
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

擦擦擦大侠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值