The QNX documentation is impressive. I’m trying to get my head around RT programming on QNX. I’m wondering if some of the experienced people on this list could comment on the following summary of the “principles” of RT programming on QNX. I don’t include principles related to embedded programming (flash, os image, BSP, IPL, power management, etc). Note that the summary is inevitably simplistic: it’s a starting point, so it ought to be simple
“Principles” of QNX programming:
everything is a process, even device drivers; they (drivers) have same capabilities, restrictions, protections (memory etc), can be debugged the same way, etc.
use “Resource Manager” (RM) to create “resource servers”, the equivalent of (but much more general than) “device drivers” on other OS’s. RM’s have the advantage of a common interface (that of a file) for interaction will client applications.
prefer a dedicated (higher priority) thread in the RM for interacting with your device (hardware) if there is one, one (lower priority) thread or thread pool for interacting with clients that want to use the “device”.
3a) interrupts are chained, so more than one device can use the same interrupt line; works ok in level-sensitive interrupt line but not edge-sensitive; presumably this means that on QNX, edge-sensitive can be used only if sure that one hardware has the given interrupt line;
3b) the device thread of your RM should be attached to an ISR, ie just waiting for interrupts from the device. Preferably, attach via InterruptAttachEvent() since this means fastest return from kernel, no risk of calling illegal functions in ISR (functions that call back into kernel); the thread can then test that its associated device generated the interrupt (since interrupts are stacked), read/write the hardware, copy to/from shared buffer so client threads can see/put new data, unmask interrupt so PIC can regenerate it, and return proper code.
the main IPC mechanisms are:
- signals and select (posix)
- messages (events) and pulses (QNX only)
- shared memory (posix)
- global data (between threads of a given process)
- synchronization (mutex, semaphore, condition, message queue, various types of locks)
- pipes and fifos
prefer the (non-portable) messaging system of QNX since it (seems) easier to use and less prone to deadlocks etc. It’s basically an MPI implemented in the kernel.
there are 63 priority levels available to your processes; QNX uses pre-emptive multi-tasking to switch between them, i.e. the highest priority thread that becomes active immediately gets the CPU, until it yields, blocks or a higher priority thread become ready; priority level is inherited by children processes and threads, but the effective priority can be different (e.g. to prevent priority inversion problems) and is time-dependent.
there are 3 scheduling policies available, used to decide which thread to run next amongst a group of threads at the same priority setting: FIFO, round-robin, and sporadic .
8 ) prefer the Add-on library to using dlopen etc
favor separate processes, with each process doing one thing well, and use IPC between them. E.g. instead of a process being a resource manager (RM) for a device, saving the data to a database, providing a photon microgui and interacting with clients, separate into several RM’s and clients: a RM for device, one RM for database, one client for GUI, etc.
There are basically two types of QNX messages: pulses (non-blocking, ie asynchronous, can only carry an int) and data-transfer (blocking – requires receive/reply – basically like MPI)
asynchronous data-carrying messaging is available but apparently still experimental in 6.3. Sounds like better than pulses but not integrated to the message passing system (Mse*() functions).