参考连接
http://www.spongeliu.com/165.html
http://blog.youkuaiyun.com/dlutbrucezhang/article/details/11577985
http://www.cnblogs.com/super119/archive/2011/03/26/1996131.html
I、信号
- 软中断信号,又简称为信号,用来通知进程发生了异步事件;
- 信号本质上是在软件层次上对中断机制的一种模拟。在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的;
- 信号是进程间通信机制中唯一的异步通信机制。一个进程不必通过任何操作来等待信号的到达。事实上,进程也不知道信号到底什么时候到达。进程之间可以互相通过系统调用kill发送软中断信号,内核也可以因为内部事件而给进程发送信号,通知进程发生了某个事件;
- 信号机制除了基本通知功能外,还可以传递附加信息;
- 信号的接收不是由用户进程来完成的,而是由内核代理;
II、信号处理流程
一个完整的信号生命周期可以分为三个阶段:
1. 信号诞生
信号事件的发生有两个来源:
- 硬件驱动
比如我们按下了键盘或者其它硬件故障; - 软件
用信号发送函数kill()等,还有一些非法运算等操作;
2. PCB中注册
在进程表的表项中有一个软中断信号域,该域中每一位对应一个信号。内核给一个进程发送软中断信号的方法,是在进程所在的进程表项的信号域设置对应于该信号的位;
如果信号发送给一个正在睡眠的进程,如果进程睡眠在可被中断的优先级上,则唤醒进程;否则仅设置进程表中信号域相应的位,而不唤醒进程。如果发送给一个处于可运行状态的进程,则只置相应的域即可;
struct sigqueue {
struct sigqueue *next;
siginfo_t info; //信号所携带的信息
}
struct sigpending {
struct sigqueue *head, *tail; //指向信号链表的首尾
sigset_t signal; //所有未决信号集,每个信号占用一位
};
信号在进程中注册指的就是信号值加入到进程的未决信号集sigset_t signal中,并且信号所携带的信息被保留到未决信号信息链的某个sigqueue结构中;
只要信号在进程的未决信号集中,表明进程已经知道这些信号的存在,但还没来得及处理或者该信号被进程阻塞;
- 当一个实时信号发送给一个进程时,不管该信号是否已经在进程中注册,都会被再注册一次,信号不会丢失,因此实时信号又叫做”可靠信号”;
- 当一个非实时信号发送给一个进程时,如果该信号已经在进程中注册则该信号将被丢弃,因此非实时信号又叫做”不可靠信号”。这意味着同一个非实时信号在进程的未决信号信息链中,至多占有一个sigqueue结构。
总之信号注册与否,与发送信号的函数如kill()或sigqueue()以及信号安装函数signal()或sigaction()无关,只与信号值有关:
信号值小于SIGRTMIN的信号最多只注册一次,信号值在SIGRTMIN及SIGRTMAX之间的信号,只要被进程接收到就被注册。
3. 信号的执行和注销
- 信号不一定会被立即处理,操作系统不会为了处理一个信号而把当前正在运行的进程挂起(切换进程),挂起(进程切换)的话消耗太大了,如果不是紧急信号,是不会立即处理的。操作系统多选择在内核态切换回用户态的时候处理信号,这样就利用两者的切换来处理了(不用单独进行进程切换以免浪费时间)。
- 进程只有处理完信号才会返回用户态,进程在用户态下不会有未处理完的信号。
内核处理一个进程收到的软中断信号是在该进程的上下文中,因此,进程必须处于运行状态。当其由于被信号唤醒或者正常调度重新获得CPU时,在其从内核空间返回到用户空间时会检测是否有信号等待处理。如果存在未决信号等待处理且该信号没有被进程阻塞,则在运行相应的信号处理函数前,进程会把信号在未决信号链中占有的结构卸掉:
- 对于非实时信号来说,由于在未决信号信息链中最多只占用一个sigqueue结构,因此该结构被释放后,应该把信号在进程未决信号集中删除(信号注销完毕);
- 对于实时信号来说,可能在未决信号信息链中占用多个sigqueue结构,因此应该针对占用sigqueue结构的数目区别对待:
- 如果只占用一个sigqueue结构(进程只收到该信号一次),则执行完相应的处理函数后应该把信号在进程的未决信号集中删除(信号注销完毕);
- 否则待该信号的所有sigqueue处理完毕后再在进程的未决信号集中删除该信号;
当所有未被屏蔽的信号都处理完毕后,即可返回用户空间。
对于被屏蔽的信号,当取消屏蔽后,在返回到用户空间时会再次执行上述检查处理的一套流程。
处理信号的整个大概过程:
1. 进程由于系统调用或者中断进入内核,完成相应任务返回用户空间的前夕,检查信号队列;
2. 如果有信号,则根据信号向量表找到信号处理函数,设置好“frame”后,跳到用户态执行信号处理函数;
3. 信号处理函数执行完毕后,返回内核态;
4. 设置“frame”,再返回到用户态继续执行程序;
5. 进程只有处理完信号才会返回用户态,进程在用户态下不会有未处理完的信号;
处理信号有三种类型:
- 退出
- 忽略, 当进程接收到一个它忽略的信号时,进程丢弃该信号,就象没有收到该信号似的继续运行
- 调用用户指定函数
如果进程收到一个要捕捉的信号,那么进程从内核态返回用户态时执行用户定义的函数。而且执行用户定义的函数的方法很巧妙,内核是在用户栈上创建一个新的层,该层中将返回地址的值设置成用户定义的处理函数的地址,这样进程从内核返回弹出栈顶时就返回到用户定义的函数处,从函数返回再弹出栈顶时,才返回原先进入内核的地方。这样做的原因是用户定义的处理函数不能且不允许在内核态下执行(如果用户定义的函数在内核态下运行的话,用户就可以获得任何权限)
III、几点说明
- 当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么它会被阻塞到当前处理结束为止。
- 如果要捕捉的信号发生于进程正在一个系统调用中时,并且该进程睡眠在可中断的优先级上(若系统调用未睡眠而是在运行,根据上面的分 析,等该系统调用运行完毕后再处理信号),这时该信号引起进程作一次longjmp,跳出睡眠状态,返回用户态并执行信号处理例程。当从信号处理例程返回时,进程就象从系统调用返回一样,但返回了一个错误如
-1,并将errno设置为EINTR,指出该次系统调用曾经被中断。 - 若进程睡眠在可中断的优先级上,则当它收到一个要忽略的信号时,该进程被唤醒但不做longjmp,一般是继续睡眠,但用户感觉不到进程曾经被唤醒,而是象没有发生过该信号一样; 若该信号在阻塞信号集中,在没有解除阻塞之前不能忽略这个信号, 因为进程仍有机会改变处理动作之后再解除阻塞。能够使pause、sleep等函数从挂起态返回的信号必须要有信号处理函数,如果没有什么动作,可以将处理函数设为空。
- 任意进程都不能给进程0( 即swapper 进程) 发信号; 发给进程1的信号都会被丢弃(除非它们被catch)。
- block阻塞信号对应阻塞信号集,收到信号集中的信号不立即处理, 被阻塞的信号将保持未决状态,直到进程解除对此信号的阻塞才执行抵达动作(可通过sigprocmask操作该信号集)。
- pending 未决信号 对应未决信号集, 表示还未被处理的信号(可通过sigpending查看)。
- 内核对子进程终止(SIGCLD)信号的处理方法与其他信号有所区别:
当进程正常或异常终止时,内核都向其父进程发一个SIGCLD 信号,缺省情况下父进程忽略该信号,就象没有收到该信号似的,如果父进程希望获得子进程终止的状态,则应该事先用signal函数为SIGCLD信号设置信号处理程序,在信号处理程序中调用wait。SIGCLD信号的作用是唤醒一个睡眠在可被中断优先级上的进程。如果该进程捕捉了这个信号,就象普通信号处理一样转到处理例程。如果进程忽略该信号,则什么也不做。其实wait不一定放在信号处理函数中,但这样的话因为不知道子进程何时终止,在子进程终止前,wait将使父进程挂起休眠。 - 忽略SIGCHLD信号,这常用于Linux并发服务器的性能的一个技巧因为并发服务器常常fork很多子进程,子进程终结之后需要服务器进程去wait清理资源。如果将此信号的处理方式设为忽略,可让内核把僵尸子进程转交给init进程去处理,省去了大量僵尸进程占用系统资源。
- 我们需要捕获SIGPIPE,或者忽略它,以防对端的close导致我们的进程被杀死。
IV、POSIX多线程与信号
POSIX 1003.1 标准对多线程程序的信号处理有更加严格的要求:
1. 多线程程序的所有线程应该共享信号处理函数 , 但是每一个线程必须有自己的阻塞信号集和未决信号集;
2. POSIX 接口kill( ), sigqueue( ) 必须把信号发给线程组 , 而不是指定线程. 另外内核产生的SIGCHLD, SIGINT, or SIGQUIT 也必须发给线程组;
3. 线程组中只有有一个线程来处理(deliver) 的共享的信号就可以了;
4. 如果线程组收到一个致命的信号 , 内核要杀死线程组的所有线程, 而不是仅仅处理该信号的线程;
本文详细介绍了信号机制的概念及其在进程间的通信作用,包括信号的诞生、注册、执行与注销过程,并探讨了信号处理的不同类型,还特别说明了POSIX多线程程序中信号处理的要求。
1406

被折叠的 条评论
为什么被折叠?



