信号与进程是分不开的,而把信号与进程的笔记分开来写,是因为我觉得这个信号太难搞懂了,特别是APUE信号这一章还把信号结合历史来介绍弄的我云里雾里。
信号本质上是在软件层次上对中断机制的一种模拟,他有几种产生方式和处理方式(APUE有介绍),下面带着疑惑从几个角度对信号进行介绍
(一) 站在进程的角度
进程发现和接受信号
我们知道,信号是异步的,一个进程不可能等待信号的到来,也不知道信号会到来,那么,进程是如何发现和接受信号呢?实际上,信号的接收不是由用户进程来完成的,而是由内核代理。当一个进程P2向另一个进程P1发送信号后,内核接受到信号,并将其放在P1的信号队列当中。当P1再次陷入内核态时,会检查信号队列,并根据相应的信号调取相应的信号处理函数
Task _struct 是进程控制块(PCB),详见 http://oss.org.cn/kernel-book/ch04/4.3.htm
信号检测和响应时机
刚才我们说,当P1再次陷入内核时,会检查信号队列。那么,P1什么时候会再次陷入内核呢?陷入内核后在什么时机会检测信号队列呢?
1. 当前进程由于系统调用、中断或异常而进入系统空间以后,从系统空间返回到用户空间的前夕。
2. 当前进程在内核中进入睡眠以后刚被唤醒的时候(必定是在系统调用中),或者由于不可忽略信号的存在而提前返回到用户空间
进入信号处理函数
对于sigprocmask 会进入内核空间、pause需要从进入睡眠这两者都符合检测处理信号函数的条件,所以存在忽略信号的情况,而APUE讲sigsuspend的之后真是晦涩难懂,其实他主要做的工作就是等待一个中断然后执行相应的handle处理
所以我感觉例子中的
sigsuspend(&zeromask);
sigprocmask(SIG_SETMASK, &oldmask,NULL);
是不是可以直接替换为
Sigsuspend(&oldmask)
因为测试情况难以出现,这里只是个人理解并未得到验证
(二) 站在信号自身的角度
信号生命周期:
对于一个完整的信号生命周期(从信号发送到相应的处理函数执行完毕)来说,可以分为三个重要的阶段,这三个阶段由四个重要事件来刻画:
1.信号诞生;
2.信号在进程中注册完毕;
3.信号在进程中的注销完毕;
4.信号处理函数执行完毕。
相邻两个事件的时间间隔构成信号生命周期的一个阶段。
详细描述各个生命周期
1. 信号"诞生"。
信号的诞生指的是触发信号的事件发生(如检测到硬件异常、定时器超时以及调用信号发送函数kill()或sigqueue()等)。
2. 信号在目标进程中"注册"。
这里注册定义不是由signal或者sigaction实现的,而是说信号发生之后内核中自动对信号的注册保存。
进程的task_struct结构中有关于本进程中未决信号的数据成员:
struct sigpending pending;
struct sigpending
{
struct sigqueue *head, **tail;
sigset_t signal;
};
第一、第二个成员分别指向一个sigqueue类型的结构链(称之为"未决信号信息链")的首尾,第三个成员是进程中所有未决信号集,信息链中的每个sigqueue结构体刻画一个特定信号所携带的信息,并指向下一个sigqueue结构:
struct sigqueue
{
struct sigqueue *next;
siginfo_t info;
};
信号在进程中注册指的就是信号值加入到进程的未决信号集中(sigpending结构的第二个成员sigset_t signal),并且信号所携带的信息被保留到未决信号信息链的某个sigqueue结构中。只要信号在进程的未决信号集中,表明进程已经知道这些信号的存在,但还没来得及处理,或者该信号被进程阻塞。
注:
当一个实时信号发送给一个进程时,不管该信号是否已经在进程中注册,都会被再注册一次,因此,信号不会丢失,因此,实时信号又叫做"可靠信号"。这意味着同一个实时信号可以在同一个进程的未决信号信息链中占有多个sigqueue结构(进程每收到一个实时信号,都会为它分配一个结构来登记该信号信息,并把该结构添加在未决信号链尾,即所有诞生的实时信号都会在目标进程中注册);
当一个非实时信号发送给一个进程时,如果该信号已经在进程中注册,则该信号将被丢弃,造成信号丢失。因此,非实时信号又叫做"不可靠信号"。这意味着同一个非实时信号在进程的未决信号信息链中,至多占有一个sigqueue结构。一个非实时信号诞生后,(1)、如果发现相同的信号已经在目标结构中注册,则不再注册,对于进程来说,相当于不知道本次信号发生,信号丢失;(2)、如果进程的未决信号中没有相同信号,则在进程中注册自己。在APUE的不可靠信号章节中需要每次重新声明sinal_hanle函数,这个是说的以前Unix系统的处理,现在可靠不可靠就是上面所说的实时与注册次数的区别。
3.信号在进程中的注销。
在目标进程执行过程中,会检测是否有信号等待处理(每次从系统空间返回到用户空间时都做这样的检查)。(“sigprocmask返回前,也至少会将其中一个未决且未阻塞的信号递送给进程”)如果存在未决信号等待处理且该信号没有被进程阻塞,则在运行相应的信号处理函数前,进程会把信号在未决信号链中占有的结构卸掉。是否将信号从进程未决信号集中删除对于实时与非实时信号是不同的。对于非实时信号来说,由于在未决信号信息链中最多只占用一个sigqueue结构,因此该结构被释放后,应该把信号在进程未决信号集中删除(信号注销完毕);而对于实时信号来说,可能在未决信号信息链中占用多个sigqueue结构,因此应该针对占用gqueue结构的数目区别对待:如果只占用一个sigqueue结构(进程只收到该信号一次),则应该把信号在进程的未决信号集中删除(信号注销完毕)。否则,不在进程的未决信号集中删除该信号(信号注销完毕)。进程在执行信号相应处理函数之前,首先要把信号在进程中注销。
4.信号生命终止。
进程注销信号后,立即执行相应的信号处理函数,执行完毕后,信号的本次发送对进程的影响彻底结束。
注:
1)信号注册与否,与发送信号的函数(如kill()或sigqueue()等)以及信号安装函数(signal()及sigaction())无关,只与信号值有关(信号值小于SIGRTMIN的信号最多只注册一次,信号值在SIGRTMIN及SIGRTMAX之间的信号,只要被进程接收到就被注册)。
2)在信号被注销到相应的信号处理函数执行完毕这段时间内,如果进程又收到同一信号多次,则对实时信号来说,每一次都会在进程中注册;而对于非实时信号来说,无论收到多少次信号,都会视为只收到一个信号,只在进程中注册一次。
(三) 进程和信号两者的角度来看
实际执行信号的处理动作称为信号递达(Delivery),信号从产生到递达之间的状态,称为信号未决(Pending)。进程可以选择阻塞(Block)某个信号。被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。信号在内核中的表示可以看作是这样的:
每个信号都有两个标志位分别表示阻塞和未决,还有一个函数指针表示处理动作。信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。在上图的例子中,
1. SIGHUP信号未阻塞也未产生过,当它递达时执行默认处理动作。
2. SIGINT信号产生过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。
3. SIGQUIT信号未产生过,一旦产生SIGQUIT信号将被阻塞,它的处理动作是用户自定义函数sighandler。
posted on 2012-02-10 17:49
李阳 阅读(613)
评论(1) 编辑 收藏 引用 所属分类:
Linux