Signals, or software interrupts, are external, asynchronous events used to alter the course of a program. These may occur at any time during the execution of a program. Because of this, they differ from other methods of inter-process communication, where two processes must be explicitly directed to wait for external messages; for example, by calling read on a pipe (see chapter 5).
The amount of information transmitted via a signal is minimal, just the type of signal, and although they were not originally intended for communication between processes, they do make it possible to transmit atomic information about the state of an external entity (e.g. the state of the system or another process).
When a process receives a signal, there are four possible outcomes:
There are several types of signals, each associated with a particular event. Table 4 lists some of them with their default behaviors.
Name | Event | Default Behavior |
sighup | Hang-up (end of connection) | Termination |
sigint | Interruption (ctrl-C ) | Termination |
sigquit | Strong interruption (ctrl-\ ) | Term. & core dump |
sigfpe | Arithmetic error (division by zero) | Term. & core dump |
sigkill | Very strong interruption (cannot be ignored) | Termination |
sigsegv | Memory protection violation | Term. & core dump |
sigpipe | Writing to a pipe without readers | Termination |
sigalrm | Timer interrupt | Ignored |
sigtstp | Temporary halt (ctrl-Z ) | Suspension |
sigcont | Resuming a stopped process | Ignored |
sigchld | A child process died or was stopped | Ignored |
The signals received by a process come from several possible sources:
ctrl-C
,
the console operator sends the sigint
signal to the processes
controlled by her terminal (that were not already put in the background).
In the same way, the sigquit
signal is sent by typing ctrl-\
1.
When the terminal is closed (either through voluntary disconnection or owing to a disconnected network link), the sighup
signal is sent.kill
. This makes it possible
to send a specific signal to a specific process. For example,
kill -KILL 194
sends the sigkill
signal to the process with id 194, which
causes the process to be killed.kill
(the preceding example being a specific case).sigfpe
signal.sigchld
signal.The system call kill makes it possible to send a signal to a process.
The first parameter is the process id of the destination process and
the second the signal number to send. An error occurs if we attempt to
send a signal to a process not owned by the user. A process may send
signals to itself. When the kill
system call returns, it is
guaranteed that the signal was delivered to the destination
process. If a process receives the same signal in rapid succession it
will execute the code associated with the signal only once. Therefore
a program cannot count the number of times it receives a signal,
rather only the number of times it responds to it.
The system call alarm makes it possible to schedule interruptions based on the system clock.
The call alarm s
returns immediately but causes the sigalrm
signal to be sent to the calling process at least s
seconds later
(note that there is no guarantee on the maximum wait time). The call returns
the number of seconds remaining to an alarm scheduled by a previous call.
If s
is 0
, the effect is simply to cancel an earlier alarm.
The system call signal makes it possible to modify the behavior of a process when it receives a signal of a certain type.
The first argument is the signal number and the second argument, a value of type signal_behavior, indicates the desired behavior for the signal. With:
Signal_ignore | The signal is ignored. |
Signal_default | The default behavior occurs. |
Signal_handle f | The function f is
invoked each time the signal is received.
|
Forking a process with the system call fork
preserves signal behavior: the initial definitions for the child are
those of the parent at the time when fork
was executed. The
execve system call sets all the behaviors to
Signal_default
except that signals ignored before are still
ignored afterward.
Occasionally we want to log-off or end a session while allowing
background tasks (large calculations, “spyware” programs, etc.)
to continue to run. If this is desired, processes which normally
exit on receiving sighup
(sent at the time the user disconnects)
should be prevented from doing so. The Unix command nohup
does
exactly this:
executes the command cmd arg1 ... argn
in a way unaffected by
the signal sighup
(certain shells execute nohup
automatically for all processes launched as background tasks). Here’s how
to implement this in three lines:
The system call execvp preserves the fact that
sighup
is ignored.
Carefully exiting when a program is misbehaving. For example,
a program like tar
can try to save important information
in a file or destroy the corrupted file before terminating. For this
it is possible to include the following lines at the beginning of the program:
where the function quit
is of the form:
Capturing user-initiated interruptions. Some interactive programs
need to return to a main control loop when a user
presses ctrl-C
. For this we just need to raise an exception when the
sigint
signal is received.
To carry out periodic tasks (animations, etc.) interleaved with the execution of the main program. For example, here is how to create “beep” sounds every 30 seconds, regardless of the activity of the main program (calculations or input/output).
Signals are useful for asynchronous communication — indeed, it is their raison d’être — but this asynchronous nature also makes them one of the major difficulties of system programming.
The signal handling function is executed asynchronously and thus pseudo-concurrently with the main program of the process. As signal handling functions cannot return a value, they usually modify global variables. This can result in race conditions between the signal handler and the main program if they try to modify the same variable. As explained in the next section one solution is to temporarily block signals when this variable is accessed by the main program.
In fact, OCaml does not treat signals in a strictly asynchronous fashion. On receiving a signal, OCaml records the receipt of the signal but the signal handling function will only be executed at certain checkpoints. These are frequent enough to provide the illusion of asynchronous execution. The checkpoints typically occur during allocations, loop controls, or interactions with the system (particularly system calls). OCaml guarantees that a program that does not loop, does not allocate, and does not interact with the system will not have its execution interleaved with that of a signal handler. In particular, storing an unallocated value (integer, boolean, etc. — but not a float!) in a reference cell cannot result in the race condition described above.
Signals may be blocked. Blocked signals are not ignored, but put on standby, generally to be delivered later. The sigprocmask system call makes it possible to change the mask for incoming signals:
sigprocmask cmd sigs
changes the list of blocked signals and
returns the list of signals that were blocked before the execution of
the function. This makes it possible to later reset the mask to its
previous state. The argument sigs
is a list of signals and cmd
a value of type sigprocmask_command which determines the
effect of the call:
SIG_BLOCK | The signals sigs are added
to the list of blocked signals. |
SIG_UNBLOCK | The signals sigs are removed
from the set of blocked signals. |
SIG_SETMASK | The signals sigs are exactly the
signals to be blocked.
|
A typical usage of sigprocmask
is to mask certain
signals temporarily.
Often, one has to guard against possible errors by using the following pattern:
Certain system calls can be interrupted by unignored signals. These
system calls are known as slow calls, which can take an
arbitrary amount of time (for example terminal i/o,
select, system, etc.). If
an interruption occurs, the system call is not completed and raises
the exception EINTR
. However file i/o is not interruptible:
although these operations can suspend the running process to execute
another process for disk i/o, when this occurs the interruption will
always be brief if the disk functions correctly. In particular, the
throughput of data depends only on the system, and not another user’s
process.
Ignored signals are never delivered and a masked signal is not
delivered until unmasked. But in all other cases we must protect our
system calls against unwanted interruptions. A typical example is a
parent waiting for the termination of a child. In this case, the
parent executes waitpid [] pid
where pid
is the
process id of the child. This is a blocking system call, it is thus
slow and could be interrupted by the arrival of a signal,
especially since the sigchld
signal is sent to the parent when a
child process dies.
The module Misc
define the function restart_on_EINTR
which
makes it possible to repeat a system call when it is interrupted by a
signal, i.e. when the EINTR
exception is raised.
To wait on a child correctly, call
restart_on_EINTR (waitpid flags) pid
.
Children can also be recovered asynchronously in the signal handler of
sigchld
, especially when their return value does not matter to
the parent. But when the process receives the sigchld
signal, it
is not possible to know the exact number of terminated processes, since if
the signal is received several times within a short period of time the
handler is invoked only once. This leads to the library function
Misc.free_children
function to handle the sigchld
signal.
free_children
executes waitpid
in
non-blocking mode (option WNOHANG
) to recover any dead children
and repeats until either there are only live children (zero is
returned instead of a child id) or there are no children (ECHILD
exception).
Note that it is not important to guard against the EINTR
exception because waitpid
is non-blocking when called with the
WNOHANG
option.
The function system
in the Unix
module is simply defined as:
The specification of the system
function in the C standard
library states that the parent ignores sigint
and
sigquit
signals and masks the sigchld
signal during the
command’s execution. This makes it possible to stop or kill the child
process without affecting the main program’s execution.
We prefer to define the function system
as a specialization of the
more general function exec_as_system
which does not necessarily
go through the shell.
Note that the signal changes must be made before the call
to fork
is executed because the parent could receive signals
(e.g. sigchld
if the child were to finish immediately) before it
proceeds. These changes are reset for the child on line 11
before executing the command. Indeed, all ignored signals
are preserved by fork
and exec
and their behavior is
preserved by fork
. The exec
system call uses the
default behavior of signals except if the calling process ignores a
signal in which case it also does.
Finally, the parent must also reset the changes immediately after the
call, even if an error occurs. This is why
try_finalize
is used on line 15.
Since the earliest versions of Unix, time has been counted in seconds.
For compatibility reasons, therefore, one can always measure time in seconds.
The current time is defined as the number of seconds since January 1st, 1970
at 00:00:00
gmt. It is returned by the function:
The sleep system call can pause the execution of a program for the number of seconds specified in its argument:
However, this function is not primitive. It is programmable with
more elementary system calls using the function alarm
(see
above) and sigsuspend
:
The sigsuspend l
system call temporarily suspends the signals in the
list l
and then halts the execution of the program until the reception
of a signal which is not ignored or suspended (on return, the
signal mask is reset to its old value).
Now we may program the sleep
function:
Initially, the behavior of the sigalrm
signal does nothing. Note
that “doing nothing” is the same as ignoring the signal. To
ensure that the process will be awakened by the reception
of the signal, the sigalrm
signal is put in an unblocked
state. Then the process is put on standby by suspending all other
signals which were not already suspended (old_mask
). After the alarm
is signaled, the preceding modifications are erased. (Note that
line 9 could be placed immediately after
line 2 because the call to sigsuspend
preserves
the signal mask.)
In more modern versions of Unix, time can also be measured in microseconds.
In OCaml, time measured in microseconds is represented by a float.
The gettimeofday function is the equivalent of the time
function for modern systems.
In present-day Unix each process is equipped with three timers, each measuring time from a different perspective. The timers are identified by a value of type interval_timer :
ITIMER_REAL | Real time (sigalrm ). |
ITIMER_VIRTUAL | User time (sigvtalrm ). |
ITIMER_PROF | User time and system time (sigprof ).
|
The state of a timer is described by the interval_timer_status
type which is a record with two fields (each a float
)
representing time:
it_interval
is the period of the timer.
it_value
is the current value of the timer;
when it turns 0
the signal sigvtalrm
is sent and
the timer is reset to the value in it_interval
.
A timer is therefore inactive when its two fields are 0
.
The timers can be queried or modified with the following functions:
The value returned by setitimer
is the old value of
the timer at the time of the modification.
To manage several timers, write a module with the following interface:
The function new_timer k f
should create a new timer of the
timer-type k
starting the action f
, and inactive on creation;
the function set_timer t
should set the value of the timer t
(and return the old value).
Modern versions of Unix also provide functions to handle dates, see the structure tm which allows dates and times to be expressed according to a calendar (year, month, etc.) and the conversion functions: gmtime, localtime, mktime, etc.
Owing to their asynchronous nature, the use of signals for inter-process communication presents some limitations and difficulties:
Signals offer only a limited form of asynchronous communication but carry all the difficulties and problems associated with it. If possible, it is therefore better not to use them. For example, to wait for a small amount of time, select can be used instead of alarms. But in certain situations signals must be taken into account (for example in command line interpreters).
Signals are possibly the least useful concept in the Unix system. On
certain older versions of Unix (System V, in particular) the behavior
of a signal is automatically reset to Signal_default
when it is
received. The signal handling function can of course register itself
again. For example in the “beep” example on
page ??, it would be necessary to write:
However the problem is that the signals that are received between the
instant were the behavior is automatically reset to
Signal_default
and the moment were set_signal
is invoked are
not treated correctly and depending on the type of the signal they may
be ignored or cause the process to die instead of invoking the signal
handling function.
Other flavors of Unix (bsd or Linux) provide better support: the behavior associated with a signal is not altered when it is received, and during the handling of a signal other signals of the same type are put on hold.