Process Manager

The process manager works closely with the Kernel, although it shares the same address space as the Kernel, and it is the only process to do so, the Process Manager runs as a true process. It is scheduled to run by the Kernel like all other processes and it uses the Kernel's message passing primitives to communicate with other processes.

The Process Manager is 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. You can easily create a process on another node by sending the process creation message to the Process Manager on that node.

Process creation primitives

QNX supports three process creation primitives:

fork()
The fork() primitive creates a new process that is an exact image of the calling process. The new process shares the same code as the calling process and inherits a copy of all the calling process's data.
exec()
The exec() primitive replaces the calling process image with a new process image. There is no return from a successful exec(), because the calling process image is overlaid by the new process image. It is common practice in POSIX systems to create a new process by first calling fork(), and then having the child of the fork() call exec().
spawn()
The spawn() primitive creates a new process as a child of the calling process. It can avoid the need to fork() and exec(), resulting in a faster and more efficient means for creating new processes. Unlike fork() and exec(), which by very nature operate on the same node as the calling process, the spawn() primitive can create processes on any node in the network.

The spawn() is unique to QNX while fork() and exec() are defined by POSIX.

Process inheritance

When a process is created by one of the three primitives described above, it inherits much of its environment from its parent:

Item inherited fork() exec() spawn()
process ID no yes no
open files yes optional optional
file locks no yes no
pending signals no yes no
signal mask yes optional optional
ignored signals yes optional optional
signal handlers yes no no
environment variables yes optional optional
session ID yes yes optional
process group yes yes optional
real UID, GID yes yes yes
effective UID, GID yes optional optional
current working directory yes optional optional
file creation mask yes yes yes
priority yes optional optional
scheduler policy yes optional optional
virtual circuits no no no
symbolic names no no no
real-time timers no no no

The loading of process images is done by a loader thread. The loader code resides in the Process Manager, but the thread runs under the process ID of the new process. Once a program code has been loaded, the process is ready for execution. Note that all processes run concurrently with their parents. In addition, the death of a parent process does not automatically cause the death of its child process.

Process termination

A process is terminated in either of two ways:
  1. a signal whose defined action is to cause process termination is delivered to the process;
  2. the process invokes the exit() C function, either explicitly or by default action when returning from main().
Termination involves two stages:
  1. A termination thread in the Process Manager is run. This "trusted" code is in the Process manager but the thread runs with the process ID of the terminating process. This thread closes all open file descriptors and releases the following:

  2. After a termination thread is run, notification of process termination is sent to the parent process (this phase runs inside the Process Manager).

    If the parent process has not issued a wait() or waitpid() call, the child process becomes a "zombie" process and will not terminate until the parent process issues a wait() or terminates. If you do not want a process to wait on the death of children, you should either set the _SPAWN_NOZOMBIE flag with qnx_spawn() or qnx_spawn_options() or set the action for SIGCHLD to be SIG_IGN via signal(). This way, children will not become zombies when they die.

    A parent process can wait on the death of a child spawned on a remote node. If the parent of a zombie process terminates, the zombie is released.

If a process is terminated by the delivery of a termination signal and the dumper utility is running, a memory image dump is generated. You can examine this dump with the symbolic debugger.

Process states

A process is always in one of the following states:

The transactions are follows:

  1. Process sends message.
  2. Target process receives message.
  3. Target process replies.
  4. Process waits for message.
  5. Process receives message.
  6. Signal unblocks process.
  7. Signal attempts to unblock process;
    target process has requested message signal catching.
  8. Target process receives signal message.
  9. Process waits on death of child.
  10. Child dies or signal unblocks process.
  11. SIGSTOP set on process.
  12. SIGCONT set on process.
  13. Process dies.
  14. Parent waits on death, terminates itself or has already terminated.
To determine the state of an individual process from within the shell, you can use ps and sin utilities; from within applications, use qnx_psinfo() function. To determine the state of the operating system as a whole, use sin utility from the shell, and qnx_osinfo() function from applications.

Process symbolic names

QNX encourages the development of applications that are split up into cooperating processes. An application that exists as a team of cooperating processes exhibits greater concurrency and can be network-distributed for greater performance.

Splitting up applications into cooperating processes requires special considerations, however. If cooperating processes are to communicate reliably with each other, they must be able to determine each other's process ID. For example, let's say you have a database server process that provides services to an arbitrary number of clients. The clients start and stop at any time, but the server always remains available. How do client processes discover the process ID of the database server so they can send messages to it?

In QNX, the solution is to let processes give themselves a symbolic name. In the context of a single node, processes can register this name with the Process Manager on the node where they are running. Other processes can then ask the Process Manager for the process ID associated with that name.

The situation becomes more complex in a network environment where a server may need to service clients from several nodes across a network. QNX accordingly provides the ability to support both global names and local names. Global names are known accross the network, whereas local names are known only on the node where they are registered. Global names start with a slash(/). In order for global names to be used, a process known as a process name locator (i.e. nameloc utility) must be running on at least one node of a network. This process maintains a record of all global names that have been registered.

Up to ten process name locators may be active on a network at a particular time. Each maintains an identical copy of all active global names. This redundancy ensures that a network can continue to function properly even if one or more nodes supporting process name location fail simultaneously.

To attach a name, a server process uses the qnx_name_attach() C function. To locate a process by name, a client process uses the qnx_name_locate() C function.

Timers

In QNX, time management is based on a system timer maintained by the operating system. The timer contains the current Coordinated Universal Time (UTC) relative to 0 hours, 0 minutes, 0 seconds, January 1, 1970. To establish local time, time management functions uses the TZ environment variable.

Shell scripts and processes can pause for a specific number of seconds. For shell scripts this facility is provided by the sleep utility; for processes, it is provided by the sleep() C function. The delay() function takes a time interval specified in milliseconds.

A process can create one or more timers using timer_create() C function. This function lets specify the type of event-reporting mechanism to be used:

You can also have a timer go off repeatedly at a specified interval. For example let's say you have a timer armed to go off at 9 am tomorrow morning. You can specify that it should also go off every five minutes thereafter.

To arm timers with an absolute or relative time interval, use timer_settime() C function.

To remove timers, you can use the timer_delete() C function. A timer can remove itsef when its time interval expires, provided these conditions are met:

Resolution of a timer can be set via the ticksize utility or the qnx_ticksize() C function. To inspect the interval outstanding on a timer, or check if the timer has benn removed, you use the timer_gettime() C function.