说在前面
- 环境: WSL、ubuntu16
- 参考: UNIX网络编程、linux manual page-sigaction、linux manual page-signal
- 目录:这里
信号
- 信号,告知某个进程发生了某个事件的通知,有时也被称为软件中断。
- 信号通常是异步发生的,即进程预先不知道信号准确的发生时刻。
- 信号传递方向
- 由一个进程发给另一个进程(或自身)
- 由内核发给某个进程
- 每个信号都有一个与之关联的处置(disposition),也称为行为(action)。可以通过调用sigaction函数来设定一个信号的处置,且有三种选择。
- 定义一个函数,只要有特定信号发生就调用该函数。这样的函数被称为信号处理函数(signal handler),这种行为被称为捕获(catching)信号。
有两个信号不可以被捕获,即SIGKILL和SIGSTOP。
信号处理函数原型如下,参数为信号值,且无返回值。void handler(int signo);
- 可以将某个信号的处置设置为SIG_IGN来忽略(ignore)。SIGKILL和SIGSTOP不可被忽略。
- 可以将某个信号的处置设置为SIG_DEF来启用它的默认(default)处置。默认处置通常是在收到信号后终止进程,其中某个信号还在当前工作目录产生一个进程的核心映像。个别信号的默认处置是忽略,如SIGCHLD和SIGURG。
- 定义一个函数,只要有特定信号发生就调用该函数。这样的函数被称为信号处理函数(signal handler),这种行为被称为捕获(catching)信号。
signal函数
- 建立信号处置的POSIX方法就是调用sigaction函数。但是直接调用比较复杂,因为该函数的参数之一(const struct sigaction *restrict act)是必须分配并填写的结构(体)。
- 简单的方法是调用signal函数。
参数说明:#include <signal.h> void (*signal(int sig, void (*func)(int)))(int);
- sig
信号名,例如SIGCHLD。 - func
指向函数的指针,或者SIG_IGN或SIG_DEF。
- sig
- 但是,signal函数的出现早于POSIX标准。所以现在可能有多种函数实现,不同的实现提供不同的信号语义(规则),但POSIX明确了调用sigaction函数时的信号语义,所以若使用POSIX,那么signal函数就只能对应POSIX规定的信号语义(因为前面不是说signal函数是建立信号处置的简单方法吗)。所以,在POSIX标准下,signal函数只是对sigaction函数的简单封装。
- signal函数实现
在UNIX网络编程一书中,signal函数为作者提供的版本。这里找了一个glibc的版本,两者的实现是差不多的。
说明:#define __sighandler_t void(*)(int) /* Set the handler for the signal SIG to HANDLER, returning the old handler, or SIG_ERR on error. */ __sighandler_t __bsd_signal (int sig, __sighandler_t handler) { struct sigaction act, oact; /* Check signal extents to protect __sigismember. */ if (handler == SIG_ERR || sig < 1 || sig >= NSIG || __is_internal_signal (sig)) { __set_errno (EINVAL); return SIG_ERR; } act.sa_handler = handler; __sigemptyset (&act.sa_mask); __sigaddset (&act.sa_mask, sig); act.sa_flags = __sigismember (&_sigintr, sig) ? 0 : SA_RESTART; if (__sigaction (sig, &act, &oact) < 0) return SIG_ERR; return oact.sa_handler; }
-
设置处理函数
sigacttion结构中的sa_handler成员被置为handler函数act.sa_handler = handler;
-
设置处理函数的信号掩码
POSIX允许指定这样一组信号,它们在信号处理函数被调用期时阻塞。任何阻塞的信号都不能被递交(delivering,或者说发送)给进程。
这里将sa_mask设置为空,表示在该信号处理函数调用期间不阻塞任何其它信号。
POSIX保证被捕获的信号在其信号处理函数运行期间总是阻塞的。这里所说的阻塞不同于在系统调用(例如read)时的阻塞。(那么被阻塞的信号在被解除阻塞后会不会被递交呢?)
__sigemptyset (&act.sa_mask);
-
设置SA_RESTART标志
该标志可选。
若设置,那么由相应信号中断的系统调用将由内核自动重启。(例如在accept阻塞期间,进程接收到某个信号,系统调用被中断,在处理完成后,accept由内核自动重启)
若被捕获的信号不是 SIGALRM且SA_RESTART有定义,那么就设置SA_RESTART。
(若被捕获的信号是 SIGALRM且SA_INTERRUPT有定义,那么就设置SA_INTERRUPT。说是早期系统) -
if (__sigaction (sig, &act, &oact) < 0) return SIG_ERR;
-
POSIX信号语义
- 一旦安装了信号处理函数(即调用了sigaction函数),它便一直被安装着。
- 在一个信号处理函数运行期间,正被递交的信号是阻塞的。并且,安装处理函数时在传递给sigaction函数的sa_mask信号集中指定的任何额外信号也会被阻塞。
- 如果一个信号在被阻塞期间产生了一次或多次,那么该信号在被解除阻塞后通常只递交一次,即UNIX信号默认是不排队的。
- 利用sigprocmask函数选择性地阻塞或者解除阻塞一组信号是可能的。这使得我们在一段临界区代码执行期间,防止捕获某些信号,以此保护这段代码。