QNX-9—QNX官网文档翻译—Resource Managers(上)

发布时间 2023-06-28 22:09:37作者: Hello-World3

注:本文翻译自:
http://www.qnx.com/developers/docs/7.1/index.html#com.qnx.doc.neutrino.getting_started/topic/s1_resmgr.html


一、本章前言

在本章中,我们将了解编写资源管理器需要了解的内容。 资源管理器是 QNX Neutrino 的另一个显着功能,它允许您通过标准 POSIX 调用访问服务。###########

What is a resource manager?

资源管理器只是一个具有一些明确定义的特征的程序。

The client's view

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

The resource manager's view

让我们从资源管理器的角度来看事情。

The resource manager library

在深入讨论有关资源管理器的所有问题之前,我们必须熟悉 QNX Neutrino 资源管理器库。

Writing a resource manager

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

Handler routines

并非所有呼出都对应于客户端消息——有些是由内核合成的,有些是由库合成的。

Alphabetical listing of connect and I/O functions

本节按字母顺序列出了您可以填写的连接和 I/O 函数入口点(传递给 resmgr_attach() 的两个表)。 请记住,如果您只是调用 iofunc_func_init(),所有这些条目都将填充适当的默认值; 仅当您希望处理特定消息时才需要修改该特定条目。 在下面的“Example”部分中,您将看到一些常用函数的示例。

Examples

我现在将向您展示一些“食谱”示例,您可以将其剪切并粘贴到代码中,以用作项目的基础。 这些不是完整的资源管理器 - 您需要添加线程池并调度下面所示的“骨架”,并确保在您完成之后将您的 I/O 函数版本放入 I/O 函数表中。 完成 iofunc_func_init(),以覆盖默认值! ############

Advanced topics

现在我们已经介绍了资源管理器的“基础知识”,是时候看看一些更复杂的方面了。

Summary

编写资源管理器是迄今为止我们在本书中讨论过的最复杂的任务。


Related concepts
Resource Managers (System Architecture)
Writing a Resource Manager


二、What is a resource manager?

1. 概述

资源管理器只是一个具有一些明确定义的特征的程序。#####

该程序在不同的操作系统上有不同的名称——有些称为“device drivers”、“I/O managers”、“filesystems”、“drivers”、“devices”等等。 然而,在所有情况下,该程序(我们将其称为资源管理器)的目标是呈现某些服务的抽象视图。########

此外,由于 QNX Neutrino 是一个符合 POSIX 规范的操作系统,因此该抽象是基于 POSIX 规范的。#######

(1) Examples of resource managers

在我们得意忘形之前,让我们看几个例子,看看它们是如何“抽象”一些“服务”的。 我们将看看一个实际的硬件(串行端口)和更抽象的东西(文件系统)。

(2) Characteristics of resource managers

正如我们在上面的示例中看到的,资源管理器灵活性的关键在于,资源管理器的所有功能都是使用标准 POSIX 函数调用来访问的 ———— 在与串行通信时,我们没有使用“特殊”函数。 但是,如果您需要做一些“特殊”的事情,一些非常特定于设备的事情,该怎么办?


2. Examples of resource managers

在我们得意忘形之前,让我们看几个例子,看看它们是如何“抽象”一些“服务”的。 我们将看看一个实际的硬件(串行端口)和更抽象的东西(文件系统)。

(1) Serial port

在典型的系统中,程序通常存在某种方式来传输输出并从串行 RS-232 类型的硬件接口接收输入。 该硬件接口由一堆硬件设备组成,其中包括一个 UART(通用异步接收器发送器)芯片,该芯片知道如何将 CPU 的并行数据流转换为串行数据流,反之亦然。

在这种情况下,串行资源管理器提供的“服务”是程序在串行端口上发送和接收字符的能力。

我们说发生了“抽象”,因为客户端程序(最终使用该服务的程序)不知道(也不关心)UART 芯片及其实现的细节。 客户端程序只知道要发送一些字符,它应该调用 fprintf() 函数,要接收一些字符,它应该调用 fgets() 函数。 请注意,我们使用标准 POSIX 函数调用来与串行端口交互。

(2) Filesystem

作为资源管理器的另一个示例,让我们检查一下文件系统。 它由许多协作模块组成:文件系统本身、块 I/O 驱动程序和磁盘驱动程序。
这里提供的“服务”是程序在某种介质上读写字符的能力。 发生的“抽象”与上面的串行端口示例相同 - 客户端程序仍然可以使用完全相同的函数调用(例如 fprintf() 和 fgets() 函数)来与存储介质交互,而不是串行端口。 事实上,客户端确实不知道或不需要知道它正在与哪个资源管理器交互。#########


3. Characteristics of resource managers

正如我们在上面的示例中看到的,资源管理器灵活性的关键在于,资源管理器的所有功能都是使用标准 POSIX 函数调用来访问的 ########## —— 在与串口通信时,我们没有使用“特殊”函数。 但是,如果您需要做一些“特殊”的事情,一些非常特定于设备的事情,该怎么办?

例如,设置串口的波特率是串口资源管理器特有的操作,对于文件系统资源管理器来说完全没有意义。 同样,通过 lseek() 设置文件位置在文件系统中很有用,但在串行端口中毫无意义。 POSIX 为此选择的解决方案很简单。 某些函数(例如 lseek())只会在不支持它们的设备上返回错误代码。###### 然后是“包罗万象”(和非 POSIX)设备控制函数,称为 devctl(),它允许提供特定于设备的功能。 不理解特定 devctl() 命令的设备只会返回错误,就像不理解 lseek() 命令的设备一样。

由于我们已经提到 lseek() 和 devctl() 作为两个常用命令,因此值得注意的是,资源管理器支持几乎所有文件描述符(或 FILE * 流)函数调用。

这自然使我们得出这样的结论:资源管理器将几乎专门处理基于文件描述符的函数调用。######## 由于 QNX Neutrino 是一个消息传递操作系统,因此 POSIX 函数会被转换为消息,然后发送到资源管理器。 ####### 正是这种“POSIX 函数到消息传递”的转换技巧让我们能够将客户端与资源管理器解耦。 资源管理器所要做的就是处理某些明确定义的消息。客户端所要做的就是生成资源管理器期望接收和处理的相同的明确定义的消息。#######

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


三、The client's view

1. 概述

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

但实际上,“幕后”还发生了一些事情。

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


(1) Finding the server

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

(2) Finding the process manager

现在我们了解了查找特定资源管理器的基本步骤之后,我们需要解决“我们是如何找到进程管理器的?”之谜。

(3) Handling directories

我们上面使用的示例是串行端口资源管理器。 我们还提出了一个假设:“让我们暂时假设我们需要精确匹配。” 这个假设只对了一半 —— 我们在本章中讨论的所有路径名匹配都必须完全匹配路径名的一个组成部分,###### 但可能不必匹配整个路径名。 我们很快就会解决这个问题。

(4) Unioned filesystems

(5) Client summary

我们已经完成了客户端的事情。


2. Finding the server

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

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

图 1.QNX Neutrino 的命名空间。


请注意,在上图和随后的描述中,我使用了名称 fs-qnx6 作为实现电源安全文件系统的资源管理器的名称 - 实际上,它有点复杂,因为文件系统驱动程序 基于一系列捆绑在一起的 DLL。 所以,实际上没有名为 fs-qnx6 的可执行文件; 我们只是将它用作文件系统组件的占位符。


假设客户端调用 open():

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


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

图 2.名称解析的第一阶段。

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

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

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

图 3. _IO_CONNECT 消息。

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

实际上,这个文件描述符就是我们刚刚用来向资源管理器发送连接消息的 connection ID! ##### 如果资源管理器没有给我们 EOK,我们就会将此错误传递回客户端(通过 errno 和 open() 返回 -1)。 (值得注意的是,进程管理器可以返回多个资源管理器的节点 ID、进程 ID 和通道 ID 来响应名称解析请求。####### 在这种情况下,客户端将依次尝试其中每一个,直到成功为止 , ######## 返回一个不是 ENOSYS、ENOENT 或 EROFS 的错误,或者客户端耗尽了列表,在这种情况下 open() 失败。稍后我们在查看“before”和“after”标志时将进一步讨论这一点 在。)


3. Finding the process manager

现在我们了解了查找特定资源管理器的基本步骤,我们需要解决“我们是如何找到进程管理器的?”之谜。

其实这个很简单。 每个进程都通过与进程管理器的连接来启动,作为标准进程环境的一部分。


4. Handling directories

我们上面使用的示例是串行端口资源管理器。 我们还提出了一个假设:“让我们暂时假设我们需要精确匹配。” 这个假设只对了一半 —— 我们在本章中讨论的所有路径名匹配都必须完全匹配路径名的一个组成部分,但可能不必匹配整个路径名。###### 我们很快就会解决这个问题。

假设我有执行此操作的代码:

fp = fopen ("/etc/passwd", "r");

回想一下,fopen() 最终调用 open(),因此我们让 open() 询问路径名 /etc/passwd。 但图中没有:

图 1.QNX Neutrino 的命名空间。


然而,我们确实注意到,fs-qnx6 已在路径名“/”处注册了 ND/PID/CHID 关联。 虽然图中没有显示,但 fs-qnx6 将自己注册为目录资源管理器,它告诉进程管理器它将负责“/”及以下内容。 ######## 这是其他“设备”资源管理器(例如串行端口资源管理器)没有做到的事情。 通过设置“directory”标志,fs-qnx6 能够处理“/etc/passwd”的请求,因为请求的第一部分是“/” —— 一个匹配的组件!

如果我们尝试执行以下操作会怎样?

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

好吧,由于串行端口资源管理器没有设置目录标志,进程管理器会查看它并说“不,抱歉,路径名 /dev/ser1 不是目录。 我不会为此请求返回该资源管理器。 然后,进程管理器将回退到较短的路径名,并且其处理方式与上面的“/etc/passwd”相同。 但重要的是,串行端口资源管理器不必处理它。


显然,正如我在上面为 open() 调用选择的参数中所暗示的,允许使用“通常”名称之外的附加参数打开一些“传统”驱动程序可能是一个聪明的主意。 然而,这里的经验法则是,“如果你能在设计评审会议上逃脱惩罚,那就继续吧。”


5. Unioned filesystems

仔细看看我们一直使用的图表:

图 1.QNX Neutrino 的命名空间。

请注意 fs-qnx6 和进程管理器如何将自己注册为负责“/”? 这很好,没什么可担心的。 事实上,有时这是一个非常好的主意。 让我们考虑一个这样的案例。

假设您的网络连接速度非常慢,并且您已在其上安装了网络文件系统。 您注意到您经常使用某些文件,并希望它们以某种方式神奇地“缓存”在您的系统上,但遗憾的是,网络文件系统的设计者没有为您提供实现这一点的方法。 因此,您自己编写了一个位于网络文件系统之上的直通文件系统(称为 fs-cache)。 从客户的角度来看,它是这样的:

图 2. 重叠文件系统。

fs-nfs(网络文件系统)和缓存文件系统(fs-cache)都已注册相同的前缀,即“/nfs”。 正如我们上面提到的,这在 QNX Neutrino 下是很好、正常且合法的。##########

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

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

给进程管理器的消息:“我应该与谁讨论文件名 /nfs/home/rk/abc.txt?”
进程管理器的响应:“首先与 fs-cache 对话,然后与 fs-nfs 对话。”

注意这里进程管理器返回了两组 ND/PID/CHID/handle; 一种用于 fs-cache,一种用于 fs-nfs。 这很关键。

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

(1) 给 fs-cache 的消息:“请打开文件 /nfs/home/rk/abc.txt 进行读取。”
(2) fs-cache 的响应:“抱歉,我从未听说过这个文件。”

此时,对于 fs-cache 资源管理器而言,客户端的 open() 函数就不那么幸运了。 该文件不存在! 然而, open() 函数知道它获得了两个 ND/PID/CHID/handle 元组的列表,因此接下来它会尝试第二个元组:

(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 中)!

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


在重叠的路径名空间上运行多个直通文件系统或资源管理器可能会导致死锁。

5.1 UFS 与 UMP

稍微离题一下术语。 联合文件系统 (UFS) 和联合挂载点 (UMP) 之间的主要区别在于,UFS 基于每个文件组织,而 UMP 基于每个挂载点组织。###########

在上面的缓存文件系统示例中,我们展示了 UFS,因为无论文件在树结构中有多深,任一资源管理器都能够为其提供服务。 在我们的示例中,考虑另一个资源管理器(我们称之为“foobar”)接管“/nfs/other”。 在 UFS 系统中,fs 缓存进程也可以通过附加到“/nfs”来缓存其中的文件。 在 UMP 实现中(这是 QNX Neutrino 中的默认设置,因为它执行最长前缀匹配),只有 foobar 资源管理器会获取打开的请求。


6. Client summary ##############

我们已经完成了客户端的事情。

以下是需要记住的要点:

(1) 客户端通常通过 open()(或 fopen())触发与资源管理器的通信。
(2) 一旦客户的请求“resolved”到特定的资源管理器,我们就永远不会更改资源管理器。
(3) 客户端会话的所有进一步消息都基于文件描述符(或 FILE * 流)(例如 read()、lseek()、fgets())。
(4) 当客户端关闭文件描述符或流(或因任何原因终止)时,会话终止(或“解除关联”)。
(5) 所有基于客户端文件描述符的函数调用都会转换为消息。


四、The resource manager's view

1. 概述

让我们从资源管理器的角度来看事情。

基本上,资源管理器需要告诉进程管理器“我将负责路径名空间的某个部分”(它需要自行注册)。###### 然后,它需要接收来自客户端的消息并处理它们。 显然,事情并没有那么简单。

让我们快速概述一下资源管理器提供的功能,然后我们将查看详细信息。

(1) Registering a pathname

资源管理器需要告诉进程管理器,一个或多个路径名现在处于其权限范围内 —— 实际上,该特定资源管理器已准备好处理客户端对这些路径名的请求。

(2) Handling messages

一旦我们注册了一个或多个路径名,我们就应该准备好接收来自客户端的消息。 这是通过调用 MsgReceive() 函数以“通常”方式完成的。 资源管理器处理的明确定义的消息类型不到 30 种。 然而,为了简化讨论和实施,它们被分为两组。


Related concepts
Resource Managers (System Architecture)
Writing a Resource Manager


2. Handling messages

一旦我们注册了一个或多个路径名,我们就应该准备好接收来自客户端的消息。 这是通过调用 MsgReceive() 函数以“通常”方式完成的。 ######## 资源管理器处理的明确定义的消息类型不到 30 种。###### 然而,为了简化讨论和实施,它们被分为两组。

(1) Connect messages

始终包含路径名; 这些要么是一次性消息,要么为进一步的 I/O 消息建立上下文。

(2) I/O messages

始终基于连接消息; 这些执行进一步的工作。

2.1 Connect messages

连接消息始终包含路径名。 我们在整个讨论中使用的 open() 函数是生成连接消息的函数的完美示例。 在这种情况下,连接消息的处理程序为进一步的 I/O 消息建立上下文。 (毕竟,我们希望在完成 open() 之后执行诸如 read() 之类的操作)。

2.2 I/O messages

仅在连接消息之后才需要 I/O 消息,并且引用该连接消息创建的上下文。 正如上面在连接消息讨论中提到的,open() 后面跟着 read() 就是一个完美的例子。

2.3 Three groups, really

除了连接和 I/O 消息之外,资源管理器还可以接收(和处理)“其他”消息。 由于它们不是正确的“资源管理器”消息,因此我们将推迟对它们的讨论。


五、The resource manager library

1. 示例

在深入讨论有关资源管理器的所有问题之前,我们必须熟悉 QNX Neutrino 资源管理器库。

这个“库”实际上由几个不同的部分组成:

(1) 线程池函数(我们在 Processes and Threads chapter under “Pools of threads” 中讨论过)
(2) 分发接口
(3) 资源管理器功能
(4) POSIX 库帮助函数

虽然您当然可以“从头开始”编写资源管理器(就像在 QNX 4 世界中所做的那样),但这比起它的价值要麻烦得多。

只是为了向您展示库方法的实用性,这里是“/dev/null”的单线程版本的源代码:

/*
 *  resmgr1.c
 *  /dev/null using the resource manager library
*/

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.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
    dpp = dispatch_create_channel( -1, DISPATCH_FLAG_NOLOCK);
    if (dpp == NULL) {
        perror ("Unable to dispatch_create_channel");
        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");
        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");
            exit(EXIT_FAILURE);
        }
        dispatch_handler(ctp);
    }
}


你有它! 通过几个函数调用即可实现一个完整的 /dev/null 资源管理器!

如果您要从头开始编写此代码,并让它支持该函数的所有功能(例如 stat() 工作、chown() 和 chmod() 工作等),您将看到数百个,如果不是数千行 C 代码。


The library really does what we just talked about

通过对该库的介绍,让我们(简要地)看看这些调用在 /dev/null 资源管理器中做了什么。

Behind the scenes at the library

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


2. The library really does what we just talked about

通过对该库的介绍,让我们(简要地)看看这些调用在 /dev/null 资源管理器中做了什么。

dispatch_create_channel()

创建调度结构; 这将用于阻塞在消息接收上。 有一个更简单的函数,称为 dispatch_create(),但大多数资源管理器应该设置 DISPATCH_FLAG_NOLOCK,以便使用无锁消息查找 #####?#######(有关详细信息,请参阅C 库参考中的 dispatch_create_channel() 条目)。

iofunc_attr_init()

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

iofunc_func_init()

初始化两个数据结构 cfuncs 和 ifuncs,它们分别包含指向 connect 和 I/O 函数的指针。##### 您可能会说这个调用最“神奇”,因为这是处理所有消息的实际“工作”例程绑定到数据结构的地方。 我们实际上没有看到任何处理连接消息的代码,或者客户端 read() 或 stat() 函数调用产生的 I/O 消息。 这是因为库为我们提供了这些函数的默认 POSIX 版本,并且 iofunc_func_init() 函数将这些相同的默认处理函数绑定到提供的两个tables中。

resmgr_attach()

创建资源管理器用于接收消息的通道,###### 并与进程管理器对话,告诉它我们将负责“/dev/null”。 虽然有很多参数,但我们稍后会详细了解它们。 现在,需要注意的是,这是调度句柄 (dpp)、路径名(字符串 /dev/null)以及连接 (cfuncs) 和 I/O (ifuncs) 消息处理程序全部绑定在一起的地方。#######

dispatch_context_alloc()
分配一个分发内部上下文块。 它包含与正在处理的消息相关的信息。


调用 dispatch_context_alloc()后,请勿调用 message_attach()或 resmgr_attach()为同一分发句柄指定更大的最大消息大小或更多数量的消息部分。 在 QNX Neutrino 7.0 或更高版本中,如果发生这种情况,这些函数会指示 EINVAL 错误。 (这不适用于 pulse_attach() 或 select_attach(),因为您无法使用这些函数指定大小。)

dispatch_block()

这是调度层的阻塞调用; 这是我们等待客户端发送消息的地方。

dispatch_handler()

一旦消息从客户端到达,就会调用该函数来处理它。


3. Behind the scenes at the library

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

while (1) {
    // wait here for a message
    if ((ctp = dispatch_block(ctp)) == NULL) {
        perror ("Unable to dispatch_block");
        exit(EXIT_FAILURE);
    }
    // handle the message
    dispatch_handler(ctp);
}


这非常方便,因为它允许您在接收函数上放置断点并在操作期间拦截消息(可能使用调试器)。

该库在 dispatch_handler() 函数内部实现了“魔法”,因为这是通过我们前面提到的连接函数和I/O函数表来分析和处理消息的地方。###########

实际上,该库由两个协作层组成:一个提供“原始”资源管理器功能的基础层,以及一个提供 POSIX 帮助程序和默认函数的 POSIX 层。 我们将简要定义这两层,然后在下面的“Resource manager structure”中,我们将了解详细信息。


(1) The base layer

最底层由名称以 resmgr_*() 开头的函数组成。 此类函数涉及使资源管理器工作的机制。

(2) The POSIX layer

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


3.1 The base layer

最底层由名称以 resmgr_*() 开头的函数组成。 此类函数涉及使资源管理器工作的机制。

我将简要提及可用的功能以及我们将在何处使用它们。 然后,我将建议您参阅 QNX 文档以获取有关这些函数的更多详细信息。

基础层功能包括:

resmgr_msggetv() 和 resmgr_msgget()

使用复制本地消息缓冲区数据和使用消息传递从客户端地址空间读取数据的最佳组合。 这些函数尽可能避免从客户端地址空间读取数据。

resmgr_msgwritev() 和 resmgr_msgwrite()

使用消息传递将数据写入客户端的地址空间。

resmgr_open_bind()

将上下文与连接函数相关联,以便稍后可由 I/O 函数使用。

resmgr_attach()

创建通道,将路径名、分发句柄、连接函数、I/O 函数和其他参数关联在一起。 向进程管理器发送消息以注册路径名。######

resmgr_detach()

与 resmgr_attach() 相反; 解除路径名和资源管理器的绑定。

pulse_attach()

将脉冲代码与功能相关联。 由于该库实现了消息接收循环,因此这是处理脉冲“获得控制”的便捷方法。

pulse_detach()

将脉冲代码与功能分离。

除了上面列出的函数之外,还有许多处理调度接口的函数。

上面列表中值得特别提及的一个函数是 resmgr_open_bind()。 当连接消息(通常是客户端调用 open() 或 fopen() 的结果)到达时,它会关联某种形式的上下文数据,以便在处理 I/O 消息时存在该数据块。 为什么我们在 /dev/null 处理程序中没有看到这个? 因为POSIX层默认函数为我们调用了这个函数。 如果我们自己处理所有消息,我们肯定会调用这个函数。


resmgr_open_bind() 函数不仅为进一步的 I/O 消息设置上下文块,而且还初始化资源管理器库本身使用的其他数据结构。

上面列表中的其余函数有些直观 - 我们将推迟对它们的讨论,直到我们使用它们为止。


3.2 The POSIX layer

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