QNX provides the POSIX standard process/thread synchronization and communication services. The following tables show a resume of all these mechanisms:
Table 11-2. Synchronization Services
Sync Mechanism | Implemented in | Used by | POSIX |
---|---|---|---|
Semaphore | Kernel | Process/Threads | YES |
Mutexes | Kernel | Threads | YES |
Recursive Mutexes | Kernel | Threads | YES |
Condition variable | External Process | Threads | YES |
Readers/Writers locks | External Process | Threads | YES |
Barriers | External Process | Threads | YES |
FIFO scheduling | Kernel | Process/Threads | NO |
Atomic operations | Kernel | Process/Threads | NO |
Table 11-3. Communication Services
Communication Mechanism | Implemented in | Used by | POSIX |
---|---|---|---|
Message Passing | kernel | Processes | NO |
Signals | -- | Processes/Threads | YES |
PIPES | external process | Processes/Threads | YES |
FIFOs | external process | Processes/Threads | YES |
Message Queues | external process | Threads | YES |
Shared Memory | process manager | Processes/Threads | YES |
QNX provides the full-POSIX semaphores calls. Therefore, both named and unamed semaphores are provided.
The following table list the primitives available on QNX for managing the semaphores, as well as, the microkernel calls used by these routines:
Table 11-4. Semaphore management primitives
POSIX Call | Kernel Call | Description |
---|---|---|
sem_init() | SyncTypeCreate() | Creates a semaphore |
sem_destroy() | SyncDestroy() | Destroy synchronization object |
sem_wait() | SyncSemWait() | Wait on a semaphore |
sem_trywait() | SyncSemWait() | Wait on a semaphore |
sem_post() | SyncSemPost() | Post a semaphore |
QNX provides the full POSIX mutexes calls where each of them, are associated with a microkernel call. The following table shows these routines:
Table 11-5. Mutexes management primitives
POSIX Call | Kernel Call | Description |
---|---|---|
pthread_mutex_init() | SyncTypeCreate() | Create a mutex. |
pthread_mutex_destroy() | SyncDesctroy() | Destroy a mutex. |
pthread_mutex_lock() | SyncMutexLock() | Lock a mutex. |
pthread_mutex_unlock() | SyncMutexUnlock() | Unlock a mutex. |
pthread_mutex_trylock() | SyncMutexLock() | Used to test whether the mutex is currently locked or not. |
The following mutex types are supported by QNX: PTHREAD_MUTEX_NORMAL,PTHREAD_MUTEX_ERRORCHECK, PTHREAD_MUTEX_RECURSIVE and PTHREAD_MUTEX_DEFAULT. Note that QNX supports the full POSIX mutex types.
Currently, there is only one POSIX mutex protocol available in QNX: the PTHREAD_PRIO_INHERIT protocol. Therefore, PTHREAD_PRIO_PROTECT protocol is not supported.
Finally, it is important to note how the classic priority inversion problem is solved: If a thread with a higher priority than the mutex owner tries to lock a mutex, then the effective priority of the current owner will be increased to that of the higher priority blocked thread waiting for mutex. The owner will returns to its real priority when it unlocks the mutex. This scheme is called priority inheritance.
This service consists of that the attribute of the mutex can be modified (using PTHREAD_MUTEX_RECURSIVE type) to allow a mutex to be recursively locked by the same thread. So, QNX provides this POSIX functioning.
As described earlier, the priority inversion control problem is solved using inmediate priority inheritance.
In the same way as semaphores and mutexes, QNX provides full POSIX condition variable functions as well as the microkernel calls that are used by these routines. These functions are: pthread_cond_init(), pthread_cond_destroy(), pthread_cond_wait(), pthread_cond_signal() and pthread_cond_broadcast().
QNX provides full POSIX reader/writers functions. Now these functions are listed: pthread_rwlock_rdlock(), pthread_rwlock_wrlock(), pthread_rwlock_unlock(), pthread_rwlock_tryrdlock(), pthread_rwlock_trywrlock().
It is important to note that Reader/writer locks are not implemented directly within the kernel and have not associated a microkernel call either, but are built from the mutex and condvar services provided by the kernel.
QNX provides full POSIX barriers functions. Those functions are: pthread_barrier_init(), pthread_barrier_wait(), pthread_barrier_destroy() and pthread_barrierattr_* family.
This type of synchronization consist of selecting the POSIX FIFO scheduling algorithm. In this way, we can guarantee that two threads of the same priority don't execute the critical section concurrently.
Note that this feature is not accomplished on SMP systems.
This synchronization mechanism allows to perform a short operation with the guarantee that the operation will performing atomically. The most important atomic operations that Neutrino provides are:
Table 11-6. Atomic Operations
Function | Description |
---|---|
atomic_add() | Add a value. |
atomic_sub() | Substract a value. |
atomic_clr() | Clear bits. |
atomic_set() | Set bits. |
atomic_toggle() | Complementing bits. |
QNX provides the message passing mechanism as the main form of IPC in QNX and Neutrino. Although other forms are available, those options are built over its native IPC.
The following illustration shows the state changes in a send-receive transaction.
As shown in figure, a thread that does a MsgSend() to another thread or process will be blocked until the receiving thread does a MsgReceive(), processes the message, and executes a MsgReply(). If a thread executes a MsgReceive() without a previously sent message pending, it will block until another thread executes a MsgSend(). Therefore, while the send and receive operations are blocked and synchronous, MsgReply() and MsgError() don't block and furthermore, unlock the client from MsgSend().
The following table lists the message passing API provided by QNX:
Table 11-7. Message Passing API
Function | Description |
---|---|
MsgSend() | Send a message and block until reply. |
MsgReceive() | Wait for a message. |
MsgReceivePulse() | Wait for a tiny, nonblocking message (pulse). |
MsgReply() | Reply to a message. |
MsgError() | Reply only with an error status. No message bytes are transferred. |
MsgRead() | Read additional data from a received message. |
MsgWrite() | Write additional data to a reply message. |
MsgInfo() | Obtain info on a received message. |
MsgSendPulse() | Send a tiny, nonblocking message (pulse). |
MsgDeliverEvent() | Deliver an event to a client. |
MsgKeyData() | Key a message to allow security checks. |
Moreover, QNX provides other message passing functions and mechanism that are based on the API defined above. This mechanism are: Multipart Transfers, Channels and Pulses.
QNX support POSIX signals, realtime signals and traditional UNIX signals. Moreover, Neutrino provides eight special signals. Therefore, a total of 64 signals have been defined. The following table resumes these signals range:
Table 11-8. Signals range
Signal range | Description |
---|---|
1...57 | 57 POSIX signals, including traditional UNIX signals. |
41...56 | 16 POSIX realtime signals. |
57...64 | 8 special Neutrino signals. |
To uses pipes in QNX, the pipe resource manager, called pipe must be loaded.
This POSIX comunication mechanism are available on QNX using the symbol | to create a pipe from shell and the pipe() or popen() functions to create a pipe from programs.
As well as pipes, for using FIFOs in QNX, the resource manager pipe must be loaded.
To manage FIFOs, QNX provides the following utilities: the mkfifo and rm command to create/delete FIFOs from shell, and the mkfifo(), remove() and unlink() functions to create/delete FIFOs from programs.
QNX provides a full POSIX message queue API. To use this synchronous mechanism in Neutrino, the message queue resouce manager called mqueue must be loaded.
There is a fundamental different between QNX messages and POSIX messages queue: while QNX messages block and copy data directly from the address spaces of the process sending to the address space of process receiving, POSIX message queue, on the other hand, do not block and may have pending messages queued.
In Neutrino microkernel, all mesages queues created will appear in the filename space under the directory /dev/mqueue.
Full POSIX shared memory is implemented in Neutrino via the process manager called procnto. The following functions are implemented as messages to procnto: shm_open(), close(), mmap(), mummap(), mprotect(), msync(), shm_ctl() and shm_unlink().