How does it work?

Updated: April 19, 2023

As explained in the System Architecture guide, QNX Neutrino client and server applications communicate by QNX Neutrino message passing.

Function calls that need to communicate with a manager application, such as the POSIX functions open(), write(), read(), ioctl(), or other functions such as devctl() are all built on QNX Neutrino message passing.

Qnet allows these messages to be sent over a network. If these messages are being sent over a network, how is a message sent to a remote manager vs a local manager?

When you access local devices or manager processes (such as a serial device, mqueue, or TCP/IP socket), you access these devices by opening a pathname under /dev. This may be apparent in the application source code:

/*Open a serial device*/
fd = open("/dev/ser1",O_RDWR....);

or it may not. For example, when you open a socket:

/*Create a UDP socket*/
sock = socket(AF_INET, SOCK_DGRAM, 0);

The socket() function opens a pathname under /dev called /dev/socket/2 (in the case of AF_INET, which is address family two). The socket() function call uses this pathname to establish a connection with the socket manager (io-pkt*), just as the open() call above established a connection to the serial device manager (devc-ser8250).

The magic of this is that you access all managers by the name that they added to the pathname space. For more information, see the Writing a Resource Manager guide.

When you enable the Qnet native network protocol, the pathname spaces of all the nodes in your Qnet network are added to yours. The pathname space of remote nodes appears (by default) under the prefix /net.

The /net directory is created by the Qnet protocol manager (lsm-qnet.so). If, for example, the other node is called node1, its pathname space appears as follows:

/net/node1/dev/socket
/net/node1/dev/ser1
/net/node1/home
/net/node1/bin
....

So with Qnet, you can now open pathnames (files or managers) on other remote Qnet nodes, in the same way that you open files locally. This means that you can access regular files or manager processes on other Qnet nodes as if they were executing on your local node.

First, let's see some basic examples of Qnet use:

In all of these uses, the application source or the libraries (for example libc) they depend on, simply open the pathnames under /net. For example, if you wish to make use of a serial device on another node node1, perform an open() function with the pathname /net/node1/dev/ser1:

fd = open("/net/node1/dev/ser1",O_RDWR...);

As you can see, the code required for accessing remote resources and local resources is identical. The only change is the pathname used.

In the TCP/IP socket() case, it's the same, but implemented differently. In the socket case, you don't directly open a filename. This is done inside the socket library. In this case, an environment variable is provided to set the pathname for the socket call (the SOCK environment variable—see io-pkt*).

Some other applications are:

Remote filesystem access
In order to access /tmp/file1 file on node1 remotely from another node, use /net/node1/tmp/file1 in open().
Message queue
You can create or open a message queue by using mq_open(). The mqueue manager must be running. When a queue is created, it appears in the pathname space under /dev/mqueue. So, you can access /dev/mqueue on node1 from another node by using /net/node1/dev/mqueue.
Note: The alternate implementation of message queues that uses the mq server and a queue within the kernel doesn't support access to a queue via Qnet.
Semaphores
Using Qnet, you can create or access named semaphores in another node. For example, use /net/node1/semaphore_location in the sem_open() function. This creates or accesses the named semaphore in node1.

This brings up an important issue for the client application or libraries that a client application uses. If you think that your application will be distributed over a network, you will want to include the capability to specify another pathname for connecting to your services. This way, your application will have the flexibility of being able to connect to local or remote services via a user-configuration adjustment. This could be as simple as the ability to pass a node name. In your code, you would add the prefix /net/node_name to any pathname that may be opened on the remote node. In the local case, or default case if appropriate, you could omit this prefix when accessing local managers.

In this example, you're using standard resource managers, such as would be developed using the resource manager framework (see the Writing a Resource Manager guide).

There's another design issue to contend with at this point: the above design is a static one. If you have services at known locations, or the user will be placing services at known locations, then this may be sufficient. It would be convenient, though, if your client application could locate these services automatically, without the need to know what nodes exist in the Qnet network, or what pathname they've added to the pathname space. You can now use the Global Name Service (gns) manager to locate services with an arbitrary name representing that service. For example, you can locate a service with a name such as printer instead of opening a pathname of /net/node/dev/par1 for a parallel port device. The printer name locates the parallel port manager process, whether it's running locally or remotely.