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.