Restartable System Calls and ERESTARTSYS

When a signal arrives while a process is blocked deep inside a system call, the kernel faces a choice: abort the call and report -EINTR to userspace, or transparently re-run the call so the program never notices the interruption. Linux implements both behaviors with a small family of kernel-internal error codes — ERESTARTSYS (512), ERESTARTNOINTR (513), ERESTARTNOHAND (514), and ERESTART_RESTARTBLOCK (516) — that a blocked syscall handler returns instead of -EINTR. These codes never reach userspace; they are instructions to the signal-delivery machinery. On the way back out to user mode, the architecture’s signal code inspects the code, the pending signal’s SA_RESTART flag, and whether a handler will run, and then either rewinds the instruction pointer to re-issue the syscall instruction or converts the code to -EINTR. The kernel header that defines them states the contract bluntly: “These should never be seen by user programs” (include/linux/errno.h, v6.12).

This note is the kernel-side mechanism. Its sibling EINTR and Interrupted System Calls covers what userspace sees and must do; SA_RESTART and Signal-Interrupted Syscalls covers the per-handler flag whose value this machinery reads. Version claims are pinned to the Linux 6.12 LTS and 6.18 LTS trees (the latter released 2025-11-30); the restart logic in arch/x86/kernel/signal.c is byte-for-byte identical between the two tags, so it is treated as current as of this writing.

Mental Model

Think of a blocking syscall as a function that may be suspended mid-flight. While it sleeps — waiting for bytes on a pipe, for a child to exit, for a futex to be woken — a signal can become pending on the task. The scheduler’s signal_pending() check inside the sleep wakes the syscall early. At that point the handler cannot return real data (it has none), so instead of -EINTR it returns one of the ERESTART* codes. That code is a verb, not an error:

  • ERESTARTNOINTR — “always restart me, no matter what.”
  • ERESTARTSYS — “restart me if SA_RESTART is set on the signal’s handler; otherwise give userspace EINTR.”
  • ERESTARTNOHAND — “restart me if no handler runs for this signal; otherwise EINTR.”
  • ERESTART_RESTARTBLOCK — “restart me, but through restart_syscall() with time-adjusted arguments (I had an absolute or relative deadline).”

The decision is not made by the syscall. It is made later, once, on the return-to-userspace path, where the kernel knows the full picture: which signal is being delivered, with what sigaction flags, and whether a userspace handler is about to run.

flowchart TB
  BLK["Blocked syscall<br/>(read/wait/futex/nanosleep)"]
  SIG["Signal becomes pending<br/>(signal_pending() true)"]
  RET["Handler returns -ERESTART*<br/>(NOT -EINTR)"]
  EXIT["exit_to_user_mode_loop<br/>sees _TIF_SIGPENDING"]
  DISP["arch_do_signal_or_restart()"]
  GS{"get_signal():<br/>handler to run?"}
  HS["handle_signal()<br/>(a handler runs)"]
  NH["no-handler path"]
  DEC1{"which ERESTART<br/>code? SA_RESTART?"}
  DEC2{"which ERESTART<br/>code?"}
  EINTR["regs->ax = -EINTR<br/>(syscall fails)"]
  REISSUE["regs->ax = orig_ax<br/>regs->ip -= 2<br/>(re-run syscall insn)"]
  RBLK["regs->ax = __NR_restart_syscall<br/>regs->ip -= 2<br/>(re-run via restart_syscall)"]

  BLK --> SIG --> RET --> EXIT --> DISP --> GS
  GS -->|yes| HS --> DEC1
  GS -->|no| NH --> DEC2
  DEC1 -->|"ERESTART_RESTARTBLOCK<br/>or ERESTARTNOHAND;<br/>ERESTARTSYS w/o SA_RESTART"| EINTR
  DEC1 -->|"ERESTARTNOINTR;<br/>ERESTARTSYS w/ SA_RESTART"| REISSUE
  DEC2 -->|"ERESTARTNOINTR/SYS/NOHAND"| REISSUE
  DEC2 -->|"ERESTART_RESTARTBLOCK"| RBLK

The restart decision tree on x86-64, transcribed from handle_signal() and arch_do_signal_or_restart() in arch/x86/kernel/signal.c. What it shows: the same four ERESTART* codes are resolved by two different code paths — one when a handler will run, one when no handler runs — and the resolution differs between them. The insight: “restart” is decided at signal-delivery time, not at syscall time, and the most subtle case is ERESTART_RESTARTBLOCK, which the handler path converts to EINTR but the no-handler path restarts through a different syscall (restart_syscall). This single asymmetry explains why timeout-bearing calls like nanosleep are “never restarted with SA_RESTART.”

Where the ERESTART Code Comes From

A blocking primitive deep in the kernel — wait_event_interruptible, schedule_timeout_interruptible, a futex wait, an hrtimer sleep — wakes whenever signal_pending(current) becomes true. By convention it then returns -ERESTARTSYS to its caller, which propagates up to the syscall wrapper. The errno header is explicit that this is only legal when a signal is actually pending: “To return one of ERESTART* codes, signal_pending() MUST be set” (errno.h, v6.12). The same header confirms the numeric values, which sit just above the userspace-visible errno range (the errno(3) values stop in the low hundreds; ERESTARTSYS is 512):

#define ERESTARTSYS           512
#define ERESTARTNOINTR        513
#define ERESTARTNOHAND        514  /* restart if no handler.. */
#define ERESTART_RESTARTBLOCK 516  /* restart by calling sys_restart_syscall */

These four codes are byte-for-byte identical between the 6.12 and 6.18 trees (errno.h, v6.18). Because they are numerically above 511, the libc convention that treats a return in [-4095, -1] as a negated errno would not obviously distinguish them — which is exactly why the kernel guarantees they are intercepted before the syscall ever returns to userspace.

The Return-to-Userspace Hook

The ERESTART* code rides back through the generic syscall-exit machinery as the value stashed in the pt_regs accumulator register (regs->ax on x86-64). Returning to user mode is not a single ret; it runs exit_to_user_mode_loop() in kernel/entry/common.c, which drains pending thread-info work before crossing the privilege boundary (see Returning to Userspace and exit_to_user_mode and Thread Info Flags and Syscall Exit Work). The relevant line:

if (ti_work & (_TIF_SIGPENDING | _TIF_NOTIFY_SIGNAL))
    arch_do_signal_or_restart(regs);

_TIF_SIGPENDING is the thread-info flag that says “a signal is queued for this task”; _TIF_NOTIFY_SIGNAL is its kernel-internal cousin (used by, e.g., io_uring task work). Either one routes into arch_do_signal_or_restart(), the architecture entry point that both delivers the signal and applies the restart decision. That single call is where the ERESTART* code is resolved.

The Two Resolution Paths

arch_do_signal_or_restart() (in arch/x86/kernel/signal.c) calls get_signal(&ksig). get_signal() (in kernel/signal.c) dequeues the next deliverable signal and decides its disposition; it returns true if a userspace handler must run and fills in ksig (the struct ksignal, carrying the sigaction in ksig.ka.sa). The two outcomes — handler vs. no handler — are resolved by two different blocks of code, and the difference between them is the whole subject of this note.

Path A — a handler will run (handle_signal)

When get_signal() returns true, handle_signal() runs. Before it builds the signal frame on the user stack, it fixes up the syscall result:

/* Are we from a system call? */
if (syscall_get_nr(current, regs) != -1) {
    /* If so, check system call restarting.. */
    switch (syscall_get_error(current, regs)) {
    case -ERESTART_RESTARTBLOCK:
    case -ERESTARTNOHAND:
        regs->ax = -EINTR;
        break;
 
    case -ERESTARTSYS:
        if (!(ksig->ka.sa.sa_flags & SA_RESTART)) {
            regs->ax = -EINTR;
            break;
        }
        fallthrough;
    case -ERESTARTNOINTR:
        regs->ax = regs->orig_ax;
        regs->ip -= 2;
        break;
    }
}

Walking it symbol by symbol:

  • syscall_get_nr(current, regs) != -1 — we only fix up if we were in a syscall (the entry path stamped the syscall number into regs->orig_ax; -1 means “not from a syscall,” e.g. an interrupted page fault).
  • syscall_get_error(...) reads the negative value the handler left in regs->ax.
  • ERESTART_RESTARTBLOCK and ERESTARTNOHANDregs->ax = -EINTR. A handler is running, so these become EINTR. This is the key fact: ERESTART_RESTARTBLOCK (used by timeout calls) is unconditionally converted to EINTR whenever a handler runs — the SA_RESTART flag is never even consulted for it on this path.
  • ERESTARTSYS → restart only if ksig->ka.sa.sa_flags & SA_RESTART; otherwise EINTR. This is the textbook SA_RESTART behavior — and the gate is read from this specific signal’s sigaction, so two signals delivered to the same blocked call can resolve differently.
  • ERESTARTNOINTR (and ERESTARTSYS that fell through with SA_RESTART) → restart unconditionally: regs->ax = regs->orig_ax puts the syscall number back into ax (it had been clobbered with the -ERESTART code), and regs->ip -= 2 rewinds the instruction pointer.

The regs->ip -= 2 rewind

On x86-64 the syscall instruction is two bytes (0F 05). When the trap was taken, the saved RIP pointed at the instruction after syscall. Subtracting 2 moves it back onto the syscall instruction itself, so that when the signal handler returns (via sigreturn) and execution resumes, the CPU re-executes syscall — with the original number restored in ax and the original arguments still in their registers (the kernel preserved them in pt_regs). From the program’s point of view, the call simply took longer; it never sees the interruption. This is transparent restart. Note the dependency on the instruction being exactly two bytes — the same trick on other architectures rewinds by that arch’s syscall-instruction width.

Path B — no handler runs (arch_do_signal_or_restart tail)

If get_signal() returns false (the signal was ignored, was a stop/continue handled by the kernel, or the task is simply about to re-check), no frame is built, and the tail of arch_do_signal_or_restart() runs:

/* Did we come from a system call? */
if (syscall_get_nr(current, regs) != -1) {
    /* 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;
    }
}

Here the resolution differs in two ways. First, ERESTARTNOHAND now restarts (its whole meaning is “restart if no handler”), and ERESTARTSYS restarts regardless of SA_RESTART (there is no sigaction to consult — no handler is running). Second, and crucially, ERESTART_RESTARTBLOCK does not restore orig_ax. Instead it sets regs->ax = get_nr_restart_syscall(regs), which returns __NR_restart_syscall (or its ia32/x32 variant). After the ip -= 2 rewind, the re-executed syscall instruction therefore invokes a different syscall — restart_syscall() — not the original one.

The restart_block Mechanism for Timeout Calls

Why the special treatment for ERESTART_RESTARTBLOCK? Because a naive restart of a timeout-bearing call would be wrong. Consider nanosleep(10s) interrupted after 3 seconds: a plain re-issue of the same syscall would sleep a fresh 10 seconds, for 13 total. The correct behavior is to sleep only the remaining 7 seconds. But the original arguments in the registers say ”10s” — they cannot carry the adjusted deadline. The restart_block solves this by saving the absolute deadline in per-task state and routing the restart through a generic restart_syscall() that reads it back.

Each task_struct carries one struct restart_block (include/linux/restart_block.h):

struct restart_block {
    unsigned long arch_data;
    long (*fn)(struct restart_block *);
    union {
        struct { /* ... */ } futex;
        struct {
            clockid_t clockid;
            enum timespec_type type;
            union {
                struct __kernel_timespec __user *rmtp;
                struct old_timespec32 __user *compat_rmtp;
            };
            u64 expires;          /* absolute deadline */
        } nanosleep;
        struct { /* ... */ } poll;
    };
};
  • fn is the continuation — the function to call to resume the operation.
  • The union holds the per-call saved state. For nanosleep, expires is the absolute deadline; for futex, the address and bitset; for poll, the file-descriptor set and remaining timeout.

The restart_syscall() syscall itself is trivial — it just calls the saved continuation (kernel/signal.c):

SYSCALL_DEFINE0(restart_syscall)
{
    struct restart_block *restart = &current->restart_block;
    return restart->fn(restart);
}

A call that does not want restart sets fn = do_no_restart_syscall, which simply returns -EINTR — a defensive default so a stale restart_block cannot accidentally re-run something. The restart_syscall(2) man page confirms there is no glibc wrapper and that it “is intended for use only by the kernel and should never be called by applications.”

Worked example: nanosleep

The hrtimer sleep path (kernel/time/hrtimer.c) is the canonical user of this mechanism:

long hrtimer_nanosleep(ktime_t rqtp, const enum hrtimer_mode mode,
                       const clockid_t clockid)
{
    struct restart_block *restart;
    struct hrtimer_sleeper t;
    int ret = 0;
 
    hrtimer_init_sleeper_on_stack(&t, clockid, mode);
    hrtimer_set_expires_range_ns(&t.timer, rqtp, current->timer_slack_ns);
    ret = do_nanosleep(&t, mode);
    if (ret != -ERESTART_RESTARTBLOCK)
        goto out;
 
    /* Absolute timers do not update the rmtp value and restart: */
    if (mode == HRTIMER_MODE_ABS) {
        ret = -ERESTARTNOHAND;
        goto out;
    }
 
    restart = &current->restart_block;
    restart->nanosleep.clockid = t.timer.base->clockid;
    restart->nanosleep.expires = hrtimer_get_expires_tv64(&t.timer);
    set_restart_fn(restart, hrtimer_nanosleep_restart);
out:
    destroy_hrtimer_on_stack(&t.timer);
    return ret;
}

When do_nanosleep() is interrupted, it returns -ERESTART_RESTARTBLOCK. hrtimer_nanosleep() then records the absolute expiry (expires) and the clock into the nanosleep arm of the union, and calls set_restart_fn() to wire up the continuation. set_restart_fn() (include/linux/thread_info.h) is itself instructive:

static inline long set_restart_fn(struct restart_block *restart,
                                  long (*fn)(struct restart_block *))
{
    restart->fn = fn;
    arch_set_restart_data(restart);
    return -ERESTART_RESTARTBLOCK;
}

It stores the continuation, lets the architecture stash any extra data (arch_set_restart_data records the compat/x32 mode in arch_data on x86), and returns the code — a tidy idiom so callers return set_restart_fn(...). The continuation re-arms an absolute timer at the saved deadline:

static long __sched hrtimer_nanosleep_restart(struct restart_block *restart)
{
    struct hrtimer_sleeper t;
    int ret;
 
    hrtimer_init_sleeper_on_stack(&t, restart->nanosleep.clockid,
                                  HRTIMER_MODE_ABS);
    hrtimer_set_expires_tv64(&t.timer, restart->nanosleep.expires);
    ret = do_nanosleep(&t, HRTIMER_MODE_ABS);
    /* ... */
    return ret;
}

Because the deadline is absolute, restarting “sleeps until time T” rather than “sleeps for N more nanoseconds,” so elapsed time is accounted for automatically — even across multiple interruptions. This is precisely what restart_syscall(2) means by “a time argument that is suitably adjusted to account for the time that has already elapsed.” The same pattern backs clock_nanosleep, poll/ppoll, futex(FUTEX_WAIT*), io_getevents, and the System V semaphore wait.

Why the Two Man-Page Lists Are What They Are

signal(7) publishes two lists: calls “automatically restarted after the signal handler returns if the SA_RESTART flag was used,” and calls that “are never restarted (regardless of the use of SA_RESTART)” — the latter including nanosleep, clock_nanosleep, poll, select, epoll_wait, sigtimedwait, System V semop, and the socket calls with a timeout set. These lists are not arbitrary policy; they fall directly out of the code above:

  • The first list is exactly the set of calls whose blocking primitive returns -ERESTARTSYS. Per handle_signal, those restart iff SA_RESTART.
  • The second list is mostly calls that return -ERESTART_RESTARTBLOCK. Per handle_signal, that code is converted to -EINTR whenever a handler runs — and the SA_RESTART bit is never tested for it. So no matter what flags you set, if you have a handler for the delivered signal, these calls return EINTR.

This is the load-bearing connection between the kernel mechanism and the userspace-visible behavior described in EINTR and Interrupted System Calls. The man page’s “never restarted with SA_RESTART” is a consequence of ERESTART_RESTARTBLOCK hitting the regs->ax = -EINTR branch before the SA_RESTART check, not an independent rule.

Uncertain

Verify: the precise membership of signal(7)‘s “never restarted” list against the actual ERESTART_RESTARTBLOCK vs ERESTARTNOHAND return codes call-by-call (e.g. whether select/pselect return ERESTARTNOHAND rather than ERESTART_RESTARTBLOCK, and where socket SO_RCVTIMEO timeouts fit). Reason: the broad correspondence is verified from handle_signal, but I did not trace every individual syscall’s blocking primitive to its exact return code. To resolve: grep each call’s implementation (fs/select.c, net/socket.c, ipc/sem.c) at the 6.12 tag for which ERESTART* it returns. uncertain

The ptrace Observability Window

The errno header notes one legitimate observer of these codes: “ptrace can observe these at syscall exit tracing, but they will never be left for the debugged user process to see” (errno.h, v6.12). A tracer stopped at the syscall-exit trap (see ptrace and Syscall Tracing) can read -ERESTARTSYS straight out of pt_regs, because the trap fires before arch_do_signal_or_restart() resolves it. This is why strace can sometimes print = ? ERESTARTSYS (To be restarted ...) — it is peeking at the internal code through the ptrace window. The traced process itself still only ever resumes the call or receives EINTR.

Failure Modes and Subtleties

  • A driver returns -ERESTARTSYS without a pending signal. The header contract requires signal_pending() to be set. If a buggy ioctl or driver returns ERESTARTSYS with no signal pending, neither resolution path triggers (no _TIF_SIGPENDING), and the raw 512 could leak toward userspace logic — a real class of driver bugs. The fix is always to gate the return on signal_pending(current).
  • ERESTARTNOINTR is rare and strong. It forces a restart even when a handler runs and SA_RESTART is clear. It is reserved for calls that have no observable side effect yet and must appear atomic, such as the kernel-internal restart of certain sigreturn/setup paths; misusing it would make a call un-interruptible, defeating signals like SIGINT.
  • Stale restart_block. Because there is a single restart_block per task, a call that uses it must set fn every time; the do_no_restart_syscall default guards against a left-over continuation firing, but a call that forgets to reset state could in principle restart with wrong arguments. The kernel zeroes the relevant fields before each use (restart->fn = do_no_restart_syscall is set in the nanosleep entry wrappers).
  • Absolute-timer calls do not restart via restart_block. Note the mode == HRTIMER_MODE_ABS branch above converts to -ERESTARTNOHAND instead — an absolute clock_nanosleep(TIMER_ABSTIME) already names a fixed wall-clock target, so it can be restarted by plain re-issue (no time adjustment needed) and uses the simpler code.

See Also