6.3 KiB
Signals and Nonlocal Jumps
Shell
Linux Process Hierachy: all is rooted from init or systemd (pid = 1).
A shell is an application that runs programs on behalf of the user.
shOriginal Unix shellcshBSD Unix C ShellbashGNU Bourne-Again Shell (default Linux shell)
Signals
All signal is a small message that notifies a process that an event has occured in the system.
Signal is sent by kernel.
Signal is identified by small integer ID (1-30).
Only information in a signal is its ID and the fact that it arrived.
| ID | name | Default Action | Corresponding Event |
|---|---|---|---|
| 2 | SIGINT | terminate | user types Ctrl+C |
| 9 | SIGKILL | terminate | kill program |
| 11 | SIGSEGV | terminate | segmentation violation |
| 14 | SIGALRM | terminate | timer expired |
| 17 | SIGCHLD | ignore | child process stopped or terminated |
Sending a signal is essentially updating some state in the context of the destination process. Kernel sends a signal for one of the following reasons:
A destination process receives a signal when it is forced by the kernel and reacts some way to the signal.
- Ignore
- Terminate (with optional core-dump)
- Catch
- executing a user-level handler function called signal handler.
Pending & Blocking
A signal is pending if sent but not yet received. There can be at most one pending signal of any particular type because signals are not queued. Signal is managed by bitmask. So subsequent signals of same type is discarded.
- sets
kbit when delivered - clears
kbit when received
Blocking signals: a process can block the receipt of certain signals. It is not received until unblocked. It is also managed by bitmask.
- can be set and cleared by using
sigprocmask
Receiving
Suppose kernel is returning from an exception handler and is ready to pass control to process p
Kernel computes pnb = pending(p) & ~blocked(p).
- if
pnb == 0, no unblocked pending signals forp, so just return topnormally. - otherwise, choose least nonzero bit
kinpnband force processpto receive signalk- The receipt of the signal triggers action by
p - Repeat for all nonzero
k
- The receipt of the signal triggers action by
Default Action & Signal Handler
Each signal type has a predefined default action:
- terminates
- stops until restarted by a SIGCONT signal
- ignores
handler_t * signal(int signum, handler_t *handler);
SIG_IGNignoreSIG_DFLrevert to default action- handler function pointer
Blocking and Unblocking
- Implicit blocking Kernel blocks any pending signals of type currently being handled.
- Explicit blocking
sigprocmask()
Supporting functions:
sigemptyset()create empty setsigfillset()add every signal number to setsigaddset()add signal number to setsigdelset()delete signal number from set
sigset_t mask, prev_mask;
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
sigprocmaksk(SIG_BLOCK, &mask, &prev_mask);
// critical section
sigprocmask(SIG_SETMASK, &prev_mask, NULL);
Safe Signal Handling
Handlers are concurrent with main program and share global data structures. This can lead to troubles.
Some guidlines help to avoid troubles:
- Keep you handlers as simple as possible
- Call only async-signal-safe functions in your handlers
- Save and restore
errnoon entry and exit - Protect accesses to shared data structure by temporarily blocking all signals
- Declare global variables as
volatile - Declare global flags as
volatile sig_atomic_t
Async-Signal-Safety
A function satisfying either two conditions(below) is Async-Signal-Safety function:
- reentrant: not modifying global data (or static data), only use local data(stack frame).
- non-interruptible: blocking singnals during modifying global data.
POSIX guarantees that the 117 functions to be async-signal-safe, including:
_exit,write,wait,waitpid,sleep,kill
IMPORTANT: printf, malloc, sprintf, eixt are NOT async-signal-safe.
Correct Signal Handling Example for reaping multiple childs
void child_handler(int sig) {
int olderrno = errno; pid_t pid;
while ((pid = wait(NULL)) > 0) {
ccount --;
sio_puts("Handler reaped child ");
sio_putl((long) pid);
sio_puts(" \n");
}
if (errno != ECHILD) {
sio_error("wait error");
}
errno = olderrno;
}
Portable Signal Handling
Different UNIX systems have different signal handling sematnics.
So sigaction is introduced to provide a portable interface for signal handling.
handler_t * Signal(int signum, handler_t *handler) {
struct sigaction action, old_action;
action.sa_handler = handler;
sigemptyset(&action.sa_mask); // block sigs of type being handled
action.sa_flags = SA_RESTART; // restart syscalls if possible
if (sigaction(signum, &action, &old_action) < 0)
unix_error("Signal error");
return (old_action.sa_handler);
}
Concurrent Flows to lead Races
void handler(int sig) {
int olderrno = errno;
pid_t pid;
while ((pid = waitpid(-1, NULL, 0)) > 0) {
deletejob(pid);
}
if (errno != ECHILD) sio_error("waitpid error");
errno = olderrno;
}
int main() {
while (1) {
if ((pid = Fork()) == 0) {
execve("/bin/date", argv, NULL);
}
}
addjob(pid);
}
In above example, the ecf flow can be executed before the deletejob; thus it cannot be deleted. Therefore, we need to synchronize the two concurrent flows.
void handler(int sig) {
int olderrno = errno;
sigset_t mask_all, prev_all;
pid_t pid;
sigfillset(&mask_all);
while ((pid = waitpid(-1, NULL, 0)) > 0) {
sigprocmask(SIG_BLOCK, &mask_all, &prev_all);
deletejob(pid);
sigprocmask(SIG_SETMASK, &prev_all, NULL);
}
if (errno != ECHILD) sio_error("waitpid error");
errno = olderrno;
}
int main() {
pid_t pid;
sigset_t mask_all, prev_one;
sigfillset(&mask_all);
signal(SIGCHLD, handler);
while (1) {
sigprocmask(SIG_BLOCK, &mask_all, &prev_one); // Block SIGCHLD
if ((pid = Fork()) == 0) {
sigprocmask(SIG_SETMASK, &prev_one, NULL); // Unblock SIGCHLD
execve("/bin/date", argv, NULL);
}
sigprocmask(SIG_BLOCK, &mask_one, NULL);
addjob(pid);
sigprocmask(SIG_SETMASK, &prev_one, NULL);
}
}