};
struct sigqueue{
struct sigqueue *next;
siginfo_t info;
}
其中 sigqueue结构组成的链称之为未决信号链,sigset_t称之为未决信号集。
*head,**tail分别指向未决信号链的头部与尾部。
siginfo_t info是信号所携带的信息。
信号注册的过程就是将信号值加入到未决信号集siginfo_t中,将信号所携带的信息加入到未决信号链的某一个sigqueue中去。
因此,对于可靠的信号,可能存在多个未决信号的sigqueue结构,对于每次信号到来都会注册。而不可靠信号只注册一次,只有一个sigqueue结构。
只要信号在进程的未决信号集中,表明进程已经知道这些信号了,还没来得及处理,或者是这些信号被阻塞。
(3)信号在目标进程中注销
在进程的执行过程中,每次从系统调用或中断返回用户空间的时候,都会检查是否有信号没有被处理。如果这些信号没有被阻塞,那么就调用相应的信号处理函数来处理这些信号 。则调用信号处理函数之前,进程会把信号在未决信号链中的sigqueue结构卸掉。是否从未决信号集中把信号删除掉,对于实时信号与非实时信号是不相同的。
非实时信号:由于非实时信号在未决信号链中只有一个sigqueue结构,因此将它删除的同时将信号从未决信号集中删除。
实时信号:由于实时信号在未决信号链中可能有多个sigqueue结构,如果只有一个,也将信号从未决信号集中删除掉。如果有多个那么不从未决信号集中删除信号,注销完毕。
(4)信号处理函数执行完毕
执行处理函数,本次信号在进程中响应完毕。
在第4步,只简单的描述了信号处理函数执行完毕,就完成了本次信号的响应,但这个信号处理函数空间是怎么处理的呢? 内核栈与用户栈是怎么工作的呢? 这就涉及到了信号处理函数的过程。
信号处理函数的过程:
(1)注册信号处理函数
信号的处理是由内核来代理的,首先程序通过sigal或sigaction函数为每个信号注册处理函数,而内核中维护一张信号向量表,对应信号处理机制。这样,在信号在进程中注销完毕之后,会调用相应的处理函数进行处理。
(2)信号的检测与响应时机
在系统调用或中断返回用户态的前夕,内核会检查未决信号集,进行相应的信号处理。
(3)处理过程:
程序运行在用户态时->进程由于系统调用或中断进入内核->转向用户态执行信号处理函数->信号处理函数完毕后进入内核->返回用户态继续执行程序
首先程序执行在用户态,在进程陷入内核并从内核返回的前夕,会去检查有没有信号没有被处理,如果有且没有被阻塞就会调用相应的信号处理程序去处理。首先,内核在用户栈上创建一个层,该层中将返回地址设置成信号处理函数的地址,这样,从内核返回用户态时,就会执行这个信号处理函数。当信号处理函数执行完,会再次进入内核,主要是检测有没有信号没有处理,以及恢复原先程序中断执行点,恢复内核栈等工作,这样,当从内核返回后便返回到原先程序执行的地方了。
信号处理函数的过程大概是这样了。
具体的可参考http://www.spongeliu.com/linux/linux%E5%86%85%E6%A0%B8%E4%BF%A1%E5%8F%B7%E5%A4%84%E7%90%86%E6%9C%BA%E5%88%B6%E4%BB%8B%E7%BB%8D/
第三部分: 基本的信号处理函数
首先看一个两个概念: 信号未决与信号阻塞
信号未决: 指的是信号的产生到信号处理之前所处的一种状态。确切的说,是信号的产生到信号注销之间的状态。
信号阻塞: 指的是阻塞信号被处理,是一种信号处理方式。
1. 信号操作
信号操作最常用的方法是信号的屏蔽,信号屏蔽主要用到以下几个函数:
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 sigismemeber(sigset_t* set,int signo);
int sigprocmask(int how,const sigset_t*set,sigset_t *oset);
信号集,信号掩码,未决集
信号集: 所有的信号阻塞函数都使用一个称之为信号集的结构表明其所受到的影响。
信号掩码:当前正在被阻塞的信号集。
未决集: 进程在收到信号时到信号在未被处理之前信号所处的集合称为未决集。
可以看出,这三个概念没有必然的联系,信号集指的是一个泛泛的概念,而未决集与信号掩码指的是具体的信号状态。
对于信号集的初始化有两种方法: 一种是用sigemptyset使信号集中不包含任何信号,然后用sigaddset把信号加入到信号集中去。
另一种是用sigfillset让信号集中包含所有信号,然后用sigdelset删除信号来初始化。
sigemptyset()函数初始化信号集set并将set设置为空。
sigfillset()函数初始化信号集,但将信号集set设置为所有信号的集合。
sigaddset()将信号signo加入到信号集中去。
sigdelset()从信号集中删除signo信号。
sigprocmask()将指定的信号集合加入到进程的信号阻塞集合中去。如果提供了oset,那么当前的信号阻塞集合将会保存到oset集全中去。
参数how决定了操作的方式:
SIG_BLOCK 增加一个信号集合到当前进程的阻塞集合中去
SIG_UNBLOCK 从当前的阻塞集合中删除一个信号集合
SIG_SETMASK 将当前的信号集合设置为信号阻塞集合
下面看一个例子:
/**
信号操作函数:
int sigemptyset(sigset_t *set);//初始化信号 集合set并将set设置为空
int sigfillset(sigset_t *set);//也是初始化信号集合,不过将信号集合设置为所有信号的集合
int sigaddset(sigset_t *set,int signo);//将信号signo加入到信号集合set中去
int sigdelset(sigset_t *set,int signo);//将指定的信号signo从信号集合set中移去
int sigismemeber(sigset_t *set,int signo)//判断此信号是否在信号集合中
int sigprocmask(int how,const sigset_t *set,sigset_t *oset)
//how 有三个取值: SIG_BLOCK,SIG_UNBLOCK,SIG_SETMASK
第一个参数:
SIG_BLOCK:增加一个信号集合到进程指定的阻塞集合中去
SIG_UNBLOCK:从阻塞集合中删除指定的信号集合
SIG_SETMASK:将当前集合设置为进程的阻塞的集合
第二个参数指的是信号集合
如果oset不空,那么当前进程信号阻塞集合将会保存到oset里面
下面的程序首先创建一个信号集合,然后把信号添加进去,最后把这个集合加入到进程的阻塞集合中去
**/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
int main(){
sigset_t initset;
int i;
sigemptyset(&initset);//初始化信号集合为空集合
sigaddset(&initset,SIGINT);//将SIGINT信号加入到此集合中去
while(1){
sigprocmask(SIG_BLOCK,&initset,NULL);//将信号集合加入到进程的阻塞集合中去
fprintf(stdout,“SIGINT singal blocked/n”);
for(i=0;i<10;i++){
sleep(1);//每1秒输出
fprintf(stdout,“block %d/n”,i);
}
//在这时按一下Ctrl+C不会终止
sigprocmask(SIG_UNBLOCK,&initset,NULL);//从进程的阻塞集合中去删除信号集合
fprintf(stdout,“SIGINT SINGAL unblokced/n”);
for(i=0;i<10;i++){
sleep(1);
fprintf(stdout,“unblock %d/n”,i);
}
}
exit(0);
}
执行结果:
SIGINT singal blocked
block 0
block 1
block 2
block 3
block 4
block 5
block 6
block 7
block 8
block 9
在执行到block 3时按下了CTRL+C并不