Chapter 5:Handling Read and Write Messages
Handling the _IO_READ message
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()宏(在<sys/iofunc.h>中定義)來確定字節數。你傳遞一個指向消息的指針:
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的抵消成員發揮作用的地方。
Sample code for handling _IO_READ messages
這是一個完整的io_read處理程序,可以正確處理這些情況:
#include <errno.h> #include <stdio.h> #include <stddef.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/iofunc.h> #include <sys/dispatch.h>
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 <errno.h> #include <unistd.h> 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(),因此它會找出讀取的字節數。
我們還在讀處理程序中更新此設備的訪問時間。有關更新訪問時間的詳細信息,請參閱下面的“更新讀寫時間”部分。
Handling the _IO_WRITE message
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()宏(在<sys/iofunc.h>中定義)來確定字節數。你傳遞一個指向消息的指針:
num_bytes = _IO_WRITE_GET_NBYTES(msg); |
客戶端寫入的數據幾乎總是遵循存儲在struct _io_write中的頭消息。如果使用pwrite()或pwrite64()完成寫入則例外。當我們討論xtype成員時,更多內容。
要訪問數據,我們建議您將其重新讀入自己的緩衝區。假設您有一個名爲inbuf的緩衝區,它足夠“容納”您希望從客戶端讀取的所有數據(如果它不夠大,則必須逐個讀取數據)。
Sample code for handling _IO_WRITE messages
以下是可以添加到其中一個簡單資源管理器示例的代碼段。它打印出它給出的任何東西(假設它只給出了字符文本):
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”部分。
Methods of returning and replying
您可以通過各種方式從處理函數返回資源管理器庫。資源管理器庫可以根據需要對您進行回覆這一事實很複雜,但您必須告訴它這樣做,並將它將使用的信息放在所有正確的位置。
在本節中,我們將討論返回資源管理器庫的以下方法。
Returning with an error
要回復客戶端,使客戶端正在調用的函數(例如,read())將返回錯誤,您只需返回適當的errno值(來自<errno.h>)。
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);
Handling other read/write details
Handling the xtype member
傳遞給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); ... } |
Handling pread*() and pwrite*()
以下是演示如何在客戶端調用時處理_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庫參考。
Updating the time for reads and writes
在上面的閱讀示例中,我們做了:
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位,指示在從緩存刷新屬性時必須更新該屬性的該字段。