赞
踩
学习一下信号的处理机制。
一、信号的产生
信号是有可能来自内核,也有可能来自进程。当然,最根本的来源是信号产生函数。
其实就是通过内核更新目标进程的数据结构以表示一个信号已经被发送。
其中为进程产生信号的函数有:
函数名
说明
send_sig()
向单一进程发送信号
send_sig_info()
与send_sig()类似,只是还使用siginfo_t结构中的扩展信息
force_sig()
发送既不能被进程显示忽略,也不能被进程阻塞的信号
force_sig_info()
与force_sig()类似,只是还使用siginfo_t结构中的扩展信息
force_sig_specific()
与force_sig()类似,但是优化了对SIGSTOP和SIGKILL信号的处理
sys_tkill()
tkill()的系统调用处理函数
sys_tgkill()
tgkill()的系统调用处理函数
为线程组产生信号的函数有:
函数名
说明
send_group_sig_info()
向某一个线程组发送信号,该线程组由它的一个成员进程的描述符来识别
kill_pg()
想一个进程组中的所有线程组发送信号
kill_pg_info()
与kill_pg()类似,只是还使用siginfo_t结构中的扩展信息
kill_pg_proc()
向某一个线程组发送信号,该线程组由它的一个成员进程的pid来识别
kill_pg_proc_info()
与kill_pg_proc ()类似,只是还使用siginfo_t结构中的扩展信息
sys_kill()
kill()的系统调用处理函数
sys_rt_sigqueueinfo()
rt_sigqueueinfo()的系统调用处理函数
二、信号的传递
当内核注意到一个信号到来,并调用相关函数为接受此信号的进程准备描述符。但是万一这个进程那一刻并不在CPU上运行,内核就只能延迟传递信号的任务。因此这里就有两个queue,分别储存私有信号和共享信号。每当内核处理完一个中断或异常时,就检查是否存在挂起信号(即检查TIF_SIGPENDING)。如果存在挂起信号,那么内核就会调用do_signal函数。这个函数会循环执行,直到将两个队列中的所有非阻塞信号都处理完才退出。
既然直到do_signal()整个过程干了什么,那么接下来就描述一下对于每一个信号,do_signal会进行怎么样的处理。首先,它会检查current接收进程是否受到其他一些进程的监控;在肯定的情况下,do_signal函数调用相关函数让监控进程知道current接收进程的信号处理;然后do_signal()要把处理信号的k_sigaction数据结构的地址赋值给局部变量ka,再根据ka内容来执行三种操作:忽略信号、执行缺省操作或执行信号处理程序。
三、信号的处理
在第二点中已经提到了进程收到信号之后的可能三种操作。这里只描述执行信号处理程序的过程。这个过程比较复杂。
如果信号有一个专门的处理程序,那么do_signal()函数就会通过调用handle_signal()来强迫该处理程序执行。但是信号处理程序都是用户态进程所定义的,并包含在用户态的代码段中。handle_signal()函数运行在内核态,而信号处理程序运行在用户态,这就意味着在当前进程恢复“正常”执行之前,它首先必须执行用户态的信号处理程序。而且当内核打算恢复进程的正常执行时,内核态堆栈不再包含被中断程序的硬件上下文,因为每当从内核态向用户态转换时内核态堆栈都会被清空。而如果信号处理程序调用了系统调用,那么这个执行过程的复杂程度就更高了。
Linux对此过程的解决方案是把保存在内核态堆栈中的硬件上下文拷贝到当前进程的用户态堆栈中。用户态堆栈也以这样的方式被修改,当信号处理程序终止时,自动调用sigreturn()系统调用把这个硬件上下文拷贝回内核态堆栈中,并回复用户态堆栈中原来的内容。
该过程流程图如下:
更多的细节不在此描述了。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。