The io_read() example was fairly simple; let's take a look at io_write(). The major hurdle to overcome with the io_write() is to access the data. Since the resource manager library reads in a small portion of the message from the client, the data content that the client sent (immediately after the _IO_WRITE header) may have only partially arrived at the io_write() function. To illustrate this, consider the client writing one megabyte—only the header and a few bytes of the data will get read by the resource manager library. The rest of the megabyte of data is still available on the client side—the resource manager can access it at will.
There are really two cases to consider:
The real design decision, however, is, "how much trouble is it worth to try to save the kernel copy of the data already present?" The answer is that it's not worth it. There are a number of reasons for this:
I think the first two points are self-explanatory. The third point deserves clarification. Let's say the client sent us a large chunk of data, and we did decide that it would be a good idea to try to save the part of the data that had already arrived. Unfortunately, that part is very small. This means that instead of being able to deal with the large chunk all as one contiguous array of bytes, we have to deal with it as one small part plus the rest. Effectively, we have to "special case" the small part, which may have an impact on the overall efficiency of the code that deals with the data. This can lead to headaches, so don't do this!
The real answer, then, is to simply re-read the data into buffers that you've prepared. In our simple io_write() example, I'm just going to malloc() the buffer each time, read the data into the buffer, and then release the buffer via free(). Granted, there are certainly far more efficient ways of allocating and managing buffers!
One further wrinkle introduced in the io_write() example is the handling of the _IO_XTYPE_OFFSET modifier (and associated data; it's done slightly differently than in the io_read() example).
Here's the code:
/* * 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; int nbytes; int off; int 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 = msg -> i.nbytes; if ((buffer = malloc (nbytes)) == NULL) { return (ENOMEM); } // 3) (re-)read the data from the client if (resmgr_msgread (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); }
As you can see, a few of the initial operations performed were identical to those done in the io_read() example — the iofunc_write_verify() is analogous to the iofunc_read_verify() function, and the xtype override check is the same.
The parameters to resmgr_msgread() are fairly straightforward; we give it the internal context pointer (ctp), the buffer into which we want the data placed (buffer), and the number of bytes that we wish read (the nbytes member of the message msg union). The last parameter is the offset into the current message, which we calculated above, in step 1. The offset effectively skips the header information that the client's C library implementation of write() put there, and proceeds directly to the data. This actually brings about two interesting points: dl