 | What is a signal?
 | A signal is a software interrupt. |
 | A signal is asynchronous event that the application must deal with.
 | Asynchronous means we do not know when it will occur. |
 | The purpose of signals is to provide a way processes can synchronize
with one another. |
|
 | We will describe POSIX reliable signals, which standardizes the signal
handling in UNIX. |
 | Now we write a program and terminate it with Ctrl-C key, or with a kill
command. |
|
 | Signal concepts
 | Every signal has a name and a number.
 | All the names start with SIG. |
 | All the numbers can be found in <signal.h>, or with 'man 7 signal',
or check out the table on page 266. |
 | No signal has the number 0. This is reserved for a null signal. |
|
 | Signal can be generated in many circumstances.
 | Typing a special key |
 | Hardware exception |
 | kill system call |
 | kill command |
 | Other conditions that a process should be aware of.
 | For example, one of the process's
children terminates. |
|
|
 | What a process can do when a signal arrives. There is no way a process
can predict when it will receive a signal, since signal is asynchronous.
 | Ignore the signal.
 | Two signals are never ignored -- SIGKILL and SIGSTOP. |
|
 | Catch the signal.
 | Write a signal handling routine and do whatever you want there. |
|
 | Let the default action apply.
 | UNIX kernel has a set of default action for different signals.
 | Terminate with coredump |
 | Terminate |
 | Ignore |
|
 | Page 266 has a complete list of the default actions. |
 | Now we test the signal processing using kill command. |
|
|
 | A list of important signals
 | SIGABRT |
 | SIGALRM |
 | SIGBUS |
 | SIGCHLD |
 | SIGFPE |
 | SIGINT |
 | SIGKILL |
 | SIGQUIT |
 | SIGSEGV |
 | SIGSTOP |
 | SIGTERM |
 | SIGUSR1 |
 | SIGUSR2 |
|
|
 | Signal API
 | signal
 | Although implied by its name, this routine does not send out signal (kill
does). It simply install (or more formally, register) a handler routine
for a particular signal. |
 | The signature is complicated. We will use the following
"simplification".
 | typedef void Sigfunc(int);
 | A Sigfunc is a function that takes a integer as the only argument,
and does not return anything. |
|
 | Sigfunc *signal(int, Sigfunc *);
 | The function 'signal' takes two arguments.
 | The first argument is an integer. |
 | The second argument is a pointer to a type Sigfunc function we
defined earlier. |
 | The return value is another pointer to a type Sigfunc function. |
|
|
 | Now put all these together. The function "signal" install a handler
(the second argument) for a particular signal (the second argument), and
returns the previous handler as the return value. |
|
 | The <signal.h> header file provides three constants for handler.
 | SIG_DFL
 | Apply the default action. |
|
 | SIG_IGN
 | Ignore the signal. |
|
 | SIG_ERR
 | Error flag for "signal" routine. |
|
|
 | Now we test the textbook example
sigusr.c.
 | Note that pause function will suspend a process until a signal
arrives. |
 | The program installs a handler for SIGUSR1 and SIGUSR2, then waits
for signals. |
 | The routine errdump terminates the process with a coredump. |
|
 | When a process 'exec's another, the installed user-defined signal
handlers are all reset to default action.
 | Without the old program text, how do you expect the user-defined
handlers remain working? |
|
 | When a process forks another, the installed user-defined signal
handlers remain working. |
|
|
 | Terminology and Semantics
 | Generated
 | A signal is generated when the event that causes the signal occurs. |
|
 | Delivered
 | A signal is delivered to a process when the action for that signal is
taken. |
|
 | Pending
 | A signal is pending from it is generated until it is delivered. |
|
 | Blocking
 | A process can block the delivery of a signal by mean of masks. When a
signal is blocked, it remains pending. |
 | A pending signal can be delivered either the process ignores the
signal, or unblock the signal. |
|
 | If more than one signal is pending, there is no guarantee that which one
will be processed first. |
 | If more than one signal of the same type is pending, it will be treated
as a single one or multiple ones?
 | POSIX allows both. |
|
|
 | kill and raise
API
 | kill
 | Send a signal to a process. |
 | The sender can specify the process id to which the signal will go.
 | pid > 0
 | Send to the process with given id. |
|
 | pid = 0
 | Send to all processes with the same process group id as the
sender. |
|
 | pid < 0
 | Send to all processes with the given process group id. |
|
|
 | Permission to send signal
 | The super user can send signals to anyone. |
 | Other users can send to processes with the same effective or real
user ids that matches the sender's effective user id. |
 | 0 is the null signal, and often is used to determine whether a
process is still alive. |
|
|
 | raise
 | Send a signal to oneself. |
|
|
 | alarm and pause
API
 | alarm
 | Deliver a SIGABRT to the given process after a specified number of
seconds. |
 | Only one alarm clock exists. If alarm is called before it is expired,
the new setting replaces the old one and the number of seconds till the
previous alarm is returned. |
|
 | pause
 | Stop the process until a signal is received, and the routines returns
when the signal handler for the caught signal returns. |
|
 | Check out the sleep implementation using alarm (sleep1.c).
 | The sleep routine will make the calling process idle for a given
number of seconds. |
 | If the sleeping process is awakened by a signal, the number of unslept
seconds should be returned. |
 | There are some problems with this program.
 | If the caller use alarm for other purpose, the value is lost. |
 | The caller may have installed his own signal handler for SIGALRM, so
we need to restore that. |
 | A race condition between the call to alarm and pause. If the SIGALRM
happens between them then no one will wake up the process when it is in
pause.
 | We need signal mask for these problems. See below. |
|
|
|
|
 | Signal Sets and signal masks
 | Before we talk about signal mask we must be able to group the signals
that we want to process as a set. |
 | Signal set processing API
 | sigemptyset
 | Make a empty set. |
|
 | sigfillset
 | Make a signal set that has all the signals. |
|
 | sigaddset
 | Add a signal into a set. |
|
 | sigdelset
 | Delete a signal from a set. |
|
 | sigmember
 | Determine whether a signal is in a set or not. |
|
 | All these API are implemented with bit string. |
|
 | Signal mask API
 | sigprocmask
 | This function modifies the signal mask of a porcess. |
 | There are three operation modes (indicated by the "how" argument).
 | SIG_BLOCK
 | Block the signals in the given signal set. |
|
 | SIG_UNBLOCK
 | Unblock the signals in the given signal set. |
|
 | SIG_SETMASK
 | Set the mask to a given value. |
|
|
 | This function also outputs the original mask through a pointer. |
 | Refer to the pr_mask routine for an example. |
|
 | sigpending
 | This function returns the set of signals that are blocked (though a
pointer). |
|
 | Now check out the example on page 295 (critical.c).
 | The program first installs a signal handler for SIGQUIT. |
 | Then the program constructs signal set with SIGQUIT in it, and uses
sigprocmask to block SIGQUIT. |
 | The program then sleeps for 5 seconds. During this period, no signal
is delivered (they are blocked). |
 | Then the program gets the mask and print a message if there is
SIGQUIT being blocked. |
 | Finally the mask is restored and as soon as this happens, the signal
is delivered |
 | Note that the signal handler is set to the default action within
sig_quit. |
|
|
 | sigaction
 | This function is similar to "signal", but it can access the signal
handler without replacing it. |
 | The arguments are the following.
 | An integer to indicate the signal number |
 | An input sigaction structure to indicate the information that we
want to impose. |
 | An output sigaction structure for the information that we retrieved. |
|
 | The interface consists of a special structure called sigaction, which
has the following:
 | The address of the handler |
 | A signal mask
 | This mask will be automatically added when the signal handler is
invoked, and removed when the handler finishes. |
 | The signal that invokes the handler will be automatically added
into the mask. As a result this mechanism guarantees that the signal
handler will not be interrupted again by the same signal. |
|
|
 | Now check out the implementation of "signal" using "sigaction" (page
298). |
|
 | sigsuspend
 | This function sets the mask to a given value and wait for signal
interrupt. These two operation are done atomically. |
 | The purpose of this function (see the example on page 303) is to
provide a way to unblock a signal and wait for it in a single step.
This could be used to implement a critical section. |
 | The signal mask is set to its original value before the sigsuspend
call if the handler returns. |
 | Test the textbook example (suspend1.c).
 | First the program constructs a mask containing SIGINT, which is set
to be the new mask. |
 | Then the program unblocks and waits for signal. |
 | The output from pr_mask indicates that in the critical section,
SIGINT is blocked. |
 | In the SIGINT handler, the SIGINT remains blocked, since at the
beginning of the handler, the mask is restored. |
 | Finally we restore the old mask. |
|
 | Use sigsuspend to synchronize processes (page 307).
 | The synchronization involves three functions.
 | TELL_WAIT
 | Two signals are used in the synchronization process -- SIGUSR1
and SIGUSR2. |
 | This function initializes the signal handlers. Note that both
signals use the same signal handler. |
 | The initialization blocks both SIGUSR1 and SIGUSR2, and store
the original mask in the variable old_mask. |
|
 | TELL_PARENT and TELL_CHILD
 | These functions send SIGUSR2 and SIGUSR1 to the parent and child
respectively. |
|
 | WAIT_CHILD and WAIT_PARENT
 | These functions wait for the signals from child and parent
respectively. |
 | These functions use a spin loop to wait until the global
variable is set to non-zero by the interrupt handler. |
 | If the sender executes first the signal will be blocked until
the receiver is ready. |
 | If the receiver executes first then it will wait until the
sender unblocks (with the zero mask). After the handler returns the
global variable sigflag must be reset back to 0. |
|
|
|
|
|
 | A correct implementation of sleep (page 318)
 | Now we are ready to implement sleep. |
 | The sigaction installs and examines the handler address. |
 | The sigprocmask manipulates the mask so that we can block the SIGALRM
when we want to. |
 | The sigsuspend unblocks and waits for SIGALRM. |
 | the race condition in the previous implementation is resolved. |
 | The semantic of sleep is as follows:
 | If the amount of time has elapsed, the function returns 0.
 | The alarm function returns 0, just as we want it to. |
|
 | Otherwise the function returns the number of unslept seconds.
 | The alarm function returns the number of seconds till it is supposed
to wake up. |
|
|
|
 | Abort function
 | The function is similar to alarm, but it sends a SIGABRT to itself. |
 | This function cannot return even if a user defines a handler that will
(required by ANSI C). |
 | The purpose of abort is to cleanup when the process exits. |
 | Now examine the implementation on page 311.
 | First the program check the current handler for SIGABRT, and replace
SIG_IGN with SIG_DFL if the user tries to ignore the SIGABRT. |
 | Then the program checks for the mask, and unblock SIGABRT if the mask
does block it. |
 | Send a SIGABRT to itself. |
 | Check if the handler returns. If it does, replace the handler with
SIGABRT and this makes sure the following SIGABRT will kill the process. |
|
|
 | System function
 | POSIX requires that the system function ignores SIGINT and SIGQUIT,
and blocks SIGCHLD. |
 | First see the example on page 312.
 | In this example, SIGINT and SIGCHLD are handled by the main program,
which launches an ed process by system. |
 | When the ed child process terminates, it sends a SIGCHLD to the main
program.
 | This is not acceptable since the main program may be waiting for
other children to return -- it cannot tell whether the child is from the
system call or from its fork. |
|
 | When we type INT character in ed all the foreground processes receive
this signal, including the main program.
 | This should not happen since the main program executes ed with
system, so it does not want to be bother by the signal. |
 | The solution is to ignore SIGINT and SIGQUIT from the caller of
system. |
|
|
 | Now check out the implementation on page 314.
 | SIGCHLD is blocked. |
 | SIGINT and SIGQUIT are ignored. |
 | After forking the child, the handlers for SIGINT and SIGQUIT,
and the mask are restored. Then the child executes the command using sh. |
 | After the child (now running sh) returns, everything is restored. |
 | We change the disposition of these signals to avoid a race condition.
If we fork first and then change the disposition, the child running sh may
send signals before the parent is prepared. |
|
|
 | Interrupted system calls
 | A signal could be "slow". For example, it could be waiting for I/O to
complete (read or write), or waiting for its child process to terminate
(wait and waitpid). |
 | When a system call is interrupted by a signal, the error number errno is
set to EINTR and the system call returns. |
 | To handle the possible interruption by signals, the programmer needs to
code like the case on page 276, which is very tedious and error-prone. |
 | If the system can restart itself when interrupted by a signal, we will
not to do it in our program. This was introduced by BSD 4.2. |
 | The automatically restarted system calls include ioctl, read, write,
writev, wait, and waitpid. |
 | Figure 10.2 indicates the ability to restart system calls by various
signal functions. |
 | Now we examine the program on page 298. Here we see that if SA_RESTART
is defined, we always restart our system calls. |
 | The function alarm can be used to limit the time a user can input. See
the code on page 289.
 | In this code we first set up an alarm of 10 seconds. |
 | If the user did not do anything, the alarm comes and the read is
interrupted. |
 | If the user does something before the alarm comes, the alarm(0) will
turn off the alarm. |
 | Now it is clear that we do not wish to restart a system call when the
signal is SIGALARM. |
 | Now go back to page 298, and we see exactly what we just described. |
|
|
 | Reentrant functions
 | A reentrant function is one that could call itself in the middle of its
own execution.
 | What is NOT a reentrant code.
 | Code that uses static data structure. |
 | Code that calls malloc |
 | Code that refers to standard I/O library |
|
 | In fact all these reasons are the same -- they all use a single global
data structure. |
|
 | We should not call functions that are not reentrant in a signal handler.
 | The reason? We could be interrupted again and go into another handler
instance, which will call the non-reentrant functions and cause errors. |
|
 | We also should save the errno in a signal handler, since the interrupted
process might want to know its value. |
 | Now check the program on page 280.
 | The main program execute a infinite loop. During each loop it checks
for inconsistent user name. |
 | The inconsistency will happen when the SIGALARM interrupts the main
program between the getpwnam and strcmp, since the handler calls getpwnam
with a different user name. |
|
|