Exit statuses and how $? works

The other day I was wondering about $? in Perl and the shell and how exit statuses work. I did some digging and now I'd like to talk a bit about exit statuses at the OS level and how Perl and the shell deal with it.

First off, there are two ways a unix process can terminate. One is by calling _exit, the other is getting killed by a signal. In both cases the resources of the process (such as memory or file descriptors) are cleaned up. All that remains is an entry in the process table (i.e. the PID is still taken) and some status information on how the process died. This "stale" process table entry is called a zombie process.

After a process has terminated, it's its parent's job to clean up after it by calling wait or simply exiting itself (a process whose parent has died is known as an orphan process; it will be adopted and cleaned up by init, the process with id 1).

In C, a successful call to wait gives you a status value of type int. The header <sys/wait.h> provides several macros to examine this status, such as:

  • WIFEXITED - true if the process died by calling _exit
  • WEXITSTATUS - the number passed to _exit (only valid if WIFEXITED is true)
  • WIFSIGNALED - true if the process was killed by a signal
  • WTERMSIG - the signal that killed it (only valid if WIFSIGNALED is true)

Something that may not be obvious: the "exit status" consists of a single byte, so only the 8 lowest bits of the number you pass to _exit will be used. In other words, WEXITSTATUS will always be in the range [0, 255].

The above interface is fairly abstract; it doesn't define the details of how signal numbers and exit statuses are encoded. But there is a traditional way unix has done this, and it's also how Perl does it.

  • The status value in $? is a 16-bit number.
  • The low 8 bits are set if the process died from a signal.
  • The low 7 bits are the signal number; bit 8 is set if the process dumped core.
  • Otherwise the exit status is stored in the high 8 bits.

Perl always computes $? to look like this, even if your OS doesn't encode status information this way natively. This means a C program may get different status bits from wait than what a Perl program would see in $?. If you really need your OS's native status code in a Perl program, you can use ${^CHILD_ERROR_NATIVE} and the W* functions from POSIX.

(The following information was taken from the bash manual but it probably applies to all Bourne-style shells.)

Finally there is another $? variable in the shell. It has the same name and function as Perl's, but its values work differently. In particular, it's always an 8-bit number, not 16 bits as in Perl.

  • If a process exits normally, its status is in $?.
  • If it is killed by a signal, $? is set to 128 + $signo (where $signo is the signal's number).

Example: On my system SIGSEGV is 11, so after a segfault the shell sets $? to 128 + 11 = 139.

Other synthetic $? values include 127 (command not found) and 126 (command found but it wasn't executable).

Leave a comment

About mauke

user-pic I blog about Perl.