linux信号要点

本文详细介绍了信号机制的概念及其在进程间的通信作用,包括信号的诞生、注册、执行与注销过程,并探讨了信号处理的不同类型,还特别说明了POSIX多线程程序中信号处理的要求。

参考连接
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、几点说明
  1. 当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么它会被阻塞到当前处理结束为止。
  2. 如果要捕捉的信号发生于进程正在一个系统调用中时,并且该进程睡眠在可中断的优先级上(若系统调用未睡眠而是在运行,根据上面的分 析,等该系统调用运行完毕后再处理信号),这时该信号引起进程作一次longjmp,跳出睡眠状态,返回用户态并执行信号处理例程。当从信号处理例程返回时,进程就象从系统调用返回一样,但返回了一个错误如-1,并将errno设置为EINTR,指出该次系统调用曾经被中断。
  3. 若进程睡眠在可中断的优先级上,则当它收到一个要忽略的信号时,该进程被唤醒但不做longjmp,一般是继续睡眠,但用户感觉不到进程曾经被唤醒,而是象没有发生过该信号一样; 若该信号在阻塞信号集中,在没有解除阻塞之前不能忽略这个信号, 因为进程仍有机会改变处理动作之后再解除阻塞。能够使pause、sleep等函数从挂起态返回的信号必须要有信号处理函数,如果没有什么动作,可以将处理函数设为空。
  4. 任意进程都不能给进程0( 即swapper 进程) 发信号; 发给进程1的信号都会被丢弃(除非它们被catch)。
  5. block阻塞信号对应阻塞信号集,收到信号集中的信号不立即处理, 被阻塞的信号将保持未决状态,直到进程解除对此信号的阻塞才执行抵达动作(可通过sigprocmask操作该信号集)。
  6. pending 未决信号 对应未决信号集, 表示还未被处理的信号(可通过sigpending查看)。
  7. 内核对子进程终止(SIGCLD)信号的处理方法与其他信号有所区别:
    当进程正常或异常终止时,内核都向其父进程发一个SIGCLD 信号,缺省情况下父进程忽略该信号,就象没有收到该信号似的,如果父进程希望获得子进程终止的状态,则应该事先用signal函数为SIGCLD信号设置信号处理程序,在信号处理程序中调用wait。SIGCLD信号的作用是唤醒一个睡眠在可被中断优先级上的进程。如果该进程捕捉了这个信号,就象普通信号处理一样转到处理例程。如果进程忽略该信号,则什么也不做。其实wait不一定放在信号处理函数中,但这样的话因为不知道子进程何时终止,在子进程终止前,wait将使父进程挂起休眠。
  8. 忽略SIGCHLD信号,这常用于Linux并发服务器的性能的一个技巧因为并发服务器常常fork很多子进程,子进程终结之后需要服务器进程去wait清理资源。如果将此信号的处理方式设为忽略,可让内核把僵尸子进程转交给init进程去处理,省去了大量僵尸进程占用系统资源。
  9. 我们需要捕获SIGPIPE,或者忽略它,以防对端的close导致我们的进程被杀死。
IV、POSIX多线程与信号

POSIX 1003.1 标准对多线程程序的信号处理有更加严格的要求:
1. 多线程程序的所有线程应该共享信号处理函数 , 但是每一个线程必须有自己的阻塞信号集和未决信号集;
2. POSIX 接口kill( ), sigqueue( ) 必须把信号发给线程组 , 而不是指定线程. 另外内核产生的SIGCHLD, SIGINT, or SIGQUIT 也必须发给线程组;
3. 线程组中只有有一个线程来处理(deliver) 的共享的信号就可以了;
4. 如果线程组收到一个致命的信号 , 内核要杀死线程组的所有线程, 而不是仅仅处理该信号的线程;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值