SIGSTOP 在 Linux 内核中如何工作?

How does SIGSTOP work in Linux kernel?

我想知道 SIGSTOP 在 Linux 内核中是如何工作的。它是如何处理的?以及内核如何在处理时停止运行?

我熟悉内核代码库。所以,如果你能引用内核函数就好了,事实上这就是我想要的。我不是在从用户的角度寻找高级描述。

我已经用 printk() 语句调试了 get_signal_to_deliver()(现在正在编译)。但我希望有人能更详细地解释事情。

几乎每次有中断时,内核都会从 运行ning 挂起一些进程并切换到 运行ning 中断处理程序(唯一的例外是没有进程 运行宁)。同样,内核将暂停 运行 太长时间的进程而不放弃 CPU(从技术上讲,这是同一件事:它只是源自定时器中断或可能是 IPI)。通常在这些情况下,内核然后将挂起的进程放回 运行 队列,当调度算法决定时间正确时,它会恢复。

在 SIGSTOP 的情况下,同样的基本事情发生:受影响的进程由于接收到停止信号而被挂起。在发送 SIGCONT 之前,它们不会被放回 运行 队列。这里没有什么特别的:SIGSTOP 只是指示内核使进程不可 运行 启用,直到另行通知。

[注意:您似乎暗示内核停止 运行 SIGSTOP。当然不是这样。只有 SIGSTOP 进程停止 运行ning。]

我已经有一段时间没有接触内核了,但我会尽量提供尽可能多的细节。我不得不在其他不同的地方查找其中的一些内容,所以一些细节可能有点混乱,但我认为这很好地了解了幕后发生的事情。

当发出信号时,在进程描述符结构中设置 TIF_SIGPENDING 标志。在 return 进入用户模式之前,内核使用 test_thread_flag(TIF_SIGPENDING) 测试此标志,这将 return 为真(因为信号未决)。

发生这种情况的确切细节似乎取决于体系结构,但您可以 see an example for um:

void interrupt_end(void)
{
    struct pt_regs *regs = &current->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_STOPPEDinclude/sched.h 中定义的宏更新当前进程描述符状态。 (see the relevant line in signal.c). Further down, it calls freezable_schedule() which in turn calls schedule(). schedule() calls __schedule() (also in the same file) in a loop until an eligible task is found. __schedule() attempts to find the next task to schedule (next in the code), and the current task is prev. The state of prev is checked, and because it was changed to TASK_STOPPED, deactivate_task() is called,将任务从运行队列移动到睡眠队列:

    } else {
        ...

        deactivate_task(rq, prev, DEQUEUE_SLEEP | DEQUEUE_NOCLOCK);

        ...

    }

deactivate_task()(也在同一文件中)通过将 task_structon_rq 字段递减为 0 从 运行 队列中删除进程并调用 dequeue_task(),将进程移动到新的(等待)队列。

然后,schedule()检查运行nable进程的数量,并根据生效的调度策略选择下一个进入CPU的任务(我觉得这个有点现在有点超出范围了)。

一天结束时,SIGSTOP 将进程从 运行 可用队列移至等待队列,直到该进程收到 SIGCONT