信号捕捉与处理
信号是一种在事件发生时对进程的通知机制,信号来源大致有硬件异常、键入能产生信号的终端字符和软件事件。信号产生后,会稍后被传递给进程,而进程会采取某些措施来响应信号,在产生和响应期间,信号处于等待状态。
进程响应信号的方式有3种:忽略信号、执行信号处理函数、执行默认动作。
每种信号都有值为正的编号,可用kill -l
命令查看信号列表,用kill -signo pid
命令则可以向PID为pid的进程发送编号为signo的信号。
signal和sigaction函数都可用来改变信号处理,其中signal的行为在不同的unix实现间存在差异,可移植性不好,因此sigaction是建立信号处理器的首选API,实际上有些signal被实现为基于sigaction的glibc库函数。
#include <signal.h>
void (*signal(int signo, void (*handler)(int)))(int);
说明:
- signo表示希望修改处理的信号编号;
- handler为回调函数,原型为
void handler(int signo)
; - 成功时返回之前的信号处理回调,失败则返回SIG_ERR;
- 两个特殊的回调:SIG_IGN表示忽略信号,SIG_DFL表示默认处理;
- SIGKILL和SIGSTOP信号不可被捕捉;
以下是个示例,捕捉处理了Ctrl+C事件(SIGINT信号),要终止程序,可按下Ctrl+\(SIGQUIT信号)。
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
void INTHandle(int signo) {
puts("Ctrl+C pressed");
}
int main() {
signal(SIGINT, INTHandle);
for (;;) pause();
return 0;
}
sigaction函数比signal更为复杂,但功能更强,用法灵活,允许在获取信号处理的同时无需将其改变,还可设置各种属性对调用信号处理函数时的行为施以更精准的控制。
#include <signal.h>
int sigaction(int signo, const struct sigaction *act, struct sigaction *oldact);
其中:
- signo标识要更改的信号编号,可以是除SIGKILL和SIGSTOP外的任何信号;
- act描述新的信号处理方式,如果非空,则修改信号处理方式;
- oldact如果非空,则带回原信号处理方式,如果不关心,设为NULL即可;
sigaction结构定义主要如下:
struct sigaction {
void (*sa_handler)(int); /* address of handler */
sigset_t sa_mask; /* signals blocked during handler invocation */
int sa_flags; /* flags controlling handler invocation; */
void (*sa_restorer)(void); /* not for application use */
};
其中:
- sa_handler对应signal中的handler参数,为信号处理回调函数,或者为SIG_IGN与SIG_DFL之一。仅当sa_handler不为SIG_IGN或SIG_DFL时,才会对sa_mask和sa_flags字段加以处理。
- sa_mask字段定义了一组信号,在调用sa_handler时将阻塞该组信号,即不允许该组信号中断回调的执行。此外,引发调用回调的信号将自动添加到进程信号掩码中,换句话说,在执行回调时,如果同一个信号第二次到达,回调将不会递归中断自己。由于不会对阻塞信号进行排队处理,在执行回调过程中重复产生的信号,稍后在传递时只有一次。
- sa_flags用于控制信号处理过程的各种选项。
- SA_NOCHLSTOP: 若signo为SIGCHLD,则当因接受信号而停止或恢复某一子进程时,不产生此信号。
- SA_NOCHLWAIT: 若signo为SIGCHLD,则当子进程终止时不会将其转化为僵尸。
- SA_NODERER: 捕获信号时,不会在执行回调时将该信号自动添加到进程掩码中。
- SA_ONSTACK: 执行回调时,使用由sigaltstack()安装的备选栈。
- SA_RESETHAND: 当捕获该信号时,会在执行回调前将信号重置为SIG_DFL。
- SA_RESTART: 自动重启由回调中断的系统调用。
- SA_INFO: 执行回调时携带了额外参数。
以下是与信号集操作相关的接口:
#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigismember(sigset_t *set, int signo);
下面是用sigaction重写上述例子。
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
void INTHandler(int signo) {
puts("Ctrl+C pressed");
}
int main() {
struct sigaction act;
sigemptyset(&act.sa_mask);
act.sa_handler = INTHandler;
act.sa_flgas = 0;
sigaction(SIGINT, &act, NULL);
for (;;) pause();
return 0;
}