Linux信号发送的尾巴
0.概述
本文介绍了Linux信号发送的代码执行流程,重点对信号发送结尾的通知机制进行了描述。
1.范围
Linux信号处理在网上有很多可以参考的资料,例如下面链接中的四篇文章就把信号讲得很透彻
http://blog.chinaunix.net/uid...
本文重点关注linux信号发送的结尾使用的通知机制,即Linux如何及时通知目的进程“你有信号要处理,赶紧来处理吧”,本文参考的内核代码为Linux 4.1.12,硬件架构为arm64。
2.Linux信号发送的流程
这一章不是重点,权当做一个备忘笔记。
用户空间发送信号的命令,kill,其实际代码执行流程如下
sys_kill->kill_something_info->kill_pid_info->group_send_sig_info->do_send_sig_info->send_signal->__send_signal->(prepare_signal,sigaddset,complete_signal)
具体每个函数做什么就不讲了,偷个懒。。
3.complete_signal函数
这个函数是本文关注的重点,因为信号准备好了,最终还是要通知目标进程尽快处理。我们知道进程处理信号的时机是在从内核态返回用户态之前,在ret_to_user函数中通过TIF_SIGPENDING标志查询自己有没有信号要处理。如果有则调用do_notify_resume->do_signal进行处理。
sigaddset函数就是实现第一步,即将信号加入进程的pengding->signal位掩码中。
complete_signal函数就负责实现第二步,通知进程尽快处理,该函数的执行流程如下
complete_signal->signal_wake_up->signal_wake_up_state->wake_up_state,kick_process
如果目标进程不在runqueue上,则wake_up_state函数会将其放在runqueue上并返回true;如果进程已经处于runqueue上了,则返回false。
void signal_wake_up_state(struct task_struct *t, unsigned int state) { set_tsk_thread_flag(t, TIF_SIGPENDING); /* * TASK_WAKEKILL also means wake it up in the stopped/traced/killable * case. We don't check t->state here because there is a race with it * executing another processor and just now entering stopped state. * By using wake_up_state, we ensure the process will wake up and * handle its death signal. */ if (!wake_up_state(t, state | TASK_INTERRUPTIBLE)) kick_process(t); }
根据signal_wake_up_state函数的代码逻辑,如果wake_up_state函数返回true,则kick_process不会执行。只有当目标进程已经在runqueue上时,才会执行kick_process。下面我们来看看kick_process函数的实现:
/*** * kick_process - kick a running thread to enter/exit the kernel * @p: the to-be-kicked thread * * Cause a process which is running on another CPU to enter * kernel-mode, without any delay. (to get signals handled.) * * NOTE: this function doesn't have to take the runqueue lock, * because all it wants to ensure is that the remote task enters * the kernel. If the IPI races and the task has been migrated * to another CPU then no harm is done and the purpose has been * achieved as well. */ void kick_process(struct task_struct *p) { int cpu; preempt_disable(); cpu = task_cpu(p); if ((cpu != smp_processor_id()) && task_curr(p)) smp_send_reschedule(cpu); preempt_enable(); }
函数的注释已经写得很清楚了,kick_process的目的就是让进程陷入内核然后在下次返回用户态之前有机会处理信号。smp_send_reschedule本质就是给进程所在的核发个IPI中断,从而导致正在运行的进程被打断陷入内核态。
但是发IPI中断也要满足两个前提条件:
- 目标进程所在的cpu不是当前cpu,也就是说信号的发送者和接收者不在一个核上。如果在一个核上,目标进程肯定已经在内核态了。
- 目标进程正在核上运行,注意这里是正在运行,而不是在runqueue上
4.总结与思考
总结一下本文的主要内容,在内核将信号相关信息放到了指定进程的pengding->signal后,需要通知进程尽快处理。如果进程处于内核态,则内核会将该进程唤醒并放入runqueue;如果进程处于用户态或者说正在运行,则往进程所在的核发送IPI中断打断进程,使其有机会在下次返回用户态时处理信号。
一个问题:如果进程A和进程B都是实时进程(调度策略是FIFO),A和B都绑定在CPU1上。A的优先级高于B且A正在运行,B也处于运行队列中。如果此时用户在shell上通过kill命令希望终止B进程,请问B进程能终止并退出吗?