这篇博客http://blog.csdn.net/l_xrui/article/details/72885978讲了信号的基本概念与产生方式。
了解以下三种概念:
信号递达(Delivery):实际执行信号的处理动作(三种);
信号未决(Pending):信号从产生到递达之间的状态;
信号阻塞(Block):进程可以选择阻塞某个信号,被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。
注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。
在每个进程的task_struct中,操作系统为每个进程提供一套信号机制:
以上可知分别有三个表:阻塞(block)表,未决(pending)表,递达表
block表与pending表在task_struct中其是以相同的数据类型sigset_t来存储,sigset_t称为信号集,这个类型变量对应每一bit位可以表示对应每个信号的“有效(1)”或“无效(0)”状态,在阻塞信号集(block)中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有效”和“无效”的含义是该信是否处于未决状态;
递达表则是以数组来表示,数组下标表示哪一个信号,数组里内容为函数指针,指向对应信号的递达动作。
每个信号都有两个标志位分别表示阻塞(block)和未决(pending),还有一个函数指针表示处理动作。信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志,若该信号被阻塞,信号产生时,一直处在未决状态,直至信号被取消阻塞。
如果在进程解除对某信号的阻塞之前这种信号产生过多次,Linux处理机制是:常规信号在递达之前产生多次只计一次,而实时信号在递达之前产生多次可以依次放在一个队列里记录。
由此也可描述上图:
(1). 1号信号未被阻塞也未产生,它的递达动作为忽略;
(2). 2号信号被阻塞并已产生,所以一直在未决状态,它的递达动作为自定义处理动作,当其被取消阻塞,则会在适当时间执行其递达动作;
(3). 3号信号被阻塞但未产生,其递达动作为默认,但当它产生因被阻塞也会一直在阻塞状态。
由上一篇博客可知指针非法操作进程异常退出可这样描述:
在进程运行中,非法指针地址通过页表映射时不可映射访问被MMU发现,此时操作系统发现MMU出错,则查找出错误原因发送正确信号(SIGSEGV)11号信号给进程,即改变进程pending表的11位改为有效状态(1),然后进程发现自己pending表发生改变,接收到信号,其再去查询block表11号信号是否被阻塞,没有被阻塞,则信号递达,信号pending表恢复无效(0),进程就去找到hander表保存的对应的函数地址,去执行了默认动作,最后退出。
信号集操作函数:
使用者只能调用以下函数来操作sigset_t变量:
#include <signal.h> int sigemptyset(sigset_t *set); //初始化set所指向的信号集,使其中所有信号的对应bit清零 int sigfillset(sigset_t *set); //初始化set所指向的信号集,使其中所有信号的对应bit置有效1位 int sigaddset(sigset_t *set, int signo); //向信号集set中添加使signo位信号有效 int sigdelset(sigset_t *set, int signo); //向信号集set中删除使signo位信号无效 int sigismember(const sigset_t *set, int signo); //判断信号集set的有效信号中是否包含signo信号,若包含则返回1,不包含则返回0,出错返回-1
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);参数:
oset:若set是空指针,oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出;
若oset和set都是非空指针,则先将原来的信号屏蔽字备份到oset里,然后根据set和how参数更改信号屏蔽字;
set:若set是非空指针,则更改进程的信号屏蔽字;
how:如何修改,可选值如下:
1. SIG_BLOCK //将set信号集中的有效信号添加到当前阻塞表中阻塞它们 2. SIG_UNBLOCK //将set信号集中的有效信号从当前阻塞表中删除对它们取消阻塞 3.SIG_SETMASK //将当前阻塞表清空只将set信号集中的有效信号添加进去阻塞它们返回值:若成功则为0,若出错则为-1
注意:如果调用sigprocmask解除了对当前若干个未决信号的阻塞,则在sigprocmask返回前,至少将其中一个信号递达。
函数:
int sigpending(sigset_t *set); //sigpending读取当前进程的未决信号集,通过set参数传出返回值:调用成功则返回0,出错则返回-1
以下用代码验证三张表以上关系:
sigblock.c:此处函数实现功能以2号信号(Ctrl-C)举例:
(1)设置信号集将2号信号加入信号集中,并调用函数sigprocmask()将其阻塞,并为2号信号设自定义信号捕捉函数(打印当前进程pid与接收到的信号sig);
(2)设一个计数器count,进入无限循环,每隔一秒打印一次block表与pending表,并使count++;
(3)在无限循环中,当count==10时,再调用函数sigprocmask()对2号信号取消阻塞。
#include <stdio.h> #include <signal.h> #include <unistd.h> #include <sys/types.h> //信号递达动作 void myhander(int sig) { printf("pid## %d receiving sig## %d\n",getpid(),sig); } // 打印pending表(信号未决) void PrintPending(sigset_t set) { int i=1; for(;i<32;++i) { if(sigismember(&set,i)) { printf("1 "); } else { printf("0 "); } } printf("\n"); } int main() { //定义信号集 sigset_t s; //初始化信号集为0 sigemptyset(&s); //将2号信号加入信号集中:ctrl c==2 sigaddset(&s,2); sigset_t oldset; //以保存block表中旧的信号屏蔽字 //sigprocmask(SIG_BLOCK,&s,&oldset); //将S信号集中信号加入block(阻塞表)中 :此处阻塞了2号信号 sigprocmask(SIG_SETMASK,&s,&oldset); //将block表中阻塞信号清空只将S信号集中的信号屏蔽(设为S的值) signal(2,myhander); //给2号信号设捕捉函数 int count=0; while(1) { //获取当前阻塞(block)表,并打印 sigset_t s1; sigprocmask(0,NULL,&s1); printf("block list:"); PrintPending(s1); //定义 pending表 sigset_t p; sigpending(&p); //获取当前进程的pending表(即进程收到哪些信号并处在未决状态) printf("pend list:"); PrintPending(p); //打印查看pending表 if(count==10) { //sigprocmask(SIG_UNBLOCK,&s,&oldset); //从block表中删除信号集S中的信号,即取消它们的阻塞 sigprocmask(SIG_SETMASK,&oldset,&s); //此代码中以此可实现同样效果 :以上oldset保存的为全0 } sleep(1); ++count; } return 0; }效果如下:
(1)在前10秒count<=10时,2号信号被阻塞,此时block表与pending表依次为:
0 1 0 0 0 0 0 0 0 0.........
0 0 0 0 0 0 0 0 0 0.........
在前10秒因为2号信号被阻塞,若这时键盘发送Ctrl-C,2号信号不会递达(不执行自定义捕捉函数),其会先保持在未决状态,此时block表与pending表依次为:
0 1 0 0 0 0 0 0 0 0.........
0 1 0 0 0 0 0 0 0 0.........
(2)当刚过10秒,count==10,2号信号被取消阻塞,这时(1)刚才发送的2号信号递达,则执行自定义捕捉函数,并且block表与pending表(2号信号不在未决状态恢复为0)依次变为:
0 0 0 0 0 0 0 0 0 0.........
0 0 0 0 0 0 0 0 0 0.........
(3)在以后,2号信号再没被阻塞,此时在发送则执行自定义捕捉函数,并且block表与pending表保持为:
0 0 0 0 0 0 0 0 0 0.........
0 0 0 0 0 0 0 0 0 0.........