内核signal信号
最近在看内核信号相关,总结了几位博主的内容,侵删!
执行信号的处理动作成为信号递达(Delivery)
信号从产生到递达之间的状态称为信号未决(Pending)。
进程可以选择阻塞(Block)某个信号。 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。
1)可靠信号与不可靠信号
>$ 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
其中 1 - 31 是标准信号(不可靠信号),34 - 64 是实时信号(可靠信号)。信号值位于SIGRTMIN和SIGRTMAX之间的信号都是可靠信号,可靠信号克服了信号可能丢失的问题。
Linux在支持新版本的信号安装函数sigation()以及信号发送函数sigqueue()的同时,仍然支持早期的signal()信号安装函数,支持信号发送函数kill()。
很多人认为因为信号会丢失所以是不可靠的,其实这么理解是不对的,不可靠的信号是指信号的行为不可靠。
信号的处理就好比现在 LZ 正在写这篇博文,忽然来了一个电话,于是打断了手头的工作,先接电话去了。
信号处理函数的执行现场不是程序员布置的,而是内核布置的,因为程序中不会有调用信号处理函数的地方。信号的可靠与不可靠只与信号值有关,与信号的发送及安装函数无关 。目前linux中的signal()是通过sigation()函数实现的,因此,即使通过signal()安装的信号,在信号处理函数的结尾也不必再调用一次信号安装函数。同时,由signal()安装的实时信号支持排队,同样不会丢失。
对于目前linux的两个信号安装函数:signal()及sigaction()来说,它们都不能把SIGRTMIN以前的信号变成可靠信号(都不支持排队,仍有可能丢失,仍然是不可靠信号),而且对SIGRTMIN以后的信号都支持排队。这两个函数的最大区别在于,经过sigaction安装的信号都能传递信息给信号处理函数,而经过signal安装的信号不能向信号处理函数传递信息。对于信号发送函数来说也是一样的。
2)实时信号与非实时信号
早期Unix系统只定义了32种信号,前32 种信号已经有了预定义值,每个信号有了确定的用途及含义, 并且每种信号都有各自的缺省动作。如按键盘的CTRL ^C时 ,会产生SIGINT信号,对该信号的默认反应就是进程终止。后32个信号表示实时信号,等同于前面阐述的可靠信号。这保证了发送的多个实时信号都被接收。
非实时信号都不支持排队,都是不可靠信号;实时信号都支持排队,都是可靠信号。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
void catch_signal(int signo, siginfo_t *info, void *p)
{
switch (signo)
{
case SIGINT:
printf("accept SIGINT! recv data=%d\n",info->si_value.sival_int);
break;
case 34:
//SIGRTMIN似乎不是一个确定的int类型
printf("accept SIGRTMIN! recv data=%d\n",info->si_value.sival_int);
break;
case SIGUSR1:
printf("accept SIGUSR1!\n");
//取消信号阻塞
sigset_t uset;
sigemptyset(&uset);
sigaddset(&uset, SIGINT);
sigaddset(&uset, SIGRTMIN);
sigprocmask(SIG_UNBLOCK, &uset, NULL);
printf("阻塞解除了!\n");
break;
}
}
int main(int arg, char *args[])
{
pid_t pid = 0;
struct sigaction act;
act.sa_sigaction = catch_signal;
sigemptyset(&act.sa_mask);
act.sa_flags = SA_SIGINFO;
//注册SIGINT信号
if (sigaction(SIGINT, &act, NULL) != 0)
{
printf("sigaction SIGINT failed !\n");
return -1;
}
//注册SIGTMIN信号
if (sigaction(SIGRTMIN, &act, NULL) != 0)
{
printf("sigaction SIGINT failed !\n");
return -1;
}
//注册SIGUSR1信号
if (sigaction(SIGUSR1, &act, NULL) != 0)
{
printf("sigaction SIGINT failed !\n");
return -1;
}
//阻塞SIGINT信号和SIGTMIN信号
sigset_t bset;
sigemptyset(&bset);
sigaddset(&bset, SIGINT);
sigaddset(&bset, SIGRTMIN);
//更新进程屏蔽信号状态字
if (sigprocmask(SIG_BLOCK, &bset, NULL) != 0)
{
printf("sigprocmask() failed !\n");
return -1;
}
pid = fork();
if (pid == -1)
{
printf("fork() failed ! error message:%s\n", strerror(errno));
return -1;
}
if (pid == 0)
{
int i = 0, ret = 0;
union sigval v1;
union sigval v2;
for (i = 0; i < 3; i++)
{
v1.sival_int = 201 + i;
ret = sigqueue(getppid(), SIGINT, v1);
if (ret != 0)
{
printf("发送不可靠信号SIGINT失败! error message:%s\n", strerror(errno));
} else
{
printf("发送不可靠信号SIGINT成功!\n");
}
}
for (i = 0; i < 3; i++)
{
v2.sival_int = 301 + i;
ret = sigqueue(getppid(), SIGRTMIN, v2);
if (ret != 0)
{
printf("发送可靠信号SIGTMIN失败! error message:%s\n", strerror(errno));
} else
{
printf("发送可靠信号SIGTMIN成功!\n");
}
}
//发送SIGUSR1信号
if (kill(getppid(), SIGUSR1) != 0)
{
printf("kill() failed ! error message;%s\n", strerror(errno));
}
exit(0);
}
while (1)
{
pause();//挂起
}
return 0;
}
一:SIGINT是不可靠信号。发送了3次父进程只接收到1次,SIGRTMIN是可靠信号,发送了3次父进程接收到3次信号。
二:对于可靠信号,Linux内核会缓存可靠信号,Linux内核可以缓存8192(各个Linux版本不同)条可靠信号;对于不可靠信号,Linux只能缓存一条不可靠信号。
运行结果:
3)信号接收机制
mask 和 pending 位图是一一对应的,它们用于反映当前进程信号的状态。每一位代表了一个标准信号。
mask 位图用于记录哪些信号可以响应。1 表示该信号可以响应,0 表示该信号不可响应(会被忽略)。
pending 位图用于记录收到了哪些信号。1 表示收到了该信号,0 表示没有收到该信号。
前面说过了,程序在执行的过程中会被内核打断无数次,也就是说程序被打断后要停止手头的工作,进入一个队列排队等待再次被调度才能继续工作。
当进程获得调度机会后,从内核态返回到用户态之前要做很多事情,其中一件事就是将 mask 位图和 pending 位图进行 & 运算,当计算的结果不为 0 时就需要调用相应的信号处理函数或执行信号的默认动作。
这就是 Linux 的信号处理机制,从这个机制中,我们可以总结出几个信号的特点:
1)如果想要屏蔽某个信号,只需将对应的 mask 位 置为 0 即可。这样当程序从内核态返回用户态进行 mask & pending 时,该信号位的计算结果一定为 0。
2)信号从收到到响应是存在延迟的,一般最长延迟 10 毫秒。因为只有程序被打断并且重新被调度的时候才有机会发现收到了信号,所以当我们向一个程序按下 Ctrl+C 时程序并没有立即挂掉,只不过这个时间非常短暂我们一般情况下感觉不到而已,我们自己以为程序是立即挂掉了。
3)当一个信号没有被处理时,无论再次接受到多少个相同的信号都只能保留一个,因为 pending 是位图,位图的特点就是只能保留最后一次的状态。这一点说的就是标准信号会丢失的特点,如果想要不丢失信号就只能使用实时信号了。