linux信号处理总结

1350阅读 0评论2011-07-23 liubingzhq
分类:LINUX

      中断是硬件与操作系统之间的一种通信方式;信号是进程间(内核进程+用户进程)的一种通信方式。信号的实现类似中断,所以很多人都称其为软中断


0-31 这 32 个信号称为标准信号。
从 32 到 63 之间的 32 个信号称为实时信号。

可以通过 man 7 signal 查看对“标准信号”和“实时信号”详细的描述。

每个进程拥有一个信号等待队列。在 task_struct 中有一个 struct sigpending pending 域, 就是进程的信号等待队列。

当向一个进程发送信号时,信号会先被送入进程的信号等待队列,然后等到进程被调度到去处理信号的时候,会从信号 等待队列中依次取出信号进行处理。

标准信号不能排队,而实时信号可以排队:
假设进程屏蔽了一个标准信号,当给它连续发送多个相同 的标准信号,则只有第一个被放入进程的信号等待队列中,后续的都被丢弃。
假设进程屏蔽了一个实时信号,当给它连续发送多个相同的实时信号,则所有 的信号都被放入进程的信号接收队列中。

实时信号是在 POSIX.4 实时信号扩展中定义的。

进程可以屏蔽它不想接收的信号。
在 task_struct 中有一个 block 域,指定了进程要屏蔽的信号集合。

屏蔽信号 带来的影响:
    1、    当向一个进程投递信号时,如果发现进程屏蔽了此信号,则即使此进程处于睡眠状态,也不唤醒它。(否则睡眠的进程会被唤醒)。
    2、    当进程开始处理它的信号等待队列的时候,对于被屏蔽的信号,不做处理。所以这些信号会一直待在等待队列中,直到进程解开对相应信号的屏蔽,才能被处 理。(按照以上理解,也就是虽说某些信号被屏蔽,但是这个信号会被保存在pending里,只是在进程从内核返回处理信号时不处理被阻塞(屏蔽)的信号, 直到该信号被解除屏蔽)

但 是 KILL 和 TERM 这两种信号是不能屏蔽的。

用户空间可以通过 kill()  或 sigqueue() 两个系统调用来向一个进程发送信号。
内核空间的入口是 sys_kill():

sys_kill()  ==>  kill_something_info()  ==>  kill_proc_info()  ==> send_sig_info()  ==>  deliver_signal() ==> send_signal()

    1、    首先,根据 PID 找到对应的目标进程。 这是通过 find_task_by_pid() 实现的。
    2、    如果目标进程对信号的处理行为是“忽略”,则无需投递
    3、    对于标准信号,如果前面已经有一个相同信号到达,进程还没来得及处理,则不再投递,信号丢失。
    4、    否则,信号被挂在进程的信号等待队列中。
    5、    如果进程屏蔽了此信号,则不唤醒此进程
    6、    否则,如果此进程处于 INTERUPABLE 状态,则唤醒此进程。
           signal_wake_up() ==>  wake_up_process()
    7、    如果进程处于其它状态,则不做处理。


    1、    从系统调用、中断处理或者异常处理返回到用户空间的前夕。这和进程调度是同一个时机

在 arch/i386/kernel/entry.S 中,有如下汇编代码:

ENTRY(ret_from_sys_call)
    cli                # need_resched and signals atomic test
    cmpl $
0 ,need_resched( % ebx)
    jne reschedule
    cmpl $
0 ,sigpending( % ebx)
    jne signal_return


可以看到,在返回到用户空间前夕,会检查 task_struct 中的 sigpending 域,如果非0,说明有信号需要处理,转而去处理信号。

    2、    给进程投递完信号后,如果发现进程处于睡眠(INTERUPABLE)状态,则唤醒此进程,而进程会检查是否有信号等待处理。
    signal_wake_up()
    {
    if (t->state & TASK_INTERRUPTIBLE) {
            wake_up_process(t);
            return;
        }
    }

 

1、    在执行信号处理程序的时候,是否要屏蔽此信号?
    在进入某个信号处理 函数前,必须暂时屏蔽掉此信号。
    当handle_signal() 执行完以后,堆栈已经被更换,接下来就会进入用户空间,执行信号处理函数。
    在handle_signal() 的最后,通过sigaddset(¤t->blocked,sig); 暂时屏蔽了对此信号。

2、我们知道,当进程进入睡眠(可打断)状态,在此期间收到另一个信号,那么,进程会被唤醒,执行新的信号处理程 序。
          但是,如果进程处于运行状态,且不执行任何系统调用,在此期间向它发送信号,则此进程不会立刻处理此信号。那么此进程什么时候能检查到有信号要处理了?

    答案是在时钟中断返回的时候!!!
    前面说了,检查信号的时机是从系统调用、中断或异常处理返回用户空间的前夕,因此,时钟中断返回的时候,也是检查信号的时机。
    由于时钟中断频繁发生,因此信号总会及时得到处理。

3、   在执行信号处理程序期间,虽然不会再被相同的信号打断,但仍然可能被其它信号打断。
    假设进程处理 A 信号,那么它首先需要把 A 信号从信号队列中取出,然后再去执行相应的信号处理函数,在执行过程中,如果又被另一个信号B打断,则不会再去处理 A,因为 A 已经被从信号队列中取出了。同样,必须先取下 B,然后再做处理,所以在此期间,如果又被 C 打断,则不会再处理到 B 。当 C 处理完毕后,又返回到 B 中,处理完 B,又返回到 A。

假设有多个信号在队列上等待。
在一次调度时机中,进程检查它的信号等待队列,对于处理方式为 SIG_IGN 和 SIG_DFL 的信号,不用返回到用户空间。对于需要返回到用户空间去执行信号处理函数的情况,在一次调度时机中只处理一个。

http://blog.csdn.net/rstevens/archive/2007/09/27/1803801.aspx

上一篇:基于Linux操作系统核心的汉字显示
下一篇:博客已升级,请注意变更地址