Interruption and Restarting of System Calls in Linux: Complete Guide
Learn Interruption and Restarting of System Calls in Linux and how signals impact system call execution.
EINTR · SA_RESTART · Slow Devices | EmbeddedPathashala
What Happens When a Signal Interrupts a System Call?
Consider this scenario: your program calls read() on a terminal — it blocks, waiting for input. While it’s waiting, a signal arrives. The signal handler runs. When the handler returns, what happens to the read()?
By default, the system call fails with errno = EINTR (“Interrupted system call”). Your program receives -1 and must decide what to do.
You have two options: manually restart the call, or use SA_RESTART to have the kernel automatically restart it.
read() returns -1
errno = EINTR
Program must handle this
Kernel re-issues read() automatically
Program sees no interruption
read() eventually returns data
read(), readv(), write(), writev() on slow devices (terminals, pipes, sockets, FIFOs)ioctl() on slow deviceswait(), waitpid(), wait3(), wait4(), waitid()open() (when it can block, e.g., on FIFOs)accept(), connect(), send(), recv() and variantsmq_receive(), mq_send() and timed variantsflock(), fcntl() (file locking)sem_wait(), sem_timedwait()pthread_mutex_lock(), pthread_cond_wait() and variantspoll(), ppoll()select(), pselect() (SUSv3 leaves behaviour unspecified)epoll_wait(), epoll_pwait()io_getevents()semop(), semtimedop()msgrcv(), msgsnd()sleep(), nanosleep(), clock_nanosleep()pause(), sigsuspend(), sigtimedwait(), sigwaitinfo()read()/write() are only restarted for “slow devices” (terminals, pipes, sockets). File I/O on regular disk files is not interruptible (disk I/O uses the buffer cache and completes near-immediately).#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
static volatile sig_atomic_t got_signal = 0;
static void handler(int sig)
{
got_signal = 1;
}
/*
* Safe read() wrapper that retries on EINTR.
* This is what you must do for all blocking I/O calls
* when NOT using SA_RESTART.
*/
ssize_t safe_read(int fd, void *buf, size_t count)
{
ssize_t n;
while (1) {
n = read(fd, buf, count);
if (n == -1 && errno == EINTR) {
/*
* read() was interrupted by a signal handler.
* The handler ran and returned. Try again.
*/
printf("[safe_read] read() interrupted (EINTR). Retrying...\n");
continue;
}
break; /* success, EOF, or real error */
}
return n;
}
int main(void)
{
struct sigaction sa;
sa.sa_handler = handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0; /* No SA_RESTART — we handle EINTR manually */
sigaction(SIGALRM, &sa, NULL);
/* Send SIGALRM in 2 seconds — will interrupt the read() */
alarm(2);
printf("Waiting for input (type something, or wait 2s for SIGALRM)...\n");
char buf[128];
ssize_t n = safe_read(STDIN_FILENO, buf, sizeof(buf) - 1);
if (n > 0) {
buf[n] = '\0';
printf("Read %zd bytes: %s", n, buf);
} else if (n == 0) {
printf("EOF\n");
} else {
perror("read failed");
}
if (got_signal)
printf("(SIGALRM fired during wait)\n");
return 0;
}
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>
static void sigusr1_handler(int sig)
{
/* Using write() — async-signal-safe */
const char msg[] = "[Handler] SIGUSR1 fired — read() will be restarted.\n";
write(STDOUT_FILENO, msg, sizeof(msg) - 1);
}
int main(void)
{
struct sigaction sa;
sa.sa_handler = sigusr1_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART; /* Tell kernel to restart interrupted syscalls */
sigaction(SIGUSR1, &sa, NULL);
printf("PID=%d. Reading from stdin. Send SIGUSR1 to interrupt:\n", (int)getpid());
printf(" kill -USR1 %d\n\n", (int)getpid());
char buf[64];
/*
* With SA_RESTART:
* - You send SIGUSR1 while this read() is blocking.
* - The handler runs and prints its message.
* - When the handler returns, the KERNEL automatically restarts read().
* - You do NOT see EINTR; the read() continues waiting for input.
* - No special retry loop needed in application code!
*/
ssize_t n = read(STDIN_FILENO, buf, sizeof(buf) - 1);
if (n > 0) {
buf[n] = '\0';
printf("Read: %s", buf);
} else {
perror("read");
}
return 0;
}
kill -USR1 <pid> several times. You’ll see the handler message each time, but the read() keeps waiting for your input — it was automatically restarted.#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
static volatile sig_atomic_t timed_out = 0;
static void alarm_handler(int sig)
{
timed_out = 1;
/* Do NOT set SA_RESTART — we WANT read() to return EINTR */
}
/*
* Read with timeout.
* Returns:
* n > 0 : bytes read
* n == 0 : EOF
* n < 0 : error or timeout (check errno: EINTR = timed out)
*/
ssize_t read_with_timeout(int fd, void *buf, size_t len, unsigned int secs)
{
struct sigaction sa, old_sa;
sa.sa_handler = alarm_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0; /* No SA_RESTART — we want EINTR on timeout */
sigaction(SIGALRM, &sa, &old_sa);
timed_out = 0;
alarm(secs); /* Set the timeout */
ssize_t n = read(fd, buf, len); /* Block here — interrupted by SIGALRM */
alarm(0); /* Cancel the alarm if read returned before timeout */
sigaction(SIGALRM, &old_sa, NULL); /* Restore old SIGALRM handler */
return n;
}
int main(void)
{
printf("You have 5 seconds to type something:\n");
fflush(stdout);
char buf[128];
ssize_t n = read_with_timeout(STDIN_FILENO, buf, sizeof(buf) - 1, 5);
if (n > 0) {
buf[n] = '\0';
printf("You typed: %s", buf);
} else if (n == 0) {
printf("EOF\n");
} else if (timed_out) {
printf("\nTimeout! No input within 5 seconds.\n");
} else {
perror("read");
}
return 0;
}
select()/poll() with timeouts became universal. The key is deliberately NOT using SA_RESTART for the alarm handler, so read() returns EINTR which you interpret as a timeout.siginterrupt() lets you change the SA_RESTART setting for a signal after its handler is already installed, without having to re-register the handler.
#include <signal.h>
/*
* siginterrupt(sig, flag):
* flag = 1: handler for 'sig' WILL interrupt blocking syscalls (EINTR)
* flag = 0: handler for 'sig' will NOT interrupt (SA_RESTART behaviour)
*
* Note: SUSv4 marks siginterrupt() as obsolete.
* Prefer setting sa_flags in sigaction() directly.
*/
int siginterrupt(int sig, int flag);
/* Example: make SIGALRM interrupt blocking syscalls */
siginterrupt(SIGALRM, 1); /* equivalent to NOT having SA_RESTART */
/* Example: make SIGUSR1 NOT interrupt blocking syscalls */
siginterrupt(SIGUSR1, 0); /* equivalent to SA_RESTART */
siginterrupt() works by fetching the current signal disposition with sigaction(), tweaking the SA_RESTART bit, and calling sigaction() again. This means it is not atomic; prefer setting sa_flags in your initial sigaction() call.EINTR (“Interrupted system call”) is set in errno when a blocking system call returns -1 because it was interrupted by a signal handler. The signal handler ran and returned normally, but the system call was not restarted. The application must handle this by either retrying the call or treating it as an error.SA_RESTART is a flag in sigaction()‘s sa_flags. When set, the kernel automatically re-issues certain interrupted system calls after the signal handler returns, instead of making them fail with EINTR. It is a per-signal flag — different signals can have different restart behaviour.select(), poll(), ppoll(), epoll_wait(), and epoll_pwait() are NEVER automatically restarted, even if SA_RESTART is set. They always return EINTR when interrupted. This is by design — the caller should re-evaluate the file descriptor set after a signal. You must always handle EINTR for these calls in a retry loop.ssize_t safe_read(int fd, void *buf, size_t n) { ssize_t r; while ((r = read(fd, buf, n)) == -1 && errno == EINTR) continue; return r; } — This loop retries on EINTR and returns on any other result (success, EOF, or a different error).alarm(n) to schedule SIGALRM after n seconds, then call read(). If the user doesn’t respond within n seconds, SIGALRM fires, the handler runs, and read() returns -1 with errno == EINTR. Check a flag set by the handler to distinguish timeout from other interruptions. Call alarm(0) to cancel if read succeeds in time.TEMP_FAILURE_RETRY(expr) is a glibc macro (from <unistd.h>, available when _GNU_SOURCE is defined) that evaluates the expression in a loop, retrying as long as the result is -1 and errno is EINTR. It is equivalent to the manual while-loop EINTR retry pattern. Usage: ssize_t n; TEMP_FAILURE_RETRY(n = read(fd, buf, len));