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.