• Keine Ergebnisse gefunden

The Neutrino microkernel provides some minimal services used by a set of cooperating tasks, which in turn provide the higher-level OS functionality. Services like filesystems and partitioning are provided by optional tasks. The kernel itself is dedicated to a

Chapter 8. Case Study 3: QNX Neutrino

few fundamental services:1

Message-passing services handle the routing of all messages between all tasks (threads) throughout the entire system. POSIX signal primitives are also included

in this service.

Synchronization services handle in addition to message passing POSIX conform thread-synchronization primitives.

Timer services provide a set of POSIX timer services.

Process management provides scheduling of tasks for execution using the various POSIX realtime scheduling algorithms (First-In-First-Out and Round-Robin). The process manager portion is responsible for managing processes, memory, and the pathname space.

Low-level interrupt handling receives all hardware interrupts and faults, then passes them on to the appropriate driver task.

All operating system services, except those provided by the mandatory microkernel, are handled via standard processes. Drivers and applications are isolated to their own memory address, and operate independently. In a multicore system, QNX allows only one thread at a time to enter the kernel. The kernel has a restart model for kernel call preemption. A task inside a kernel call can be preempted by a higher priority task.

If the lower priority task is executed again, the first thing it will do is to re-execute the system call instruction. This will restart the kernel call that was being worked on when it was interrupted in the first place.

This section gives an overview of the main QNX Neutrino microkernel services. For more information on QNX, see [Sys05, Chap. 2] and [Hil92].

8.2.1. Process Management

Process management in Neutrino is split into two components. The scheduling of tasks according to the First-In-First-Out and Round-Robin policies is handled directly by the kernel. Creation and destruction of tasks is done in a single module called procnto. This module is required for all runtime systems. procntoruns as a true process and is scheduled to run by the kernel like all other processes. It is the only process which shares the same address space as the kernel. Communication with other processes takes place via the kernel’s message passing primitives (Section 8.2.3).

procntois responsible for creating new processes in the system and managing the most fundamental resources associated with a process. These services are all provided via messages.2

1QNX can be driven as a distributed system. The related network services are not listed here.

2Since messages are network-wide, it is easy to create a process on another node by sending the process-creation message to the instance of procntorunning on that node.

108

8.2. Microkernel Architecture

procnto is capable of creating multiple POSIX processes, each of which may contain multiple POSIX threads. Like Linux systems, QNX supports some process creation primitives (fork(),exec() and spawn()). Both fork() andexec() are defined by POSIX, while the implementation of spawn()is unique to QNX. It can create processes on any node in the network. When a process is created by one of the three primitives, it inherits much of its environment from its parent. A process goes through four phases:

Creation of a process consists of allocating a process ID for the new process. The information that defines the environment of the new process is initialized. Most of it is inherited from the parent process.

Loading of a process image. This is done by a separateloader thread. Actually, the loader thread is the newly created task in an early state. The loader code resides in theprocntomodule, but the thread runs under the process ID of the new process.

Execution of the new process. All processes run concurrently with their parents.

In addition, the termination of a parent process does not automatically cause the termination of its child processes.

Termination in either of two ways: a signal whose defined action is to cause process termination is delivered to the process or the process invokesexit().

Similar to Linux systems, every task in Neutrino is assigned a priority level . The scheduler selects the next task to run by looking at the priority assigned to every task that is ready for execution. The task with the highest priority is selected to run.

The default priority for a new process is inherited from its parent. The priority level can be adjusted at runtime with the two system callsgetprio() andsetprio().

Although a task inherits its scheduling class from its parent process. It can be changed with the two system callsgetscheduler()andsetscheduler(). procntohas no priority assigned. It listens on a channel for incoming messages. Whenprocnto receives a message itfloats to the same priority as the client thread.

The POSIX semantics for device and file access is presented in QNX by the so calledpathname space. Managing the pathname space is part of theprocntomodule.

The details of the pathname space implementation can be found at [Sys05, Chap. 5].

QNX encourages the development of applications that are split up into cooperating processes. Processes can register names within the pathname space. Other processes can then ask the process manager for the process ID associated with that name.

The process manager is responsible for memory management. On task creation, the loader thread starts in a new virtual memory space. The process manager will then take over this environment, changing the mapping tables as required by the processes it starts. POSIX memory locking is supported by QNX, so that a process can avoid the latency of fetching a page of memory, by locking the memory.

Chapter 8. Case Study 3: QNX Neutrino

8.2.2. Interrupt Handling

User-level processes can attach (and detach) hardware interrupt handlers to (and from) interrupt vectors at runtime. When the hardware interrupt occurs, the processor will enter the interrupt redirector in the microkernel. Here the processor context is set so that the handler has access to the code and data that are part of the thread the handler is contained within. This allows the handler to use the buffers and code in the user-level thread. If higher-level work is required, QNX provides an API for queuing events to the thread the handler belongs to . Since interrupt handler run with the memory-mapping of the thread containing it, the handler can directly manipulate devices mapped into the thread’s address space, or directly perform I/O instructions.

As a result, device drivers that manipulate hardware don’t need to be linked into the kernel.

The interrupt redirector code in the microkernel will call each interrupt handler attached to that hardware interrupt. If the handler indicates that an event has to be passed to a process, the kernel will queue the event. When the last handler has been called for that vector, the kernel interrupt handler will finish manipulating the interrupt control hardware. If a queued event causes a higher-priority thread to become ready for execution, the interrupt return will switch into the context of the higher-priority thread.

While the interrupt handler is executing, it has full hardware access, but cannot invoke other kernel calls. If necessary, the handler can cause a thread to be scheduled at some user-specified priority to do further work.

Example programs that use the QNX interrupt handling can be found in Sec-tion 8.4.4.

8.2.3. Message Passing

Message passing is the primary form of inter process communication (IPC) in QNX Neutrino. Other forms of IPC are built over the native message passing. In a multicore system, QNX uses interprocessor interrupts (IPI) for inter processor communication.

Messages are passed via communication channels. TheChannelCreate()system call creates a channel that can be used to receive messages. Once created, the channel is owned by the process and is not bound to the creating thread. To establish a connection between a process and a channel, the system call ConnectAttach() is used. In addition, the system callsname_attach()andname_open()can be used in order to create or open a channel and associate a name with it. Communication over channels is synchronized . It always follows the send - receive - reply scheme. A reader task is blocked until it receives a message. The sending task then is suspended until the receiving task sends a reply message.

To dequeue and read messages from a channel, theMsgReceive() system call is used. On the other side, the MsgSend() system call is used to enqueue messages on the channel. Messages are enqueued in priority order. An example application that uses message passing can be seen in Section 8.4.2.

110