io_read处理程序负责在收到_IO_READ消息后将数据字节返回给客户端。发送此消息的函数示例包括read(),readdir(),fread()和fgetc()。让我们首先看一下消息本身的格式:
struct _io_read { uint16_t type; uint16_t combine_len; uint32_t nbytes; uint32_t xtype; uint32_t zero; };
struct _io_read64 { uint16_t type; uint16_t combine_len; uint32_t nbytes; uint32_t xtype; uint32_t nbytes_hi; };
typedef union { struct _io_read i; struct _io_read i64; /* unsigned char data[nbytes]; */ /* nbytes is returned with MsgReply */ } io_read_t; |
与所有资源管理器消息一样,我们已经定义了一个包含输入(进入资源管理器)结构和回复或输出(返回客户端)结构的联合。 io_read处理程序使用io_read_t *msg的参数进行原型化,这是指向包含消息的union的指针。
由于这是一个read(),因此类型成员的值为_IO_READ或_IO_READ64。仅当长度大于4 GB时,客户端库才使用_IO_READ64表单。输入结构中感兴趣的项目是:
combine_len
此字段对组合消息有意义 - 请参阅“组合消息”一章。
nbytes
客户端期望的字节数。对于_IO_READ64消息,长度的高32位是nbytes_hi。
xtype
如果资源管理器支持,则按消息覆盖。即使您的资源管理器不支持它,您仍应检查此成员。有关xtype的更多信息(请参阅“处理xtype成员”部分)。
nbytes_hi
(仅限_IO_READ64)长度的高32位。
您可以使用_IO_READ_GET_NBYTES()宏(在
num_bytes = _IO_READ_GET_NBYTES(msg); |
我们将创建一个实际返回一些数据的io_read处理程序(固定字符串“Hello,world\n”)。我们将使用OCB跟踪我们在返回客户端的缓冲区中的位置。
当我们收到_IO_READ消息时,nbytes成员会告诉我们客户端想要读取多少字节。假设客户发出:
read (fd, buf, 4096); |
在这种情况下,将输出缓冲区中的整个“Hello,world\n”字符串返回并告诉客户端我们返回13个字节(即字符串的大小)是一件简单的事情。
但是,请考虑客户端执行以下操作的情况:
while (read (fd, &character, 1) != EOF) { printf ("Got a character \"%c\"\n", character); } |
当然,这对于客户端执行读取来说并不是一种非常有效的方式!在这种情况下,我们将msg-> i.nbytes设置为1(客户端想要获取的缓冲区的大小)。我们不能简单地将整个字符串一次性返回给客户端 - 我们必须一次将其移出一个字符。这是OCB的抵消成员发挥作用的地方。
这是一个完整的io_read处理程序,可以正确处理这些情况:
#include #include #include #include #include #include #include #include
int io_read (resmgr_context_t *ctp, io_read_t *msg, RESMGR_OCB_T *ocb);
static char *buffer = "Hello world\n";
static resmgr_connect_funcs_t connect_funcs; static resmgr_io_funcs_t io_funcs; static iofunc_attr_t attr;
int main(int argc, char **argv) { /* declare variables we'll be using */ resmgr_attr_t resmgr_attr; dispatch_t *dpp; dispatch_context_t *ctp; int id;
/* initialize dispatch interface */ if((dpp = dispatch_create()) == NULL) { fprintf(stderr, "%s: Unable to allocate dispatch handle.\n", argv[0]); return EXIT_FAILURE; }
/* initialize resource manager attributes */ memset(&resmgr_attr, 0, sizeof resmgr_attr); resmgr_attr.nparts_max = 1; resmgr_attr.msg_max_size = 2048;
/* initialize functions for handling messages, including our read handlers */ iofunc_func_init(_RESMGR_CONNECT_NFUNCS, &connect_funcs, _RESMGR_IO_NFUNCS, &io_funcs); io_funcs.read = io_read; io_funcs.read64 = io_read;
/* initialize attribute structure used by the device */ iofunc_attr_init(&attr, S_IFNAM | 0666, 0, 0); attr.nbytes = strlen(buffer)+1;
/* attach our device name */ if((id = resmgr_attach(dpp, &resmgr_attr, "/dev/sample", _FTYPE_ANY, 0, &connect_funcs, &io_funcs, &attr)) == -1) { fprintf(stderr, "%s: Unable to attach name.\n", argv[0]); return EXIT_FAILURE; }
/* allocate a context structure */ ctp = dispatch_context_alloc(dpp);
/* start the resource manager message loop */ while(1) { if((ctp = dispatch_block(ctp)) == NULL) { fprintf(stderr, "block error\n"); return EXIT_FAILURE; } dispatch_handler(ctp); } return EXIT_SUCCESS; }
int io_read (resmgr_context_t *ctp, io_read_t *msg, RESMGR_OCB_T *ocb) { size_t nleft; size_t nbytes; int nparts; int status;
if ((status = iofunc_read_verify (ctp, msg, ocb, NULL)) != EOK) return (status);
if ((msg->i.xtype & _IO_XTYPE_MASK) != _IO_XTYPE_NONE) return (ENOSYS);
/* * On all reads (first and subsequent), calculate * how many bytes we can return to the client, * based upon the number of bytes available (nleft) * and the client's buffer size */
nleft = ocb->attr->nbytes - ocb->offset; nbytes = min (_IO_READ_GET_NBYTES(msg), nleft);
if (nbytes > 0) { /* set up the return data IOV */ SETIOV (ctp->iov, buffer + ocb->offset, nbytes);
/* set up the number of bytes (returned by client's read()) */ _IO_SET_READ_NBYTES (ctp, nbytes);
/* * advance the offset by the number of bytes * returned to the client. */
ocb->offset += nbytes;
nparts = 1; } else { /* * they've asked for zero bytes or they've already previously * read everything */
_IO_SET_READ_NBYTES (ctp, 0);
nparts = 0; }
/* mark the access time as invalid (we just accessed it) */
if (msg->i.nbytes > 0) ocb->attr->flags |= IOFUNC_ATTR_ATIME;
return (_RESMGR_NPARTS (nparts)); } |
ocb通过存储偏移字段来维护我们的上下文,偏移字段给出了缓冲区内的位置,并通过指向属性结构attr的指针,它告诉我们缓冲区实际上通过其nbytes成员有多大。
当然,我们必须给资源管理器库提供io_read处理程序的地址,以便它知道调用它。所以我们调用iofunc_func_init()的main()中的代码变为:
/* initialize functions for handling messages */ iofunc_func_init(_RESMGR_CONNECT_NFUNCS, &connect_funcs, _RESMGR_IO_NFUNCS, &io_funcs); io_funcs.read = io_read; |
我们还需要将以下内容添加到main()上方的区域:
#include #include int io_read (resmgr_context_t *ctp, io_read_t *msg, RESMGR_OCB_T *ocb); static char *buffer = "Hello world\n";" |
属性结构的nbytes成员在哪里填写?在main()中,就在我们执行了iofunc_attr_init()之后。我们稍微修改了main():
iofunc_attr_init (&attr, S_IFNAM | 0666, 0, 0); |
在这一行之后:
attr.nbytes = strlen (buffer)+1; |
我们添加了这个:
# cat /dev/sample Hello, world |
此时,如果您要运行资源管理器(我们的简单资源管理器使用名称/dev/sample),您可以执行以下操作:
reply to the client for us
reply with nparts IOVs
它在哪里获得IOV阵列?它使用的是ctp-> iov。这就是为什么我们首先使用SETIOV()宏来使ctp-> iov指向要回复的数据。
如果我们没有数据,就像读取零字节的情况一样,那么我们将返回(_RESMGR_NPARTS(0))。但read()返回成功读取的字节数。我们在哪里提供这些信息?这就是_IO_SET_READ_NBYTES()宏的用途。它需要我们提供的nbytes并将其存储在上下文结构(ctp)中。然后,当我们返回到库时,库将获取此nbytes并将其作为第二个参数传递给MsgReplyv()。第二个参数告诉内核MsgSend()应该返回什么。由于read()函数正在调用MsgSend(),因此它会找出读取的字节数。
我们还在读处理程序中更新此设备的访问时间。有关更新访问时间的详细信息,请参阅下面的“更新读写时间”部分。
io_write处理程序负责在收到客户端的_IO_WRITE消息后将数据字节写入介质。发送此消息的函数示例是write()和fflush()。这是消息:
struct _io_write { uint16_t type; uint16_t combine_len; uint32_t nbytes; uint32_t xtype; uint32_t zero; /* unsigned char data[nbytes]; */ };
struct _io_write64 { uint16_t type; uint16_t combine_len; uint32_t nbytes; uint32_t xtype; uint32_t nbytes_hi; /* unsigned char data[nbytes]; */ };
typedef union { struct _io_write i; struct _io_write i64; /* nbytes is returned with MsgReply */ } io_write_t; |
与io_read_t一样,我们有一个输入和输出消息的并集,输出消息为空(资源管理器库将实际写入的字节数直接返回给客户端的MsgSend())。
您可以使用_IO_WRITE_GET_NBYTES()宏(在
num_bytes = _IO_WRITE_GET_NBYTES(msg); |
客户端写入的数据几乎总是遵循存储在struct _io_write中的头消息。如果使用pwrite()或pwrite64()完成写入则例外。当我们讨论xtype成员时,更多内容。
要访问数据,我们建议您将其重新读入自己的缓冲区。假设您有一个名为inbuf的缓冲区,它足够“容纳”您希望从客户端读取的所有数据(如果它不够大,则必须逐个读取数据)。
以下是可以添加到其中一个简单资源管理器示例的代码段。它打印出它给出的任何东西(假设它只给出了字符文本):
int io_write (resmgr_context_t *ctp, io_write_t *msg, RESMGR_OCB_T *ocb) { int status; char *buf; size_t nbytes;
if ((status = iofunc_write_verify(ctp, msg, ocb, NULL)) != EOK) return (status);
if ((msg->i.xtype & _IO_XTYPE_MASK) != _IO_XTYPE_NONE) return(ENOSYS);
/* Extract the length of the client's message. */ nbytes = _IO_WRITE_GET_NBYTES(msg);
/* Filter out malicious write requests that attempt to write more data than they provide in the message. */ if(nbytes > (size_t)ctp->info.srcmsglen - (size_t)ctp->offset - sizeof(io_write_t)) { return EBADMSG; }
/* set up the number of bytes (returned by client's write()) */ _IO_SET_WRITE_NBYTES (ctp, nbytes);
buf = (char *) malloc(nbytes + 1); if (buf == NULL) return(ENOMEM);
/* * Reread the data from the sender's message buffer. * We're not assuming that all of the data fit into the * resource manager library's receive buffer. */
resmgr_msgread(ctp, buf, nbytes, sizeof(msg->i)); buf [nbytes] = '\0'; /* just in case the text is not NULL terminated */ printf ("Received %d bytes = '%s'\n", nbytes, buf); free(buf);
if (nbytes > 0) ocb->attr->flags |= IOFUNC_ATTR_MTIME | IOFUNC_ATTR_CTIME;
return (_RESMGR_NPARTS (0)); } |
当然,我们必须给资源管理器库提供io_write处理程序的地址,以便它知道调用它。在我们调用iofunc_func_init()的main()代码中,我们将添加一行来注册我们的io_write处理程序:
/* initialize functions for handling messages */ iofunc_func_init(_RESMGR_CONNECT_NFUNCS, &connect_funcs, _RESMGR_IO_NFUNCS, &io_funcs); io_funcs.write = io_write; |
您可能还需要添加以下原型:
int io_write (resmgr_context_t *ctp, io_write_t *msg, RESMGR_OCB_T *ocb); |
此时,如果您要运行资源管理器(我们的简单资源管理器使用名称/dev/sample),您可以通过执行echo Hello > /dev/sample来写入它,如下所示:
# echo Hello > /dev/sample Received 6 bytes = 'Hello' |
注意我们如何将最后一个参数传递给resmgr_msgread()(offset参数)作为输入消息缓冲区的大小。这有效地跳过标题并转到数据组件。
如果您提供的缓冲区不够大,无法包含来自客户端的整个消息(例如,您有一个4 KB的缓冲区,而客户端想要写1兆字节),则必须分阶段读取缓冲区,使用for循环,以每次读取的数量推进传递给resmgr_msgread()的偏移量。
与io_read处理程序示例不同,这次我们没有对ocb-> offset做任何事情。在这种情况下,没有理由。如果我们管理具有前进位置(例如文件位置)的东西,那么ocb-> offset会更有意义。
回复比使用io_read处理程序更简单,因为write()调用不会期望任何数据返回。相反,它只是想知道写入是否成功,如果是,则写入了多少字节。为了告诉它写了多少字节,我们使用了_IO_SET_WRITE_NBYTES()宏。它需要我们提供的nbytes并将其存储在上下文结构(ctp)中。然后,当我们返回到库时,库将获取此nbytes并将其作为第二个参数传递给MsgReplyv()。第二个参数告诉内核MsgSend()应该返回什么。由于write()函数正在调用MsgSend(),因此它会找出写入的字节数。
由于我们正在写入设备,因此我们还应该更新修改,并可能更新创建时间。有关更新修改和更改文件状态时间的详细信息,请参阅下面的“Updating the time for reads and writes”部分。
您可以通过各种方式从处理函数返回资源管理器库。资源管理器库可以根据需要对您进行回复这一事实很复杂,但您必须告诉它这样做,并将它将使用的信息放在所有正确的位置。
在本节中,我们将讨论返回资源管理器库的以下方法。
Returning with an error
要回复客户端,使客户端正在调用的函数(例如,read())将返回错误,您只需返回适当的errno值(来自
return (ENOMEM);
在read()的情况下,这会导致read返回-1,并将errno设置为ENOMEM。
您有时可能会在资源管理器的代码中看到这一点:
_RESMGR_ERRNO(error_code)
但这与直接返回error_code相同。不推荐使用_RESMGR_ERRNO()宏。
Returning using an IOV array that points to your data
有时你会想要回复一个标题,后跟一个N缓冲区,每次你回复时使用的缓冲区都会有所不同。为此,您可以设置一个IOV数组,其元素指向标题和缓冲区。
上下文结构已经有一个IOV数组。如果您希望资源管理器库为您做出回复,则必须使用此数组。但是阵列必须包含足够的元素以满足您的需求。要确保这种情况,请在路径名空间中注册名称时,设置传递给resmgr_attach()的resmgr_attr_t结构的nparts_max成员。
以下示例假定变量i包含要回复的所需缓冲区的缓冲区数组中的偏移量。 _RESMGR_NPARTS(2)中的2告诉库ctp-> iov中有多少元素要回复。
my_header_t header; a_buffer_t buffers[N];
...
SETIOV(&ctp->iov[0], &header, sizeof(header)); SETIOV(&ctp->iov[1], &buffers[i], sizeof(buffers[i])); return (_RESMGR_NPARTS(2)); |
Returning with a single buffer containing data
一个例子是回复一个read(),其中所有数据都存在于一个缓冲区中。您通常会以两种方式看到这一点:
return (_RESMGR_PTR(ctp, buffer, nbytes));
和:
SETIOV (ctp->iov, buffer, nbytes);
return (_RESMGR_NPARTS(1));
第一种方法,使用_RESMGR_PTR()宏,只是方便第二种方法返回单个IOV。
Returning success but with no data
这可以通过几种方式完成。最简单的是:
return (EOK);
但你经常会看到:
return (_RESMGR_NPARTS(0));
请注意,在任何情况下都不会导致MsgSend()以0返回。MsgSend()返回的值是传递给_IO_SET_READ_NBYTES(),_IO_SET_WRITE_NBYTES()和其他类似宏的值。这两个用于上面的读写示例。
Getting the resource manager library to do the reply
在这种情况下,您向客户端提供数据并获取资源管理器库以便为您执行回复。但是,到那时,回复数据将无效。例如,如果回复数据位于您想要在返回之前释放的缓冲区中,则可以使用以下内容:
resmgr_msgwrite (ctp, buffer, nbytes, 0); |
resmgr_msgwrite()函数立即将缓冲区的内容复制到客户端的回复缓冲区中。请注意,仍然需要回复才能取消阻止客户端,以便它可以检查数据。接下来我们释放缓冲区。最后,我们返回资源管理器库,以便它使用零长度数据进行回复。由于回复长度为零,因此不会覆盖已写入客户端回复缓冲区的数据。当客户端从其发送调用返回时,数据在那里等待它。
Performing the reply in the server
在前面的所有示例中,资源管理器库调用MsgReply*()或MsgError()来取消阻止客户端。 在某些情况下,您可能不希望图书馆为您回复。 例如,您可能已经自己做过回复,或者您稍后会回复。 无论哪种情况,您都会返回如下:
return (_RESMGR_NOREPLY);
Leaving the client blocked, replying later
稍后将回复客户端的资源管理器的示例是管道资源管理器。如果客户端正在读取您的管道,但您没有客户端的数据,那么您可以选择:
您可以回复错误(EAGAIN)。
要么:
您可以阻止客户端,稍后,当您调用写入处理程序函数时,您可以使用新数据回复客户端。
另一个例子可能是客户端希望您写入某个设备但不想在数据完全写出之前得到回复。以下是可能遵循的事件序列:
您的资源管理器对硬件执行一些I/O以告知它数据可用。
硬件在准备好数据包时会产生中断。
您可以通过将数据写入硬件来处理中断。
在写入所有数据之前可能会发生许多中断,然后才会回复客户端。
然而,第一个问题是客户是否想要被阻止。如果客户端不希望被阻止,则会打开O_NONBLOCK标志:
fd = open("/dev/sample", O_RDWR | O_NONBLOCK);
默认设置是允许您阻止它。
在上面的读取和写入示例中,首先要做的事情之一就是调用一些POSIX验证函数:iofunc_read_verify()和iofunc_write_verify()。 如果我们将int的地址作为最后一个参数传递,那么在返回时,如果客户端不希望被阻塞(设置了O_NONBLOCK标志),则函数将填充非零的int,如果客户端想要被阻塞,则为零。
int nonblock; ... |
如果有时间决定我们是否应该回复错误或稍后回复,我们会:
if (nonblock) { |
问题依然存在:你如何自己做出回复?要注意的唯一细节是要回复的rcvid 是ctp-> rcvid。如果您稍后回复,那么您将保存ctp-> rcvid并在回复中使用保存的值: MsgReply(saved_rcvid, 0, buffer, nbytes);
要么:
iov_t iov[2];
SETIOV(&iov[0], &header, sizeof(header));
SETIOV(&iov[1], &buffers[i], sizeof(buffers[i]));
MsgReplyv(saved_rcvid, 0, iov, 2);
请注意,您可以使用resmgr_msgwrite()和resmgr_msgwritev()在数据可用时填充客户端的回复缓冲区。只记得在某个时候执行MsgReply *()以取消阻止客户端。
如果您要回复_IO_READ或_IO_WRITE消息,则MsgReply *()的status参数必须是读取或写入的字节数。
还有另一种方法可以恢复被阻止的操作,但它没有其他方法那么高效:你可以调用resmgr_msg_again()。此函数将resmgr_context_t结构恢复为资源管理器收到与rcvid关联的消息时的方式,然后再次处理该消息,就像刚收到该消息一样。
如果您的资源管理器阻止客户端,则需要跟踪哪些客户端被阻止,以便您可以在必要时取消阻止它们。有关详细信息,请参阅“取消阻止客户端和处理中断”一章中的“取消阻止,如果有人关闭文件描述符”。
Returning and telling the library to do the default action
在大多数情况下,默认操作是库使客户端的功能失败并使用ENOSYS:
return (_RESMGR_DEFAULT);
传递给io_read,io_write和io_openfd处理程序的消息结构包含一个名为xtype的成员。来自struct _io_read:
struct _io_read { ... uint32_t xtype; ... } |
基本上,xtype包含可用于调整标准I / O函数行为的扩展信息。 此信息包括类型和可选的一些标志:
对于_IO_READ和_IO_WRITE消息,名称的格式为_IO_XTYPE_ *或_IO_XFLAG_ *。
对于_IO_OPENFD消息,名称的格式为_IO_OPENFD_ *。 您可能不得不担心的唯一一个(假设您甚至想要编写自己的处理程序)是_IO_OPENFD_NONE。
要将类型与_IO_READ和_IO_WRITE消息中的标志隔离,请使用带有_IO_XTYPE_MASK的xtype成员的按位AND:
if ((msg->i.xtype & _IO_XTYPE_MASK) == ...) |
大多数资源管理员只关心几种类型:
_IO_XTYPE_NONE
没有提供扩展类型信息。
_IO_XTYPE_OFFSET
如果客户端正在调用pread(),pread64(),pwrite()或pwrite64(),那么他们不希望您使用OCB中的偏移量。 相反,他们提供一次性偏移。 该偏移量遵循驻留在消息缓冲区开头的struct _io_read或struct _io_write标头。
例如:
struct myread_offset { struct _io_read read; struct _xtype_offset offset; } |
一些资源管理员可以确保他们的客户永远不会调用pread *()或pwrite *()。 (例如,控制机器人手臂的资源管理器可能不关心。)在这种情况下,您可以将此类消息视为错误。
_IO_XTYPE_READCOND
如果客户端正在调用readcond(),则他们希望对读取施加时序和返回缓冲区大小约束。 这些约束遵循消息缓冲区开头的struct _io_read或struct _io_write标头。 例如:
struct myreadcond { struct _io_read read; struct _xtype_readcond cond; } |
与_IO_XTYPE_OFFSET一样,如果资源管理器未准备好处理readcond(),则可以将此类消息视为错误。
_IO_XTYPE_READDIR
readdir()函数在_IO_READ消息中设置此标志,以指示客户端正在读取目录。在此消息的处理程序中,如果客户端对目录执行read(),则可能会给出EISDIR错误。
以下类型用于特殊目的,因此您的资源管理器可能不需要处理它们:
_IO_XTYPE_MQUEUE
_IO_XTYPE_REGISTRY
_IO_XTYPE_TCPIP
_IO_XTYPE_TCPIP_MMSG(QNX Neutrino 7.0或更高版本)
_IO_XTYPE_TCPIP_MSG
_IO_XTYPE_TCPIP_MSG2
xtype成员还可以包括一些标志。您的资源经理可能对以下内容感兴趣:
_IO_XFLAG_DIR_EXTRA_HINT
此标志仅在您从目录中读取时有效。文件系统通常应该在容易获取时返回额外的目录信息。如果设置了此标志,则提示文件系统更加努力(可能导致媒体查找)返回额外信息。最常见的用法是返回_DTYPE_LSTAT信息。
如果您使用dircntl()为目录设置D_FLAG_STAT,则readdir()函数会在_IO_READ消息中设置此标志。
_IO_XFLAG_DIR_STAT_FORM_*
(QNX Neutrino 7.0或更高版本)这些位指定当客户端读取目录时资源管理器应返回的统计结构的形式。客户端可以通过dircntl()指定表单,readdir()相应地设置标志:您可以使用_IO_XFLAG_DIR_STAT_FORM_MASK将这些位与xtype的其余部分隔离开来。
其他定义的标志用于特殊目的,因此您的资源管理器可能不需要处理它们:
_IO_XFLAG_BLOCK
_IO_XFLAG_NONBLOCK
If you aren't expecting extended types (xtype)
以下代码示例演示了如何处理您不期望任何扩展类型的情况。在这种情况下,如果您收到包含xtype的消息,则应使用ENOSYS进行回复。该示例可以在io_read或io_write处理程序中使用。
int io_read (resmgr_context_t *ctp, io_read_t *msg, RESMGR_OCB_T *ocb) { int status;
if ((status = iofunc_read_verify(ctp, msg, ocb, NULL)) != EOK) { return (status); }
/* No special xtypes */ if ((msg->i.xtype & _IO_XTYPE_MASK) != _IO_XTYPE_NONE) return (ENOSYS); ... } |
以下是演示如何在客户端调用时处理_IO_READ或_IO_WRITE消息的代码示例。
Sample code for handling _IO_READ messages in pread*()
以下示例代码演示了如何在客户端调用其中一个pread *()函数的情况下处理_IO_READ。
/* we are defining io_pread_t here to make the code below simple */ typedef struct { struct _io_read read; struct _xtype_offset offset; } io_pread_t;
int io_read (resmgr_context_t *ctp, io_read_t *msg, RESMGR_OCB_T *ocb) { off64_t offset; /* where to read from */ int status;
if ((status = iofunc_read_verify(ctp, msg, ocb, NULL)) != EOK) { return(status); }
switch(msg->i.xtype & _IO_XTYPE_MASK) { case _IO_XTYPE_NONE: offset = ocb->offset; break; case _IO_XTYPE_OFFSET: /* * io_pread_t is defined above. * Client is doing a one-shot read to this offset by * calling one of the pread*() functions */ offset = ((io_pread_t *) msg)->offset.offset; break; default: return(ENOSYS); }
... } |
Sample code for handling _IO_WRITE messages in pwrite*()
以下示例代码演示了如何在客户端调用其中一个pwrite *()函数的情况下处理_IO_WRITE。请记住,struct _xtype_offset信息遵循发送方消息缓冲区中的struct _io_write。这意味着要写入的数据遵循struct _xtype_offset信息(而不是遵循struct _io_write的正常情况)。因此,在执行resmgr_msgread()调用时必须考虑到这一点,以便从发送方的消息缓冲区中获取数据。
/* we are defining io_pwrite_t here to make the code below simple */ typedef struct { struct _io_write write; struct _xtype_offset offset; } io_pwrite_t;
int io_write (resmgr_context_t *ctp, io_write_t *msg, RESMGR_OCB_T *ocb) { off64_t offset; /* where to write */ int status; size_t skip; /* offset into msg to where the data resides */
if ((status = iofunc_write_verify(ctp, msg, ocb, NULL)) != EOK) { return(status); }
switch(msg->i.xtype & _IO_XTYPE_MASK) { case _IO_XTYPE_NONE: offset = ocb->offset; skip = sizeof(io_write_t); break; case _IO_XTYPE_OFFSET: /* * io_pwrite_t is defined above * client is doing a one-shot write to this offset by * calling one of the pwrite*() functions */ offset = ((io_pwrite_t *) msg)->offset.offset; skip = sizeof(io_pwrite_t); break; default: return(ENOSYS); }
...
/* * get the data from the sender's message buffer, * skipping all possible header information */ resmgr_msgreadv(ctp, iovs, niovs, skip);
... } |
Handling readcond()
为处理pread()/_ IO_XTYPE_OFFSET情况而执行的相同类型的操作可用于处理客户端的readcond()调用:
typedef struct { struct _io_read read; struct _xtype_readcond cond; } io_readcond_t |
然后:
struct _xtype_readcond *cond ... CASE _IO_XTYPE_READCOND: cond = &((io_readcond_t *)msg)->cond break; } |
然后你的经理必须正确地解释和处理readcond()的参数。有关更多信息,请参阅QNX Neutrino C库参考。
在上面的阅读示例中,我们做了:
if (msg->i.nbytes > 0) ocb->attr->flags |= IOFUNC_ATTR_ATIME; |
根据POSIX,如果读取成功并且读者要求超过零字节,则必须标记访问时间以进行更新。但是POSIX没有说它必须立即更新。如果您正在进行多次读取,则可能不希望从每次读取的内核中读取时间。在上面的代码中,我们仅将时间标记为需要更新。当处理下一个_IO_STAT或_IO_CLOSE_OCB消息时,资源管理器库将看到需要更新的时间,然后从内核获取它。这当然具有缺点,即时间不是读取的时间。
类似地,对于上面的写样本,我们做了:
if (msg->i.nbytes > 0) ocb->attr->flags |= IOFUNC_ATTR_MTIME | IOFUNC_ATTR_CTIME; |
所以同样的事情会发生。
如果您确实希望时间代表读取或写入时间,那么在设置标志后,您只需要调用iofunc_time_update()辅助函数。所以读取线变为:
if (msg->i.nbytes > 0) { ocb->attr->flags |= IOFUNC_ATTR_ATIME; iofunc_time_update(ocb->attr); } |
并且写入行变为:
if (msg->i.nbytes > 0) { ocb->attr->flags |= IOFUNC_ATTR_MTIME | IOFUNC_ATTR_CTIME; iofunc_time_update(ocb->attr); } |
在清除任何缓存的属性之前,应该调用iofunc_time_update()。作为更改时间字段的结果,属性结构将在flags字段中设置IOFUNC_ATTR_DIRTY_TIME位,指示在从缓存刷新属性时必须更新该属性的该字段。