接前一篇文章:Linux内核进程管理子系统有什么第二十六回 —— 进程主结构详解(22)
本文内容参考:
Linux内核进程管理专题报告_linux rseq-CSDN博客
《趣谈Linux操作系统 核心原理篇:第三部分 进程管理》—— 刘超
《图解Linux内核 基于6.x》 —— 姜亚华 机械工业出版社
特此致谢!
进程管理核心结构 —— task_struct
3. 信号处理相关成员
上一回继续讲解task_struct结构中信号处理相关的成员,包括:
/* Signal handlers: */
struct signal_struct *signal;
struct sighand_struct __rcu *sighand;
sigset_t blocked;
sigset_t real_blocked;
/* Restored if set_restore_sigmask() was used: */
sigset_t saved_sigmask;
struct sigpending pending;
unsigned long sas_ss_sp;
size_t sas_ss_size;
unsigned int sas_ss_flags;
上一回沿以下路径深入跟进:
do_send_sig_info函数 --->
send_signal_locked函数 --->
__send_signal_locked函数 --->
complete_signal函数
讲到了__send_signal_locked函数的最后一步 —— complete_signal函数。
(6)调用complete_signal函数查找一个处理信号的进程(线程)
上一回解析到了complete_signal函数的第3步即最后一步,本回详细解析此步骤具体内容。为了便于理解和回顾,再次贴出complete_signal函数源码,在同文件(kernal/signal.c)中,如下:
static void complete_signal(int sig, struct task_struct *p, enum pid_type type)
{
struct signal_struct *signal = p->signal;
struct task_struct *t;
/*
* Now find a thread we can wake up to take the signal off the queue.
*
* If the main thread wants the signal, it gets first crack.
* Probably the least surprising to the average bear.
*/
if (wants_signal(sig, p))
t = p;
else if ((type == PIDTYPE_PID) || thread_group_empty(p))
/*
* There is just one thread and it does not need to be woken.
* It will dequeue unblocked signals before it runs again.
*/
return;
else {
/*
* Otherwise try to find a suitable thread.
*/
t = signal->curr_target;
while (!wants_signal(sig, t)) {
t = next_thread(t);
if (t == signal->curr_target)
/*
* No thread needs to be woken.
* Any eligible threads will see
* the signal in the queue soon.
*/
return;
}
signal->curr_target = t;
}
/*
* Found a killable thread. If the signal will be fatal,
* then start taking the whole group down immediately.
*/
if (sig_fatal(p, sig) &&
(signal->core_state || !(signal->flags & SIGNAL_GROUP_EXIT)) &&
!sigismember(&t->real_blocked, sig) &&
(sig == SIGKILL || !p->ptrace)) {
/*
* This signal will be fatal to the whole group.
*/
if (!sig_kernel_coredump(sig)) {
/*
* Start a group exit and wake everybody up.
* This way we don't have other threads
* running and doing things after a slower
* thread has the fatal signal pending.
*/
signal->flags = SIGNAL_GROUP_EXIT;
signal->group_exit_code = sig;
signal->group_stop_count = 0;
t = p;
do {
task_clear_jobctl_pending(t, JOBCTL_PENDING_MASK);
sigaddset(&t->pending.signal, SIGKILL);
signal_wake_up(t, 1);
} while_each_thread(p, t);
return;
}
}
/*
* The signal is already in the shared-pending queue.
* Tell the chosen thread to wake up and dequeue it.
*/
signal_wake_up(t, sig == SIGKILL);
return;
}
signal_wake_up函数在include/linux/sched/signal.h中,代码如下:
static inline void signal_wake_up(struct task_struct *t, bool fatal)
{
unsigned int state = 0;
if (fatal && !(t->jobctl & JOBCTL_PTRACE_FROZEN)) {
t->jobctl &= ~(JOBCTL_STOPPED | JOBCTL_TRACED);
state = TASK_WAKEKILL | __TASK_TRACED;
}
signal_wake_up_state(t, state);
}
signal_wake_up函数在得到了t->jobctl和state的值之后,将实际工作交给了signal_wake_up_state函数。其在kernel/signal.c中,代码如下:
/*
* Tell a process that it has a new active signal..
*
* NOTE! we rely on the previous spin_lock to
* lock interrupts for us! We can only be called with
* "siglock" held, and the local interrupt must
* have been disabled when that got acquired!
*
* No need to set need_resched since signal event passing
* goes through ->blocked
*/
void signal_wake_up_state(struct task_struct *t, unsigned int state)
{
lockdep_assert_held(&t->sighand->siglock);
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函数中主要做了两件事情:
1)给这个线程设置TIF_SIGPENDING标志(位)
代码片段如下:
set_tsk_thread_flag(t, TIF_SIGPENDING);
从这一点就可以看出,其实信号的处理采取的是和进程的调度是类似的机制(进程调度的机制后文书会详细讲解),即:当信号到来的时候,并不直接处理这个信号,而是设置一个标志位TIF_PENDING,以标识(记录)已经有信号等待处理。与进程调度类似,在系统调用结束、或者中断处理结束,从内核态返回用户态的时候,再进行信号的处理。
2)试图唤醒这个进程或者线程
代码片段如下:
/*
* 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);
wake_up_state函数在kernel/sched/core.c中,代码如下:
int wake_up_state(struct task_struct *p, unsigned int state)
{
return try_to_wake_up(p, state, 0);
}
try_to_wakeup函数在后文书会详细讲解,这里先大概知道其功能即可。try_to_wakeup函数将进程或线程设置为TASK_RUNNING,然后放在运行队列中。后续随着时钟不断地滴答(tick),迟早会被调用。
回到signal_wake_up_state函数中。如果wake_up_state函数即try_to_wake_up函数返回0,则说明进程或线程已经处于TASK_RUNNING状态了。如果它在另外一个CPU上运行,则调用kill_process函数发送一个处理器间中断,强制该进程或线程重新调度。重新调度完毕后,会返回用户态执行(此时是一个检查TIF_SIGPENDING标志的时机)。
至此,complete_signal函数就讲解完了。以下路径也就解析完了:
do_send_sig_info函数 --->
send_signal_locked函数 --->
__send_signal_locked函数 --->
complete_signal函数
信号处理流程的第2步 —— 发送信号以及整体信号处理流程也就总体上讲完了。
下一回结合此整体流程,对于task_struct结构中的相关成员进行讲解。