Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does SIGSTOP work in Linux kernel?

I am wondering how SIGSTOP works inside the Linux Kernel. How is it handled? And how the kernel stops running when it is handled?

I am familiar with the kernel code base. So, if you can reference kernel functions that will be fine, and in fact that is what I want. I am not looking for high level description from a user's perspective.

I have already bugged the get_signal_to_deliver() with printk() statements (it is compiling right now). But I would like someone to explain things in better details.

like image 842
hebbo Avatar asked Aug 11 '15 16:08

hebbo


2 Answers

It's been a while since I touched the kernel, but I'll try to give as much detail as possible. I had to look up some of this stuff in various other places, so some details might be a little messy, but I think this gives a good idea of what happens under the hood.

When a signal is raised, the TIF_SIGPENDING flag is set in the process descriptor structure. Before returning to user mode, the kernel tests this flag with test_thread_flag(TIF_SIGPENDING), which will return true (because a signal is pending).

The exact details of where this happens seem to be architecture dependent, but you can 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);
}

Anyway, it ends up calling arch_do_signal(), which is also architecture dependent and is defined in the corresponding signal.c file (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();
}

As you can see, arch_do_signal() calls get_signal(), which is also in signal.c.

The bulk of the work happens inside get_signal(), it's a huge function, but eventually it seems to process the special case of SIGSTOP here:

    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() does the necessary processing to handle SIGSTOP, you can also find it in signal.c. It sets the task state to TASK_STOPPED with set_special_state(TASK_STOPPED), a macro that is defined in include/sched.h that updates the current process descriptor status. (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, which moves the task from the run queue to the sleep queue:

    } else {
        ...

        deactivate_task(rq, prev, DEQUEUE_SLEEP | DEQUEUE_NOCLOCK);

        ...

    }

deactivate_task() (also in the same file) removes the process from the runqueue by decrementing the on_rq field of the task_struct to 0 and calling dequeue_task(), which moves the process to the new (waiting) queue.

Then, schedule() checks the number of runnable processes and selects the next task to enter the CPU according to the scheduling policies in effect (I think this is a little bit out of scope by now).

At the end of the day, SIGSTOP moves a process from the runnable queue to a waiting queue until that process receives SIGCONT.

like image 136
Filipe Gonçalves Avatar answered Sep 28 '22 23:09

Filipe Gonçalves


Nearly every time there is an interrupt, the kernel suspends some process from running and switches to running the interrupt handler (the only exception being when there is no process running). Likewise, the kernel will suspend processes that run too long without giving up the CPU (and technically that's the same thing: it just originates from the timer interrupt or possibly an IPI). Ordinarily in these cases, the kernel then puts the suspended process back on the run queue and when the scheduling algorithm decides the time is right, it is resumed.

In the case of SIGSTOP, the same basic thing happens: the affected processes are suspended due to the reception of the stop signal. They just don't get put back on the run queue until SIGCONT is sent. Nothing extraordinary here: SIGSTOP is just instructing the kernel to make a process non-runnable until further notice.

[One note: you seemed to imply that the kernel stops running with SIGSTOP. That is of course not the case. Only the SIGSTOPped processes stop running.]

like image 34
Gil Hamilton Avatar answered Sep 28 '22 22:09

Gil Hamilton