Today's plan
- Unix/Posix signals
- Minix signals
Unix signals
- any process may set a signal handler for a given signal
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
- the argument to the signal handler is the signal number, so
the same signal handler can handle multiple signals
- the return value of signal is the old signal handler
- the signal handlers default to SIG_DFL, which:
- aborts if the signal is HUP, INT, PIPE, ALARM, TERM, USR1, USR2
(and other non-Posix signals such as POLL)
- aborts and dumps core if the signal is QUIT, ILL, ABRT, FPE, SEGV, etc
- returns (ignoring the signal) if the signal is CHLD, URG, or others
See signal(7) for details
- the signal handlers can also be set to SIG_IGN, which ignores
the signal (except KILL and STOP cannot be blocked or ignored)
- "Unix" signal handling varies among systems, but typically the
signal is blocked during execution of the signal handler
Unix/Posix sigaction
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
}
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
int sigpending(sigset_t *set);
int sigsuspend(const sigset_t *mask);
- sigaction is defined portably across Unix-like systems
- the second parameter (if non-null) is used to define a signal
handler, either a sa_handler or a
sa_sigaction, but not both
- the sa_handler can be set to SIG_DFL or SIG_IGN
- the mask specifies which signals should be blocked during execution
of the handler
- the flags can be used to specify
- whether we are specifying a sigaction or a handler
- that certain signals should be ignored (e.g. child stop signals)
- whether the signal handler should be permanently installed, or
executed at most once
- whether system calls should continue across a signal
- whether further instances of the signal being handled should be deferred
while the signal handler is executing
- sigprocmask lets us block or unblock the given signals
- sigpending returns raised signals that are currently blocked
- sigsuspend suspends the process after temporarily installing the
given signal mask
Minix signal handling
- each process table entry in MM has the following sets of signals:
- signals to block (the sigmask)
- signals to ignore
- signals pending (i.e. previously blocked, not yet delivered)
- signals to catch (handle)
- for each signal, a mask of signals to block while the signal handler
is executing
- a second copy of the sigmask (which signals to block) so sigsuspend
can replace it with a temporary sigmask
- in addition, each process table entry has an optional signal
handler for each signal
Signal handling and system calls
- a process making a system call is blocked on receive
- what is the appropriate behavior of a long-running system call
(e.g. read from a pipe or a socket) when its process gets
a signal?
- some OSs think the system call should be ended with
errno = EINTR, so the caller can (or must) call it again
- some OSs execute the signal handler while the main part of
the process is blocked on the system call -- a system call return
then has to make sure it is returning to the right place
- Linux and Solaris (at least) give a choice via the
SA_RESTART flag (in sigaction), which if
set, means slow system calls are automatically restarted
- Minix completes/aborts the system call, by sending it a message
with result code EINTR
- how about other OSs?
Minix signal handling implementation
- sig_proc (p. 775) delivers a signal, may be called
from the kernel or the memory manager
- sig_proc
- makes sure the process is not dead,
- returns if the signal is ignored or blocked
(if blocked, after recording it in the pending signals), then
- checks for a handler, and if so, executes, or
- if no handler, kills the process (calling mm_exit), optionally
dumping core
- to execute a signal handler, sig_proc builds two large
structures on the stack (Fig 4-46 on p. 392):
- a sigcontext structure holds a copy of significant
parts of the kernel process table entry, particularly all the saved registers
- a sigframe structure holds a valid stack frame for
the execution of sigreturn, including a return address and some parameters
because these structures are large, the stack may overflow, in which
case the entire process is killed
- the signal is removed from the set of pending processes
- if the process is paused on a system call (including but
not limited to pause), it is unblocked by sending it
a reply -- the stack is set up so that the signal handler will execute
first, and only then will the system call complete
Minix signal handling functions
- check_pending is called whenever the set of signals for
a process may have changed, and calls sig_proc as appropriate
- do_sigaction, do_sigpending,
do_sigprocmask, do_sigsuspend,
and do_sigreturn
do the bit set and handler table manipulation, contacting the
kernel or calling check_pending as appropriate
- check_sig is called to make sure the signal can be sent,
and to send it to a group of processes if appropriate (e.g. by the kernel
when rebooting)
- do_kill and do_ksig are called to send a signal
(from user space and from the kernel, respectively), and eventually
call check_sig. The major difference between them is the kernel
may send several signals at once
- do_alarm and set_alarm turn alarms on and off
by contacting the clock task
- do_alarm and set_alarm turn alarms on and off
- dump_core uses several system calls to create
and write a core file