
 Continuing the cycle of 
QNX real-time operating system notes. This time, I would like to talk about cross-tasking interaction in QNX Neutrino (we will consider QNX 6.5.0). In RTOS, there is a wide range of inter-tasking mechanisms — from QNX-specific messaging to familiar signals to developers of UNIX and POSIX and shared memory. And although most of the notes will be devoted to messaging, the features of using signals, POSIX messages and shared memory will also be described. And those who read to the end will get two buns for tea.

 Understanding the messaging principle is essential for a QNX system programmer, since This mechanism plays a fundamental role in the RTOS. Many familiar and familiar to the developers of the functions of the operating system are only add-ons and implemented through messaging (for example, 
read() and 
write() ). 
Qtx RTOS Interaction Forms
As mentioned above, messaging is a fundamental mechanism for inter-task interaction in QNX, on which several other mechanisms are based. All forms of inter-task interaction considered in this note are listed in the table below, indicating who is responsible for implementing this or that mechanism.
')
Table 1. Forms of inter-task interaction.| Mechanism | Scope of implementation | 
|---|
| Message exchange | microkernel | 
| Signals | microkernel | 
| Shared memory | procnto process procnto | 
| POSIX Message Queues | mqueue manager | 
| Unnamed (pipe) and named (FIFO) program channels | pipe manager | 
In QNX RTOS 6.5.0, another form of inter-task interaction appeared - Persistent Publish / Subscribe (PPS). This is quite an interesting technology, which I will try to write about another time. Those interested can read the translation 
of the PPS section of the QNX Neutruno System Architecture.
Message exchange
This is a synchronous mezhzadachny interaction mechanism implemented in the QNX Neutrino micronucleus. When developing the RTOS, this form of inter-task interaction was not accidentally chosen as the main one. Firstly, the mechanism itself is quite simple. Secondly, the synchronous transmission and reception of messages facilitates debugging. Third, testing of high-level forms of interaction (for example, software channels) based on QNX Neutrino messaging and implemented in a monolithic core revealed approximately the same performance characteristics.
The messaging mechanism in QNX is also called the SRR mechanism, after the first letters of the three main functions used in messaging 
1 . 
MsgSend() is for sending a message, 
MsgReceive() is for receiving a message, and 
MsgReply() is for transmitting a response to the caller. First, consider each function separately to understand how they work, and then combine them in one example. All the arguments of the functions will not be intentionally cited so as not to be distracted and first understand the principle of operation.
MsgSend() is used to send a message from the 
client to the 
server and receive a response. The concepts of 
client and 
server are quite arbitrary here, because the same program can be a server for some tasks and, at the same time, a client of others. For example, a database server is a server for database clients 
2 . And at the same time, the database server will be the client for the file system manager. When the 
MsgSend() function is 
MsgSend() client is blocked in one of two states: 
SEND or 
REPLY . 
SEND status means that the 
client has sent a message, and the 
server has not yet received it. After the 
server receives the message, the 
client enters the 
REPLY state. When the 
server returns a response message, the 
client is unlocked.
MsgReceive() is used to receive messages from 
clients . 
The server calls 
MsgReceive() and is blocked in the 
RECEIVE state if none of the 
clients have yet sent him a message, i.e. did not call the 
MsgSend() function. After this happened (a message was sent to the 
server ). 
The server unlocks and continues its execution. 
The server usually needs to perform some actions to process the received message and prepare to receive a new one. If the 
server is working in several threads, then another thread can execute the message processing and response to the 
client . Most often, the thread receiving the message works in the "perpetual" cycle and after processing the received message, it calls 
MsgReceive() again.
MsgReply() used to send a response message to 
client 3 . When the 
MsgReply() function is 
MsgReply() lock does not occur, i.e. 
the server will continue to work. This is done because the 
client is already in a locked state ( 
REPLY ) and no additional synchronization is required.
What else needs to be learned in order to make up from those building blocks of knowledge that we have, a bridge to understanding the QNX messaging mechanism? Not so much. Be patient, now the picture will begin to take shape.
The QNX Neutrino microkernel does not care about the contents of the transmitted message. Messages do not have any format. The message makes sense only for the 
client and 
server . The microkernel only copies the message (i.e., just the data buffer) from the 
client 's address space to the 
server's address space (and vice versa when responding) and there is no intermediate buffer for storing messages. And that means there is no intermediate copy, because the microkernel copies data directly from the 
client ’s memory to the 
server’s memory (and vice versa when responding). As a result, the speed of the message passing mechanism increases.
To synchronize the transmission, reception, and response to a message, the microkernel blocks the threads involved in exchanging messages in one of three states: 
SEND , 
RECIEVE and 
REPLY . 
The client is blocked in the 
SEND state until the 
server accepts its message. 
The server is locked in the 
RECEIVE state if none of the 
clients sent a message to it. After receiving the message by the 
server , it is unlocked, and the 
client enters the locked state 
REPLY . After the 
server returns a response to the 
client , the latter is unlocked. That's all.
So, we figured out how the QNX messaging engine works. Fig. 1 simply illustrates the above.
Fig. 1. Messaging in QNX Neutrino.How does the client find the server ?
If you understand how the messaging mechanism works in the QNX RTOS, you can move on. Probably, you should have one question, without deciding which, you will not be able to exchange messages in QNX. How does the 
client find the 
server ?
Messages are not transmitted directly between threads. Instead, channels and connections are used. 
The server creates a channel using the 
ChannelCreate() function. Now, finally, the 
server can call 
MsgReceive() and 
MsgReply() with a clear conscience. A piece of code below illustrates the operation of the 
server :
 chid = ChannelCreate( flags );  for (;;) { rid = MsgReceive( chid, &msg, sizeof( msg ), NULL );  switch ( msg.type ) {  } MsgReply( rid, EOK, NULL, 0 );  } 
In turn, the 
client creates a connection to the 
server channel using 
ConnectAttach() , and then calls 
MsgSend() . 
Client code 
is very simple:
 coid = ConnectAttach( nd, pid, chid, _NTO_SIDE_CHANNEL, 0 );   MsgSend( coid, smsg, sizeof( smsg ), rmsg, sizeof( rmsg ) );   
Now the last question remains. Where 
chid client find out the 
server parameters: 
nd , 
pid , 
chid ? These parameters are the 
server address or even the phone number with the city code and extension number. Half of the answer to this question is that the 
server itself knows all these parameters. But how can the 
server report them to the 
client ?
There are various ways to get this information from the 
server . You can use 
.pid files or global variables. But the right way for small applications is to use the 
name_attach() function in the 
server , and 
name_open() in the 
client . An even more correct way is to implement the 
server as a 
resource manager 4 , when it is responsible for the namespace element.
Composite messages
As already mentioned, one of the main advantages of the QNX Neutrino messaging engine is its high performance. This is achieved by the fact that there is no intermediate copying of data, i.e. The message is copied directly from the 
client ’s memory to the 
server’s memory. It often happens that these messages are in different places. A typical case is when the data is a raw buffer received from the hardware and a structure with information about the data (message header). The raw data itself may be located in a circular buffer. Is it really necessary in this case to prepare a separate buffer and copy the header and data from the ring buffer there? Wouldn't it be overkill? This unnecessary copying of data before sending a message can be avoided in the QNX RTOS by using composite messages.
To form composite messages in QNX Neutrino, you need to declare an array of type 
iov_t , with the number of elements equal (or more) to the number of messages, and initialize each element using the 
SETIOV() macro, i.e. specify the address and size of each buffer. Fig. 2 illustrates the principle of operation of composite messages.
Fig. 2. An example of a composite message.To work with composite messages, the familiar functions of 
MsgReceive() and 
MsgReply() , but with the end of v, i.e. 
MsgReceivev() and 
MsgReplyv() . Since the function of sending a message 
MsgSend() also receives the result, it acquires a whole family: 
MsgSendv() , 
MsgSendsv() and 
MsgSendvs() . Now the microkernel will do all the extra work for us, and no additional copying and buffer with the whole message. I like that!
Impulses
Sometimes it is only required to inform another thread that something has happened, and no response is required. So it is not necessary and blocking on 
MsgSend() . In this case, the 
MsgSendPulse() function comes to the 
MsgSendPulse() . The pulse contains 8 bits of code and 32 bits of data. Very often, pulses are used in interrupt handlers. For pulses, queues are used, i.e. impulses will not be lost if the flow for some time did not accept them. But be prepared, sooner or later, to get an 
EAGAIN error if you send impulses to a stream that does not have time to read them.
Signals
The QNX Neutrino RTOS supports a signaling mechanism that should be familiar to UNIX developers. Both standard POSIX and real-time POSIX signals are supported. To work with both types of signals, the same microkernel code is used. As a result, the microkernel itself becomes more compact, and POSIX signals (at the request of an application) can be queued, just like their colleagues from the POSIX real-time signal group. Among other things, QNX Neutrino extends the POSIX standard and allows you to send signals to a specific stream. And this is sometimes very useful.
By the way, POSIX real-time signals contain 8 bits of code and 32 bits of data. Nothing like? Precisely, the same code that implements the signal mechanism is also used in the transmission of pulses. Convenient and reliable.
Signals use the familiar functions 
kill() , 
sigaction() , 
sigprocmask() , etc., as well as more interesting functions, for example, 
pthread_kill() and 
pthread_sigmask() .
Programming channels
Software channels should be familiar to UNIX users and developers. In QNX, all the familiar commands, functions and techniques also exist. For example, this is how an unnamed program pipe (pipe) is created on the command line:
  
To create a named pipe (FIFO), you must use the 
mkfifo command or the 
mkfifo() function.
There is only one feature. In order for QNX Neutrino to work with software channels, it is necessary to start the 
pipe manager.
POSIX Message Queues
Message queues are similar to named programmatic channels (FIFOs), but are more complex mechanisms because support message priorities. To work with POSIX message queues in QNX Neutrino, you must run the 
mqueue manager.
Note that message queues in the QNX RTOS can contain more than one slash '/', which means you can create directories. This extends the POSIX standard, which requires that the queue name begin with a slash and no longer contain this character. A rather convenient feature, since You can group the queues of one software package or one company in one directory.
Shared memory
Working with shared memory in QNX Neutrino is the same as in other UNIX systems. Since the shared memory mechanism is implemented in the 
procnto process 
procnto , which also contains the microkernel, you do not need to run anything else. You can read more in the documentation on the functions 
shm_open() and 
mmap() .
Separately, it should be noted that the shared memory itself is not suitable for inter-task interaction. Even if the 
server only writes to the shared memory, and the 
client only reads from it, it may happen that the client subtracts partially modified data. Such an error can be difficult to catch later, so it is better not to make it at all. To eliminate such a situation, it is necessary to apply one of the synchronization primitives, for example, mutexes or semaphores.
Not least of interest is the method of inter-task interaction, with the simultaneous use of the messaging mechanism and shared memory. This is especially useful if you plan to build a distributed system, because shared memory is not available over the network. This solution has a very high performance due to the use of shared memory, and also has the ability to synchronize and network transparency through the exchange of messages.
You should not try to build any interaction using shared memory, based on the fact that this is the fastest way. If synchronization primitives are used, then the speed can be comparable to the speed of the messaging mechanism. Significant gain will be only in the exchange of data of a very large amount.
Promised buns
Since I promised that there will be buns, they will. I did not forget, and I do not mind. The first thing is that when sending a message, you can use the same buffer for the message itself and the response to it. I would even say that they do it quite often. To make it more convenient, all structures describing various messages and the answers to them are grouped into one union. Normally, the message sent is not required by the 
client after receiving a response from the 
server . This way you can save on the buffer.
The second thing is that the POSIX file descriptor in QNX Neutrino is the same as the connection (for sending messages). And this means that functions that use file descriptors ( 
write() , 
read() , etc.) are just wrappers with minor overheads that convert their arguments into messages to the 
server . Serious enough optimization. So, if you want to develop system software for QNX, then learn to write a resource manager.
Bibliography
- QNX Neutrino Real-Time Operating System 6.3. System architecture ISBN 5-94157-827-X
 - QNX Neutrino Real-Time Operating System 6.3. User's manual. ISBN 978-5-9775-0370-9
 - Rob Krten, “An Introduction to QNX Neutrino 2. A Guide for Real-Time Application Developers,” 2nd Edition. ISBN 978-5-9775-0681-6
 
1 The prefix Msg in the name of the functions MsgSend() and others appeared only in QNX Neutrino, and in QNX4 these functions were called simply Send() , Receive() and Reply() . Hence the name SRR.2 In the example, the database server and clients do not use messaging functions directly, but we already know that in QNX, under each read() , write() , sendto() and others, the SRR mechanism is hidden.3 If the server should return only an error code, for example, if the message is not supported, it is more convenient to use the MsgError() function.4 A description of resource managers is beyond the scope of this note. It is possible that I will write about this some other time.