我已经有一段时间没有接触内核了,但我会尽量提供尽可能多的细节。我不得不在其他不同的地方查找其中的一些内容,所以有些细节可能有点混乱,但我认为这可以很好地了解幕后发生的事情。
当产生一个信号时,TIF_SIGPENDING 标志会在进程描述符结构中设置。在返回用户模式之前,内核会使用test_thread_flag(TIF_SIGPENDING) 测试这个标志,这将返回真(因为有信号未决)。
发生这种情况的确切细节似乎取决于架构,但您可以see an example for um:
void interrupt_end(void)
{
struct pt_regs *regs = ¤t->thread.regs;
if (need_resched())
schedule();
if (test_thread_flag(TIF_SIGPENDING))
do_signal(regs);
if (test_and_clear_thread_flag(TIF_NOTIFY_RESUME))
tracehook_notify_resume(regs);
}
无论如何,它最终都会调用arch_do_signal(),这也是架构相关的,并在相应的signal.c文件中定义(see the example for x86):
void arch_do_signal(struct pt_regs *regs)
{
struct ksignal ksig;
if (get_signal(&ksig)) {
/* Whee! Actually deliver the signal. */
handle_signal(&ksig, regs);
return;
}
/* Did we come from a system call? */
if (syscall_get_nr(current, regs) >= 0) {
/* Restart the system call - no handlers present */
switch (syscall_get_error(current, regs)) {
case -ERESTARTNOHAND:
case -ERESTARTSYS:
case -ERESTARTNOINTR:
regs->ax = regs->orig_ax;
regs->ip -= 2;
break;
case -ERESTART_RESTARTBLOCK:
regs->ax = get_nr_restart_syscall(regs);
regs->ip -= 2;
break;
}
}
/*
* If there's no signal to deliver, we just put the saved sigmask
* back.
*/
restore_saved_sigmask();
}
如您所见,arch_do_signal() 调用了get_signal(),它也在signal.c 中。
大部分工作发生在get_signal()内部,这是一个巨大的功能,但最终它似乎在这里处理SIGSTOP的特殊情况:
if (sig_kernel_stop(signr)) {
/*
* The default action is to stop all threads in
* the thread group. The job control signals
* do nothing in an orphaned pgrp, but SIGSTOP
* always works. Note that siglock needs to be
* dropped during the call to is_orphaned_pgrp()
* because of lock ordering with tasklist_lock.
* This allows an intervening SIGCONT to be posted.
* We need to check for that and bail out if necessary.
*/
if (signr != SIGSTOP) {
spin_unlock_irq(&sighand->siglock);
/* signals can be posted during this window */
if (is_current_pgrp_orphaned())
goto relock;
spin_lock_irq(&sighand->siglock);
}
if (likely(do_signal_stop(ksig->info.si_signo))) {
/* It released the siglock. */
goto relock;
}
/*
* We didn't actually stop, due to a race
* with SIGCONT or something like that.
*/
continue;
}
See the full function here.
do_signal_stop() 做了必要的处理来处理SIGSTOP,你也可以在signal.c 中找到它。它使用set_special_state(TASK_STOPPED) 将任务状态设置为TASK_STOPPED,这是一个在include/sched.h 中定义的宏,用于更新当前进程描述符状态。 (see the relevant line in signal.c)。再往下,it calls freezable_schedule() 又调用schedule()。 schedule() 在循环中调用__schedule()(也在同一个文件中),直到找到符合条件的任务。 __schedule() 尝试查找下一个要调度的任务(代码中的next),当前任务是prev。检查prev的状态,因为改成TASK_STOPPED,所以deactivate_task() is called,把任务从运行队列移到睡眠队列:
} else {
...
deactivate_task(rq, prev, DEQUEUE_SLEEP | DEQUEUE_NOCLOCK);
...
}
deactivate_task()(也在同一个文件中)通过将task_struct 的on_rq 字段递减为0 并调用dequeue_task(),将进程移至新的(等待) 排队。
然后,schedule() 检查可运行进程的数量,并根据有效的调度策略选择下一个进入 CPU 的任务(我认为现在这有点超出范围)。
在一天结束时,SIGSTOP 将一个进程从可运行队列移动到等待队列,直到该进程收到 SIGCONT。