【问题标题】:How does SIGSTOP work in Linux kernel?SIGSTOP 如何在 Linux 内核中工作?
【发布时间】:2015-08-11 16:12:01
【问题描述】:

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

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

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

【问题讨论】:

    标签: linux linux-kernel signals linux-device-driver


    【解决方案1】:

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

    当产生一个信号时,TIF_SIGPENDING 标志会在进程描述符结构中设置。在返回用户模式之前,内核会使用test_thread_flag(TIF_SIGPENDING) 测试这个标志,这将返回真(因为有信号未决)。

    发生这种情况的确切细节似乎取决于架构,但您可以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_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_structon_rq 字段递减为0 并调用dequeue_task(),将进程移至新的(等待) 排队。

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

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

    【讨论】:

    • 收到SIGSTOP后,内核是否将进程放入挂起队列?通过添加几行将 SIGCONT 传递到内核,您的答案将是完整的。 :)
    • @shami 代码似乎表明该进程被放置在睡眠队列中(DEQUEUE_SLEEP)。我做了一些更多的挖掘,并用有关此步骤的详细信息更新了我的答案。我一直在寻求改进,因此请随时提出进一步的改进建议!
    【解决方案2】:

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

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

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

    【讨论】:

    • “SIGSTOP 只是指示内核使进程不可运行,直至另行通知”是什么意思?它在某处设置了一些标志吗?以及在收到 sigcont 时如何使该进程再次可运行?
    • 是的,进程'task_struct.state 设置了__TASK_STOPPED 位。使其再次可运行需要清除停止位并将进程放回运行队列。请参阅prepare_signalwake_up_state 的调用。
    • 中断不会暂停进程。它们在当前运行的进程的上下文中执行。
    • 我不知道你想在这里表达什么观点。是的,中断 do 暂停进程。中断处理程序在当前运行的任务的上下文中执行,但任务的代码在中断处理程序运行的同时运行。因此,根据定义,它已被中断处理程序挂起。当中断处理程序完成执行时,任务可能(或可能不)被允许恢复。
    猜你喜欢
    • 1970-01-01
    • 2012-01-06
    • 2020-05-16
    • 2021-10-31
    • 1970-01-01
    • 2013-04-11
    • 2015-07-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多