Process termination

Updated: April 19, 2023

A process can terminate in one of the following basic ways:

In some operating systems, if a parent process dies, then all of its child processes die too. This isn't the case in QNX Neutrino.

When a process terminates—no matter why—all of its resources are cleaned up:

Process termination from exit()

A process can terminate itself by having any thread in the process call exit(). Returning from main() (i.e., in the main thread) also terminates the process, because the code that's returned to calls exit(). This isn't true of threads other than the main thread; returning normally from one of them causes pthread_exit() to be called, which terminates only that thread.

The value passed to exit() or returned from main() is called the exit status.

When a process dies by calling exit(), “normal exit processing” happens. This includes:

Process termination from signals

A process can be terminated by a signal for a number of reasons. Ultimately, all of these reasons will result in a signal's being set on the process. A signal is something that can interrupt the flow of your threads at any time. The default action for most signals is to terminate the process.

  • What causes a particular signal to be generated is sometimes processor-dependent.
  • The cleanup of the terminated process occurs by default at the priority of the thread that sent the signal. As a QNX Neutrino extension to POSIX functions, if you OR the SIG_TERMER_NOINHERIT flag (defined in <signal.h>) into the signal number, the cleanup occurs at the priority of the thread that received the signal.

Here are some of the reasons that a process might be terminated by a signal:

When a process dies due to a signal that isn't handled or masked, “normal exit processing” doesn't happen, so this is often called abnormal termination of a process.

To get the kernel to display some diagnostics whenever a process terminates abnormally, configure procnto with multiple -v options. If the process has fd 2 open, then the diagnostics are displayed using (stderr); otherwise; you can specify where the diagnostics get displayed by using the -D option to your startup. For example, the -D as used in this buildfile excerpt will cause the output to go to a serial port:

[virtual=x86_64,bios +compress] .bootstrap = {
    startup-x86 -D 8250..115200
    procnto-smp-instr -vvvv

You can also have the current state of a terminated process written to a file so that you can later bring up the debugger and examine just what happened. This type of examination is called postmortem debugging. This happens only if the process is terminated due to one of these signals:

Signal Description
SIGABRT Program-called abort function
SIGBUS Parity error
SIGEMT EMT instruction (emulation trap)

Note that SIGEMT and SIGDEADLK refer to the same signal, which indicates mutex deadlock (for details, see SyncMutexEvent()).

SIGFPE Floating-point error or division by zero
SIGILL Illegal instruction executed

One possible cause for this signal is trying to perform an operation that requires I/O privileges. To request these privileges:

  1. The process must have the PROCMGR_AID_IO ability enabled. For more information, see procmgr_ability().
  2. The thread must call ThreadCtl(), specifying the _NTO_TCTL_IO flag:
    ThreadCtl( _NTO_TCTL_IO, 0 );
SIGSEGV Segmentation violation
SIGSYS Bad argument to a system call
SIGTRAP Trace trap (not reset when caught)
SIGXCPU Exceeded the CPU limit

The process that dumps the state to a file when the process terminates is called dumper, which must be running when the abnormal termination occurs. This is extremely useful, because embedded systems may run unassisted for days or even years before a crash occurs, making it impossible to reproduce the actual circumstances leading up to the crash.

Process termination from thread loss

A process must have one or more threads. If the number of threads in a process goes to 0, the process is terminated.

When a thread calls pthread_exit(), that thread is terminated. If any thread but the main thread returns from its thread function, then the wrapping code calls pthread_exit(). If the last thread in a process calls pthread_exit(), then the process is terminated; in this case, “normal exit processing” doesn't happen.