注:本文翻译自
QNX Software Development Platform --> Programming --> Getting Started with QNX Neutrino --> Resource Managers
http://www.qnx.com/developers/docs/7.1/index.html#com.qnx.doc.neutrino.getting_started/topic/s1_resmgr_examples.html
我现在将向您展示一些“食谱”示例,您可以将其剪切并粘贴到代码中,以用作项目的基础。 这些不是完整的资源管理器 - 您需要添加线程池并调度下面所示的“骨架”,并确保在您完成后将您的 I/O 函数版本放入 I/O 函数表中。 完成 iofunc_func_init(),以覆盖默认值!
我将从一些简单的示例开始,这些示例展示了各种资源管理器消息处理程序的基本功能:
read I/O function handler
write I/O function handler
device control I/O function handler
然后在高级主题部分,我们将了解返回目录条目的读取 I/O 函数处理程序 —— a read I/O function handler that returns directory entries.。
资源管理器的基本框架
以下可以用作具有多线程的资源管理器的模板。
一个简单的读 I/O 函数处理程序示例
为了说明资源管理器如何将数据返回给客户端,请考虑一个始终返回常量字符串“Hello, world!\n”的简单资源管理器。 即使在这个非常简单的案例中,也涉及到许多问题:
一个简单的写 I/O 函数处理程序示例
读 I/O 函数处理程序示例相当简单; 让我们看一下写 I/O 函数处理程序。
一个简单的设备控制 I/O 函数处理程序示例
相关概念
Resource Managers (System Architecture)
Writing a Resource Manager
一、The basic skeleton of a resource manager
以下可以用作具有多线程的资源管理器的模板。
当我们讨论 /dev/null 资源管理器时,我们已经在上面的“资源管理器库”中看到了一个可用于单线程的模板。
#include <stdio.h> #include <stdlib.h> #include <stddef.h> #include <sys/iofunc.h> #include <sys/dispatch.h> #include <string.h> static resmgr_connect_funcs_t connect_func; static resmgr_io_funcs_t io_func; static iofunc_attr_t attr; int main (int argc, char **argv) { thread_pool_attr_t pool_attr; thread_pool_t *tpp; dispatch_t *dpp; resmgr_attr_t resmgr_attr; 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 = (void *) dispatch_context_alloc; pool_attr.block_func = (void *) dispatch_block; pool_attr.handler_func = (void *) dispatch_handler; pool_attr.context_free = (void *) 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); return (EXIT_SUCCESS); }
有关调度接口(即 dispatch_create()函数)的更多信息,请参阅 QNX Neutrino C 库参考中的文档。
步骤1
在这里,您将使用线程池函数创建一个线程池,该线程池将能够为资源管理器中的消息提供服务。 一般来说,我建议您从单线程资源管理器开始,就像我们对上面提到的 /dev/null 示例所做的那样。 一旦运行了基本功能,就可以添加线程。 您需要修改 pool_attr 结构的 lo_water、hi_water、increment 和 maximum 成员,如我们讨论线程池函数的"Threads & Processes" 一章中所述。
第2步
在这里您可以添加您想要提供的任何功能。 这些是我们刚刚讨论的outcalls(例如,读取 I/O 函数处理程序、设备控制 I/O 函数处理程序)。例如,为 _IO_READ 消息添加您自己的处理程序,该消息指向您提供的名为 my_io_read() 的函数 ,您需要添加以下代码行:
io_func.io_read = my_io_read;
这会覆盖由 iofunc_func_init(), 使用指向您的函数 my_io_read() 的指针放入表中的 POSIX 层默认函数。
步骤3
您可能不希望您的资源管理器被称为 /dev/whatever,因此您应该选择一个合适的名称。 请注意,resmgr_attach() 函数是将属性结构(attr 参数)绑定到名称的地方 - 如果您希望资源管理器处理多个设备,则可以使用不同的属性结构多次调用 resmgr_attach() (这样您就可以在运行时区分不同的注册名称)。
二、A simple read I/O function handler example
为了说明资源管理器如何将数据返回给客户端,请考虑一个始终返回常量字符串“Hello, world!\n”的简单资源管理器。 即使在这个非常简单的案例中,也涉及到许多问题:
(1) 客户端数据区域大小与返回数据的匹配
在我们的例子中,资源管理器返回一个 14 字节的固定字符串 —— 正好有那么多可用数据。 这与磁盘上包含相关字符串的只读文件相同; 唯一真正的区别是这个“文件”是通过以下语句在我们的 C 程序中维护的:
char *data_string = "Hello, world!\n";
另一方面,客户端可以发出任意大小的 read() 请求 — 客户端可以请求 1 个字节、14 个字节或更多。 这对您要提供的读取 I/O 函数处理程序的影响是您必须能够将客户端请求的数据大小与可用数据大小相匹配。
(2) EOF案例的处理
处理客户端数据区域大小注意事项的方式的自然后果是处理固定字符串上的文件结尾 (EOF) 的极端情况。 一旦客户端读取了最后一个 \n 字符,客户端进一步尝试读取更多数据应返回 EOF。
(3) 维护上下文信息(lseek() 索引)
“数据区域大小注意事项”和“EOF 情况处理”场景都要求在传递给读 I/O 函数处理程序的 OCB 中维护上下文,特别是偏移成员。
(4) 更新 POSIX stat() 信息
最后一个考虑因素:当从资源管理器读取数据时,需要更新 POSIX 访问时间 (atime) 变量。 这样客户端 stat() 函数就会显示有人确实访问了该设备。
The code
这是解决上述所有问题的代码。 我们将在接下来的讨论中逐步进行讨论。
Effective use of other messaging functions
您还记得在“消息传递”一章中,我们讨论了其他一些消息传递函数,即 MsgWrite()、MsgWritev() 和 MsgReplyv()。 我在这里再次提到它们的原因是因为您的读 I/O 函数处理程序可能处于使用这些函数的绝佳位置。 在上面所示的简单示例中,我们从一个内存位置返回一组连续的字节数组。 在现实世界中,您可能需要从已分配的各个缓冲区返回多条数据。
2.1 The code
这是解决上述所有问题的代码。 我们将在接下来的讨论中逐步进行讨论。
/* * io_read1.c */ #include <stdio.h> #include <stdlib.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) { /* the first nbytes value, the status parameter, tells the client how many bytes we're giving them */ 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) no more data to return, tell the client we have 0 bytes left through the status parameter */ MsgReply (ctp->rcvid, 0, NULL, 0); } // 9) indicate we already did the MsgReply to the library return (_RESMGR_NOREPLY); }
步骤1
在这里,我们确保客户端的 open() 调用实际上指定了要打开设备进行读取。 如果客户端打开设备仅用于写入,然后尝试从中执行读取,则会被视为错误。 在这种情况下,辅助函数 iofunc_read_verify() 将返回 EBADF,而不是 EOK,因此我们将该值返回给库,然后库将其传递给客户端。
第2步
在这里,我们检查客户端是否指定了 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
为了计算我们实际可以返回给客户端的字节数,我们执行步骤 3 和 4,计算出设备上有多少字节可用(通过从 ocb->attr->nbytes 中获取总设备大小并减去当前的偏移到设备中)。 一旦我们知道还剩下多少字节,我们就取该数字和客户端指定的他们希望读取的字节数中的较小者。 例如,我们可能还剩下七个字节,而客户端只想读取两个字节。 在这种情况下,我们只能返回两个字节。 或者,如果客户端想要 4096 字节,但我们只剩下 7 个字节,那么我们只能返回 7 个字节。
步骤5
现在我们已经计算出了要返回给客户端的字节数,我们需要根据是否返回数据执行不同的操作。 如果我们要返回数据,则在第 5 步检查后,我们将数据回复给客户端。 请注意,我们使用 data_string + off 来返回从正确偏移量开始的数据(off 是根据 xtype 覆盖计算的)。
另请注意 MsgReply() 的第二个参数 - 它被记录为状态参数,但在本例中我们使用它来返回字节数。 这是因为客户端的 read() 函数的实现知道其 MsgSendv() 的返回值(顺便说一下,它是 MsgReply() 的状态参数)是已读取的字节数。 这是一个常见的约定。
步骤6
由于我们从设备返回数据,因此我们知道该设备已被访问。 我们在属性结构的 flags 成员中设置 IOFUNC_ATTR_ATIME 和 IOFUNC_ATTR_DIRTY_TIME 位。 这可以提醒 stat I/O 函数处理程序访问时间无效,应在回复之前从系统时钟中获取。 如果我们真的愿意,我们可以将当前时间填充到属性结构的 atime 成员中,并清除 IOFUNC_ATTR_DIRTY_TIME 标志。 但这不是很有效,因为我们期望从客户端获得比 stat() 请求更多的 read() 请求。 但是,您的使用模式可能另有规定。
那么客户端最终会在什么时间看到调用 stat() 呢? 资源管理器库提供的 iofunc_stat_default() 函数将查看属性结构的 flags 成员,以查看时间是否有效(atime、ctime 和 mtime 字段)。 如果不是(调用读取 I/O 函数处理程序并返回数据后就会出现这种情况),iofunc_stat_default() 函数将使用当前时间更新时间。 正如您所期望的,时间的实际值也会在 close() 上更新。
步骤7
现在,仅当我们不处理 _IO_XTYPE_OFFSET 覆盖修饰符时,我们才将 lseek() 偏移量字节数提前返回给客户端。 这确保了,在非 _IO_XTYPE_OFFSET 情况下,如果客户端调用 lseek() 来获取当前位置,或者(更重要的是)当客户端调用 read() 来获取接下来的几个字节时,则会设置资源的偏移量到正确的值。 在 _IO_XTYPE_OFFSET 覆盖的情况下,我们保留偏移量的 ocb 版本。
步骤8
将步骤 5 到 7 与此步骤进行对比。 这里我们只回复客户端,同时使用状态参数告诉它我们还剩下 0 个字节(read() 将返回这个 0 表示文件结束,EOF); 我们不执行任何其他功能。 还要注意,没有为 MsgReply() 指定数据区域,因为我们没有返回数据。
步骤9
最后,在步骤 9 中,无论是否向客户端返回数据,我们都会执行常见的处理。 由于我们已经通过 MsgReply() 解锁了客户端,因此我们当然不希望资源管理器库为我们执行此操作,因此我们通过返回 _RESMGR_NOREPLY 告诉它我们已经完成了该操作。
2.2 Effective use of other messaging functions
您还记得在“消息传递”一章中,我们讨论了其他一些消息传递函数,即 MsgWrite()、MsgWritev() 和 MsgReplyv()。 我在这里再次提到它们的原因是因为您的读 I/O 函数处理程序可能处于使用这些函数的绝佳位置。 在上面所示的简单示例中,我们从一个内存位置返回一组连续的字节数组。 在现实世界中,您可能需要从已分配的各个缓冲区返回多条数据。
在上面所示的简单示例中,我们从一个内存位置返回一组连续的字节数组。 在现实世界中,您可能需要从已分配的各个缓冲区返回多条数据。
一个典型的例子是环形缓冲区,可以在串行设备驱动程序中找到。 部分数据可能位于缓冲区末尾附近,其余数据“包装”到缓冲区顶部。 在这种情况下,您需要使用由两部分组成的 IOV 和 MsgReplyv() 来返回两个部分。 IOV 的第一部分将包含数据底部部分的地址(和长度),IOV 的第二部分将包含数据顶部部分的地址(和长度)。 或者,如果数据将分段到达,您可以选择使用 MsgWrite() 或 MsgWritev() 在数据到达时将其放入客户端的地址空间,然后指定最终的 MsgReply() 或 MsgReplyv() 解锁客户端。 正如我们在上面所看到的,不需要使用 MsgReply() 函数实际传输数据 - 您可以使用它来简单地解除对客户端的阻塞。
三、A simple write I/O function handler example
读 I/O 函数处理程序示例相当简单; 让我们看一下写 I/O 函数处理程序。
写 I/O 函数处理程序要克服的主要障碍是访问数据。 由于资源管理器库从客户端读取一小部分消息,因此客户端发送的数据内容(紧接在 _IO_WRITE 标头之后)可能仅部分到达写 I/O 函数处理程序。 例如,对于写入 1 MB 的客户端,资源管理器库仅读取标头和最多 1500 字节的数据。
您可以使用 resmgr_msgget() 获取本地消息缓冲区中可用的超过 1500 字节左右的任何数据。 但是,您可以使用 resmgr_msgget() 获取本地消息缓冲区中的数据和客户端发送缓冲区中的数据,而不是直接使用本地消息缓冲区中的数据,从而简化流程并减少出现错误的可能性。 直接使用消息缓冲区中的数据可能会提高性能(因为您避免了从消息缓冲区到传递给 resmgr_msgget() 的缓冲区的 memcpy() 操作),但您不太可能注意到任何改进,除非您的资源管理器处理 包含少量数据的大量写入操作。 提高性能的最佳方法是让客户端执行更大的写入。
写 I/O 函数处理程序示例中引入的另一个问题是 _IO_XTYPE_OFFSET 修饰符(和相关数据)的处理; 它与读 I/O 函数处理程序示例中的操作略有不同。
这是代码:
/* * 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; size_t nbytes; size_t off; size_t 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 = _IO_WRITE_GET_NBYTES(msg); if ((buffer = malloc (nbytes)) == NULL) { return (ENOMEM); } // 3) read the message data if (resmgr_msgget (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); }
正如您所看到的,执行的一些初始操作与读 I/O 函数处理程序示例中执行的操作相同 - iofunc_write_verify() 类似于 iofunc_read_verify() 函数,并且 xtype 覆盖检查相同。
步骤1
这里,我们对“xtype override”执行了与读 I/O 函数处理程序示例中相同的处理,除了偏移量没有存储为传入消息结构的一部分。 它不存储在那里的原因是,常见的做法是使用传入消息结构的大小来确定从客户端传输的实际数据的起点。 我们特别努力确保 xtype 处理代码中数据开头的偏移量 (doffset) 是正确的。
第2步
这里我们分配一个足够大的缓冲区来容纳数据。 客户端正在写入的字节数在 msg 联合体的 nbytes 成员中呈现给我们。这是由客户端的 C 库自动填充到 write() 例程中的。 请注意,如果我们没有足够的内存来处理 malloc() 请求,我们将向客户端返回错误号 ENOMEM — 实际上,我们将返回代码传递给客户端,让其知道其请求为何不正确,没完成。
步骤3
这里我们使用辅助函数 resmgr_msgget() 将客户端的全部数据内容直接读取到新分配的缓冲区中。 在大多数情况下,我们可以只使用 MsgRead(),但 resmgr_msgget() 通常更有效,并且在该消息是“组合消息”的一部分的情况下,resmgr_msgget() 会为我们执行适当的“魔法”(请参阅 有关我们为什么需要这样做的更多信息,请参阅 “Combine message”部分。)
resmgr_msgget() 的参数相当简单; 我们给它内部上下文指针(ctp),我们想要放置数据的缓冲区(buffer),以及我们希望读取的字节数(消息msg联合的nbytes成员)。 最后一个参数是当前消息的偏移量,这是我们在上面的步骤 1 中计算的。该偏移量有效地跳过了客户端的 C 库实现 write() 放置在那里的标头信息,并直接继续处理数据。 这实际上带来了两个有趣的点:
(1)我们可以使用任意偏移值以我们想要的任何顺序和大小读取客户端数据块。
(2)我们可以使用 resmgr_msggetv()(注意“v”)将数据从客户端读取到 IOV,也许描述各种缓冲区,类似于我们在消息传递章节中的文件系统讨论中对缓存缓冲区所做的操作。
步骤4
在这里,您可以对数据执行任何您想要的操作 - 我刚刚调用了一个名为 process_data() 的虚构函数,并向其传递了缓冲区和大小。
步骤5
现在我们释放缓冲区。 这一步很关键! 忘记这样做很容易,并且会导致“内存泄漏”。 请注意,在步骤 3 失败的情况下,我们还如何注意释放内存。
步骤6
我们使用宏 _IO_SET_WRITE_NBYTES()(请参阅 QNX Neutrino C 库参考中的 iofunc_write_verify() 条目)来存储我们写入的字节数,然后将其作为客户端的write()的返回值传递回客户端。 需要注意的是,您应该返回实际的字节数! 客户依赖于此。
步骤7
现在,我们对 stat()、lseek() 和其他 write() 函数执行类似的内务处理,就像我们对读 I/O 函数处理程序例程所做的那样(同样,我们仅在这种情况下修改 ocb 中的偏移量) 是 _IO_XTYPE_OFFSET 类型的消息)。 然而,由于我们正在写入设备,因此我们使用 IOFUNC_ATTR_MTIME 常量而不是 IOFUNC_ATTR_ATIME 常量。 MTIME 标志意味着“修改”时间,对资源的 write() 肯定会“修改”它。
步骤8
最后一步很简单:我们返回常量 EOK,它告诉资源管理器库它应该回复客户端。 我们的处理到此结束。 资源管理器将使用我们在回复中通过 _IO_SET_WRITE_NBYTES() 宏存储的字节数,并且客户端将解除阻塞; 客户端的 C 库 write() 函数将返回我们的设备写入的字节数。
四、A simple device control I/O function handler example
客户端的 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) 的采样率。 您可能还会发现需要根据应用程序将录制声音的采样率从 22kHz 更改为 44.1kHz。 devctl() 函数是执行此操作的适当方法。 当您编写资源管理器时,您可能会发现根本不需要任何 devctl() 支持,并且只需通过标准 read() 和 write() 函数即可执行所需的所有功能。 另一方面,您可能会发现需要将 devctl() 调用与 read() 和 write() 调用混合使用,或者实际上您的设备仅使用 devctl() 函数而不使用 read() 或 write() 。
devctl() 函数采用以下参数:
fd
您要将 devctl() 发送到的资源管理器的文件描述符。
dcmd
命令本身 — 两位方向和 30 位命令的组合(请参阅下面的讨论)。
dev_data_ptr
指向可发送、接收或两者的数据区域的指针。
nbytes
dev_data_ptr 数据区域的大小。
dev_info_ptr
可以由资源管理器设置的额外信息变量。
dcmd 中的前两位对数据传输的方向(如果有)进行编码。 有关详细信息,请参阅 I/O 参考部分(“Device control I/O function handler”下)中的描述。
当资源管理器收到 _IO_DEVCTL 消息时,它由设备控制 I/O 函数处理程序处理。 这是一个非常简单的示例,我们假设它用于设置上面讨论的音频设备的通道数和采样率:
/* * io_devctl1.c */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <sys/neutrino.h> #include <sys/iofunc.h> #include <devctl.h> #define AUDIO_DAC 1 #define AUDIO_ADC 2 extern void audio_set_nchannels(unsigned num); extern uint32_t audio_get_samplerate(unsigned port); extern void audio_set_samplerate(unsigned port, unsigned rate); #define DCMD_AUDIO_SET_CHANNEL_MONO __DION(_DCMD_MIXER, 1) #define DCMD_AUDIO_SET_CHANNEL_STEREO __DION(_DCMD_MIXER, 2) #define DCMD_AUDIO_GET_SAMPLE_RATE_DAC __DIOF(_DCMD_MIXER, 4, uint32_t) #define DCMD_AUDIO_SET_SAMPLE_RATE_DAC __DIOT(_DCMD_MIXER, 5, uint32_t) #define DCMD_AUDIO_GET_SAMPLE_RATE_ADC __DIOF(_DCMD_MIXER, 6, uint32_t) #define DCMD_AUDIO_SET_SAMPLE_RATE_ADC __DIOT(_DCMD_MIXER, 7, uint32_t) int io_devctl (resmgr_context_t *ctp, io_devctl_t *msg, iofunc_ocb_t *ocb) { int sts; /* 1) Create the reply buffer at the start of ctp->msg so we make sure we have enough space */ struct _io_devctl_reply *reply = (struct _io_devctl_reply *)ctp->msg; /* Create a pointer to the rate variable for the _GET_ devctls. */ unsigned nbytes = 0; uint32_t *rate = _DEVCTL_DATA(*reply); /* 2) Verify we have the entire devctl header in the buffer */ if( ctp->size < sizeof(msg->i) ) { return EBADMSG; } /* 3) See if it's a standard devctl() */ if ((sts = iofunc_devctl_default (ctp, msg, ocb)) != _RESMGR_DEFAULT) { return (sts); } /* How many bytes did the client send in addition to the devctl header? */ size_t payload_size = ctp->size - sizeof(msg->i); /* 4) See which command it was, and act on it */ switch (msg->i.dcmd) { case DCMD_AUDIO_SET_CHANNEL_MONO: /* Read or write access are sufficient */ if( !(ocb->ioflag & (_IO_FLAG_WR | _IO_FLAG_RD)) ) { return EPERM; } audio_set_nchannels (1); break; case DCMD_AUDIO_SET_CHANNEL_STEREO: /* Read or write access are sufficient */ if( !(ocb->ioflag & (_IO_FLAG_WR | _IO_FLAG_RD)) ) { return EPERM; } audio_set_nchannels (2); break; case DCMD_AUDIO_GET_SAMPLE_RATE_DAC: /* Write access is required for accessing the DAC */ if( !(ocb->ioflag & _IO_FLAG_WR) ) { return EPERM; } /* Verify that the client wants enough bytes */ if( ctp->info.dstmsglen < (sizeof(*reply) + sizeof(uint32_t)) ) { return EINVAL; } /* Set up the reply to the client */ nbytes = sizeof(uint32_t); *rate = audio_get_samplerate (AUDIO_DAC); break; case DCMD_AUDIO_SET_SAMPLE_RATE_DAC: /* Write access is required for accessing the DAC */ if( !(ocb->ioflag & _IO_FLAG_WR) ) { return EPERM; } /* Verify that we got all the bytes. */ if( payload_size < sizeof(uint32_t) ) { return EBADMSG; } rate = _DEVCTL_DATA(msg->i); audio_set_samplerate (AUDIO_DAC, *rate); break; case DCMD_AUDIO_GET_SAMPLE_RATE_ADC: /* Read access is required for accessing the ADC */ if( !(ocb->ioflag & _IO_FLAG_RD) ) { return EPERM; } /* Verify that the client wants enough bytes */ if( ctp->info.dstmsglen < (sizeof(*reply) + sizeof(uint32_t)) ) { return EINVAL; } /* Set up the reply to the client */ nbytes = sizeof(uint32_t); *rate = audio_get_samplerate (AUDIO_ADC); break; case DCMD_AUDIO_SET_SAMPLE_RATE_ADC: /* Read access is required for accessing the ADC */ if( !(ocb->ioflag & _IO_FLAG_RD) ) { return EPERM; } /* Verify that we got all the bytes. */ if( payload_size < sizeof(uint32_t) ) { return EBADMSG; } rate = _DEVCTL_DATA(msg->i); audio_set_samplerate (AUDIO_ADC, *rate); break; /* 5) In case it's a command that we don't recognize, fail it */ default: return (ENOSYS); } /* 6) Tell the client that it worked */ memset(reply, 0, sizeof(*reply)); SETIOV (ctp->iov, reply, sizeof(*reply) + nbytes); return (_RESMGR_NPARTS (1)); }
步骤1
我们设置了回复的写入位置。 客户端期待 _io_devctl_reply 结构中的响应,采样率紧随内存中的结构(取决于 dcmd 的值)。 该响应将占用 sizeof(msg->o) + sizeof(uint32_t) 字节。 我们拥有并可用于发送回复的缓冲区是 ctp->msg,其长度为 ctp->msg_max_size 字节。 因为它与我们用于从客户端接收命令的缓冲区相同,所以在开始将输出写入缓冲区之前,我们需要小心地完成输入处理。 来自客户端的 devctl() 消息位于缓冲区 ctp->msg 中的偏移量 ctp->offset 处。 如果 ctp->offset 和 ctp->msg_max_size 之间有足够的空间来容纳整个回复,我们可以使用它。 然而,在缓冲区的开头写入我们的回复要安全得多,这为我们提供了完整的 ctp->msg_max_size 字节数据来写入我们的响应。
第2步
对应代码中的 3),我们再次看到辅助函数的使用,这次是 iofunc_devctl_default(),它用于执行 devctl() 的所有默认处理。 当您不提供自己的设备控制 I/O 函数处理程序并让 iofunc_func_init() 为您初始化 I/O 并连接函数表时,将调用 iofunc_devctl_default() 函数。
我们在设备控制 I/O 函数处理函数中包含 iofunc_devctl_default() ,因为我们希望它为我们处理所有常规的 devctl() 情况。我们检查返回值; 如果它不是 _RESMGR_DEFAULT,则这意味着 iofunc_devctl_default() 函数“处理”了该请求,因此我们只需将其返回值作为我们的返回值传递。
如果常量 _RESMGR_DEFAULT 是返回值,那么我们就知道辅助函数没有处理请求,我们应该检查它是否是我们的请求之一。
步骤3
对应代码中的 4),对我们的自定义 devctl() 命令的检查是在此处完成的。 我们只需检查客户端代码发送的 dcmd 值以查看是否存在匹配。 请注意,我们调用虚构的函数 audio_set_nchannels()、audio_get_samplerate() 和 audio_set_samplerate() 来为客户端完成实际的“工作”。
作为处理各种音频 devctl() 命令的一部分,我们需要检查权限。 如果客户端正在获取或设置输出的数模转换器 (DAC) 的采样率,那么他们需要打开资源管理器句柄进行写入。 如果客户端正在设置输入的模数转换器 (ADC) 的采样率,那么他们需要打开资源管理器句柄进行读取。 这里请注意,所检查的权限与是否正在访问 DAC 或 ADC 有关,而不是基于 devctl() 是否正在从客户端发送或接收数据(即 __DIOF 和 __DIOT 的使用与读写无关) 权限)。
在设置采样率的 devctl() 调用中,我们还需要确保客户端发送了足够的数据。 devctl() 函数开头的长度检查是不够的,因为每个 devctl() 可能期望客户端发送不同数量的数据。 既然我们确切知道需要多少数据,我们就对数据进行全面检查。 完成此检查后,我们就可以安全地访问此数据并设置采样率。
在获取采样率的 devctl() 调用中,我们确保客户端有足够的空间来接收来自资源管理器的响应。
步骤4
对应代码中的 5),这一步简直就是很好的防御性编程。 我们返回 ENOSYS 错误代码,告诉客户端我们不理解他们的请求。
步骤5
如果到达这一行,我们就成功处理了传递给 devctl() 的 dcmd。 我们需要回复客户。 我们向资源管理器库返回一个由宏 _RESMGR_NPARTS() 编码的值,告诉它我们正在返回一个单部分 IOV。 然后将该值返回给客户端。
我们可以使用 _RESMGR_PTR() 宏,而不是使用宏 _RESMGR_NPARTS():
/* 6) tell the client it worked */ memset(reply, 0, sizeof(*reply)); return (_RESMGR_PTR (ctp, reply, sizeof(*reply) + nbytes));
devctl() 命令要求我们向客户端发送响应才能使 devctl() 成功。 仅返回 EOK 而没有任何响应消息是不够的。
- QNX Managers Examples Resource 文档qnx managers examples resource qnx managers resource文档 qnx managers resource manager qnx managers resource routines qnx managers resource writing qnx managers advanced resource 开篇qnx managers resource performance qnx文档tuning scheduling qnx sporadic文档 programming qnx overview文档