Process termination
A process can terminate in one of the following basic ways:
- by exiting (i.e., the process terminates itself)
- by being signalled
- by having no running threads (i.e., the thread count goes to 0 )
When a process terminates—no matter why—all of its resources are cleaned up. These include but aren't limited to:
- Any remaining threads are terminated.
- All file descriptors are closed.
- All channels and side-channel connections are destroyed.
- All memory mappings for the process are unmapped. This includes code, data, heap, hardware mappings, and shared memory.
- Registered path manager mountpoints are removed.
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:
- flushing stdio buffers, including stdout
- calling at-exit handlers registered by the atexit() function
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 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 OS 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:
- If any process thread tries to use a pointer that doesn't contain a valid virtual address for the process, then the hardware will generate a fault and the kernel will handle it by setting the SIGSEGV signal on the process. By default, this will terminate the process. When delivered in response to this type of fault, the signal is a synchronous signal and ignores the value of the thread's signal mask.
- A floating-point exception will cause the kernel to set the SIGFPE signal on the process. The default is to terminate the process.
- If you create a shared memory object and then map in more than the size of the object, when you try to write past the size of the object you'll be hit with SIGBUS. In this case, the virtual address used is valid (since the mapping succeeded), but the memory cannot be accessed.
- Another process might explicitly send a signal to your process; for example:
kill( pid, SIGTERM );
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. |
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:
|
SIGQUIT | Quit |
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.