Ch20.17 – sigaction()

 

Ch20.17 – sigaction()
Linux System Programming Β· EmbeddedPathashala
πŸ“‘ Topic 17 of 19
🎯 Advanced
πŸ’» 4 Code Examples
❓ Interview Q&A
πŸ”‘ Key Terms
sigaction() struct sigaction sa_handler sa_mask sa_flags SA_RESTART SA_NODEFER SA_RESETHAND SA_SIGINFO
Why sigaction() over signal()?

signal() is the simple, historical API for installing signal handlers, but its behaviour varies across UNIX implementations β€” especially regarding whether the handler is reset to default after invocation, and whether interrupted system calls are automatically restarted.

sigaction() is the POSIX-preferred API. It gives you full control: you can specify which signals to block during the handler, whether to auto-restart syscalls, retrieve the old disposition without changing it, and more. Always use sigaction() in production code.

πŸ“‹ Prototype and struct sigaction
#include <signal.h>

int sigaction(int sig, const struct sigaction *act, struct sigaction *oldact);
/* Returns 0 on success, or -1 on error */

struct sigaction {
    void        (*sa_handler)(int);   /* SIG_DFL, SIG_IGN, or handler address */
    sigset_t    sa_mask;              /* signals to block during handler */
    int         sa_flags;             /* SA_RESTART, SA_NODEFER, etc. */
    void        (*sa_restorer)(void); /* NOT for application use */
};
Field Purpose
sa_handler Handler function, SIG_DFL, or SIG_IGN
sa_mask Extra signals blocked while handler runs (in addition to sig itself)
sa_flags Bitmask of options (SA_RESTART, SA_NODEFER, SA_RESETHAND, SA_SIGINFO…)
The signal itself is automatically blocked during its own handler execution β€” you don’t need to add it to sa_mask. This prevents recursive invocation.
βš™οΈ sa_flags Reference
Flag Effect
SA_RESTART Automatically restart interrupted system calls (read, write, accept…)
SA_NODEFER Do NOT block the signal during its own handler (allows reentrant delivery)
SA_RESETHAND Reset disposition to SIG_DFL after first delivery (one-shot handler)
SA_SIGINFO Use sa_sigaction instead of sa_handler; handler gets siginfo_t details
SA_NOCLDSTOP For SIGCHLD: don’t send when child is stopped/resumed
SA_NOCLDWAIT For SIGCHLD: don’t create zombies when children terminate
SA_ONSTACK Use alternate signal stack (set up via sigaltstack())
πŸ’» Code Example 1 – Basic sigaction() handler
/* Install a handler for SIGINT using sigaction()
   Compile: gcc -o sa_basic sa_basic.c
   Run: ./sa_basic  then press Ctrl-C */

#include <stdio.h>
#include <signal.h>
#include <unistd.h>

static void sigint_handler(int sig)
{
    /* write() is async-signal-safe; printf() is NOT */
    const char msg[] = "\nCaught SIGINT. Press Ctrl-C again to exit.\n";
    write(STDOUT_FILENO, msg, sizeof(msg) - 1);
}

int main(void)
{
    struct sigaction sa;

    sa.sa_handler = sigint_handler;
    sigemptyset(&sa.sa_mask);   /* no extra signals blocked */
    sa.sa_flags   = SA_RESTART; /* restart interrupted syscalls */

    if (sigaction(SIGINT, &sa, NULL) == -1) {
        perror("sigaction");
        return 1;
    }

    printf("PID %ld β€” Waiting. Press Ctrl-C.\n", (long)getpid());
    for (;;)
        pause(); /* sleep until a signal arrives */

    return 0;
}
πŸ’» Code Example 2 – Retrieve existing disposition without changing it
/* Pass NULL for act to retrieve current disposition only */

#include <stdio.h>
#include <signal.h>

int main(void)
{
    struct sigaction old;

    /* act = NULL β†’ don't change, just retrieve */
    if (sigaction(SIGTERM, NULL, &old) == -1) {
        perror("sigaction");
        return 1;
    }

    if (old.sa_handler == SIG_DFL)
        printf("SIGTERM disposition: SIG_DFL (default)\n");
    else if (old.sa_handler == SIG_IGN)
        printf("SIGTERM disposition: SIG_IGN (ignored)\n");
    else
        printf("SIGTERM disposition: custom handler at %p\n",
               (void *)old.sa_handler);

    return 0;
}
πŸ’» Code Example 3 – sa_mask: block SIGUSR2 while handling SIGUSR1
/* Use sa_mask to block SIGUSR2 during the SIGUSR1 handler.
   This prevents SIGUSR2 from interrupting our handler mid-execution. */

#include <stdio.h>
#include <signal.h>
#include <unistd.h>

static void usr1_handler(int sig)
{
    printf("In SIGUSR1 handler β€” SIGUSR2 is blocked now\n");
    sleep(3); /* SIGUSR2 sent during this sleep will be pending */
    printf("SIGUSR1 handler done β€” SIGUSR2 will be delivered now\n");
}

static void usr2_handler(int sig)
{
    printf("SIGUSR2 delivered\n");
}

int main(void)
{
    struct sigaction sa1, sa2;

    /* Install SIGUSR2 handler first */
    sa2.sa_handler = usr2_handler;
    sigemptyset(&sa2.sa_mask);
    sa2.sa_flags = 0;
    sigaction(SIGUSR2, &sa2, NULL);

    /* Install SIGUSR1 handler, blocking SIGUSR2 during it */
    sa1.sa_handler = usr1_handler;
    sigemptyset(&sa1.sa_mask);
    sigaddset(&sa1.sa_mask, SIGUSR2); /* block SIGUSR2 inside handler */
    sa1.sa_flags = 0;
    sigaction(SIGUSR1, &sa1, NULL);

    printf("PID %ld β€” send SIGUSR1, then SIGUSR2 quickly\n", (long)getpid());
    pause();
    pause();
    return 0;
}
πŸ’» Code Example 4 – SA_RESETHAND (one-shot handler)
/* SA_RESETHAND: handler fires once, then disposition resets to SIG_DFL.
   On the second SIGINT, default action (terminate) occurs. */

#include <stdio.h>
#include <signal.h>
#include <unistd.h>

static void once_handler(int sig)
{
    printf("\nFirst Ctrl-C caught. Second will terminate the process.\n");
}

int main(void)
{
    struct sigaction sa;

    sa.sa_handler = once_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESETHAND; /* reset to SIG_DFL after first delivery */

    sigaction(SIGINT, &sa, NULL);

    printf("PID %ld β€” press Ctrl-C twice\n", (long)getpid());
    for (;;)
        pause();

    return 0;
}
SA_RESETHAND is useful for “clean-up on first interrupt, terminate on second” patterns in CLI tools.
❓ Interview Questions
Q1. What is the main advantage of sigaction() over signal()?
sigaction() has well-defined, portable behaviour (POSIX). It lets you specify extra signals to block during the handler (sa_mask), control SA_RESTART behaviour, get the previous disposition atomically, and use one-shot or extended handler modes. signal() behaviour varies by OS and implementation.
Q2. What does SA_RESTART do?
Normally, if a signal arrives while a process is blocked in a slow system call (read, write, accept, etc.), the call fails with EINTR. With SA_RESTART set, the kernel automatically restarts such calls after the handler returns, so the application does not need to handle EINTR.
Q3. Is the current signal automatically blocked during its own handler?
Yes. When a signal handler is invoked, the signal that triggered it is automatically added to the process signal mask for the duration of the handler. This prevents the handler from being recursively interrupted by the same signal. SA_NODEFER disables this behaviour.
Q4. What is the purpose of sa_mask in struct sigaction?
sa_mask specifies additional signals to block while the handler is running, on top of the automatically blocked signal itself. This is useful to prevent a related signal from interrupting handler logic that should complete atomically.
Q5. How do you retrieve the current disposition of SIGTERM without changing it?
Call sigaction(SIGTERM, NULL, &old). Passing NULL for the act argument tells sigaction() to only retrieve (not change) the disposition. The current disposition is written into old.

Leave a Reply

Your email address will not be published. Required fields are marked *