Linux 2.4 内核学习笔记: 信号
版权申明
1. 标准信号与实时信号
0-31 这 32 个信号称为标准信号。
从 32 到 63 之间的 32 个信号称为实时信号。
可以通过 man 7 signal 查看对“标准信号”和“实时信号”详细的描述。
2. 信号排队
每个进程拥有一个信号等待队列。在 task_struct 中有一个 struct sigpending pending 域, 就是进程的信号等待队列。当向一个进程发送信号时,信号会先被送入进程的信号等待队列,然后等到进程被调度到去处理信号的时候,会从信号等待队列中依次取出信号进行处理。
标准信号不能排队,而实时信号可以排队:
假设进程屏蔽了一个标准信号,当给它连续发送多个相同的标准信号,则只有第一个被放入进程的信号等待队列中,后续的都被丢弃。
假设进程屏蔽了一个实时信号,当给它连续发送多个相同的实时信号,则所有的信号都被放入进程的信号接收队列中。
实时信号是在 POSIX.4 实时信号扩展中定义的。
3. 信号屏蔽
进程可以屏蔽它不想接收的信号。在 task_struct 中有一个 block 域,指定了进程要屏蔽的信号集合。
屏蔽信号带来的影响:
1、 当向一个进程投递信号时,如果发现进程屏蔽了此信号,则即使此进程处于睡眠状态,也不唤醒它。(否则睡眠的进程会被唤醒)。
2、 当进程开始处理它的信号等待队列的时候,对于被屏蔽的信号,不做处理。所以这些信号会一直待在等待队列中,直到进程解开对相应信号的屏蔽,才能被处理。
但是 KILL 和 TERM 这两种信号是不能屏蔽的。
4. 发送信号的处理过程
用户空间可以通过 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、 如果进程处于其它状态,则不做处理。
5. 执行信号处理函数
5.1. 进程检测信号的时机
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
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;
}
}
5.2. 执行信号处理函数
TBW6. 问题
1、 在执行信号处理程序的时候,是否要屏蔽此信号?在进入某个信号处理 函数前,必须暂时屏蔽掉此信号。
当handle_signal() 执行完以后,堆栈已经被更换,接下来就会进入用户空间,执行信号处理函数。
在handle_signal() 的最后,通过sigaddset(¤t->blocked,sig); 暂时屏蔽了对此信号。
2、我们知道,当进程进入睡眠(可打断)状态,在此期间收到另一个信号,那么,进程会被唤醒,执行新的信号处理程序。
但是,如果进程处于运行状态,且不执行任何系统调用,在此期间向它发送信号,则此进程不会立刻处理此信号。那么此进程什么时候能检查到有信号要处理了?
答案是在时钟中断返回的时候!!!
前面说了,检查信号的时机是从系统调用、中断或异常处理返回用户空间的前夕,因此,时钟中断返回的时候,也是检查信号的时机。
由于时钟中断频繁发生,因此信号总会及时得到处理。
3、 在执行信号处理程序期间,虽然不会再被相同的信号打断,但仍然可能被其它信号打断。
假设进程处理 A 信号,那么它首先需要把 A 信号从信号队列中取出,然后再去执行相应的信号处理函数,在执行过程中,如果又被另一个信号B打断,则不会再去处理 A,因为 A 已经被从信号队列中取出了。同样,必须先取下 B,然后再做处理,所以在此期间,如果又被 C 打断,则不会再处理到 B 。当 C 处理完毕后,又返回到 B 中,处理完 B,又返回到 A。
7. 对信号队列的处理
假设有多个信号在队列上等待。在一次调度时机中,进程检查它的信号等待队列,对于处理方式为 SIG_IGN 和 SIG_DFL 的信号,不用返回到用户空间。对于需要返回到用户空间去执行信号处理函数的情况,在一次调度时机中只处理一个。