Under the client's covers

When a client calls a function that requires pathname resolution (e.g. open(), rename(), stat(), or unlink()), the function sends messages to both the process manager and the resource manager to obtain a file descriptor. Once the file descriptor is obtained, the client can use it to send messages to the device associated with the pathname, via the resource manager.

In the following, the file descriptor is obtained, and then the client writes directly to the device:

/*
 * In this stage, the client talks 
 * to the process manager and the resource manager.
 */
fd = open("/dev/ser1", O_RDWR);

/*
 * In this stage, the client talks directly to the
 * resource manager.
 */
for (packet = 0; packet < npackets; packet++)
{
    write(fd, packets[packet], PACKET_SIZE);
}
close(fd);

For the above example, here's the description of what happened behind the scenes. We'll assume that a serial port is managed by a resource manager called devc-ser8250, that's been registered with the pathname prefix /dev/ser1:

Figure 1. Under-the-cover communication between the client, the process manager, and the resource manager.
  1. The client's library sends a "query" message. The open() in the client's library sends a message to the process manager asking it to look up a name (e.g. /dev/ser1).
  2. The process manager indicates who's responsible and it returns the nd, pid, chid, and handle that are associated with the pathname prefix.

    Here's what went on behind the scenes...

    When the devc-ser8250 resource manager registered its name (/dev/ser1) in the namespace, it called the process manager. The process manager is responsible for maintaining information about pathname prefixes. During registration, it adds an entry to its table that looks similar to this:

    0, 47167, 1, 0, 0, /dev/ser1
          
    

    The table entries represent:

    0
    Node descriptor (nd).
    47167
    Process ID (pid) of the resource manager.
    1
    Channel ID (chid) that the resource manager is using to receive messages with.
    0
    Handle given in case the resource manager has registered more than one name. The handle for the first name is 0, 1 for the next name, etc.
    0
    The open type passed during name registration (0 is _FTYPE_ANY).
    /dev/ser1
    The pathname prefix.

    A resource manager is uniquely identified by a node descriptor, process ID, and a channel ID. The process manager's table entry associates the resource manager with a name, a handle (to distinguish multiple names when a resource manager registers more than one name), and an open type.

    When the client's library issued the query call in step 1, the process manager looked through all of its tables for any registered pathname prefixes that match the name. If another resource manager had previously registered the name /, more than one match would be found. So, in this case, both / and /dev/ser1 match. The process manager will reply to the open() with the list of matched servers or resource managers. The servers are queried in turn about their handling of the path, with the longest match being asked first.

  3. The client's library sends a "connect" message to the resource manager. To do so, it must create a connection to the resource manager's channel:
    fd = ConnectAttach(nd, pid, chid, 0, 0);
          
    

    The file descriptor that's returned by ConnectAttach() is also a connection ID and is used for sending messages directly to the resource manager. In this case, it's used to send a connect message (_IO_CONNECT defined in <sys/iomsg.h>) containing the handle to the resource manager requesting that it open /dev/ser1.

    Note: Typically, only functions such as open() call ConnectAttach() with an index argument of 0. Most of the time, you should OR _NTO_SIDE_CHANNEL into this argument, so that the connection is made via a side channel, resulting in a connection ID that's greater than any valid file descriptor.

    When the resource manager gets the connect message, it performs validation using the access modes specified in the open() call (e.g. are you trying to write to a read-only device?).

  4. The resource manager generally responds with a pass (and open() returns with the file descriptor) or fail (the next server is queried).
  5. When the file descriptor is obtained, the client can use it to send messages directly to the device associated with the pathname.

    In the sample code, it looks as if the client opens and writes directly to the device. In fact, the write() call sends an _IO_WRITE message to the resource manager requesting that the given data be written, and the resource manager responds that it either wrote some of all of the data, or that the write failed.

Eventually, the client calls close(), which sends an _IO_CLOSE_DUP message to the resource manager. The resource manager handles this by doing some cleanup.