当你行走在马路上,看见红灯,你需得停止脚步,等待绿灯的到来,好,我们就称红灯为信号,称你看见红灯为信号通知,停止行走这一当前正在进行的事情为中断,等待绿灯的到来为进程处理。在这个例子中我们引出了信号及其相关的几个重要概念。现在我们细谈信号。
信号,即软件中断,用于通知进程发生的事情,正式点讲是用于提供处理异步事件的一种方法。
Linux中信号共有62种,这些信号都是通过宏的方式给它们赋予了编号,其中信号1~31为非可靠信号,34~64为可靠信号(至于可靠/非可靠的区别后面谈)。
你需要了解:SIGABRT,SIGALRM,SIGBUS,SIGCHILD,SIGFPE,SIGHUP,SIGINT,SIGKILL,SIGPIPE,SIGSTOP。
既谈了信号是什么,干什么之后,问题是怎么干?且看信号流程:
信号产生-->信号注册-->(信号阻塞-->)信号注销-->信号处理
对于信号的流程有几点我需要多嘴一下:
1、信号阻塞可以被认为是信号流程中的一个异常,所以我在这儿用括号括起来;
2、信号主要用于通知进程发生的事情,所以当进程得知该事情后,后续的事情处理跟信号没有关系了,所以先信号注销,再信号处理。
接下来我们较详细谈信号流程中的每一个环节。
一、信号产生
1、用户通过终端输入信号,例如用户键盘输入“Ctrl + c”即产生SIGINT(中断信号);
2、硬件异常产生信号,例如除数为0会产生SIGFPE(算数异常信号);
3、kill函数、kill命令发送信号(kill命令是调用kill函数实现的)
int kill(pid_t pid,int signo);
pid为要通知的进程,signo为要发送的信号;成功返回0,失败返回-1。
4、软件条件产生,例如进程退出,会发出SIGCHILD信号给它的父进程(所有进程都有父进程)
二、信号注册
1、信号集(sigset)
进程对于每一个信号都用一个bit位来表示该信号是否存在或其相应状态(即用位图表示信号),但一个int类型并没有64个bit位的空间,所以创建新的类型:信号集类型(setset_t,结构体,内含一个64bit位的位图)。一个进程PCB中含有许多信号集,但有两个特殊信号集需要注意,即未决信号集和阻塞信号集(信号屏蔽字)。
信号集操作:int sigemptyset(sigset_t *set);//清空set信号集
int sigfillset(sigset_t *set);//使set信号集中包含系统支持的所有信号
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);//查看signo信号是否在信号集set中
2、未决信号集
未决:表示信号在产生到处理的这个阶段,进程通过该信号集中的位图来查看该进程是否收到并未做处理的某个信号,1表示收到,0表示未收到。
你无须考虑信号的注册,当信号产生后,进程自动将该信号添加到未决信号集上的。
3、信号注册:通过上述的未决信号,不难想出信号注册的方式:将信号在未决信号集中位图上相应的位置1,并在信号链表中添加相应的信号节点。
三、信号阻塞
信号阻塞:收到某个信号后暂时不作处理,直到阻塞解除(注意只是阻塞,并非不接收)
前面讲信号阻塞可以被看成是信号流程中的一个异常,所以信号需要用户自己来阻塞。
1、阻塞信号集:进程在某个信号产生后判断是否立即处理该信号的依据。即:当我们将阻塞信号集上的某个信号位置1,则进程在后序接收到该信号时发现阻塞信号集的相应信号位为1,则对该信号暂时不处理。
2、阻塞信号:int sigprocmask(int how,const sigset_t *set,sigset_t *oset);
oset:非空,则返回未修改时的进程阻塞信号集,为空,不关心则可设置为NULL;
set:非空,则how指示如何修改当前信号屏蔽字,(此时,how选项,SIG_BLOCK:设置set信号集中信号为阻塞,SIG_UNBLOCK:解除set信号集中信号的阻塞,SIG_SETMASK:设置当前信号屏蔽字为set指向的值)
为空,则how无意义。
返回值:成功返回0,失败返回-1。
3、可靠信号、不可靠信号
不可靠信号:当一个信号产生时,它将未决信号集中的对应位置1,并添加相应节点到链表中,假如在该信号未被处理之前再产生该信号,则该信号将会被丢弃,进程也不会知道第二个进程曾今产生过。这种信号称为不可靠信号。
可靠信号:与不可靠信号不同,可靠信号不管它以前是否已经注册,都会将未决信号集中对应位置1,并添加节点到信号链表中,这样,即使信号在同一时间重复产生,也不会丢失。
四、信号注销
既然我们了解了信号的注册,就不难猜测信号的注销,即将信号在未决信号集中位图上对应位置置0即可。
要注意,可靠信号的注销与不可靠的注销有一定的区别:可靠信号注销前进程会查看信号链表有没有待处理的相同信号,如果有,信号屏蔽字中相应位图不置0,如果没有,则置0。而不可靠信号就相对简单一些,直接在位图上置0即可。
五、信号处理
信号的处理分为三种方式:
1、忽略,大多数信号的处理方式,除SIGKILL,SIGTOP。
因为SIGKILL,SIGTOP为内核提供了杀死一个进程的可靠方法。
2、默认处理,大多数信号的系统默认操作为终端进程。
3、自定义处理(信号捕捉):
1、void (* signal(int signo,void (*func)(int)))(int);
当进程接收到signo信号后,直接执行func函数。func的值有:SIG_IGN(信号动作为系统默认),SIG_DFL(忽略此信号),自定义函数(参数为一个int型,无返回值)。
2、int sigaction(int signo,const struct sigaction *restrict act, struct sigaction *restrict oact);
signo是要检测或修改其具体动作的信号编号。若act为非空,则要修改其动作。如果oact非空,则系统由oact指针返回信号的上一个。
内核的信号处理流程:
1、由于系统调用、程序中断或者程序异常而从用户态转到了内核态;
2、内核系统调用结束或者解决异常后准备回用户态之前处理当前进程中可以递送得到信号;
3、如果信号的处理方式是自定义的,则转到程序调用自定义信号处理函数;
4、返回用户态,从主流程中断的地方继续向下执行。