Layers in a resource manager

Updated: October 28, 2024

A resource manager is composed of some of the following layers:

Let's look at these from the bottom up.

The iofunc layer

This layer consists of a set of functions that take care of most of the POSIX filesystem details for you—they provide a POSIX personality. If you're writing a device resource manager, you'll want to use this layer so that you don't have to worry too much about the details involved in presenting a POSIX filesystem to the world.

This layer consists of default handlers that the resource manager library uses if you don't provide a handler. For example, if you don't provide an io_open handler, iofunc_open_default() is called.

The iofunc layer also contains helper functions that the default handlers call. If you override the default handlers with your own, you can still call these helper functions. For example, if you provide your own io_read handler, you should call iofunc_read_verify() at the start of it to make sure that the client has access to the resource.

The names of the functions and structures for this layer have the form iofunc_*. The header file is <sys/iofunc.h>. For more information, see the QNX Neutrino C Library Reference.

The resmgr layer

This layer manages most of the resource manager library details. It:

The names of the functions and structures for this layer have the form resmgr_*. The header file is <sys/resmgr.h>. For more information, see the QNX Neutrino C Library Reference.

Note: Be sure to #include <sys/iofunc.h> before <sys/resmgr.h>, or else the data structures won't be defined properly.
Figure 1. You can use the resmgr layer to handle _IO_* messages.

The dispatch layer

This layer acts as a single blocking point for a number of different types of things. With this layer, you can handle:

_IO_* messages
It uses the resmgr layer for this.
Select
Processes that do TCP/IP often call poll() (historically this was select(), and our support in the resource manager framework is named for this, but it's actually built on top of ionotify() here) to block while waiting for packets to arrive, or for there to be room for writing more data. With the dispatch layer, you register a handler function that's called instead. The functions for this are the select_*() functions.
Pulses
As with the other layers, you register a handler function that's called when a specific pulse arrives. The functions for this are the pulse_*() functions.
Other messages
You can give the dispatch layer a range of message types that you make up, and a handler. So if a message arrives and the first few bytes of the message contain a type in the given range, the dispatch layer calls your handler. The functions for this are the message_*() functions.
Figure 2. You can use the dispatch layer to handle _IO_* messages, select, pulses, and other messages.

Here's how messages are handled via the dispatch layer (or more precisely, through dispatch_handler()):

The thread pool layer

This layer allows you to have a single- or multithreaded resource manager. This means that one thread can be handling a write() while another thread handles a read().

You provide the blocking function for the threads to use as well as the handler function that's to be called when the blocking function returns. Most often, you give it the dispatch layer's functions. However, you can also give it the resmgr layer's functions or your own.

You can use this layer independently of a resource manager, as a general-purpose dynamic thread pool implementation.