QNX-9—QNX官网文档翻译—Resource Managers—Advanced topics

发布时间 2023-07-09 21:48:28作者: Hello-World3

注:翻译自:
QNX Software Development Platform --> Programming --> Getting Started with QNX Neutrino --> Resource Managers --> Advanced topics
http://www.qnx.com/developers/docs/7.1/index.html#com.qnx.doc.neutrino.getting_started/topic/s1_resmgr_Advanced.html


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

扩展 OCB

锁定资源管理器

资源管理器框架提供了资源管理器中安全线程同步的设施。 在编写多线程资源管理器之前,请确保您熟悉线程同步使用的共享数据结构的默认行为以及安全覆盖它们的要求。

扩展属性结构

如果您需要存储有关资源的附加信息,您可能希望扩展属性结构。 由于属性结构是在“每个资源”的基础上关联的,因此引用该资源的所有 OCB 都可以访问您存储在其中的任何额外信息(因为 OCB 包含指向属性结构的指针)。 通常,诸如串行波特率之类的信息存储在扩展属性结构中。

资源管理器内的阻塞

到目前为止,我们已经避免讨论资源管理器内的阻塞。 我们假设您将提供一个外呼函数(例如,读取 I/O 函数处理程序),并且数据将立即可用。 如果需要阻塞等待数据怎么办? 例如,在串行端口上执行 read() 可能需要阻塞,直到字符到达。 显然,我们无法预测这需要多长时间。

返回目录条目

在上面的读取 I/O 函数处理程序的示例中,我们了解了如何返回数据。 正如读 I/O 函数处理程序的描述中提到的(在“按字母顺序排列的连接和 I/O 函数列表”中),该处理程序也可以返回目录条目。 由于这不是每个人都想做的事情,所以我在这里讨论一下。


一、Extending the OCB

要扩展 OCB,您需要提供两个函数 — 一个用于分配(和初始化)新的 OCB,另一个用于释放它 — 来覆盖默认值 iofunc_ocb_calloc() 和 iofunc_ocb_free()。 然后,您需要将两个自定义函数绑定到安装结构中。 (是的,这确实意味着您需要一个安装结构,即使只是为了这一目的。)最后,您需要定义自己的 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 可以由正常打开连接函数处理程序之外的函数分配,例如,内存管理器可以分配 OCB。 这样做的影响是您的 OCB 分配函数必须能够使用 attr 参数初始化 OCB。

这两个函数有两个有趣的用途(与扩展 OCB 无关):

(1)监控 OCB 的分配和取消分配

在这种情况下,您可以简单地“绑定”到分配器/解除分配器并监视 OCB 的使用情况(例如,您可能希望限制在任何给定时间未完成的 OCB 总数)。 如果您不接管打开连接函数处理程序,但仍需要拦截 OCB 的创建(以及可能的删除),这可能是一个好主意。

(2)提供更有效的分配和释放

覆盖库的内置 OCB 分配器/释放器的另一个原因是您可能希望将 OCB 保留在空闲列表中,而不是使用库的 calloc() 和 free() 函数。 如果您以较高的速率分配和取消分配 OCB,这可能会更有效。


二、Locking in the resource manager

资源管理器框架提供了资源管理器中安全线程同步的设施。 在编写多线程资源管理器之前,请确保您熟悉线程同步使用的共享数据结构的默认行为以及安全覆盖它们的要求。

1. 多线程资源管理器的共享数据结构

OCB 包含文件描述符的打开模式(RDONLY、WRONLY、RDWR)、其当前偏移量以及它已被复制的次数(使用 dup())。 其中一些项目是静态的,但偏移量经常变化。 对同一文件描述符的连续操作依赖于前一个操作设置的偏移量。

属性结构代表文件本身。 它存储快速变化的信息,例如文件凭据、访问权限、大小和时间戳

由于 POSIX 要求同时读取和写入文件不会导致半更新,因此需要锁定所有操作的属性结构。 资源管理器框架通过为每个属性结构有效地提供单个互斥体来满足此要求。 默认情况下,该互斥体由 io_func_attr_lock() 和 io_func_attr_unlock() 操作。 资源管理器框架在调用任何 I/O 处理程序之前在相关属性结构上获取它,并在处理程序返回后释放它。

由于此机制通过与文件描述符关联的 OCB 查找要锁定的属性结构,因此它仅适用于基于文件描述符的操作。 连接操作基于路径名,没有文件描述符或 OCB。 相反,挂载点的属性结构(提供给 resmgr_attach() 的路径)是 resmgr_attach() 的句柄参数。 在文件系统资源管理器中(即,当 _RESMGR_FLAG_DIR 标志传递给 resmgr_attach() 时),框架可以提供挂载点的属性结构,但不知道如何为正在处理的文件或目录找到正确的属性结构。 因此,您自己的连接处理程序有责任确定并获取与指定文件关联的属性结构。

不要简单地在每个 open() 上分配新的属性结构。 为了使锁定文件和内存映射文件都能正确运行,对文件调用的每个 open() 都需要针对相同的属性结构调用 iofunc_ocb_attach()

2. 覆盖锁定行为

在调用 I/O 处理函数之前,资源管理器框架会调用 resmgr_io_funcs 成员 lock_ocb 和 unlock_ocb 指向的函数来锁定和解锁 OCB 结构 (请参阅 iofunc_func_init() 和 resmgr_attach())。 默认情况下,使用 iofunc_lock_ocb_default() 和 iofunc_unlock_ocb_default(),它们只是锁定或解锁与 OCB 关联的属性结构。

属性结构通过 iofunc_mount_t 成员 funcs 指向的函数(_iofunc_funcs 成员 attr_lock、attr_unlock 和 attr_trylock)进行锁定和解锁。 当这些函数指针未填充时(默认行为),OCB 锁定函数调用 iofunc_attr_lock() 或 iofunc_attr_unlock(),这意味着资源管理器框架期望对 lock_ocb() 和 unlock_ocb() 的调用也锁定或解锁相关的属性结构。 此默认行为是“单级”锁定(属性锁用于锁定 OCB),并且对于大多数资源管理器来说应该足够了。

但是,如果需要,您可以实现“两级”锁定,这允许您的资源管理器处理程序释放共享属性上的锁,但保留 OCB 锁。 如果您的资源管理器支持对同一命名条目进行独立操作,则此行为非常有用。


对于“一级”和“二级”锁定:

(1)您可以使用默认的 iofunc_attr_lock() 和 iofunc_attr_unlock() 作为属性锁,也可以实现您自己的并填充 attr_lock 和 attr_unlock。

(2)确保锁定是递归的(请参阅 pthread_mutexattr_setrecursive())。

对于“一级”锁, iofunc_lock_ocb_default() 和 iofunc_unlock_ocb_default() 使用您的 attr_lock 和 attr_unlock (如果您填充它们)。 您不需要在 resmgr_io_funcs 中为 lock_ocb 和 unlock_ocb 创建自己的函数。

创建“两级”锁需要您为 lock_ocb 和 unlock_ocb 创建自己的函数。 为了让您的代码能够与默认资源管理器处理程序良好配合,请确保这些函数使用与 iofunc_mount_t 引用的函数相同的属性结构锁定函数。


对于“两级”锁,请确保您的代码还包含以下行为:

(1)调用 lock_ocb() 也会锁定该属性。

(2)首先锁定 OCB,然后锁定属性结构,这允许您根据需要解锁 iofunc_attr_t,同时保留 OCB 锁。

对于默认锁定和自定义锁定(使用一层或两级),处理程序可以安全地临时解锁属性结构(或 OCB,或者在“两级”锁的情况下同时解锁属性和 OCB),如下所示 只要它在返回之前锁定受影响的项目即可。 如果您使用自定义锁定功能,请确保它们不会失败。

拥有临时解锁结构的处理程序意味着允许其他线程运行处理程序。 特别是,如果您解锁 OCB,另一个线程可能会关闭它。 再次锁定 OCB 后,OCB 仍然有效(在处理程序返回之前它不是空闲的),但您可能需要小心 OCB 中您的 close_ocb 处理程序可能已失效的任何内容。


三、Extending the attributes structure

如果您需要存储有关资源的附加信息,您可能希望扩展属性结构。 由于属性结构是在“每个资源”的基础上关联的,因此引用该资源的所有 OCB 都可以访问您存储在其中的任何额外信息(因为 OCB 包含指向属性结构的指针)。 通常,诸如串行波特率之类的信息存储在扩展属性结构中。

扩展属性结构比处理扩展 OCB 简单得多,因为属性结构无论如何都是由代码分配和释放的。

您必须执行相同的“技巧”,使用“新”属性结构覆盖头文件,就像我们对上面的扩展 OCB 所做的那样:

#define IOFUNC_ATTR_T struct my_attr
#include <sys/iofunc.h>

接下来,您实际上定义扩展属性结构的内容。 请注意,扩展属性结构必须将“正常”属性结构封装为第一个元素, 就像我们对扩展 OCB 所做的那样(并且出于相同的原因)。


四、Blocking within the resource manager

到目前为止,我们已经避免讨论资源管理器内的阻塞。 我们假设您将提供一个外呼函数(例如,读取 I/O 函数处理程序),并且数据将立即可用。 如果需要阻塞等待数据怎么办? 例如,在串行端口上执行 read() 可能需要阻塞,直到字符到达。 显然,我们无法预测这需要多长时间。

资源管理器中的阻塞基于我们在消息传递一章中讨论的相同原则——毕竟,资源管理器实际上是处理某些明确定义的消息的服务器。 当与客户端的 read() 请求相对应的消息到达时,它会使用接收 ID 来完成此操作,并且客户端会被阻止。 如果资源管理器有可用的数据,它将简单地返回数据,正如我们在上面的各个示例中已经看到的那样。 但是,如果数据不可用,资源管理器将需要使客户端保持阻塞(如果客户端确实为该操作指定了阻塞行为)以继续处理其他消息。 这真正意味着从客户端接收消息的线程(在资源管理器中)不应阻塞以等待数据。 如果它确实阻塞,您可以想象这最终会耗尽资源管理器中的大量线程,每个线程都等待来自某个设备的一些数据。

正确的解决方案是将随客户端消息到达的接收 ID 存储到某个队列中,并从处理程序返回特殊常量 _RESMGR_NOREPLY。 这告诉资源管理器库该消息的处理已完成,但客户端不应被解除阻止。

一段时间后,当数据到达时,您将检索正在等待消息的客户端的接收 ID,并构造包含该数据的回复消息。 最后,你会回复客户

您还可以将此概念扩展到在服务器内实现超时,就像我们在“时钟、计时器和每隔一段时间进行一次”一章(在 “Server-maintained timeouts” 部分中)中的示例所做的那样。 总而言之,一段时间后,客户端的请求被视为“超时”,服务器以某种形式的失败消息回复其存储的接收 ID。


五、Returning directory entries

在上面的读取 I/O 函数处理程序的示例中,我们了解了如何返回数据。 正如读 I/O 函数处理程序的描述中提到的(在“按字母顺序排列的连接和 I/O 函数列表”中),该处理程序也可以返回目录条目。 由于这不是每个人都想做的事情,所以我在这里讨论一下。

首先,让我们看看为什么以及何时想要从读 I/O 函数处理程序返回目录条目而不是原始数据。

如果您在路径名空间中离散地显示条目,并且这些条目没有用 _RESMGR_FLAG_DIR 标记,那么您不必在读 I/O 函数处理程序中返回目录条目。 如果您从“文件系统”的角度考虑这一点,那么您实际上是在创建“文件”类型的对象。 另一方面,如果您确实指定了 _RESMGR_FLAG_DIR,那么您正在创建“目录”类型的对象。 除了您之外没有人知道该目录的内容是什么,因此您必须是提供此数据的人。 这正是您从读取 I/O 函数处理程序返回目录条目的原因。


一般来说...

一般来说,返回目录条目就像返回原始数据一样,但此处列出了例外情况。

struct dirent 结构和朋友

让我们看一下 struct dirent 结构,因为这是读取目录时由读 I/O 函数处理程序返回的数据结构。 我们还将快速浏览一下处理目录条目的客户端调用,因为与 struct dirent 结构有一些有趣的关系。

例子

在此示例中,我们将创建一个名为 /dev/atoz 的资源管理器,它将作为目录资源管理器。 它将显示 /dev/atoz/a 到 dev/atoz/z 的“文件”,任何文件的 cat 都会返回与文件名相对应的大写字母。


1. Generally speaking...

一般来说,返回目录条目就像返回原始数据一样,但此处列出了例外情况。

(1)您必须返回整数个 struct dirent 条目。

(2)您必须填写 struct dirent 条目。

第一点意味着您不能返回例如七个半 struct dirent 条目。 如果其中八个结构不适合分配的空间,那么您必须只返回七个。

第二点是相当明显的; 这里提到它只是因为与“普通”读 I/O 函数处理程序的“原始数据”方法相比,填充 struct dirent 可能有点棘手。


2. The struct dirent structure and friends

让我们看一下 struct dirent 结构,因为这是读取目录时由读 I/O 函数处理程序返回的数据结构。 我们还将快速浏览一下处理目录条目的客户端调用,因为与 struct dirent 结构有一些有趣的关系。

为了使客户端能够使用目录,客户端使用函数 closedir()、opendir()、readdir()、rewinddir()、seekdir() 和 telldir()。

请注意与“正常”文件类型函数的相似性(以及资源管理器消息的通用性):

-------------------------------------------------------
Directory Function    File Function    Message (resmgr)
-------------------------------------------------------
closedir()            close()          _IO_CLOSE
opendir()             open()           _IO_CONNECT
readdir()             read()           _IO_READ
rewinddir()           lseek()          _IO_LSEEK
seekdir()             lseek()          _IO_LSEEK
telldir()             lseek()          _IO_LSEEK
-------------------------------------------------------

如果我们暂时假设 opendir() 和 closeir() 函数将为我们自动处理,那么我们可以只关注 _IO_READ 和 _IO_LSEEK 消息及相关函数。


偏移量

_IO_LSEEK 消息和相关函数用于在文件内“查找”(或“移动”)。 它在目录中执行完全相同的操作; 您可以移动到“第一个”目录条目(通过显式地向 seekdir()给出偏移量或通过调用 rewinddir()),或移动到任意任意条目(通过使用seekdir()),或者您可以在目录条目列表(通过使用telldir())。

内容

所以现在剩下的就是“简单地”用我们目录的“内容”填充 struct dirent 。


2.1 Offsets

_IO_LSEEK 消息和相关函数用于在文件内“查找”(或“移动”)。 它在目录中执行完全相同的操作; 您可以移动到“第一个”目录条目(通过显式地向 seekdir()给出偏移量或通过调用rewinddir()),或移动到任意任意条目(通过使用seekdir()),或者您可以在 目录条目列表(通过使用telldir())。

然而,目录的“技巧”是,查找偏移量完全由您来定义和管理。 这意味着您可以决定将目录条目偏移量称为“0”、“1”、“2”等,或者也可以将它们称为“0”、“64”、“128”等。 这里唯一重要的是,设置文件位置 I/O 函数处理程序和读取 I/O 函数处理程序中的偏移量必须一致。

在下面的示例中,我们假设我们使用简单的“0”、“1”、“2”...方法。 (如果这些数字对应于某种媒体偏移量,您可以使用“0”、“64”、“128”……方法。您的选择。)


2.2 Contents

所以现在剩下的就是“简单地”用我们目录的“内容”填充 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 对应的条目是空闲/空的)。

d_offset

我们上面刚刚讨论过的目录的偏移量。 在我们的示例中,这将是一个简单的数字,如“0”、“1”、“2”等。在某些文件系统中,这是下一个目录条目的偏移量。

d_reclen

整个 struct dirent 字段的大小以及可能放置在其中的任何扩展。 该尺寸包括所需的任何对齐填充物。

d_namelen

d_name 字段中的字符数,不包括 NUL 终止符。

d_name

此目录条目的名称,必须以 NUL 结尾。

返回 struct dirent 条目时,传回客户端的返回码是返回的字节数。


3. Example

在此示例中,我们将创建一个名为 /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。 对特定文件(例如 e)执行 ls 操作表明该文件可供所有人(-r--r--r-- 部分)读取,并且大小为一个字节。 最后,随机进行一些测试表明这些文件具有指定的内容。 (请注意,由于文件仅包含一个字节,因此打印字符后没有换行,这就是提示与输出显示在同一行的原因。)

现在我们已经了解了这些特征,让我们看一下代码,它被组织成以下函数:


main() 和声明

主功能; 这是我们初始化所有内容并启动资源管理器运行的地方。

my_open()

_IO_CONNECT 消息的处理程序例程。

my_read()

_IO_READ 消息的处理程序例程。

my_read_dir() 和 my_read_file()

这两个例程执行 my_read() 函数的实际工作。

dirent_size() 和 dirent_fill()

处理 struct dirent 结构的实用函数。


请注意,虽然这里的代码被分成几个带有文本的简短部分,但您可以在示例程序附录中找到 atoz.c 的完整版本。

main() 和声明

代码的第一部分是 main() 函数和一些声明。 有一个方便的宏 ALIGN(),用于 dirent_fill() 和 dirent_size() 函数进行对齐。

my_open()

虽然 my_open() 非常短,但它有许多关键点。

my_read()

在 my_read() 函数中,为了决定我们需要执行哪种处理,我们查看了属性结构的模式成员。 如果 S_ISDIR() 宏表示它是一个目录,我们调用 my_read_dir(); 如果 S_ISREG() 宏表明它是一个文件,我们就调用 my_read_file()。 (有关这些宏的详细信息,请参阅《QNX Neutrino C 库参考》中的 stat() 条目。)请注意,如果我们无法分辨它是什么,我们将返回 EBADF; 这向客户表明发生了不好的事情。

my_read_dir()

my_read_dir() 是乐趣的开始。 从高层角度来看,我们分配一个缓冲区来保存此操作的结果(称为 reply_msg)。 然后,我们使用 dp 沿着输出缓冲区“行走”,并在行走过程中填充 struct dirent 条目。 辅助例程 dirent_size() 用于确定输出缓冲区中是否有足够的空间来填充下一个条目; 辅助例程 dirent_fill() 用于执行填充。

my_read_file()

在 my_read_file() 中,我们看到的代码与上面的简单读取示例中看到的代码大致相同。 我们正在做的唯一奇怪的事情是我们“知道”只有一个字节的数据被返回,所以如果 nbytes 不为零,那么它一定是 1(而不是其他)。 所以,我们可以通过直接填充字符变量字符串来构造返回给客户端的数据。

dirent_size()

辅助例程 dirent_size() 只是计算给定对齐约束的 struct dirent 所需的字节数。

dirent_fill()

最后,辅助例程 dirent_fill() 用于将传递给它的值(即 inode、offset 和 fname 字段)填充到同时传递的目录条目中。 作为一个额外的好处,它返回一个指向下一个目录条目应该开始的位置的指针,同时考虑到对齐情况。


3.1 main() and declarations

代码的第一部分是 main() 函数和一些声明。 有一个方便的宏 ALIGN(),用于 dirent_fill() 和 dirent_size() 函数进行对齐。

atoz_attrs 数组包含本示例中用于“文件”的属性结构。 我们声明 NUM_ENTS 个数组成员,因为我们有 NUM_ENTS (26) 个文件“a”到“z”。 用于目录本身(即 /dev/atoz 目录)的属性结构在 main() 中声明,并简称为 attr。 请注意两种类型的属性结构填充方式的差异:

(1)文件属性结构

标记为常规文件(S_IFREG 常量),访问模式为 0444(意味着每个人都具有读访问权限,没有人具有写访问权限)。 大小为“1”——文件只包含一个字节,即文件名对应的大写字母。 这些单独文件的索引节点编号为“1”到“26”(包括“1”到“26”)(将它们编号为“0”到“25”会更方便,但“0”是保留的)。

(2)目录属性结构

标记为访问模式为 0555 的目录文件(S_IFDIR 常量)(意味着每个人都具有读取和查找访问权限,没有人具有写入访问权限)。 大小是“26”——这只是根据目录中的条目数选择的数字。 索引节点是“27”——已知任何其他属性结构都没有使用该数字。

请注意,我们如何仅覆盖 connect_func 结构的 open 成员和 io_func 结构的 read 成员。 我们让所有其他的都使用 POSIX 默认值。

最后,请注意我们如何使用 resmgr_attach() 创建名称 /dev/atoz。 最重要的是,我们使用了标志 _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");
        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
    // the open connect function handler and read I/O function handlers
    connect_func.open = my_open;
    io_func.read = my_read;
    io_func.read64 = 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");
        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");
            exit (EXIT_FAILURE);
        }
        dispatch_handler (ctp);
    }

    // you'll never get here
    return (EXIT_SUCCESS);
}

3.2 my_open()

虽然 my_open() 非常短,但它有许多关键点。

请注意我们如何仅根据路径名长度来确定要打开的资源是“文件”还是“目录”。 我们可以做这个“技巧”,因为我们知道除了主目录之外,这个资源管理器中没有其他目录。 如果希望挂载点下面有多个目录,则必须对msg结构的路径成员进行更复杂的分析。 对于我们的简单示例,如果路径名中没有任何内容,我们就知道它是目录。 另外,请注意极其简化的路径名验证检查:我们只需进行比较以确保只有一个字符传递给我们,并且该字符位于“a”到“z”(含)范围内。 同样,对于更复杂的资源管理器,您将负责解析注册安装点之后的名称。

现在,最重要的功能! 请注意我们如何使用 POSIX 层默认函数来为我们完成所有工作! iofunc_open_default() 函数通常安装在连接函数表中,与我们的新 my_open() 函数现在占用的位置相同。 这意味着它采用相同的参数集! 我们所要做的就是决定我们希望将哪个属性结构与默认函数要创建的 OCB 绑定:要么是目录一(在这种情况下我们传递 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);
    }
}

3.3 my_read()

在 my_read() 函数中,为了决定我们需要执行哪种处理,我们查看了属性结构的模式成员。 如果 S_ISDIR() 宏表示它是一个目录,我们调用 my_read_dir(); 如果 S_ISREG() 宏表明它是一个文件,我们就调用 my_read_file()。 (有关这些宏的详细信息,请参阅《QNX Neutrino C 库参考》中的 stat() 条目。)请注意,如果我们无法分辨它是什么,我们将返回 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);
    }
}

3.4 my_read_dir()

my_read_dir() 是乐趣的开始。 从高层角度来看,我们分配一个缓冲区来保存此操作的结果(称为 reply_msg)。 然后,我们使用 dp 沿着输出缓冲区“行走”,并在行走过程中填充 struct dirent 条目。 辅助例程 dirent_size() 用于确定输出缓冲区中是否有足够的空间来填充下一个条目; 辅助例程 dirent_fill() 用于执行填充。

请注意,这些例程不是资源管理器库的一部分;下面对它们进行了讨论和记录。

乍一看,这段代码可能看起来效率低下; 我们使用 sprintf() 在 _POSIX_PATH_MAX (256) 字节长的缓冲区中创建一个两字节文件名(文件名字符和 NUL 终止符)。 这样做是为了使代码尽可能通用。

最后,请注意,我们使用 OCB 的 offset 成员来指示我们在任何给定时间为其生成 struct dirent 的特定文件名。 这意味着每当我们返回数据时,我们还必须更新偏移字段。

将数据返回给客户端是通过 MsgReply() 以“通常”的方式完成的。 请注意,MsgReply() 的状态字段用于指示发送到客户端的字节数

static int my_read_dir (resmgr_context_t *ctp, io_read_t *msg, iofunc_ocb_t *ocb)
{
    size_t  nbytes;
    size_t  nleft;
    struct  dirent *dp;
    char    *reply_msg;
    char    fname [_POSIX_PATH_MAX];

    // allocate a buffer for the reply
    reply_msg = calloc (1, _IO_READ_GET_NBYTES(msg));
    if (reply_msg == NULL) {
        return (ENOMEM);
    }

    // assign output buffer
    dp = (struct dirent *) reply_msg;

    // we have "nleft" bytes left
    nleft = _IO_READ_GET_NBYTES(msg);

    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);
}

3.5 my_read_file()

在 my_read_file() 中,我们看到的代码与上面的简单读取示例中看到的代码大致相同。 我们正在做的唯一奇怪的事情是我们“知道”只有一个字节的数据被返回,所以如果 nbytes 不为零,那么它一定是 1(而不是其他)。 所以,我们可以通过直接填充字符变量字符串来构造返回给客户端的数据。

请注意我们如何使用属性结构的 inode 成员作为返回数据的基础。 这是必须处理多个资源的资源管理器中常用的技巧。 另一个技巧是扩展属性结构(如上面“Extending the attributes structure”中所述)并直接存储数据或指向它的指针。

static int my_read_file (resmgr_context_t *ctp, io_read_t *msg, iofunc_ocb_t *ocb)
{
    size_t  nbytes;
    size_t  nleft;
    char    string;

    // we don't do any xtypes here...
    if ((msg->i.xtype & _IO_XTYPE_MASK) != _IO_XTYPE_NONE) {
        return (ENOSYS);
    }

    // figure out how many bytes are left
    nleft = ocb->attr->nbytes - ocb->offset;

    // and how many we can return to the client
    nbytes = min (nleft, _IO_READ_GET_NBYTES(msg));

    if (nbytes) {
        // create the output string
        string = ocb->attr->inode - 1 + 'A';

        // return it to the client
        MsgReply (ctp->rcvid, nbytes, &string + ocb->offset, nbytes);

        // update flags and offset
        ocb->attr->flags |= IOFUNC_ATTR_ATIME | IOFUNC_ATTR_DIRTY_TIME;
        ocb->offset += nbytes;
    } else {
        /* nothing to return, reply with the status (the 2nd argument) 
        set to 0 causing read() to return 0 indicating End Of File */
        MsgReply (ctp->rcvid, 0, NULL, 0);
    }

    // already done the reply ourselves
    return (_RESMGR_NOREPLY);
}

3.6 dirent_size()

辅助例程 dirent_size() 只是计算给定对齐约束的 struct dirent 所需的字节数。

int dirent_size (char *fname)
{
    return (ALIGN (sizeof (struct dirent) - 4 + strlen (fname) + 1));
}

我们减去四个字节,因为 dirent 结构包含名称前四个字符的空间,并且我们为名称末尾的空字符添加一个字节。 我们还可以这样计算大小:

ALIGN (offsetof (struct dirent, d_name) + strlen (fname) + 1)

同样,这个例程对于我们简单的资源管理器来说有点大材小用,因为我们知道每个目录条目有多大——所有文件名的长度都是一个字节。 然而,它是一个有用的实用例程。

3.7 dirent_fill()

最后,辅助例程 dirent_fill() 用于将传递给它的值(即 inode、offset 和 fname 字段)填充到同时传递的目录条目中。 作为一个额外的好处,它返回一个指向下一个目录条目应该开始的位置的指针,同时考虑到对齐情况。

struct dirent *dirent_fill (struct dirent *dp, int inode, int offset, char *fname)
{
    dp->d_ino = inode;
    dp->d_offset = offset;
    strcpy (dp->d_name, fname);
    dp->d_namelen = strlen (dp->d_name);
    dp->d_reclen = ALIGN (sizeof (struct dirent) - 4 + dp->d_namelen + 1); //?
    return ((struct dirent *) ((char *) dp + dp->d_reclen));
}

完整例子见 atoz.c: http://www.qnx.com/developers/docs/7.1/index.html#com.qnx.doc.neutrino.getting_started/topic/examples_atoz.c.html