信号(Signal)在Linux中的应用非常久远了,但在实际的工程中还真是用得少得可怜。因为信号给的印象一直是不可靠的,所以一般能不用就不用它了。再说,开发过程中好像除了原来在实际在生产环境触发性能测试的情况下应用过,其它还真的没怎么在生产中应用。
好多年过去,再用到的时候儿发现,信号有可靠信号和不可靠信号这分。一般来说,信号的应用只能用在比较简单的情况下,而且不能发送较复杂的数据并且只能在信号触发情况下才会发送。但信号也有其优势,它可以理解为软中断,所以它是异步的。既然是异步的,其实就会有一定的延时,只不过,这种延时比较小,一般是无法感觉出来的。
在内核的处理中,信号有三种方式,一种是按照设定进行处理(调用信号处理函数);另外一种是忽略(设置信号掩码);最后一种是默认动作。
可以使用kill -l来查看当前支持的命令:
$ kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
前31个信号是普通信号,后面的为实时信号,主要和底层有关。需要注意的是,这里可没有0号信号。SIGRTMIN以前的为不可靠的信号,其后为可靠信号。
所谓可靠与不可靠的信号,其实是不是指信号本身,指的信号代表的处理是否可靠。其实可靠就是通过某种手段保障信号的可靠传递而不可靠则指信号传送不保证送达(可能丢失)。在Linux新版本中支持了信号安装函数sigation()以及信号发送函数sigqueue(),但对早期的signal()信号处理函数和信号发送函数kill()仍然兼容。
信号处理在前面提到过类似于软中断,而中断基本都是由内核自己控制的。因此,整体的信号核心控制模块,一定是内核来决定,所以对于信号的应用,其实上层是没有太多的发言权的。目前的Linux中有两个信号处理函数signal()及sigaction(),但是它们对信号处理的可靠性上是一致的即也无法修改信号代表的可靠和不可靠性。不过现在内核内部对信号的处理其实只有sigaction()一种,另外的signal()也是通过其实现的。故而在某种情况下,可以认为只是一个应用的大小集罢了。
内核对可靠信号(SIGRTMIN以后)支持的底层也很简单,只是增加了队列和相关控制方式罢了。
信号处理函数的执行现场不是程序员布置的,而是内核布置的,因为程序中不会有调用信号处理函数的地方。信号的可靠与不可靠只与信号值有关,与信号的发送及安装函数无关 。目前linux中的signal()是通过sigation()函数实现的,因此,即使通过signal()安装的信号,在信号处理函数的结尾也不必再调用一次信号安装函数。同时,由signal()安装的实时信号支持排队,同样不会丢失。
对于目前linux的两个信号安装函数:signal()及sigaction()来说,它们都不能把SIGRTMIN以前的信号变成可靠信号(都不支持排队,仍有可能丢失,仍然是不可靠信号),而且对SIGRTMIN以后的信号都支持排队。这两个函数的最大区别在于,经过sigaction安装的信号都能传递信息给信号处理函数,而经过signal安装的信号不能向信号处理函数传递信息。对于信号发送函数来说也是一样的。
信号在实际应用场景中,其实是有很多限制的。即使简单的应用,如果涉及到数据共享,需要考虑缓存的情况,所以要把共用数据定义为volatile。即使如此,仍然不建议在多线程环境下信号处理。当然在可控制的情况下,是否应用还是要看应用者自主决定的。
信号有四种状态,即挂起(pending)、阻塞(blocked)、传递(delivered)和忽略(ignore)。所以可以使用Mask来处理相关的信号,具体的看下面的代码:
下面看两个例子:
1、Signal例程
void handler(int signal) {
printf("call SIGINT");
}
int main() {
signal(SIGINT, handler);
sleep(10);
}
2、通用例子
#include
#include
#include
#include
#include
#include
#include
#include
void doSignal(int n, siginfo_t *info, void *tmp) {
switch (n) {
case SIGINT:
printf("recv SIGINT, data value:%d\n", info->si_value.sival_int);
break;
case 35:
// SIGRTMIN = 34 ,non const
printf("recv SIGRTMIN+1! data value:%d\n", info->si_value.sival_int);
break;
case SIGUSR1:
printf("recv SIGUSR1!\n");
// unblock
sigset_t ub;
sigemptyset(&ub);
sigaddset(&ub, SIGINT);
sigaddset(&ub, SIGRTMIN + 1);
sigprocmask(SIG_UNBLOCK, &ub, NULL);
break;
}
}
void sendSignal(union sigval &sval, int sig, int count) {
for (int i = 0; i < count; i++) {
sval.sival_int = sig + i;
int ret = sigqueue(getppid(), sig, sval);
if (ret != 0) {
printf("send SIG error! ");
} else {
printf("send SIG ok!\n");
}
}
}
int testSignal() {
pid_t pid = 0;
struct sigaction sigAct;
sigAct.sa_sigaction = doSignal;
sigemptyset(&sigAct.sa_mask);
sigAct.sa_flags = SA_SIGINFO;
// Register SIGINT signal
if (sigaction(SIGINT, &sigAct, NULL) != 0) {
printf("sigaction err!\n");
return -1;
}
// Register SIGTMIN+1 signal
if (sigaction(SIGRTMIN + 1, &sigAct, NULL) != 0) {
printf("sigaction err!\n");
return -1;
}
if (sigaction(SIGUSR1, &sigAct, NULL) != 0) {
printf("sigaction err !\n");
return -1;
}
// block SIGINT and SIGTMIN
sigset_t block;
sigemptyset(&block);
sigaddset(&block, SIGINT);
sigaddset(&block, SIGRTMIN + 1);
// update mask
if (sigprocmask(SIG_BLOCK, &block, NULL) != 0) {
printf("sigprocmask() err !\n");
return -1;
}
pid = fork();
if (pid == -1) {
printf("fork err ! message:%s\n", strerror(errno));
return -1;
}
if (pid == 0) {
int i = 0, ret = 0;
union sigval si;
union sigval sm;
sendSignal(si, SIGINT, 3);
sendSignal(sm, SIGRTMIN + 1, 3);
// send SIGUSR1
if (kill(getppid(), SIGUSR1) != 0) {
printf("kill failed ! );
}
exit(0);
}
sleep(5);
return 0;
}
只要照着文档来,基本不会有什么 大问题,重点在怎么使用上,而不是这个具体的技术问题。
对于信号这种古老的应用,还是要理清其原理,在实际应用中根据场景来决定应用的情况。比如前面提到的在测试性能的场景下使用,有一两次问题也无所谓。而对于应用场景非常高的情况下,尽量还是谨慎应用。
还是那句话,没有最好,只有最合适。