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
-EINTRto 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), andERESTART_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’sSA_RESTARTflag, and whether a handler will run, and then either rewinds the instruction pointer to re-issue thesyscallinstruction 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 ifSA_RESTARTis set on the signal’s handler; otherwise give userspaceEINTR.”ERESTARTNOHAND— “restart me if no handler runs for this signal; otherwiseEINTR.”ERESTART_RESTARTBLOCK— “restart me, but throughrestart_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 intoregs->orig_ax;-1means “not from a syscall,” e.g. an interrupted page fault).syscall_get_error(...)reads the negative value the handler left inregs->ax.ERESTART_RESTARTBLOCKandERESTARTNOHAND→regs->ax = -EINTR. A handler is running, so these becomeEINTR. This is the key fact:ERESTART_RESTARTBLOCK(used by timeout calls) is unconditionally converted toEINTRwhenever a handler runs — theSA_RESTARTflag is never even consulted for it on this path.ERESTARTSYS→ restart only ifksig->ka.sa.sa_flags & SA_RESTART; otherwiseEINTR. This is the textbookSA_RESTARTbehavior — and the gate is read from this specific signal’ssigaction, so two signals delivered to the same blocked call can resolve differently.ERESTARTNOINTR(andERESTARTSYSthat fell through withSA_RESTART) → restart unconditionally:regs->ax = regs->orig_axputs the syscall number back intoax(it had been clobbered with the-ERESTARTcode), andregs->ip -= 2rewinds 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;
};
};fnis the continuation — the function to call to resume the operation.- The union holds the per-call saved state. For
nanosleep,expiresis the absolute deadline; forfutex, the address and bitset; forpoll, 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 = ¤t->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 = ¤t->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. Perhandle_signal, those restart iffSA_RESTART. - The second list is mostly calls that return
-ERESTART_RESTARTBLOCK. Perhandle_signal, that code is converted to-EINTRwhenever a handler runs — and theSA_RESTARTbit is never tested for it. So no matter what flags you set, if you have a handler for the delivered signal, these calls returnEINTR.
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_RESTARTBLOCKvsERESTARTNOHANDreturn codes call-by-call (e.g. whetherselect/pselectreturnERESTARTNOHANDrather thanERESTART_RESTARTBLOCK, and where socket SO_RCVTIMEO timeouts fit). Reason: the broad correspondence is verified fromhandle_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 whichERESTART*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
-ERESTARTSYSwithout a pending signal. The header contract requiressignal_pending()to be set. If a buggyioctlor driver returnsERESTARTSYSwith no signal pending, neither resolution path triggers (no_TIF_SIGPENDING), and the raw512could leak toward userspace logic — a real class of driver bugs. The fix is always to gate the return onsignal_pending(current). ERESTARTNOINTRis rare and strong. It forces a restart even when a handler runs andSA_RESTARTis clear. It is reserved for calls that have no observable side effect yet and must appear atomic, such as the kernel-internal restart of certainsigreturn/setup paths; misusing it would make a call un-interruptible, defeating signals likeSIGINT.- Stale
restart_block. Because there is a singlerestart_blockper task, a call that uses it must setfnevery time; thedo_no_restart_syscalldefault 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_syscallis set in thenanosleepentry wrappers). - Absolute-timer calls do not restart via
restart_block. Note themode == HRTIMER_MODE_ABSbranch above converts to-ERESTARTNOHANDinstead — an absoluteclock_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
- EINTR and Interrupted System Calls — the userspace-facing symptom this machinery produces; the retry-loop burden and the
EINTRvsEAGAINdistinction - SA_RESTART and Signal-Interrupted Syscalls — the per-handler
sigactionflag thathandle_signalreads to resolveERESTARTSYS - Returning to Userspace and exit_to_user_mode — the
exit_to_user_mode_loopthat callsarch_do_signal_or_restart - Thread Info Flags and Syscall Exit Work —
_TIF_SIGPENDING/_TIF_NOTIFY_SIGNALthat gate the signal path - The pt_regs Register Frame —
regs->ax,regs->orig_ax,regs->ipmanipulated by the restart logic - ptrace and Syscall Tracing — the one observer that can see
ERESTART*codes at the exit trap - The errno Convention and Negative Return Values — why
ERESTART*values sit above the userspace errno range - Linux System Call Interface MOC — parent map (§6, Errors, Restart, and Interruption)
- Linux IPC MOC — signals are the trigger for every restart decision