The Signal Delivery Process in the Linux Kernel

  • 1 Execute the default action of the signal

  • 2 Capture the signal

  • 3 Re-execute the system call

  • 4 x86_64 architecture – do_signal()

Previously, we introduced how the kernel notices the arrival of a signal and calls related functions to update the process descriptor so that the process can receive and handle the signal. However, if the target process is not running at that moment, the kernel delays the signal delivery. Now, let’s see how the kernel handles signals that are pending for a process.

As mentioned in section 4 of Chapter 4, the kernel allows checking the process’s <span>TIF_SIGPENDING</span> flag before the process returns to user mode. Therefore, after the kernel completes handling an interrupt or exception, it checks for any pending signals. To handle non-blocking pending signals, the kernel calls the <span>do_signal()</span> function, which accepts two parameters:

  • <span>regs</span>

    <span>current</span> The address of the location in the kernel stack where the user mode register contents of the current process are saved.

  • <span>oldset</span>

    The address of the variable used to save the blocked signal bitmask array.

The description of <span>do_signal()</span> mainly focuses on the general mechanism of signal delivery; the actual code covers many details, such as handling race conditions and other special cases (like <span>freezing the system</span>, <span>generating core dumps</span>, <span>stopping and killing the entire thread group</span>, etc.). We will ignore these details.

As mentioned earlier, the <span>do_signal()</span> function is typically called only when the <span>CPU</span> intends to return to user mode. Therefore, if <span>do_signal()</span> is called within an interrupt handler, the function returns directly:

if ((regs->xcs & 3) != 3)
    return 1;

If <span>oldset</span> is <span>NULL</span>, it is initialized using the address of <span>current->blocked</span>:

if (!oldset)
    oldset = &amp;current->blocked;

<span>do_signal()</span> is centered around a loop that repeatedly calls the <span>dequeue_signal()</span> function until there are no unblocked pending signals in the private and shared pending signal queues. The return value of <span>dequeue_signal()</span> is stored in the local variable <span>signr</span>; if it equals <span>0</span>, it means all pending signals have been processed, and <span>do_signal()</span> completes; if it returns a non-zero value, there are pending signals waiting to be processed. After handling this signal, <span>do_signal()</span> will call the <span>dequeue_signal()</span> function again.

<span>dequeue_signal()</span> first considers all signals in the private pending signal queue (in ascending order), followed by signals in the shared pending queue. It updates the corresponding data structures to indicate that the signal is no longer pending and returns the signal value. This involves clearing the corresponding bits in <span>current->pending.signal</span> or <span>current->signal->shared_pending.signal</span>, and calling <span>recalc_sigpending()</span> to update the value of <span>TIF_SIGPENDING</span>.

Let’s look at how <span>do_signal()</span> handles each pending signal returned by <span>dequeue_signal()</span>. First, it checks whether the currently receiving process is being monitored by other processes; if so, <span>do_signal()</span> calls <span>do_notify_parent_cldstop()</span> and <span>schedule()</span> to make the monitoring thread aware of the signal handling.

Then, <span>do_signal()</span> loads the address of the <span>k_sigaction</span> data structure for the pending signal into the local variable <span>ka</span>:

ka = &amp;current->sig-&gt;action[signr-1];

Depending on the specific content, it may execute three types of actions: ignore the signal, execute the default action, or execute the signal handler.

If the delivered signal is ignored, <span>do_signal()</span> continues to process other pending signals:

if (ka->sa.sa_handler == SIG_IGN)
    continue;

In the next two sections, we will describe how to execute the default action and the signal handler.

1 Execute the Default Action of the Signal

If <span>ka->sa.sa_handler</span> equals <span>SIG_DFL</span>, <span>do_signal()</span> executes the default action of the signal. The only exception is when the receiving process is <span>init</span>; in this case, the signal is discarded:

if (current->pid == 1)
    continue;

For other processes, the default handling of ignored signals is also very simple:

if (signr==SIGCONT || signr==SIGCHLD ||
        signr==SIGWINCH || signr==SIGURG)
    continue;

For signals with a default action of <span>stop</span>, all processes in the thread group will be stopped. To do this, <span>do_signal()</span> sets their state to <span>TASK_STOPPED</span> and then calls <span>schedule()</span> to schedule the processes:

if (signr==SIGSTOP || signr==SIGTSTP ||
        signr==SIGTTIN || signr==SIGTTOU) {
    if (signr != SIGSTOP &amp;&amp;
            is_orphaned_pgrp(current->signal->pgrp))
        continue;
    do_signal_stop(signr);
}

<span>SIGSTOP</span> and other signals are somewhat different: <span>SIGSTOP</span> always stops the thread group, while other signals only stop the thread group when it is in an orphaned process group. The <span>POSIX</span> standard specifies that as long as a process in the thread group has a parent process in a different process group of the same session, it is not an orphaned process group. Therefore, if the parent process has died, but the user of the initiating process is still logged in, the process group is not an orphaned process group.

<span>do_signal_stop()</span> checks whether the current process is the first process in the thread group to be stopped. If so, it is responsible for stopping all processes: essentially, this function sets the <span>group_stop_count</span> field in the signal descriptor to a positive value and wakes up each process in the thread group. Then, each process checks this field in turn to identify the ongoing <span>group stop</span>, changes its state to <span>TASK_STOPPED</span>, and calls <span>schedule()</span> to reschedule the processes. The <span>do_signal_stop()</span> function also sends a <span>SIGCHLD</span> signal to the parent process of the thread group <span>leader</span>, unless the parent process has set the <span>SIGCHLD</span> flag of <span>SA_NOCLDSTOP</span>.

The default action for signals that is <span>dump</span> creates a core dump file in the working directory of the process: this file lists the complete contents of the process’s address space and registers. After creating the core dump file, <span>do_signal()</span> kills the thread group. The default action for the remaining <span>18</span> signals is <span>terminate</span>, which means killing the process. To do this, it calls <span>do_group_exit()</span>, executing a graceful <span>group exit</span> handler (refer to section on <span>process termination</span> in Chapter 3).

2 Capture the Signal

If the signal specifies a handler, <span>do_signal()</span> executes that program by calling <span>handle_signal()</span>

handle_signal(signr, &amp;info, &amp;ka, oldset, regs);
if (ka->sa.sa_flags &amp; SA_ONESHOT)
    ka->sa.sa_handler = SIG_DFL;
return 1;

If the received signal has the <span>SA_ONESHOT</span> flag set, it must be reset to the default action so that the same signal will not trigger the signal handler again. Note how <span>do_signal()</span> returns after handling a single signal. No other pending signals will be considered until the next call to <span>do_signal()</span>. This approach ensures that real-time signals are processed in the appropriate order.

Executing a signal handler is a rather complex task because careful stack switching is required when transitioning between user mode and kernel mode. We will explain this in detail:

<span>Signal handlers</span> are functions defined by user processes, contained in the user code segment. The <span>handle_signal()</span> function runs in kernel mode, while the signal handler runs in user mode; this means the current process must first execute the signal handler in user mode before being allowed to resume its “normal” execution. Additionally, when the kernel attempts to restore the process’s normal execution, the kernel stack no longer contains the hardware context of the interrupted program because the kernel stack is cleared each time it transitions from user mode to kernel mode.

The following figure illustrates the execution flow of capturing a signal. Assume a non-blocking signal is sent to the process. When an interrupt or exception occurs, the process switches to kernel mode. Just before returning to user mode, the kernel calls the <span>do_signal()</span> function, sequentially handling signals (<span>handle_signal()</span>) and configuring the user mode stack (<span>setup_frame()</span> or <span>setup_rt_frame()</span><span>). When the process switches to user mode, it begins executing the signal handler because the address of that handler has been forcibly loaded into the </span><code><span>PC</span> program counter. When the signal program terminates, <span>setup_frame()</span> or <span>setup_rt_frame()</span><span> will load the return code into the user mode stack. This return code will call the </span><code><span>sigreturn()</span> and <span>rt_sigreturn()</span> system calls; the corresponding service routines will copy the hardware context of the normal program into the kernel mode stack and restore the user mode stack to its original state (<span>restore_sigcontext()</span>). When the system call terminates, the normal program continues its execution.

The Signal Delivery Process in the Linux Kernel

Figure <span>11-2</span> Capturing a Signal

Now, let’s look at the execution details:

2.1 Setting up the Frame

To correctly set up the user mode stack of the process, the <span>handle_signal()</span> function can either call <span>setup_frame()</span> (for signals that do not require <span>siginfo_t</span>) or call <span>setup_rt_frame()</span> (for signals that definitely require <span>siginfo_t</span>). Which function is called depends on the <span>sigaction</span> table’s <span>sa_flags</span> field’s <span>SA_SIGINFO</span> flag.

Next, let’s look at the specific implementation of the <span>setup_frame()</span> function: (Linux kernel version <span>v2.6.11</span>, file location: <span>arch/x86_64/kernel/signal.c</span>)

/* Definitions of these symbols are in the vsyscall memory page, see vsyscall-sigreturn.S file */
extern void __user __kernel_sigreturn;
extern void __user __kernel_rt_sigreturn;

static void setup_frame(int sig, struct k_sigaction *ka,
            sigset_t *set, struct pt_regs * regs) {
    void __user *restorer;
    struct sigframe __user *frame;
    int err = 0;
    int usig;

    frame = get_sigframe(ka, regs, sizeof(*frame));

    if (!access_ok(VERIFY_WRITE, frame, sizeof(*frame)))
        goto give_sigsegv;

    usig = current_thread_info()->exec_domain
        &amp;&amp; current_thread_info()->exec_domain->signal_invmap
        &amp;&amp; sig < 32
        ? current_thread_info()->exec_domain->signal_invmap[sig]
        : sig;

    err = __put_user(usig, &amp;frame->sig);
    if (err)
        goto give_sigsegv;

    err = setup_sigcontext(&amp;frame->sc, &amp;frame->fpstate, regs, set->sig[0]);
    if (err)
        goto give_sigsegv;

    if (_NSIG_WORDS > 1) {
        err = __copy_to_user(&amp;frame->extramask, &amp;set->sig[1],
                      sizeof(frame->extramask));
        if (err)
            goto give_sigsegv;
    }

    restorer = &amp;__kernel_sigreturn;
    if (ka->sa.sa_flags &amp; SA_RESTORER)
        restorer = ka->sa.sa_restorer;

    /* Set up to return from userspace.  */
    err |= __put_user(restorer, &amp;frame->pretcode);
     
    /*
     * This is popl %eax ; movl $,%eax ; int $0x80
     *
     * WE DO NOT USE IT ANY MORE! It's only left here for historical
     * reasons and because gdb uses it as a signature to notice
     * signal handler stack frames.
     */
    err |= __put_user(0xb858, (short __user *)(frame->retcode+0));
    err |= __put_user(__NR_sigreturn, (int __user *)(frame->retcode+2));
    err |= __put_user(0x80cd, (short __user *)(frame->retcode+6));

    if (err)
        goto give_sigsegv;

    /* Configure registers for the signal handler */
    regs->esp = (unsigned long) frame;
    regs->eip = (unsigned long) ka->sa.sa_handler;
    regs->eax = (unsigned long) sig;
    regs->edx = (unsigned long) 0;
    regs->ecx = (unsigned long) 0;

    /* Restore user mode segment registers */
    set_fs(USER_DS);
    regs->xds = __USER_DS;
    regs->xes = __USER_DS;
    regs->xss = __USER_DS;
    regs->xcs = __USER_CS;

    /* Clear the TF flag when entering the signal handler, but notify the tracer that is single stepping,
     * the tracer may also want to single step inside the signal handler
     */
    regs->eflags &amp;= ~TF_MASK;
    if (test_thread_flag(TIF_SINGLESTEP))
        ptrace_notify(SIGTRAP);
    // ... omitted, print debug info and then return.

give_sigsegv:
    force_sigsegv(sig, current);
}

<span>setup_frame()</span> receives four parameters as follows:

  • <span>sig</span>

    Signal

  • <span>ka</span>

    Address of the <span>k_sigaction</span> table for the signal

  • <span>oldset</span>

    Address of the bitmask group for blocked signals

  • <span>regs</span>

    Location where the user mode register contents are saved in the kernel stack

<span>setup_frame()</span> pushes a data structure called <span>frame</span> onto the user mode stack, which stores the information needed to handle the signal and correctly return to the <span>sys_sigreturn()</span> function. The <span>frame</span> is a <span>sigframe</span> table containing the following fields (see Figure <span>11-3</span>):

  • <span>pretcode</span>

    The return address of the signal handler. It is actually the address of the assembly code at the <span>__kernel_sigreturn</span> label.

  • <span>sig</span>

    The signal, which is a parameter needed by the signal handler.

  • <span>sc</span>

    Contains the process context content just before the user mode process switches to kernel mode, with the data type being <span>sigcontext</span> (this information is copied from the kernel mode stack of <span>current</span>). Additionally, it contains an array of bits for blocking signals for the process.

  • <span>fpstate</span>

    Used to save the floating-point register information of the user mode process, with the data structure type being <span>_fpstate</span>. (Refer to the section on <span>Saving and Loading FPU, MMX, and XMM Registers</span> in Chapter 3).

  • <span>extramask</span>

    Specifies the bit array for blocking real-time signals.

  • <span>retcode</span>

    The 8-byte code that initiates the <span>sigreturn()</span> system call. In early versions of <span>Linux</span>, this code was used to return from the signal handler; however, after version <span>Linux 2.6</span>, it is only used as a signature so that the debugger can recognize signal stack frames.

The Signal Delivery Process in the Linux Kernel

Figure <span>11-3</span> The <span>frame</span> on the user mode stack

<span>setup_frame()</span> function calls <span>get_sigframe()</span> to calculate the first memory location of the <span>frame</span>; since this memory location is on the user mode stack, the value returned by the function is <span>(regs->esp - sizeof(struct sigframe)) & 0xfffffff8</span>.

  • Linux allows processes to call the <span>signaltstack()</span> system call to specify an alternate stack for their signal handlers; this feature is also required by the <span>X/Open</span> standard. If an alternate stack is used, the <span>get_sigframe()</span> function returns an address in the alternate stack. We will not discuss this feature in detail; conceptually, it is very similar to regular signal handling.

Because on the <span>x86</span> architecture, the stack grows downwards, the address of the <span>frame</span> is equal to the current stack top position’s address minus the size of the <span>frame</span>, with the result aligned to 8 bytes.

The return address is verified using <span>access_ok</span>; if valid, the function repeatedly calls <span>__put_user()</span> to fill in all fields of the <span>frame</span>. The <span>pretcode</span> field is initialized to <span>&amp;__kernel_sigreturn</span>, which is the address of a piece of assembly code on the <span>vsyscall</span> memory page. (Refer to the section on <span>Making System Calls via the sysenter Instruction</span> in Chapter 10).

Next, the contents of the <span>regs</span> in the kernel mode stack are modified to ensure that when <span>current</span> switches to user mode, the CPU control can be passed to the signal handler:

    regs->esp = (unsigned long) frame;
    regs->eip = (unsigned long) ka->sa.sa_handler;
    regs->eax = (unsigned long) sig;
    regs->edx = (unsigned long) 0;
    regs->ecx = (unsigned long) 0;

    /* Restore user mode segment registers */
    set_fs(USER_DS);
    regs->xds = __USER_DS;
    regs->xes = __USER_DS;
    regs->xss = __USER_DS;
    regs->xcs = __USER_CS;

    /* Clear the TF flag when entering the signal handler, but notify the tracer that is single stepping,
     * the tracer may also want to single step inside the signal handler
     */
    regs->eflags &amp;= ~TF_MASK;
    if (test_thread_flag(TIF_SINGLESTEP))
        ptrace_notify(SIGTRAP);
    // ... omitted, print debug info and then return.

Finally, the <span>setup_frame()</span> function resets the segment registers stored in the kernel mode stack to their user mode default values and terminates. Now, the information needed by the signal handler is at the top of the user mode stack.

<span>setup_rt_frame()</span> function is similar to <span>setup_frame()</span>, but it pushes an extended frame (data structure is <span>rt_sigframe</span>) onto the user mode stack, which also includes the contents of the <span>siginfo_t</span> table related to the signal. Additionally, this function points the <span>pretcode</span> field to the code segment of <span>__kernel_rt_sigreturn</span> in the <span>vsyscall</span> memory page.

2.2 Calculating Signal Flags

After configuring the user mode stack, the <span>handle_signal()</span> function checks the flags associated with the signal. If the signal does not have the <span>SA_NODEFER</span> flag set, all signals in the <span>sa_mask</span> field of the <span>sigaction</span> table must be blocked during the execution of the signal handler to ensure that the signal is processed quickly:

    if (!(ka->sa.sa_flags &amp; SA_NODEFER)) {
        spin_lock_irq(&amp;current->sighand-&gt;siglock);
        sigorsets(&amp;current->blocked, &amp;current->blocked, &amp;ka->sa.sa_mask);
        sigaddset(&amp;current->blocked, sig);
        recalc_sigpending(current);
        spin_unlock_irq(&amp;current->sighand-&gt;siglock);
    }

As previously described, the <span>recalc_sigpending()</span> function checks whether the process has any non-blocking pending signals and sets its corresponding <span>TIF_SIGPENDING</span> flag.

After this, it returns to <span>do_signal()</span>, which also returns.

2.3 Starting the Signal Handler

When returning from <span>do_signal()</span>, the current process switches to user mode for execution. Because of the preparations made by <span>setup_frame()</span>, the <span>eip</span> register points to the first instruction of the signal handler, while the <span>esp</span> points to the first memory location of the <span>frame</span> pushed onto the user mode stack. Thus, the execution of the signal handler begins.

2.4 Terminating the Signal Handler

When the signal handler execution completes, the return address at the top of its stack points to the <span>vsyscall</span> memory page (the <span>pretcode</span> field in the <span>frame</span>)

__kernel_sigreturn:
    popl %eax
    movl $__NR_sigreturn, %eax
    int $0x80

Thus, the signal (i.e., the <span>sig</span> field in the <span>frame</span>) is discarded from the stack; then, the <span>sigreturn()</span> system call is invoked.

<span>sys_sigreturn()</span> function calculates the address of <span>regs</span> (of type <span>pt_regs</span>), which contains the hardware context of the user process, to complete the transition from kernel mode to user mode execution. Since the kernel mode stack has been corrupted during the transition to user mode for executing the signal handler, a temporary kernel mode stack needs to be established, with data sourced from the <span>frame</span> data structure configured in the user mode stack.

asmlinkage int sys_sigreturn(unsigned long __unused) {
    /* Establish a temporary stack for the process in kernel mode */
    struct pt_regs *regs = (struct pt_regs *) &amp;__unused;
    // User storage in kernel mode stack
    struct sigframe __user *frame = (struct sigframe __user *)(regs->esp - 8);
    sigset_t set;
    int eax;

    /* Verify that the `frame` data structure is correct */
    if (verify_area(VERIFY_READ, frame, sizeof(*frame)))
        goto badframe;
    /* Handle real-time signals */
    if (__get_user(set.sig[0], &amp;frame->sc.oldmask)
        || (_NSIG_WORDS > 1
        &amp;&amp; __copy_from_user(&amp;set.sig[1], &amp;frame->extramask,
                    sizeof(frame->extramask))))
        goto badframe;

    /* Restore pending signals that were blocked during the signal handler */
    sigdelsetmask(&amp;set, ~_BLOCKABLE);
    spin_lock_irq(&amp;current->sighand-&gt;siglock);
    current->blocked = set;
    recalc_sigpending();
    spin_unlock_irq(&amp;current->sighand-&gt;siglock);
    
    /* Copy the user process hardware context from the frame in the user mode stack to the kernel mode stack and remove the frame */
    if (restore_sigcontext(regs, &amp;frame->sc, &amp;eax))
        goto badframe;
    return eax;

    /* Error data handling */
badframe:
    force_sig(SIGSEGV, current);
    return 0;
}

<span>sys_sigreturn()</span> function calculates the address of <span>regs</span> (of type <span>pt_regs</span>), which contains the hardware context of the user process (refer to the section on <span>Parameter Passing</span> in Chapter 10). Based on the <span>esp</span> field in <span>regs</span>, the address of the <span>frame</span> in the user stack can be inferred.

Then, the blocked signals that were blocked during the execution of the signal handler are copied from the <span>frame</span>‘s <span>sc</span> field to the current process’s <span>current</span>‘s <span>blocked</span> field. This means these blocked signals are unblocked. The call to <span>recalc_sigpending()</span> will rejoin these signals to the pending signal queue.

Next, the <span>sys_sigreturn()</span> function needs to copy the process’s hardware context from the <span>frame</span>‘s <span>sc</span> field to the kernel mode stack and remove the <span>frame</span> data from the user mode stack. Both of these steps are implemented by the <span>restore_sigcontext()</span> function.

If the signal was sent by a system call (for example, <span>rt_sigqueueinfo()</span>), the signal-related <span>siginfo_t</span> table data is required, and its mechanism is similar to the above. The <span>pretcode</span> field in the extended frame points to the assembly code at the <span>__kernel_rt_sigreturn</span> label (located in the <span>vsyscall</span> memory page), which will call the <span>rt_sigreturn()</span> system call. The corresponding system service routine <span>sys_rt_sigreturn()</span> will copy the process’s hardware context from the extended frame to the kernel mode stack and remove the extended frame from the user mode stack to restore the original user mode stack.

3 Re-execution of System Calls

For system call requests, the kernel sometimes cannot fulfill them immediately. At this point, the process that initiated the system call will be set to <span>TASK_INTERRUPTIBLE</span> or <span>TASK_UNINTERRUPTIBLE</span> state.

If the process is in the <span>TASK_INTERRUPTIBLE</span> state and other processes send signals to it, the kernel will set the process to <span>TASK_RUNNING</span> without completing the system call (refer to the section on <span>Returning from Interrupts and Exceptions</span> in Chapter 4). When the process wants to switch back to user mode while a signal is being delivered, the system call service routine has not yet completed its work, so it will return error codes <span>EINTR</span>, <span>ERESTARTNOHAND</span>, <span>ERESTART_RESTARTBLOCK</span>, <span>ERESTARTSYS</span>, <span>ERESTARTNOINTR</span>.

In fact, in this scenario, the only error code that the user mode process can receive is <span>EINTR</span>, which means the system call has not been completed. Application programmers can check this error code and decide whether to re-initiate the system call. The other error codes are used internally by the kernel to specify whether to automatically re-execute the system call after the signal handler ends.

Table <span>11-11</span> lists the error codes related to unfinished system calls and their impact on the three default behaviors of signals. The terms in the table are explained as follows:

  • <span>Terminate</span>

    The system call will not be automatically re-executed; the process will continue executing in user mode after the <span>int $0x80</span> or <span>sysenter</span> instruction, returning the value <span>-EINTR</span> through the <span>eax</span> register.

  • <span>Reexecute</span>

    The kernel forces the user process to reload the system call number (<span>eax</span>) and then re-invokes <span>int $0x80</span> or <span>sysenter</span>; the process will not be aware of the re-execution and will not receive an error code.

  • <span>Depends</span>

    The system call will only be re-executed if the signal that was delivered has the <span>SA_RESTART</span> flag set; otherwise, the system call will terminate and return the error code <span>-EINTR</span>.

Table <span>11-11</span> System Call Re-execution

Signal Behavior EINTR ERESTARTSYS ERESTARTNOHAND ERESTART_RESTARTBLOCK* ERESTARTNOINTR
Default <span>Terminate</span> <span>Reexecute</span> <span>Reexecute</span> <span>Reexecute</span>
Ignore <span>Terminate</span> <span>Reexecute</span> <span>Reexecute</span> <span>Reexecute</span>
Catch <span>Terminate</span> <span>Depends</span> <span>Terminate</span> <span>Reexecute</span>
  • <span>ERESTARTNOHAND</span> and <span>ERESTART_RESTARTBLOCK</span> have different mechanisms for restarting system calls.

When delivering signals, the kernel must ensure that the process initiated the system call before re-executing it. This is where the <span>regs</span> register context’s <span>orig_eax</span> field plays a crucial role. Let’s recall how this field is initialized when the interrupt or exception handler starts:

  • <span>Interrupt</span>

    This field is set to the interrupt <span>IRQ</span> minus <span>256</span> (because the number of interrupt numbers is less than <span>224</span>, subtracting <span>256</span> indicates that the kernel uses negative numbers to represent <span>IRQ</span> (refer to the section on <span>Saving Registers for Interrupt Handlers</span> in Chapter 4).

  • <span>0x80 Exception (including sysenter)</span>

    This field contains the system call number (refer to the section on <span>Entering and Exiting System Calls</span> in Chapter 10).

  • <span>Other Exceptions</span>

    This field is set to <span>-1</span> (refer to the section on <span>Saving Registers for Exception Handlers</span> in Chapter 4).

Thus, a non-negative value in <span>orig_eax</span> indicates that the signal woke up a process that was sleeping in a system call (<span>TASK_INTERRUPTIBLE</span>). The service routine realizes that the system call has been interrupted and therefore returns one of the previously mentioned error codes.

3.1 Restarting System Calls Interrupted by Non-Caught Signals

For signals that are ignored or execute the default action, <span>do_signal()</span> analyzes the error codes of the system call to determine whether the system call should be automatically re-executed, as shown in Table <span>11-1</span>. If the system call must be restarted, the <span>regs</span> context is modified:<span>eip-2</span> indicates that the <span>eip</span> should point to <span>int $0x80</span> or <span>sysenter</span>, while <span>eax</span> contains the system call number:

    if (regs->orig_eax >= 0) {
        if (regs->eax == -ERESTARTNOHAND || regs->eax == -ERESTARTSYS ||
                regs->eax == -ERESTARTNOINTR) {
            regs->eax = regs->orig_eax;
            regs->eip -= 2;
        }
        if (regs->eax == -ERESTART_RESTARTBLOCK) {
            regs->eax = __NR_restart_syscall;
            regs->eip -= 2;
        }
    }

<span>regs->eax</span> contains the return code of the system call service routine (refer to the section on <span>Entering and Exiting System Calls</span> in Chapter 10). Since both <span>int $0x80</span> and <span>sysenter</span> instructions are two bytes long, <span>eip-2</span> points to <span>int $0x80</span> or <span>sysenter</span>, which can trigger the system call again.

The error code <span>ERESTART_RESTARTBLOCK</span> is special because <span>eax</span> is set to the system call number of <span>restart_syscall()</span>; therefore, the user will not restart the same system call that was interrupted by the signal. This error code is only used by time-related system calls, which should adjust their user mode parameters when restarted. A typical example is the <span>nanosleep()</span> system call (refer to the section on <span>Dynamic Timers: nanosleep() System Call</span> in Chapter 6): suppose the process calls it to pause execution for 20 milliseconds, and then a signal occurs after 10 milliseconds. If the system call is restarted as usual, the total delay time will exceed 30 milliseconds.

In contrast, if the <span>nanosleep()</span> system call service routine is interrupted, the address of a special service routine is filled into the <span>current</span> process’s <span>thread_info</span> structure’s <span>restart_block</span> field and returns the <span>ERESTART_RESTARTBLOCK</span> error code.<span>sys_restart_syscall()</span> service routine only executes the previous special service routine, calculating the time elapsed between the first call and the restart, thus adjusting the delay.

3.2 Restarting System Calls Interrupted by Caught Signals

If the signal requires capture handling, <span>handle_signal()</span> analyzes the error code and determines whether to restart based on the <span>SA_RESTART</span> flag in the <span>sigaction</span>:

    if (regs->orig_eax >= 0) {
        switch (regs->eax) {
            case -ERESTART_RESTARTBLOCK:
            case -ERESTARTNOHAND:
                regs->eax = -EINTR;
                break;
            case -ERESTARTSYS:
                if (!(ka->sa.sa_flags &amp; SA_RESTART)) {
                    regs->eax = -EINTR;
                    break;
                }
            /* fallthrough */
            case -ERESTARTNOINTR:
                regs->eax = regs->orig_eax;
                regs->eip -= 2;
        }
    }

If the system call must be restarted, the handling of <span>handle_signal()</span> is exactly the same as that of <span>do_signal()</span>; otherwise, it will return an <span>-EINTR</span> error code to the user process.

4 x86_64 Architecture – do_signal()

The Linux kernel version is <span>v2.6.11</span>, file location: <span>arch/x86_64/kernel/signal.c</span>:

/*
 * Note that init is a special process: it will not receive signals it does not want to handle. So, even if a `SIGKILL` signal is mistakenly sent to it, it will not be killed.
 */
int do_signal(struct pt_regs *regs, sigset_t *oldset) {
    struct k_sigaction ka;
    siginfo_t info;
    int signr;

    /* If not returning to user mode, return directly. */
    if ((regs->cs & 3) != 3) {
        return 1;
    }   

    // ... omitted

    if (!oldset)
        oldset = &amp;current->blocked;

    signr = get_signal_to_deliver(&amp;info, &amp;ka, regs, NULL);
    if (signr > 0) {
        /*
         * Restart all observation points before delivering the signal to user space.
         * If the observation point is triggered inside the kernel, the registers will be cleared.
         */
        if (current->thread.debugreg7)
            asm volatile("movq %0,%%db7"    : : "r" (current->thread.debugreg7));

        /* Deliver the signal */
        handle_signal(signr, &amp;info, &amp;ka, oldset, regs);
        return 1;
    }

no_signal:
    /* Is it a system call? */
    // ... omitted (see the handling in section 3.1)
    return 0;
}

<span>x86-64</span> architecture’s <span>handle_signal()</span> function (file location: <span>arch/x86_64/kernel/signal.c</span>):

static void handle_signal(unsigned long sig, siginfo_t *info, struct k_sigaction *ka,
        sigset_t *oldset, struct pt_regs *regs) {
    // ... omitted (debug signal info)

    // Omitted (handling of interrupted system calls)

    // Omitted (IA32_EMULATION configuration)

    setup_rt_frame(sig, ka, info, oldset, regs);

    if (!(ka->sa.sa_flags &amp; SA_NODEFER)) {
        spin_lock_irq(&amp;current->sighand-&gt;siglock);
        sigorsets(&amp;current->blocked,&amp;current->blocked,&amp;ka->sa.sa_mask);
        sigaddset(&amp;current->blocked,sig);
        recalc_sigpending();
        spin_unlock_irq(&amp;current->sighand-&gt;siglock);
    }
}

<span>i386</span> architecture’s <span>handle_signal()</span> function (file location: <span>arch/i386/kernel/signal.c</span>):

static void handle_signal(unsigned long sig, siginfo_t *info, struct k_sigaction *ka,
          sigset_t *oldset, struct pt_regs * regs) {
    // Omitted (handling of interrupted system calls)

    /* Set up the stack frame */
    if (ka->sa.sa_flags &amp; SA_SIGINFO)
        setup_rt_frame(sig, ka, info, oldset, regs);
    else
        setup_frame(sig, ka, oldset, regs);

    if (!(ka->sa.sa_flags &amp; SA_NODEFER)) {
        spin_lock_irq(&amp;current->sighand-&gt;siglock);
        sigorsets(&amp;current->blocked,&amp;current->blocked,&amp;ka->sa.sa_mask);
        sigaddset(&amp;current->blocked,sig);
        recalc_sigpending();
        spin_unlock_irq(&amp;current->sighand-&gt;siglock);
    }
}
static void setup_rt_frame(int sig, struct k_sigaction *ka, siginfo_t *info,
               sigset_t *set, struct pt_regs * regs) {
    struct rt_sigframe __user *frame;
    struct _fpstate __user *fp = NULL;
    int err = 0;
    struct task_struct *me = current;

    if (used_math()) {
        fp = get_stack(ka, regs, sizeof(struct _fpstate)); 
        frame = (void __user *)round_down((unsigned long)fp - sizeof(struct rt_sigframe), 16) - 8;

        if (!access_ok(VERIFY_WRITE, fp, sizeof(struct _fpstate))) { 
        goto give_sigsegv;
        }

        if (save_i387(fp) < 0) 
            err |= -1; 
    } else {
        frame = get_stack(ka, regs, sizeof(struct rt_sigframe)) - 8;
    }

    if (!access_ok(VERIFY_WRITE, frame, sizeof(*frame))) {
        goto give_sigsegv;
    }

    if (ka->sa.sa_flags &amp; SA_SIGINFO) { 
        err |= copy_siginfo_to_user(&amp;frame->info, info);
        if (err) { 
            goto give_sigsegv;
    }
    }
        
    /* Create the ucontext.  */
    err |= __put_user(0, &amp;frame->uc.uc_flags);
    err |= __put_user(0, &amp;frame->uc.uc_link);
    err |= __put_user(me->sas_ss_sp, &amp;frame->uc.uc_stack.ss_sp);
    err |= __put_user(sas_ss_flags(regs->rsp),
              &amp;frame->uc.uc_stack.ss_flags);
    err |= __put_user(me->sas_ss_size, &amp;frame->uc.uc_stack.ss_size);
    err |= setup_sigcontext(&amp;frame->uc.uc_mcontext, regs, set->sig[0], me);
    err |= __put_user(fp, &amp;frame->uc.uc_mcontext.fpstate);
    if (sizeof(*set) == 16) { 
        __put_user(set->sig[0], &amp;frame->uc.uc_sigmask.sig[0]);
        __put_user(set->sig[1], &amp;frame->uc.uc_sigmask.sig[1]); 
    } else {        
    err |= __copy_to_user(&amp;frame->uc.uc_sigmask, set, sizeof(*set));
    }

    /* Set up to return from userspace.  If provided, use a stub
       already in userspace.  */
    /* x86-64 should always use SA_RESTORER. */
    if (ka->sa.sa_flags &amp; SA_RESTORER) {
        err |= __put_user(ka->sa.sa_restorer, &amp;frame->pretcode);
    } else {
        /* could use a vstub here */
        goto give_sigsegv; 
    }

    if (err) { 
        goto give_sigsegv;
    } 

#ifdef DEBUG_SIG
    printk("%d old rip %lx old rsp %lx old rax %lx\n", current->pid,regs->rip,regs->rsp,regs->rax);
#endif

    /* Set up registers for signal handler */
    { 
        struct exec_domain *ed = current_thread_info()->exec_domain;
        if (unlikely(ed &amp;&amp; ed->signal_invmap &amp;&amp; sig < 32))
            sig = ed->signal_invmap[sig];
    } 
    regs->rdi = sig;
    /* In case the signal handler was declared without prototypes */
    regs->rax = 0;  

    /* This also works for non SA_SIGINFO handlers because they expect the
       next argument after the signal number on the stack. */
    regs->rsi = (unsigned long)&amp;frame->info; 
    regs->rdx = (unsigned long)&amp;frame->uc; 
    regs->rip = (unsigned long) ka->sa.sa_handler;

    regs->rsp = (unsigned long)frame;

    set_fs(USER_DS);
    if (regs->eflags &amp; TF_MASK) {
        if ((current->ptrace &amp; (PT_PTRACED | PT_DTRACE)) == (PT_PTRACED | PT_DTRACE)) {
            ptrace_notify(SIGTRAP);
        } else {
            regs->eflags &amp;= ~TF_MASK;
        }
    }

#ifdef DEBUG_SIG
    printk("SIG deliver (%s:%d): sp=%p pc=%p ra=%p\n",
        current->comm, current->pid, frame, regs->rip, frame->pretcode);
#endif

    return;

give_sigsegv:
    force_sigsegv(sig, current);
}

All basic knowledge of C language and example codes organized by Yikou Jun are compiled into a PDF document 【Regularly Updated

The Signal Delivery Process in the Linux Kernel

end

Yikou Linux

Follow, reply 【1024】 to receive a wealth of Linux materials

Collection of Wonderful Articles

Article Recommendations

【Collection】ARM【Collection】Fan Q&A【Collection】All OriginalsCollectionLinuxIntroductionCollectionComputer NetworksCollectionLinux Drivers【Essentials】Learning Path for Embedded Driver Engineers【Essentials】All Knowledge Points of Linux Embedded – Mind Map

Leave a Comment