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. What this note owns that its large neighbours do not: The Generic Syscall Entry and Exit Layer owns the entry/exit work loop and the SYSCALL_WORK_*/TIF_* gating that reaches the restart decision, but stops at the call to arch_do_signal_or_restart(); this note owns everything inside that call — the four restart verbs, the two resolution paths, and the restart_block continuation machinery. libc Syscall Wrappers and errno Translation owns the userspace wrapper that turns the resulting -EINTR into errno; it never sees an ERESTART* code at all. The errno Convention and Negative Return Values owns the numeric convention these codes live inside, including why 512 is legal as an internal code but illegal as a userspace-visible one.

Version pin. Every code claim below is read from the Linux 6.12 LTS tree, with the 6.18 LTS tree fetched at the same paths for comparison (kernel.org/releases.json lists both 6.18 and 6.12 as longterm as of 2026-09-12; 6.12 is this vault’s pin). The internal-code list in include/linux/errno.h is byte-for-byte identical between the two tags (verified by diff). arch/x86/kernel/signal.c is not byte-for-byte identical — v6.18 differs in three lines, all of them the FPU accessor change from current->thread.fpu to x86_task_fpu(current) — but the restart switch statements in handle_signal() and arch_do_signal_or_restart() are unchanged. include/linux/restart_block.h differs only in a comment, and include/linux/err.h gains one macro (IOMEM_ERR_PTR). So the mechanism described here is current for both LTS lines.

Everything in this note that can be measured, was measured. The per-syscall restart-code table and the SA_RESTART truth table below are not transcribed from documentation — they were produced by running programs on Fedora 44 (kernel 7.1.13-200.fc44.x86_64, glibc 2.43-8.fc44, GCC 16.2.1, GDB 17.2) and reading the raw internal code out of RAX through the ptrace syscall-exit trap. The source and output are in Demonstrating It.

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 Four Restart Verbs, Side by Side

The single most important fact about these codes is that the same code resolves differently depending on whether a userspace handler is about to run, and only one of the four even looks at SA_RESTART. This table is the whole note in miniature; the sections that follow derive each column from source.

CodeValueLiteral meaningHandler runs (handle_signal)No handler runs (arch_do_signal_or_restart tail)Consults SA_RESTART?Canonical kernel users (verified v6.12)
ERESTARTSYS512“restart me if the program said it wants restarts”SA_RESTART set → re-issue; clear → -EINTRre-issue (ax = orig_ax, ip -= 2)yes — the only onewait_event_interruptible and everything built on it; wait_for_completion_interruptible; futex_wait with no timeout; read/write on a pipe/tty/socket; wait4; sock_intr_errno() when no socket timeout is set
ERESTARTNOINTR513“restart me unconditionally”re-issue, ignoring SA_RESTARTre-issuenocopy_process() — fork/clone/vfork bail out with this if a signal is already pending, so the fork is always retried
ERESTARTNOHAND514“restart me only if no handler runs”-EINTR, alwaysre-issuenopause, sigsuspend/rt_sigsuspend, core_sys_select (so select/pselect6), do_sys_poll (before sys_poll upgrades it), clock_nanosleep(TIMER_ABSTIME)
ERESTART_RESTARTBLOCK516“restart me through restart_syscall(), which knows my adjusted deadline”-EINTR, alwaysre-issue as a different syscall: ax = __NR_restart_syscallnonanosleep/clock_nanosleep (relative), poll/ppoll via do_restart_poll, futex_wait with a timeout, alarmtimer, posix-cpu-timers

The four restart verbs and their two resolutions. What it shows: three of the four codes have an unconditional answer on the handler path — two of them -EINTR, one of them “always restart” — and only ERESTARTSYS is a question whose answer SA_RESTART supplies. The insight to take: SA_RESTART is not a global “make my syscalls restartable” switch; it is a single bit consulted in exactly one case label of one switch statement. A call that returns any of the other three codes is completely indifferent to it. The whole “which syscalls does SA_RESTART cover?” question reduces to “which syscalls return ERESTARTSYS?”

515, 517, 518, 519 — the codes that are not about restarting

include/linux/errno.h holds more than the restart family. The full v6.12 list runs 512–519 and then 521–531, and the header’s “should never be seen by user programs” contract covers all of them, not just the ERESTART* four. The non-restart members are worth naming because they are the most common source of confusion about what the 512+ range is for:

CodeValuePurposeWho consumes it
ENOIOCTLCMD515“this ioctl command is not mine” — a driver’s way of saying “fall through to generic handling”the VFS/driver ioctl dispatch, which translates it to -ENOTTY for userspace
EPROBE_DEFER517“a resource this driver needs does not exist yet; re-probe me later”the driver core’s deferred-probe list
EOPENSTALE518“open() raced with a rename/unlink and hit a stale dentry; retry the lookup”path_openat()’s retry loop
ENOPARAM519“this mount parameter is not one of mine”the fs_context parameter parser
EBADHANDLE … ENOGRACE521–531NFS/NFSv3-protocol-internal conditions (note 520 is unallocated)the NFS client/server, which map them to on-the-wire NFS status codes

The rest of the kernel-internal errno space at v6.12. What it shows: the 512+ range is a general-purpose “private error channel” between kernel subsystems, of which syscall restarting is only the first and most famous user. The insight to take: none of these values can ever reach userspace, and the reason is structural rather than a matter of discipline — see the header-split argument in The errno Convention and Negative Return Values. They are also not all “retry” codes: ENOIOCTLCMD and ENOPARAM are “not mine, ask someone else,” which is a different idea wearing the same numeric clothing.

Where in the kernel the code is actually produced

The ERESTART* code is almost never written by hand in a syscall handler. It comes out of a sleeping primitive that noticed signal_pending(current), and the primitive’s documented return value is the convention:

  • wait_event_interruptible() — the workhorse. Its kerneldoc is explicit: “The function will return -ERESTARTSYS if it was interrupted by a signal and 0 if @condition evaluated to true” (include/linux/wait.h, v6.12). Everything layered on it — wait_event_interruptible_timeout, waitqueue sleeps in drivers, pipe and socket reads — inherits that.

  • wait_for_completion_interruptible() — “Return: -ERESTARTSYS if interrupted, 0 if completed” (kernel/sched/completion.c, v6.12).

  • schedule_timeout_interruptible() and the hrtimer sleepers — these return whether the timer expired, and the caller turns “woke early with a signal pending” into -ERESTART_RESTARTBLOCK (see do_nanosleep() below).

  • sock_intr_errno() — a one-line decision function that is the single cleanest statement of the whole timeout rule in the tree (include/net/sock.h, v6.12):

    /* Alas, with timeout socket operations are not restartable.
     * Compare this to poll().
     */
    static inline int sock_intr_errno(long timeo)
    {
    	return timeo == MAX_SCHEDULE_TIMEOUT ? -ERESTARTSYS : -EINTR;
    }

    Read it slowly, because it explains an entire bullet of signal(7). timeo is the socket’s effective timeout in jiffies; MAX_SCHEDULE_TIMEOUT is the sentinel for “no timeout at all.” If there is no timeout, the socket call returns -ERESTARTSYS and SA_RESTART works normally. If SO_RCVTIMEO or SO_SNDTIMEO is set, the call returns plain -EINTR — it never enters the restart machinery in the first place. That is why signal(7) lists the socket calls in both of its lists, distinguished only by whether a timeout has been set.

flowchart LR
  subgraph P["1. the sleeping primitive notices signal_pending()"]
    W1["wait_event_interruptible()<br/>→ -ERESTARTSYS"]
    W2["wait_for_completion_interruptible()<br/>→ -ERESTARTSYS"]
    W3["do_nanosleep() / hrtimer sleepers<br/>→ -ERESTART_RESTARTBLOCK"]
    W4["sock_intr_errno(timeo)<br/>→ -ERESTARTSYS or plain -EINTR"]
    W5["mutex_lock_interruptible()<br/>→ -EINTR  (different convention!)"]
  end
  subgraph S["2. the syscall wrapper may UPGRADE or DOWNGRADE it"]
    U1["sys_poll: 514 → set_restart_fn()<br/>→ 516"]
    U2["futex_wait: 512 → set_restart_fn()<br/>→ 516  (only if a timeout was given)"]
    U3["hrtimer_nanosleep: 516 → 514<br/>if HRTIMER_MODE_ABS"]
    U4["poll_select_finish: 514 → -EINTR<br/>only if the timeout writeback faulted"]
  end
  subgraph R["3. regs->ax carries the final code out"]
    OUT["-512 / -513 / -514 / -516<br/>or a plain -EINTR that<br/>neither switch will match"]
  end
  W1 --> S
  W2 --> S
  W3 --> S
  W4 --> OUT
  W5 --> OUT
  U1 --> OUT
  U2 --> OUT
  U3 --> OUT
  U4 --> OUT

How a restart code is produced, in two stages. What it shows: the sleeping primitive picks a code, and then the syscall’s own wrapper is free to change it — poll turns 514 into 516, futex turns 512 into 516 but only when a timeout is present, and absolute clock_nanosleep turns 516 back into 514. The insight to take: you cannot predict a syscall’s restart behaviour by looking only at what it sleeps on; the wrapper gets the last word, and that is exactly why poll and select — which sleep on the same do_select-family machinery — end up with different codes and different no-handler behaviour. It is also why a plain -EINTR from mutex_lock_interruptible() bypasses the whole apparatus: it never enters stage 2 and matches nothing in stage 3.

A trap for driver authors: the interruptible sleeping primitives do not all use the same convention. mutex_lock_interruptible() documents “Return: 0 if the lock was successfully acquired or -EINTR if a signal arrived” (kernel/locking/mutex.c, v6.12) — -EINTR, not -ERESTARTSYS. A driver that forwards a mutex_lock_interruptible() failure straight out of its ioctl has silently made that path non-restartable even under SA_RESTART. Converting it (if (mutex_lock_interruptible(&m)) return -ERESTARTSYS;) is a real and common idiom precisely because of this mismatch.

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 ERESTARTNOHAND → regs->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 whole trip, as a sequence

sequenceDiagram
    autonumber
    participant U as User code
    participant E as entry_SYSCALL_64<br/>(entry_64.S)
    participant H as syscall handler<br/>(e.g. pipe_read)
    participant X as syscall_exit_to_user_mode<br/>(kernel/entry/common.c)
    participant S as arch_do_signal_or_restart<br/>(arch/x86/kernel/signal.c)
    participant G as get_signal<br/>(kernel/signal.c)

    U->>E: syscall instruction (2 bytes, 0F 05)
    Note over E: orig_ax = nr<br/>ax = -ENOSYS (pre-loaded)
    E->>H: dispatch through the syscall table
    Note over H: wait_event_interruptible():<br/>sleeps in TASK_INTERRUPTIBLE
    H-->>H: another task calls kill() →<br/>TIF_SIGPENDING set, sleeper woken
    H->>X: return -ERESTARTSYS  (regs->ax = -512)
    Note over X: ptrace syscall-exit trap fires HERE —<br/>a tracer can read -512 out of RAX
    X->>S: ti_work & _TIF_SIGPENDING
    S->>G: get_signal(&ksig)
    alt a userspace handler is installed
        G-->>S: true, ksig.ka.sa.sa_flags carries SA_RESTART
        Note over S: handle_signal():<br/>SA_RESTART set → ax = orig_ax, ip -= 2<br/>SA_RESTART clear → ax = -EINTR
        S->>U: build signal frame, jump to handler
        U->>U: handler returns → rt_sigreturn
        Note over U: RIP now points AT the syscall insn
        U->>E: the same 2 bytes execute again
    else no handler (ignored / stop / continue)
        G-->>S: false
        Note over S: tail switch:<br/>NOHAND/SYS/NOINTR → ax = orig_ax, ip -= 2<br/>RESTARTBLOCK → ax = __NR_restart_syscall
        S->>U: return to user mode, no frame built
        U->>E: the same 2 bytes execute again
    end

One interrupted read() from the syscall instruction to the re-executed syscall instruction. What it shows: the restart decision is taken in step 9/12, long after the handler has given up, and the only state it acts on is three fields of pt_regs (ax, orig_ax, ip) plus one bit of the delivered signal’s sigaction. The insight to take: userspace never observes a restart happening; from the program’s point of view the syscall instruction at that address simply took longer, and (on the handler path) its signal handler ran somewhere in the middle. Notice also that the ptrace exit trap in step 8 fires before the resolution in step 9 — that ordering is what makes the internal codes observable to a debugger and to nothing else, and it is what the Demonstrating It measurements below exploit.

The SA_RESTART Truth Table

This is the table the documentation does not print, and the reason SA_RESTART confuses people. There are three independent inputs — which ERESTART* code the handler returned, whether a userspace handler is about to run, and whether that handler’s sigaction has SA_RESTART — and the outcome is fully determined by the two switch statements quoted above.

ERESTART* codeHandler will run?SA_RESTART set?Code path takenOutcome userspace sees
ERESTARTSYS (512)yesyeshandle_signal, case -ERESTARTSYS → fallthroughtransparent restart — the same syscall re-runs
ERESTARTSYS (512)yesnohandle_signal, case -ERESTARTSYS → regs->ax = -EINTR-1 / EINTR
ERESTARTSYS (512)no(irrelevant)tail switch, case -ERESTARTSYStransparent restart
ERESTARTNOINTR (513)yesyeshandle_signal, case -ERESTARTNOINTRtransparent restart
ERESTARTNOINTR (513)yesnosame case — the flag is never testedtransparent restart (uninterruptible by design)
ERESTARTNOINTR (513)no(irrelevant)tail switchtransparent restart
ERESTARTNOHAND (514)yesyeshandle_signal, case -ERESTARTNOHAND → regs->ax = -EINTR-1 / EINTR — SA_RESTART does not help
ERESTARTNOHAND (514)yesnosame case-1 / EINTR
ERESTARTNOHAND (514)no(irrelevant)tail switchtransparent restart
ERESTART_RESTARTBLOCK (516)yesyeshandle_signal, case -ERESTART_RESTARTBLOCK → regs->ax = -EINTR-1 / EINTR — SA_RESTART does not help
ERESTART_RESTARTBLOCK (516)yesnosame case-1 / EINTR
ERESTART_RESTARTBLOCK (516)no(irrelevant)tail switch, ax = get_nr_restart_syscall(regs)restart through restart_syscall(), with the saved deadline

The complete SA_RESTART decision matrix for x86-64 at v6.12, derived from the two switch statements and confirmed row-by-row by the measurements in Demonstrating It. What it shows: SA_RESTART appears in exactly two of the twelve rows, and changes the outcome in exactly those two. Every other row is decided before the flag is consulted. The insight to take: the folk model “set SA_RESTART and my syscalls will restart” is right for one of four codes and wrong for the other three. Worse, the two codes that ignore it (NOHAND, RESTARTBLOCK) are precisely the ones used by the timeout-bearing calls people most want to protect — poll, select, nanosleep, clock_nanosleep. And note the counter-intuitive middle band: the three “no handler” rows all restart, which is why a SIGSTOP/SIGCONT pair usually does not break a blocked read(), while a caught SIGWINCH from a terminal resize does.

flowchart TD
  START["regs->ax holds an<br/>-ERESTART* code"] --> Q1{"get_signal():<br/>will a userspace<br/>handler run?"}
  Q1 -->|"no"| B{"which code?"}
  B -->|"512 / 513 / 514"| BRE["ax = orig_ax<br/>ip -= 2<br/>RE-ISSUE SAME SYSCALL"]
  B -->|"516"| BRB["ax = __NR_restart_syscall<br/>ip -= 2<br/>RE-ISSUE AS restart_syscall()"]
  Q1 -->|"yes"| A{"which code?"}
  A -->|"514 or 516"| AEI["ax = -EINTR<br/>SA_RESTART NEVER READ"]
  A -->|"513"| ARE["ax = orig_ax<br/>ip -= 2<br/>RE-ISSUE"]
  A -->|"512"| Q2{"ksig->ka.sa.sa_flags<br/>&amp; SA_RESTART ?"}
  Q2 -->|"set"| ARE
  Q2 -->|"clear"| AEI
  AEI --> UEI["userspace: read() == -1,<br/>errno == EINTR"]
  ARE --> UOK["userspace: never notices"]
  BRE --> UOK
  BRB --> UOK2["userspace: never notices,<br/>and the deadline was honoured"]

The same matrix as a decision tree, reading top to bottom the way the kernel executes it. What it shows: there is exactly one diamond in the whole graph that mentions SA_RESTART, and reaching it requires both “a handler will run” and “the code was ERESTARTSYS”. The insight to take: this graph is the answer to “why did my SA_RESTART not work?” — trace your call’s code to one of the four leaves and you will almost always find you landed on 514/516, where the flag is not consulted, or that no handler was going to run at all and the call restarted for a completely different reason.

Which Code Does Each Syscall Return? Measured, Not Guessed

signal(7) publishes the behaviour (restartable vs. never restarted) but not the mechanism (which internal code produces it). The mapping can be read out of the source, and it can also be observed directly: a ptrace tracer stopped at the syscall-exit trap sees the raw internal code in RAX before arch_do_signal_or_restart() rewrites it. The table below gives both — the code path from the v6.12 source, and the raw RAX value actually measured with gdb on this machine (method and full transcript in Demonstrating It).

Syscall (blocked, interrupted by a caught signal)Raw RAX measuredCodeSource of the codesignal(7) classification
read on a pipe−512ERESTARTSYSwait_event_interruptible in the pipe read pathrestartable with SA_RESTART
wait4−512ERESTARTSYSdo_wait()’s interruptible sleeprestartable with SA_RESTART
futex(FUTEX_WAIT), timeout == NULL−512ERESTARTSYS__futex_wait() returns it directlyrestartable with SA_RESTART
futex(FUTEX_WAIT), timeout != NULL−516ERESTART_RESTARTBLOCKfutex_wait() converts the 512 via set_restart_fn(restart, futex_wait_restart)never restarted
nanosleep (relative)−516ERESTART_RESTARTBLOCKdo_nanosleep() → hrtimer_nanosleep() → set_restart_fn(..., hrtimer_nanosleep_restart)never restarted
poll−516ERESTART_RESTARTBLOCKdo_sys_poll() returns 514; SYSCALL_DEFINE3(poll) upgrades it via set_restart_fn(..., do_restart_poll)never restarted
select−514ERESTARTNOHANDcore_sys_select() returns it; poll_select_finish() lets it throughnever restarted
pause−514ERESTARTNOHANDSYSCALL_DEFINE0(pause) returns it literallynever restarted
clock_nanosleep(TIMER_ABSTIME)−514ERESTARTNOHANDhrtimer_nanosleep()’s mode == HRTIMER_MODE_ABS branch downgrades 516 → 514never restarted
ppoll(not separately measured)ERESTARTNOHANDSYSCALL_DEFINE5(ppoll) does not call set_restart_fn; it returns poll_select_finish(...)never restarted
semop / semtimedop(not separately measured)no restart code at allipc/sem.c contains no ERESTART* at v6.12; the sleep loop stores and returns plain -EINTRnever restarted
socket read/write, SO_RCVTIMEO set(not separately measured)no restart code at allsock_intr_errno() returns plain -EINTRnever restarted
socket read/write, no timeout(not separately measured)ERESTARTSYSsock_intr_errno()’s MAX_SCHEDULE_TIMEOUT branchrestartable with SA_RESTART
fork / clone / vfork with a signal already pending(not observable — never reaches userspace)ERESTARTNOINTRcopy_process() in kernel/fork.cn/a — always restarted

Per-syscall restart codes at Linux 6.12, measured on a 7.1.13 kernel. What it shows: signal(7)‘s “never restarted” list is not one rule but three different mechanisms: ERESTARTNOHAND (select, pause, absolute clock_nanosleep), ERESTART_RESTARTBLOCK (poll, relative nanosleep, timed futex), and plain -EINTR with no restart machinery involved at all (semop, timed sockets). The insight to take: two calls that look interchangeable can sit in different rows — poll returns 516 while select returns 514, and futex(FUTEX_WAIT) changes from 512 to 516 purely by being given a timeout. The behaviour is identical for a caught signal (all give EINTR), but it diverges the moment no handler runs: the 516 rows resume with a corrected deadline, the 514 rows re-issue with whatever arguments are still in the registers, and the plain-EINTR rows do not resume at all.

Why select can get away with a plain re-issue

ERESTARTNOHAND re-issues the original syscall with the original register arguments — which, for a call with a timeout, would normally restart the clock. select gets away with it because on Linux the select syscall overwrites its own timeval argument in userspace memory with the remaining time before returning. poll_select_finish() does that write, and only if it fails does the code convert -ERESTARTNOHAND into -EINTR, with a comment that states the dependency outright: “because we’re not updating the timeval, we can’t restart the system call” (fs/select.c, v6.12). The two triggers for that fallback are a faulting write (a read-only timeval) and the STICKY_TIMEOUTS personality bit. So the argument-in-memory is the restart state for select, playing the role restart_block plays for poll — and the measurement below confirms it: after an interrupted 1-second select, the caller’s tv reads 0.7999s. This is also why the glibc pselect/ppoll wrappers pass a local copy of the timeout to the kernel: the syscall modifies it, and POSIX says it must not.

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.”

Correction to an earlier draft of this note

An earlier revision listed “the System V semaphore wait” among the restart_block users. That is wrong, and grepping the file settles it: ipc/sem.c at v6.12 contains no occurrence of any ERESTART* symbol at all. semtimedop()’s sleep loop writes -EINTR into queue.status directly and returns it — so semop/semtimedop are “never restarted” for a third reason, namely that they never enter the restart machinery. io_getevents was also listed without verification and is not confirmed here.

Who actually uses restart_block

The union in struct restart_block has exactly three arms at v6.12 — futex, nanosleep, and poll — but the fn pointer is generic, so more subsystems ride the mechanism using one of those arms or none at all.

UserArm of the unionContinuation (fn)What is savedWhy a plain re-issue would be wrong
nanosleep, relative clock_nanosleepnanosleephrtimer_nanosleep_restartclockid, expires (absolute), type, rmtpthe register argument says “10 s”; re-issuing would sleep 10 s again
poll, and do_restart_poll re-arming itselfpolldo_restart_pollufds, nfds, has_timeout, tv_sec/tv_nsec of the absolute end timesame — the timeout_msecs argument is relative
futex(FUTEX_WAIT) with a timeoutfutexfutex_wait_restartuaddr, val, bitset, flags | FLAGS_HAS_TIMEOUT, time (absolute)same, plus the futex value/bitset must be re-checked against the original expectation
alarmtimer (alarm_timer_nsleep)nanosleepalarm_timer_nsleep_restartas nanosleepsame
posix-cpu-timers (posix_cpu_nsleep)nanosleepposix_cpu_nsleep_restartas nanosleep, on a CPU clocksame
any call that does not want a restart—do_no_restart_syscallnothingit is the defensive default: it returns -EINTR unconditionally

The restart_block clients at v6.12 (the last three confirmed from the file list of commit 5abbe51a5262, which converted every restart->fn = … assignment in the tree to set_restart_fn()). What it shows: every client is a call with a deadline, and every one of them saves that deadline in absolute form. The insight to take: restart_block is not a general “resume anything” facility — it exists because relative timeouts are the one class of syscall argument that becomes stale the instant the call is interrupted. Arguments that are still valid after an interruption (a file descriptor, a buffer pointer, a futex address with no timeout) need no saved state at all, which is why those calls use the cheaper ERESTARTSYS/ERESTARTNOHAND re-issue instead.

sequenceDiagram
    autonumber
    participant U as User code
    participant K as nanosleep handler<br/>(hrtimer_nanosleep)
    participant RB as current->restart_block
    participant S as arch_do_signal_or_restart
    participant RS as sys_restart_syscall

    U->>K: syscall nr=35 (nanosleep), req = 500 ms
    Note over K: t0. deadline := now + 500 ms<br/>arms an hrtimer, sleeps
    Note over K: t0+200 ms: SIGSTOP arrives,<br/>signal_pending() → sleeper wakes
    K->>RB: nanosleep.clockid = CLOCK_MONOTONIC<br/>nanosleep.expires = t0 + 500 ms (ABSOLUTE)<br/>fn = hrtimer_nanosleep_restart
    K->>U: return -ERESTART_RESTARTBLOCK (-516)
    Note over U: (user code never runs — we are<br/>still inside the exit path)
    U->>S: exit_to_user_mode_loop → TIF_SIGPENDING
    Note over S: get_signal() handles the stop<br/>internally and returns FALSE
    S->>S: ax = __NR_restart_syscall (219)<br/>ip -= 2
    Note over U: task is STOPPED until t0+1500 ms,<br/>then SIGCONT resumes it
    U->>RS: the same 2 bytes re-execute, now as nr=219
    RS->>RB: restart->fn(restart)
    RB->>K: hrtimer_nanosleep_restart():<br/>HRTIMER_MODE_ABS at t0+500 ms
    Note over K: that deadline is already in the past<br/>→ returns immediately
    K->>U: return 0 (success)

A relative nanosleep(500 ms) that is stopped at t0+200 ms and resumed at t0+1500 ms. What it shows: the syscall number in RAX is replaced — the re-executed syscall instruction at the same address invokes restart_syscall (219), not nanosleep (35) — and the only thing carrying the original intent across that swap is the absolute expires field in current->restart_block. The insight to take: this is the one place in the whole restart mechanism where the restarted call is not the call the program made. The absolute deadline is what makes the answer correct: because expires is a point in time and not a duration, the 1.3 seconds the task spent stopped are accounted for for free, and the measurement below shows exactly this — the call returns 0 after 1.500 s of wall time, not after 2.000 s as a naive re-issue would give.

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.

Resolved — this note previously flagged the per-call mapping as unverified

An earlier revision carried an #uncertain callout here asking whether select/pselect return ERESTARTNOHAND rather than ERESTART_RESTARTBLOCK, and where socket SO_RCVTIMEO timeouts fit. Both are now settled, from source and from measurement (see the per-syscall table above and Demonstrating It below). select and pselect6 return -ERESTARTNOHAND from core_sys_select(); socket calls with a timeout do not return a restart code at all but plain -EINTR from sock_intr_errno(); semop/semtimedop likewise return plain -EINTR with no restart machinery. The correction the callout was hedging against was real: signal(7)‘s “never restarted” list is three mechanisms, not one.

So the second list is really three lists that happen to share an observable behaviour:

MechanismCallsWhat happens when a handler runsWhat happens when no handler runs
ERESTARTNOHAND (514)select, pselect6, pause, sigsuspend, clock_nanosleep(TIMER_ABSTIME), ppoll-EINTRre-issued, arguments unchanged (safe because select/ppoll rewrote their own timeout in user memory, and TIMER_ABSTIME never needed adjusting)
ERESTART_RESTARTBLOCK (516)poll, relative nanosleep/clock_nanosleep, timed futex, alarmtimer, POSIX CPU-clock sleeps-EINTRre-issued as restart_syscall, with an absolute deadline recovered from current->restart_block
plain -EINTR, no restart codesemop/semtimedop, socket calls with SO_RCVTIMEO/SO_SNDTIMEO set-EINTR-EINTR — the call simply fails; there is nothing for the restart machinery to do

Three routes to the same “never restarted with SA_RESTART” line in signal(7). What it shows: the observable behaviour under a caught signal is identical across all three rows, which is why the man page can collapse them into one list — but the behaviour under a stop signal, where no handler runs, is completely different. The insight to take: if you are debugging why a call did or did not come back after a SIGSTOP/SIGCONT (or after a signal you had set to SIG_IGN), the man-page list will not help you; you need the row. That is the practical payoff of knowing the internal code, and it is why the gdb recipe below is worth having.

Stop signals: the fourth column signal(7) does print

signal(7) has a second, much less famous subsection — “Interruption of system calls and library functions by stop signals” — which notes that “even in the absence of signal handlers, certain blocking interfaces can fail with the error EINTR after the process is stopped by one of the stop signals and then resumed via SIGCONT. This behavior is not sanctioned by POSIX.1, and doesn’t occur on other systems.” The Linux interfaces it names are the timed socket calls, epoll_wait/epoll_pwait, semop/semtimedop, and sigtimedwait/sigwaitinfo.

Map that onto the table above and it stops being a mystery: every interface on the stop-signal list is one that returns plain -EINTR rather than a restart code. Because -EINTR is not an ERESTART* value, neither switch statement matches it, regs->ax is left alone, and the -4 sails out to userspace — even though get_signal() returned false and the “no handler” column of the truth table would have restarted any of the three restart codes. The stop-signal list is, precisely, the list of calls that opted out of the restart mechanism.

The seccomp user-notification escape hatch

There is one documented way for a call in the “never restarted” list to be restarted anyway, and it is worth knowing because it looks like a kernel bug when you hit it. seccomp_unotify(2) describes a target process blocked in a seccomp user-space notification while its supervisor decides what to do; if a signal handler runs in the target during that wait, “the kernel will restart the target’s system call” and the supervisor receives a second notification for the same call. The man page flags the consequence explicitly: “One oddity is that system call restarting as described in this scenario will occur even for the blocking system calls listed in signal(7) that would never normally be restarted by the SA_RESTART flag.”

The mechanism falls straight out of this note. In seccomp_do_user_notification() (kernel/seccomp.c, v6.12) the target waits with wait_for_completion_interruptible(&n.ready), whose documented return on interruption is -ERESTARTSYS. That value is returned from the syscall-entry work, before the real handler ever runs — so the syscall that gets an ERESTARTSYS is whatever call triggered the filter, poll and nanosleep included. Those calls never produce ERESTARTSYS themselves, but here they never got as far as their own implementation. Row 1 of the truth table then applies, and with SA_RESTART set the call transparently restarts. Nothing special-cased it; the notification wait simply substituted its own restart verb for the one the call would have used. (Ownership note: Seccomp and seccomp-BPF and seccomp and Syscall Filtering own the notification protocol itself; this note owns only the restart consequence.)

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.

Demonstrating It

Everything above is directly observable. All measurements below were taken on Fedora 44, kernel 7.1.13-200.fc44.x86_64, glibc 2.43-8.fc44, GCC 16.2.1, GDB 17.2, on x86-64. (strace and ltrace are not installed on this machine, so the raw-register observations use gdb -batch with catch syscall instead — the recipe is given in full below and is a strictly better tool for this particular job, because it lets you read orig_ax and rip as well as rax.)

The truth table, executed

The harness installs a SIGUSR1 handler with and without SA_RESTART, forks a helper that pokes the parent partway through a blocking call, and times the result. The signal always arrives; the only variable is the flag.

== Path A: a handler runs (get_signal() -> true -> handle_signal) ==
-- ERESTARTSYS: SA_RESTART decides --
  read(pipe)  SA_RESTART=0 -> ret=-1 errno=EINTR   (Interrupted system call)  elapsed=0.150s  handler_hits=1
  read(pipe)  SA_RESTART=1 -> ret=1  errno=-       (ok)                       elapsed=0.500s  handler_hits=1
-- ERESTART_RESTARTBLOCK / ERESTARTNOHAND: SA_RESTART is never consulted --
  nanosleep(1s) SA_RESTART=0 -> ret=-1 errno=EINTR  elapsed=0.300s  rem=0.699965422s  hits=1
  nanosleep(1s) SA_RESTART=1 -> ret=-1 errno=EINTR  elapsed=0.300s  rem=0.699959711s  hits=1
  poll(1000ms)  SA_RESTART=0 -> ret=-1 errno=EINTR  elapsed=0.200s  hits=1
  poll(1000ms)  SA_RESTART=1 -> ret=-1 errno=EINTR  elapsed=0.200s  hits=1
  select(1s)    SA_RESTART=0 -> ret=-1 errno=EINTR  elapsed=0.200s  tv_left=0.799895s  hits=1
  select(1s)    SA_RESTART=1 -> ret=-1 errno=EINTR  elapsed=0.200s  tv_left=0.799927s  hits=1
  pause()       SA_RESTART=0 -> ret=-1 errno=EINTR  elapsed=0.200s  hits=1
  pause()       SA_RESTART=1 -> ret=-1 errno=EINTR  elapsed=0.200s  hits=1

== Path B: no handler runs (get_signal() -> false) ==
  read(pipe) stopped+continued (NO handler) -> ret=1 errno=-  elapsed=0.900s
  nanosleep(500ms) stopped 200ms..1500ms (NO handler) -> ret=0 errno=-  elapsed=1.500s  rem=0.299960819s
  pause() stopped 200ms..600ms then SIGWINCH@1200ms -> ret=-1 errno=EINTR  elapsed=1.200s

Read the numbers against the truth table, one row at a time:

  • read on a pipe, SA_RESTART=0 — the writer child was going to write at 500 ms; the signal arrived at 150 ms; read returned EINTR at 0.150 s. Row 2 of the table.
  • read on a pipe, SA_RESTART=1 — same signal at the same moment, but read returned 1 at 0.500 s, exactly when the writer wrote, with handler_hits=1 proving the handler did run. The syscall was re-issued underneath the program. Row 1.
  • nanosleep, poll, select, pause — all four returned EINTR at the moment of the signal, identically with and without SA_RESTART. The flag changed nothing, because none of these calls returns ERESTARTSYS. Rows 7–12.
  • select’s tv_left = 0.7999 s — the caller’s struct timeval was rewritten by the kernel with the remaining time. This is the Linux-specific behaviour that makes a plain ERESTARTNOHAND re-issue correct for select, and it is visible from userspace.
  • nanosleep’s rem = 0.6999 s — the classic “resume the sleep yourself” value, which is what a userspace retry loop must feed back into the next nanosleep.
  • read stopped and continued, no handler — SIGSTOP at 150 ms, SIGCONT at 500 ms, writer at 900 ms. read returned 1 at 0.900 s: it restarted through the “no handler” column and the program never saw an error. This is the row people forget exists, and it is why “I never installed a handler so my read is safe” is true for a different reason than they think.
  • nanosleep(500 ms) stopped from 200 ms to 1500 ms, no handler — returned 0 at 1.500 s. This is the restart_block proof. The absolute deadline t0 + 500 ms had already passed when SIGCONT arrived, so hrtimer_nanosleep_restart() returned immediately. A naive re-issue of nanosleep(500 ms) would have slept a fresh half-second and finished at 2.000 s. The 500 ms difference is the mechanism, measured.
  • pause() stopped from 200 ms to 600 ms — did not return at 600 ms. It kept waiting until a genuine caught signal (SIGWINCH at 1200 ms) arrived, and only then returned EINTR at 1.200 s. ERESTARTNOHAND under the no-handler column restarts; under the handler column it does not. Rows 9 and 7.

One genuine oddity in that output deserves its own note, because it surprised me and it is not documented anywhere I could find:

nanosleep's rem can be clobbered on a successful call

Look at the Path-B nanosleep line: it returned 0 (success) and yet rem reads 0.2999 s, a value that describes an intermediate state rather than the final one. The cause is in do_nanosleep() (kernel/time/hrtimer.c, v6.12): when the sleep is interrupted it calls nanosleep_copyout(restart, &rmt) — which writes the remaining time into the caller’s rmtp — before returning -ERESTART_RESTARTBLOCK. If the call then goes on to restart and succeed, that write is never undone. POSIX only defines rem when the call fails with EINTR, so this is not a standards violation, but code that reads rem unconditionally will read a stale value. Treat rem as meaningful only when nanosleep returned -1 with errno == EINTR.

Reading the raw internal code through the ptrace window

This is the measurement that produced the per-syscall table. The program blocks in exactly one syscall with a handler installed without SA_RESTART; gdb stops at the syscall-exit trap, which fires inside syscall_exit_to_user_mode() before arch_do_signal_or_restart() runs, so RAX still holds the unresolved internal code.

The portable recipe — useful anywhere strace is unavailable, and strictly more informative than strace here because it exposes orig_ax and rip too:

set confirm off
set pagination off
set follow-fork-mode parent
set detach-on-fork on
handle SIGUSR1 nostop noprint pass    # let the signal through without stopping
tbreak blocking_call                  # skip libc startup, which also does syscalls
run
catch syscall nanosleep               # arm only after we are in our own code
continue
printf "  entry trap: rax=%ld  orig_rax=%ld\n", $rax, $orig_rax
continue
printf "  EXIT  trap: rax=%ld  orig_rax=%ld  rip=%#lx\n", $rax, $orig_rax, $rip
x/2xb $rip-2
continue

Three of the lines are load-bearing and easy to get wrong. handle SIGUSR1 nostop noprint pass is mandatory: by default gdb intercepts the signal, so the inferior never reaches get_signal() and nothing is ever restarted. tbreak before catch syscall matters because glibc’s startup issues plenty of syscalls of the same name (an unqualified catch syscall read fires first on the loader reading the binary). And x/2xb $rip-2 prints the instruction the restart logic rewinds onto.

Output, one run per syscall (abridged to the two printf lines plus the byte dump):

---------- read (catch syscall read) ----------
  entry trap: rax=-38  orig_rax=0
  EXIT  trap: rax=-512  orig_rax=0  rip=0x7ffff7e9437d
0x7ffff7e9437b <syscall+27>:	0x0f	0x05
read                 libc-visible ret=-1 errno=4 (Interrupted system call)
---------- nanosleep ----------
  entry trap: rax=-38  orig_rax=35
  EXIT  trap: rax=-516  orig_rax=35  rip=0x7ffff7e9437d
---------- poll ----------
  entry trap: rax=-38  orig_rax=7
  EXIT  trap: rax=-516  orig_rax=7  rip=0x7ffff7e9437d
---------- select ----------
  entry trap: rax=-38  orig_rax=23
  EXIT  trap: rax=-514  orig_rax=23  rip=0x7ffff7e9437d
---------- pause ----------
  entry trap: rax=-38  orig_rax=34
  EXIT  trap: rax=-514  orig_rax=34  rip=0x7ffff7e9437d
---------- futex (FUTEX_WAIT, timeout = NULL) ----------
  entry trap: rax=-38  orig_rax=202
  EXIT  trap: rax=-512  orig_rax=202  rip=0x7ffff7e9437d
---------- futex_to (FUTEX_WAIT, timeout = 5 s) ----------
  entry trap: rax=-38  orig_rax=202
  EXIT  trap: rax=-516  orig_rax=202  rip=0x7ffff7e9437d
---------- wait4 ----------
  entry trap: rax=-38  orig_rax=61
  EXIT  trap: rax=-512  orig_rax=61  rip=0x7ffff7e9437d
---------- clock_nanosleep (TIMER_ABSTIME) ----------
  entry trap: rax=-38  orig_rax=230
  EXIT  trap: rax=-514  orig_rax=230  rip=0x7ffff7e9437d

Five separate claims from earlier in this note fall out of those twenty lines:

  1. The internal codes are real and observable. -512, -514, -516 appear verbatim in RAX. Every one of them turns into a libc-visible errno == 4 (EINTR) by the time the program itself runs — the last line of the read block shows the conversion.
  2. orig_ax holds the syscall number while ax holds the error. nanosleep shows orig_rax=35 and rax=-516 simultaneously. That is precisely the state regs->ax = regs->orig_ax restores from.
  3. The rewind target really is a two-byte instruction. x/2xb $rip-2 prints 0x0f 0x05 — the syscall opcode — at every single stop. regs->ip -= 2 lands exactly on it.
  4. The per-syscall mapping is as the source says. poll → 516 but select → 514; futex flips from 512 to 516 purely by acquiring a timeout; clock_nanosleep(TIMER_ABSTIME) → 514, matching the mode == HRTIMER_MODE_ABS downgrade branch in hrtimer_nanosleep() exactly.
  5. RAX is pre-loaded with -ENOSYS at entry. Every entry-trap line reads rax=-38. That is not the handler’s doing — entry_SYSCALL_64 pushes it as part of the register frame (PUSH_AND_CLEAR_REGS rax=$-ENOSYS, arch/x86/entry/entry_64.S, v6.12), so a tracer that cancels the syscall by setting orig_ax = -1 gets ENOSYS for free. The errno Convention and Negative Return Values covers why that particular value.

What this does not show

ERESTARTNOINTR (513) cannot be produced by this harness, and in fact cannot be observed from userspace at all by design: its only in-tree producer at v6.12 is copy_process(), and its meaning is “restart unconditionally”, so fork() never returns it to anyone. Confirming it requires reading kernel/fork.c rather than running anything.

Uncertain

Verify by measurement: the rows of the per-syscall table marked “not separately measured” — ppoll, semop/semtimedop, and socket read/write with and without SO_RCVTIMEO. Reason: each of those claims is read from source (SYSCALL_DEFINE5(ppoll) not calling set_restart_fn; ipc/sem.c containing no ERESTART* symbol at all; sock_intr_errno()’s MAX_SCHEDULE_TIMEOUT ternary) but was not run through the gdb harness the way the other nine were, so a wrapper-level conversion I did not notice could still change the observed value. To resolve: extend the peek2.c harness with a ppoll case, a semtimedop case on a System V semaphore, and a pair of socket cases differing only in setsockopt(SO_RCVTIMEO), and read $rax at the exit trap for each. I would expect -514, no restart code (plain -4), -512, and plain -4 respectively. uncertain

Also unverified: which specific blocking primitive each of epoll_wait, sigtimedwait, msgrcv/msgsnd, io_getevents, mq_receive and getrandom uses. signal(7) places them confidently on one list or the other, but I traced none of them to a return statement, so their rows are absent from the table above rather than guessed at.

Design History — Why It Looks Like This

The restart machinery is a 1980s BSD decision that Linux inherited and then had to keep patching. Neil Brown’s LWN series Ghosts of Unix past names signal delivery as its first example of an unfixable design, and the restart semantics specifically as one of the reasons: the Berkeley developers, having introduced sigvec() to make signals reliable, “also changed the semantics of some system calls so that they would restart rather than abort if a signal arrived while the system call was active” (Brown 2010). Brown enumerates the option space the way the kernel still sees it today — “Options include waiting for the system call to complete, aborting it completely, allowing it to return partial results, or allowing it to restart after the signal has been handled. Each of these can be the right answer in different contexts” — and observes that sigvec(), and later System V’s sigaction(), existed largely to let the programmer choose among them. His verdict on the result is blunt: “the exact semantics of interrupting a system call was hard to get right.” The four ERESTART* codes are Linux’s encoding of that option space, and the fact that there are four rather than one is the cost of “each of these can be the right answer.”

The kernel’s own comments carry the archaeology too. fs/select.c has preserved, unchanged for decades, a note explaining why select is ERESTARTNOHAND rather than ERESTARTSYS:

/*
 * We can actually return ERESTARTSYS instead of EINTR, but I'd
 * like to be certain this leads to no problems. So I return
 * EINTR just for safety.
 *
 * Update: ERESTARTSYS breaks at least the xview clock binary, so
 * I'm trying ERESTARTNOHAND which restart only when you want to.
 */

That is the “we do not break userspace” rule operating in 1990s miniature: a more transparent restart was tried, one existing binary (the XView desktop clock) misbehaved, and the weaker code was adopted permanently. The comment appears twice in the file at v6.12 — once above core_sys_select() and once above compat_core_sys_select() — and is the direct reason select sits in signal(7)‘s “never restarted” list today.

timeline
    title How the restart machinery accumulated, with the evidence for each step
    4.2BSD era : sigvec() adds per-signal blocking : "some system calls ... would restart rather than abort" (Brown, LWN 2010)
    System V : sigaction() adds flag bits : SA_RESTART becomes the programmer-visible switch
    early Linux : ERESTARTSYS tried for select() : reverted — "ERESTARTSYS breaks at least the xview clock binary" (fs/select.c comment, still present at v6.12)
    early Linux : ERESTARTNOHAND introduced : "which restart only when you want to" — same comment
    2.6.x : restart_block + sys_restart_syscall : relative timeouts get an absolute deadline they can resume from
    2016 (v4.5) : 95d97adb2bb8 : get_nr_restart_syscall() switched from TIF_IA32 to TS_COMPAT
    2021 (v5.12-rc4) : 5abbe51a5262 + 8c150ba2fb59 : compat bit moved into restart_block.arch_data after a 32-bit sleep() restarted as madvise() under a 64-bit debugger
    2026 (v6.12 / v6.18) : unchanged : the four codes and both switch statements are identical across the two LTS trees

The accretion of the restart mechanism, each entry tied to a source that was actually read for this note. What it shows: nothing here was designed in one go — ERESTARTNOHAND exists because ERESTARTSYS broke a desktop clock, restart_block exists because relative timeouts could not survive a re-issue, and arch_data exists because a debugger could make a 32-bit sleep restart as the wrong syscall entirely. The insight to take: the shape of the truth table is archaeological, not principled. That is the strongest argument for reading the two switch statements rather than reasoning about what the semantics “should” be.

The most recent structural change is from 2021 and is a small masterclass in how subtle this code is. Commit 5abbe51a5262 (Oleg Nesterov, merged for v5.12-rc4) introduced set_restart_fn() and arch_set_restart_data(), converting every restart->fn = …; return -ERESTART_RESTARTBLOCK; pair in fs/select.c, kernel/futex.c, kernel/time/alarmtimer.c, kernel/time/hrtimer.c and kernel/time/posix-cpu-timers.c into the single helper. The commit message calls it “Preparation for fixing get_nr_restart_syscall() on X86 for COMPAT” — and the bug it was preparing to fix, described in the companion commit 8c150ba2fb59, is worth reading in full:

Suppose that a 64bit debugger plays with a 32bit tracee and

  • Tracee calls sleep(2) // TS_COMPAT is set
  • User interrupts the tracee by CTRL-C after 1 sec and does “(gdb) call func()”
  • gdb saves the regs by PTRACE_GETREGS … sets %rip='func' and %orig_rax=-1 … PTRACE_CONT // TS_COMPAT is cleared
  • func() hits int3 … Restore original regs by PTRACE_SETREGS … PTRACE_CONT

get_nr_restart_syscall() wrongly returns __NR_restart_syscall==219, the tracee calls ia32_sys_call_table[219] == sys_madvise.

A 32-bit sleep() that was interrupted, debugged, and resumed would restart as madvise(). The fix moved the “was this a compat syscall?” bit out of the volatile thread-info status field and into the restart_block itself, which survives the return to user mode — which is why struct restart_block carries an arch_data field and why arch_set_restart_data() on x86 is exactly restart->arch_data = current_thread_info()->status (arch/x86/include/asm/thread_info.h, v6.12). The get_nr_restart_syscall() quoted earlier in this note reads that saved bit back:

#ifdef CONFIG_IA32_EMULATION
	if (current->restart_block.arch_data & TS_COMPAT)
		return __NR_ia32_restart_syscall;
#endif

The moral for anyone reasoning about this code: the restart decision is taken after the syscall has finished and after a tracer may have rewritten the register frame, so every input it depends on must be stored somewhere a tracer cannot plausibly corrupt. restart_block is that place.

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, and its real user is fork. It forces a restart even when a handler runs and SA_RESTART is clear. An earlier revision of this note described its users vaguely as “the kernel-internal restart of certain sigreturn/setup paths”; grepping v6.12 settles it — the canonical producer is copy_process() in kernel/fork.c, which does this immediately after collecting delayed multiprocess signals:

    spin_lock_irq(&current->sighand->siglock);
    if (!(clone_flags & CLONE_THREAD))
            hlist_add_head(&delayed.node, &current->signal->multiprocess);
    recalc_sigpending();
    spin_unlock_irq(&current->sighand->siglock);
    retval = -ERESTARTNOINTR;
    if (task_sigpending(current))
            goto fork_out;

    The logic is exactly what the code name promises. If a signal is already pending when fork()/clone()/vfork() starts, the kernel refuses to build a child that would inherit an ambiguous signal state; it bails out before any observable side effect and demands an unconditional retry. Because no child was created and nothing was published, restarting is not merely safe but mandatory — returning EINTR here would make fork() spuriously fail for a program that merely had a SIGCHLD queued. This also means fork() is the one syscall for which SA_RESTART is irrelevant in both directions: it always restarts. The surrounding comment explains the ordering — “Force any signals received before this point to be delivered before the fork happens. Collect up signals sent to multiple processes that happen during the fork and delay them so that they appear to happen after the fork.”

  • Returning -EINTR where -ERESTARTSYS was meant. The mirror of the above. A driver that returns -EINTR from an interruptible wait has hard-coded “never restart” into a call that SA_RESTART should have covered; since -EINTR (4) matches neither switch, the value passes through untouched. This is the single most common way a well-behaved-looking driver ends up on the wrong side of the truth table, and mutex_lock_interruptible()’s -EINTR return is its most common source (see Where the ERESTART code is actually produced above).

  • Assuming a restart means the syscall never started. It does not. signal(7) is explicit for the I/O case: “If an I/O call on a slow device has already transferred some data by the time it is interrupted by a signal handler, then the call will return a success status (normally, the number of bytes transferred).” A partially-completed read() returns a short count, not EINTR and not a restart, because by then it has a real result to report. A correct retry loop therefore has to handle three outcomes — full success, short count, and EINTR — not two.

  • SA_RESTART is per-sigaction, not per-process. The gate handle_signal() reads is ksig->ka.sa.sa_flags, the flags of the signal currently being delivered. Two signals arriving at the same blocked read() can resolve differently, and a library that installs a handler for SIGCHLD without SA_RESTART silently makes every blocking call in the process EINTR-prone whenever a child exits — a failure mode raised in the LWN discussion of Brown’s article, where a commenter notes that “if, for example, you set a signal handler for SIGCHLD you have major problems since SA_RESTART can’t be considered reliable (portably)” (comment thread).

  • 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