complement 9.md 10.md 11.md
This commit is contained in:
218
notes/10.md
Normal file
218
notes/10.md
Normal file
@@ -0,0 +1,218 @@
|
||||
# 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.
|
||||
|
||||
* `sh` Original Unix shell
|
||||
* `csh` BSD Unix C Shell
|
||||
* `bash` GNU 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 `k` bit when delivered
|
||||
* clears `k` bit 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 for `p`, so just return to `p` normally.
|
||||
* otherwise, choose least nonzero bit `k` in `pnb` and force process `p` to receive signal `k`
|
||||
* The receipt of the signal triggers action by `p`
|
||||
* Repeat for all nonzero `k`
|
||||
|
||||
### Default Action & Signal Handler
|
||||
|
||||
Each signal type has a predefined default action:
|
||||
* terminates
|
||||
* stops until restarted by a SIGCONT signal
|
||||
* ignores
|
||||
|
||||
```c
|
||||
handler_t * signal(int signum, handler_t *handler);
|
||||
```
|
||||
|
||||
* `SIG_IGN` ignore
|
||||
* `SIG_DFL` revert 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 set
|
||||
* `sigfillset()` add every signal number to set
|
||||
* `sigaddset()` add signal number to set
|
||||
* `sigdelset()` delete signal number from set
|
||||
|
||||
```c
|
||||
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:
|
||||
|
||||
0. Keep you handlers as simple as possible
|
||||
1. Call only **async-signal-safe** functions in your handlers
|
||||
2. Save and restore `errno` on entry and exit
|
||||
3. Protect accesses to shared data structure by temporarily blocking all signals
|
||||
4. Declare global variables as `volatile`
|
||||
5. 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
|
||||
|
||||
```c
|
||||
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.
|
||||
|
||||
```c
|
||||
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
|
||||
|
||||
```c
|
||||
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.
|
||||
|
||||
```c
|
||||
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);
|
||||
}
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user