Why Linux Signals Aren’t One‑Shot Anymore: Exploring signal, sigaction, and sysv_signal
This article explains how Linux signals work, why the original signal() API behaved as a one‑shot handler, how modern glibc’s signal() maps to rt_sigaction with safer flags, and demonstrates the differences with code samples, strace traces, and kernel source snippets.
Signal fundamentals
In Linux a signal is an asynchronous notification from the kernel to a process that a specific event has occurred. Signals can be generated by hardware (e.g., Ctrl‑C), by another process ( kill, sigqueue) or by the process itself ( raise).
Historical problems with the traditional signal() API
Early UNIX implemented signal() so that the installed handler was one‑shot : after the first delivery the disposition was reset to SIG_DFL. The kernel achieved this with the flags SA_ONESHOT (alias SA_RESETHAND) and SA_NOMASK (alias SA_NODEFER), which also meant the signal was not automatically blocked during handling.
#ifdef __ARCH_WANT_SYS_SIGNAL
/* For backwards compatibility. Functionality superseded by sigaction. */
SYSCALL_DEFINE2(signal, int, sig, __sighandler_t, handler)
{
struct k_sigaction new_sa, old_sa;
int ret;
new_sa.sa.sa_handler = handler;
new_sa.sa.sa_flags = SA_ONESHOT | SA_NOMASK;
sigemptyset(&new_sa.sa.sa_mask);
ret = do_sigaction(sig, &new_sa, &old_sa);
return ret ? ret : (unsigned long)old_sa.sa.sa_handler;
}
#endif /* __ARCH_WANT_SYS_SIGNAL */The flag SA_ONESHOT is defined as #define SA_ONSHOT SA_RESETHAND. When a signal becomes pending the kernel records it in the process descriptor and later selects it via get_signal_to_deliver() (see kernel/signal.c).
The diagram shows how the kernel chooses a pending signal and invokes the handler.
Experiment: comparing signal() , sysv_signal() and glibc’s signal()
The following program installs a handler that prints a message, performs heavy work, and then returns.
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
#define MSG "OMG , I catch the signal SIGINT
"
#define MSG_END "OK,finished process signal SIGINT
"
int do_heavy_work()
{
int i, k;
srand(time(NULL));
for (i = 0; i < 100000000; i++)
k = rand() % 1234589;
return 0;
}
void signal_handler(int signo)
{
write(2, MSG, strlen(MSG));
do_heavy_work();
write(2, MSG_END, strlen(MSG_END));
}
int main()
{
char input[1024] = {0};
#if defined TRADITIONAL_SIGNAL_API
if (syscall(SYS_signal, SIGINT, signal_handler) == -1)
#elif defined SYSTEMV_SIGNAL_API
if (sysv_signal(SIGINT, signal_handler) == -1)
#else
if (signal(SIGINT, signal_handler) == SIG_ERR)
#endif
{
fprintf(stderr, "signal failed
");
return -1;
}
printf("input a string:
");
if (fgets(input, sizeof(input), stdin) == NULL)
{
fprintf(stderr, "fgets failed(%s)
", strerror(errno));
return -2;
}
else
{
printf("you entered:%s", input);
}
return 0;
}Compiling and running the program under three configurations yields distinct behaviours:
glibc signal() (default): the handler is persistent, the signal is automatically blocked during execution, and system calls are automatically restarted ( SA_RESTART).
sysv_signal() : implements the unreliable System V semantics – the handler is one‑shot, the signal is not blocked ( SA_NODEFER), and blocking system calls are interrupted with EINTR.
traditional signal() (forced with -DTRADITIONAL_SIGNAL_API): identical to sysv_signal() – one‑shot, no automatic blocking, no restart.
Strace confirms the system calls used. For glibc the trace shows:
rt_sigaction(SIGINT, {0x8048736, [INT], SA_RESTART}, {SIG_DFL, [], 0}, 8) = 0For sysv_signal() and the traditional API the trace shows a signal() system call with SA_ONESHOT and SA_NODEFER flags.
Signal blocking and re‑entrancy
The kernel function signal_delivered() adds the currently handled signal to the blocked set unless the handler specified SA_NODEFER. This explains why glibc’s signal() (which uses rt_sigaction with SA_RESTART and without SA_NODEFER) prevents re‑entrancy.
void signal_delivered(int sig, siginfo_t *info, struct k_sigaction *ka,
struct pt_regs *regs, int stepping)
{
sigset_t blocked;
clear_restore_sigmask();
sigorsets(&blocked, ¤t->blocked, &ka->sa.sa_mask);
if (!(ka->sa.sa_flags & SA_NODEFER))
sigaddset(&blocked, sig);
set_current_blocked(&blocked);
tracehook_signal_handler(sig, info, ka, regs, stepping);
}When SA_NODEFER is absent, the current signal is automatically added to the blocked set, making the handler non‑re‑entrant.
System‑call interruption
Signals can interrupt system calls, causing them to return -EINTR. The kernel checks the SA_RESTART flag in handle_signal() to decide whether to restart the call. The legacy signal() and sysv_signal() lack SA_RESTART, so calls such as fgets() fail with “Interrupted system call”. Glibc’s version includes SA_RESTART, so the same calls automatically resume.
static void handle_signal(unsigned long sig, siginfo_t *info,
struct k_sigaction *ka, struct pt_regs *regs)
{
if (syscall_get_nr(current, regs) >= 0) {
switch (syscall_get_error(current, regs)) {
case -ERESTART_RESTARTBLOCK:
case -ERESTARTNOHAND:
regs->ax = -EINTR;
break;
case -ERESTARTSYS:
if (!(ka->sa.sa_flags & SA_RESTART)) {
regs->ax = -EINTR;
break;
}
/* fallthrough */
case -ERESTARTNOINTR:
regs->ax = regs->orig_ax;
regs->ip -= 2;
break;
}
}
/* ... */
}Conclusion
Glibc’s signal() wrapper is reliable: it invokes rt_sigaction with sensible defaults ( SA_RESTART, the signal itself blocked, no SA_NODEFER), eliminating the one‑shot and re‑entrancy problems of the historic API. The legacy signal() system call and sysv_signal() retain their historical quirks for compatibility, which motivated the introduction of real‑time signals with more robust semantics.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
ITPUB
Official ITPUB account sharing technical insights, community news, and exciting events.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
