QNX RTOS: A Little About Micronucleus, Threads, and Processes

    Since my first small review of the QNX real-time operating system showed that among the residents of Habr there is interest in it, I decided to continue the series of notes. It seems to me that it is worth a little talk about the system architecture of QNX6. I think that it is worthwhile to define what a microkernel is and what tasks it solves. In the course of the narrative, two myths related to QNX will also be debunked. But first…

    A bit about POSIX


    With the release of each new version of QNX (and it should be noted that the first version of the QNX RTOS appeared now back in 1981), the developers used the previously accumulated experience and made the system better, including more convenient for developers. That is why QNX Neutrino supports POSIX 1003.1 standards, such as thread management, real-time extensions, additional real-time extensions and application environment profiles (AEP).

    Why am I talking about this? Very simple. I would like to note two things that are characteristic of QNX and are related to POSIX. It is believed that any POSIX operating system hides UNIX. And if so, then such a system is too cumbersome and cannot be used in embedded solutions. However, in the case of QNX this is not true. The POSIX standard describes an interface, not an implementation. This means that under the POSIX layer anything can be hidden, including the microkernel.

    The second point that I would like to dwell on is probably obvious. These, of course, are the benefits that POSIX provides in QNX:
    • Reuse of code. Once developed, debugged, and tested code for one POSIX operating system can be reused on another POSIX system, including QNX.
    • "Portability" of programmers. A developer or development team familiar with POSIX and UNIX can easily start developing a real-time embedded system (of course, I mean QNX). After all, most of the OS will already be familiar to them.
    So, we can conclude that POSIX in QNX provides a number of advantages.

    True core


    QNX RTOS is based on microkernel architecture. I think it’s worth stopping, and before moving on, determine what the microkernel is (at least in QNX terminology). The term micronucleus has become very popular for some time and many systems containing a small core are called micronuclei. And if the core is even smaller, then it is called a nanonucleus. This is not entirely true for QNX. The small size of the kernel is not the main goal. The idea is that many functions of the operating system (including such familiar ones as, for example, file system support) are taken out of the kernel into the area of ​​user applications. And the microkernel itself performs only a small set of functions, most of which provide inter-tasking interaction:
    • messaging (this is the main thing that the microkernel does);
    • flow management;
    • scheduling (flows);
    • synchronization (streams);
    • signal management;
    • timer management.
    Please note that even process control is performed not by the microkernel, but by the process administrator (which, however, is combined with the microkernel into one module). Is this not the true core of the system?

    What I have listed above is all that the QNX microkernel does. Things like network drivers or disk support are placed in separate modules that run and work like normal user processes. With all the ensuing advantages (over systems based on a monolithic core). And what are these advantages? Very simple:
    • The source code of a microkernel is much smaller than that of a monolithic kernel, which means that the kernel is easier to debug and test.
    • The microkernel increases modularity. The final target system can be easily configured to meet your requirements. It is enough to launch those managers who are required.
    • Microkernel improves system reliability. If an error occurs in the driver, this will not lead to the collapse of the system (and the microkernel), and the driver itself can be restarted at any time, without restarting the entire system.

    Myth: QNX microkernel is written in assembler


    Among some developers, there is an opinion that the high performance and compactness of the QNX Neutrino microkernel is the result of the fact that it is written in assembly language. But this is not so. The microkernel is almost completely written in C. Performance and compactness are consequences of the use of well-established algorithms and structures.

    Processes and Threads in QNX


    According to the POSIX specification, QNX RTOS supports streams. And even more, the QNX microkernel controls only threads, and it is threads that can be considered the minimum “unit of execution”. Each process in QNX can contain one or more threads. In this vein, a process can be seen as a container of threads.

    In the simplest case, a process can (and should) contain one thread. But sometimes parallel execution of several algorithms in one process is required. I will give a small example from QNX. The file system manager can receive and process requests from several clients (other processes) in parallel. If the manager worked in one thread, then the second client contacting him would have to wait for the completion of the previously requested operation. Fortunately, the file system manager in QNX works in several threads, which allows you to serve multiple applications simultaneously and in parallel.

    All processes in QNX are isolated from each other and run in their own virtual address space. The Memory Management Unit (MMU) is responsible for implementing such protection. The presence of the MMU device in the processor is a prerequisite for running QNX. At the same time, threads of one process work in one address space.

    There are systems implemented according to the scheme when all tasks in the system are flows that operate in the same address space. On the one hand, this facilitates interprocess communication. But on the other hand, any error in any thread can lead to the collapse of the entire system. And debugging and maintaining a system built on this principle is much more difficult.

    Myth: QNX microkernel slowly switches contexts between processes


    Threads originally appeared on UNIX systems as a solution to the problem of switching contexts between processes too slowly. Most likely, since then it is generally accepted that context switching between processes is very slow. And since when using a microkernel and sending messages, more context switches are performed than when using a monolithic kernel, a simple conclusion is made - QNX is slow. However, the QNX Neutrino architecture solves the context switching performance issue. And in QNX there is practically no difference in the speed of context switching between processes and threads.

    How the stream lives


    During the process, threads in it can be created and deleted dynamically. When creating a thread, for example, a function pthread_create()allocates and initializes the necessary resources and execution of the thread begins with the specified function in the address space of the process. At the end of a stream, for example using a function, pthread_exit()resources are released.

    While the thread is running, it can be in two states: ready (ready) or blocked (blocked). But in fact, there are various reasons why a thread can be blocked, and when displaying information about processes and threads with the help of a command pidin(QNX-specific version of the utility ps), we can observe different conditions, for example, SEND, RECEIVE, SIGWAITINFO, NANOSLEEP and others. For example, in this state, I now have USB manager threads:

    # pidin -P io-usb
         pid tid name               prio STATE       Blocked
        4101   1 proc/boot/io-usb    10o SIGWAITINFO
        4101   2 proc/boot/io-usb    21r RECEIVE     1
        4101   3 proc/boot/io-usb    10o RECEIVE     4
        4101   4 proc/boot/io-usb    10r NANOSLEEP
        4101   5 proc/boot/io-usb    10o RECEIVE     4

    Here pid is the process identifier (process ID) in the system, tid is the thread identifier (thread ID) in the process, prio is the thread priority number and scheduling discipline, STATE is the thread status, Blocked is the value depends on the reason for blocking. In the above example, threads 2, 3, and 5 are in a Receive-blocked state (i.e., they are ready to receive messages from other threads).

    Epilogue


    I tried to talk about the benefits of POSIX and a little about the role of the QNX microkernel. Hope this was interesting. Actually, it would be nice to talk about planning disciplines and mechanisms of inter-task interaction, but I thought it was better to do this in separate topics so that all this information does not turn into a mess. I promise that next time it will be more interesting.

    And further. If someone is interested in QNX, then everything that I described and even much more can be read in the System Architecture of QNX Neutrino. This documentation is available in electronic form on the website www.qnx.com (in English), and there is also a Russian translation in print:
    1. Real-time operating system QNX Neutrino 6.3. System architecture. ISBN 5-94157-827-X
    2. Real-time operating system QNX Neutrino 6.3. User's manual. ISBN 978-5-9775-0370-9

    Also popular now: