Characteristics of resource managers

As we saw in our examples (above), the key to the flexibility of the resource managers is that all the functionality of the resource manager is accessed using standard POSIX function calls—we didn't use “special” functions when talking to the serial port. But what if you need to do something “special,” something very device-specific?

For example, setting the baud rate on a serial port is an operation that's very specific to the serial port resource manager—it's totally meaningless to the filesystem resource manager. Likewise, setting the file position via lseek() is useful in a filesystem, but meaningless in a serial port. The solution POSIX chose for this is simple. Some functions, like lseek(), simply return an error code on a device that doesn't support them. Then there's the “catch-all” (and non-POSIX) device control function, called devctl(), that allows device-specific functionality to be provided. Devices that don't understand the particular devctl() command simply return an error, just as devices that don't understand the lseek() command would.

Since we've mentioned lseek() and devctl() as two common commands, it's worthwhile to note that pretty much all file-descriptor (or FILE * stream) function calls are supported by resource managers.

This naturally leads us to the conclusion that resource managers will be dealing almost exclusively with file-descriptor based function calls. Since QNX Neutrino is a message-passing operating system, it follows that the POSIX functions get translated into messages, which are then sent to resource managers. It is this “POSIX-function to message-passing” translation trick that lets us decouple clients from resource managers. All a resource manager has to do is handle certain well-defined messages. All a client has to do is generate the same well-defined messages that the resource manager is expecting to receive and handle.

Note: Since the interaction between clients and resource managers is based on message passing, it makes sense to make this “translation layer” as thin as possible. For example, when a client does an open() and gets back a file descriptor, the file descriptor is in fact the connection ID! This connection ID (file descriptor) gets used in the client's C library functions (such as read()) where a message is created and sent to the resource manager.