22.12 & 22.13 — IPC with Signals & Legacy APIs

 

22.12 & 22.13 — IPC with Signals & Legacy APIs
Why signals are a poor IPC mechanism, and the old System V/BSD signal APIs you may find in legacy code

22.12 — Interprocess Communication with Signals

Signals can technically be used to communicate between processes — but they are generally a poor choice for IPC. Understanding why helps you make the right design decisions.

Limitations of Signals as IPC

Why Signals Are Unsuitable for General IPC
Limitation Details
Asynchronous complexity Standard signal handlers must follow strict async-signal-safety rules. Reentrancy, race conditions, and global variable access are all tricky to get right.
Not queued (standard signals) If the same standard signal is sent 5 times while pending, only 1 is delivered. Data loss is guaranteed under load.
Low bandwidth Each signal carries at most one word of data (with sigqueue). Compare this to pipes (unlimited bytes) or shared memory (megabytes).
Realtime limits Even realtime signals have a hard queue limit (RLIMIT_SIGPENDING). Under heavy load, sigqueue() returns EAGAIN.
No request-reply flow After sending a signal, you have no built-in way to wait for a response. You must set up a second channel.

When Are Signals Legitimately Used for Process Communication?

  • Process synchronization — Parent waits for child to initialize before proceeding (using SIGUSR1 as a “ready” signal)
  • Daemon control — SIGHUP to re-read config, SIGTERM to shut down gracefully, SIGUSR1/SIGUSR2 for custom control
  • Job control — Shell sends SIGSTOP/SIGCONT to manage foreground/background processes
  • Timer expiration — SIGALRM from alarm(), POSIX timer signals
  • Event notification — SIGCHLD when a child exits
/* signal_sync_demo.c - Parent/child synchronization via signals */
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>

static volatile sig_atomic_t child_ready = 0;

void handler(int sig) {
    child_ready = 1;
}

int main(void) {
    /* Parent blocks SIGUSR1 before fork so it doesn't miss it */
    sigset_t block, prev;
    sigemptyset(&block);
    sigaddset(&block, SIGUSR1);
    sigprocmask(SIG_BLOCK, &block, &prev);

    struct sigaction sa = { .sa_handler = handler };
    sigemptyset(&sa.sa_mask);
    sigaction(SIGUSR1, &sa, NULL);

    pid_t child = fork();

    if (child == 0) {
        /* Child: do initialization work, then signal parent */
        printf("Child PID=%d: initializing...\n", getpid());
        sleep(2);  /* Simulate initialization */
        printf("Child: initialization complete, signaling parent\n");
        kill(getppid(), SIGUSR1);  /* Tell parent we're ready */

        /* Continue with actual work */
        sleep(5);
        printf("Child: done\n");
        exit(0);
    }

    /* Parent: wait for child to be ready using sigsuspend */
    printf("Parent PID=%d: waiting for child to initialize...\n", getpid());

    while (!child_ready)
        sigsuspend(&prev);  /* atomically unblock SIGUSR1 and wait */

    sigprocmask(SIG_SETMASK, &prev, NULL);
    printf("Parent: child is ready! Proceeding with work.\n");

    /* Now do work that depends on child being initialized */
    printf("Parent: doing work...\n");
    sleep(2);

    wait(NULL);
    printf("Parent: child exited\n");
    return 0;
}
/* daemon_control.c - Using signals to control a daemon */
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <syslog.h>

static volatile sig_atomic_t reload_config = 0;
static volatile sig_atomic_t shutdown_requested = 0;

void sighup_handler(int sig) {
    reload_config = 1;   /* flag: main loop will reload */
}

void sigterm_handler(int sig) {
    shutdown_requested = 1;  /* flag: main loop will exit cleanly */
}

int main(void) {
    /* Install daemon control signal handlers */
    struct sigaction sa;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART;

    sa.sa_handler = sighup_handler;
    sigaction(SIGHUP, &sa, NULL);

    sa.sa_handler = sigterm_handler;
    sigaction(SIGTERM, &sa, NULL);

    openlog("myDaemon", LOG_PID, LOG_DAEMON);
    syslog(LOG_INFO, "Daemon started (PID=%d)", getpid());

    printf("Daemon PID=%d running.\n", getpid());
    printf("  kill -HUP %d   → reload config\n", getpid());
    printf("  kill -TERM %d  → graceful shutdown\n\n", getpid());

    while (!shutdown_requested) {
        if (reload_config) {
            reload_config = 0;
            printf("Reloading configuration...\n");
            syslog(LOG_INFO, "Config reloaded");
        }
        /* Main daemon work */
        sleep(1);
        printf(".");
        fflush(stdout);
    }

    printf("\nShutdown signal received. Cleaning up...\n");
    syslog(LOG_INFO, "Daemon shutting down gracefully");
    closelog();
    return 0;
}

22.13 — Earlier Signal APIs: System V and BSD

Before POSIX standardized signals, both System V and BSD UNIX had their own signal APIs. You will encounter these in older codebases. Linux supports both for compatibility. All new code should use the POSIX API (sigaction, sigprocmask, etc.).

System V Signal API

System V Functions → POSIX Equivalents
System V Function POSIX Equivalent Notes
sigset(sig, handler) sigaction(sig, &sa, NULL) Provides reliable handler. Can pass SIG_HOLD instead of handler to just add to mask.
sighold(sig) sigprocmask(SIG_BLOCK, …) Adds signal to process mask
sigrelse(sig) sigprocmask(SIG_UNBLOCK, …) Removes signal from process mask
sigignore(sig) signal(sig, SIG_IGN) or sigaction Sets disposition to SIG_IGN
sigpause(sig) sigsuspend() Removes ONE signal from mask and suspends
/* sysv_api_demo.c - System V signal API (for legacy porting only) */
#define _XOPEN_SOURCE 500
#include <stdio.h>
#include <signal.h>
#include <unistd.h>

void handler(int sig) {
    printf("Handler: caught signal %d\n", sig);
}

int main(void) {
    /* POSIX-preferred equivalent shown in comments */

    /* System V: sigset() -- equivalent to sigaction() with reliable semantics */
    sigset(SIGINT, handler);       /* POSIX: sigaction(SIGINT, &sa, NULL) */

    /* System V: sighold() -- equivalent to sigprocmask(SIG_BLOCK) for one sig */
    sighold(SIGINT);               /* POSIX: sigaddset + sigprocmask SIG_BLOCK */

    printf("SIGINT blocked (held). Press Ctrl+C -- signal will be pending.\n");
    sleep(3);

    /* System V: sigrelse() -- equivalent to sigprocmask(SIG_UNBLOCK) */
    sigrelse(SIGINT);              /* POSIX: sigprocmask(SIG_UNBLOCK, ...) */
    printf("SIGINT unblocked (released)\n");

    sleep(1);

    /* System V: sigignore() -- equivalent to signal(sig, SIG_IGN) */
    sigignore(SIGINT);             /* POSIX: signal(SIGINT, SIG_IGN) */
    printf("SIGINT now ignored. Ctrl+C will have no effect.\n");
    sleep(2);

    /* System V: sigset() with SIG_HOLD -- adds to mask without changing handler */
    sigset(SIGTERM, SIG_HOLD);     /* POSIX: sigprocmask(SIG_BLOCK, ...) */

    printf("Done. PID=%d\n", getpid());
    return 0;
}

BSD Signal API

BSD Functions → POSIX Equivalents
BSD Function POSIX Equivalent Notes
sigvec(sig, vec, ovec) sigaction(sig, sa, old_sa) sigvec struct ≈ sigaction struct. sv_mask is int (not sigset_t), limited to 31 signals on 32-bit.
sigblock(mask) sigprocmask(SIG_BLOCK, …) Returns previous mask as int
sigsetmask(mask) sigprocmask(SIG_SETMASK, …) Sets absolute mask value
sigmask(sig) sigset + sigaddset Converts signal number to bitmask. Returns 32-bit mask value.
sigpause(sigmask) sigsuspend() BSD version takes a mask int; System V version takes a single signal
/* bsd_api_demo.c - BSD signal API (legacy porting reference) */
#define _BSD_SOURCE
#include <stdio.h>
#include <signal.h>
#include <unistd.h>

void handler(int sig) {
    printf("Handler: caught signal %d\n", sig);
}

int main(void) {
    /* BSD: sigvec -- analog of sigaction */
    struct sigvec vec, old_vec;
    vec.sv_handler = handler;
    vec.sv_mask = 0;       /* sigmask() to set which signals to block in handler */
    vec.sv_flags = 0;      /* SV_INTERRUPT = don't restart system calls (non-default) */
    sigvec(SIGINT, &vec, &old_vec);

    /* BSD: sigblock -- block multiple signals at once using bitmask */
    int old_mask = sigblock(sigmask(SIGINT) | sigmask(SIGQUIT));
    /* POSIX equivalent:
       sigset_t mask;
       sigemptyset(&mask);
       sigaddset(&mask, SIGINT); sigaddset(&mask, SIGQUIT);
       sigprocmask(SIG_BLOCK, &mask, NULL); */

    printf("SIGINT and SIGQUIT blocked. PID=%d\n", getpid());
    sleep(3);

    /* BSD: sigsetmask -- set mask to an absolute value */
    sigsetmask(old_mask);  /* restore old mask -- unblocks SIGINT and SIGQUIT */
    /* POSIX: sigprocmask(SIG_SETMASK, &old_mask, NULL) */

    printf("Signals unblocked\n");
    sleep(2);
    return 0;
}
/* Implementing System V functions using POSIX (for learning/portability) */
#define _POSIX_C_SOURCE 200809L
#include <signal.h>
#include <errno.h>

typedef void (*sighandler_t)(int);

/* Implement sighold() using POSIX sigprocmask */
int my_sighold(int sig) {
    sigset_t mask;
    sigemptyset(&mask);
    sigaddset(&mask, sig);
    return sigprocmask(SIG_BLOCK, &mask, NULL);
}

/* Implement sigrelse() using POSIX sigprocmask */
int my_sigrelse(int sig) {
    sigset_t mask;
    sigemptyset(&mask);
    sigaddset(&mask, sig);
    return sigprocmask(SIG_UNBLOCK, &mask, NULL);
}

/* Implement sigignore() using sigaction */
int my_sigignore(int sig) {
    struct sigaction sa;
    sa.sa_handler = SIG_IGN;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    return sigaction(sig, &sa, NULL);
}

/* Implement sigpause() using sigsuspend */
int my_sigpause(int sig) {
    sigset_t mask;
    /* Get current mask */
    sigprocmask(SIG_SETMASK, NULL, &mask);
    /* Remove just this signal from the mask */
    sigdelset(&mask, sig);
    /* Atomically: use this mask and suspend */
    return sigsuspend(&mask);
    /* sigsuspend always returns -1 with errno=EINTR */
}

Interview Questions — IPC with Signals & Legacy APIs

Q1. Why are signals generally not suitable for general-purpose IPC?
Answer:

Three main reasons: (1) Standard signals are not queued — duplicates are dropped, so you lose information under load. (2) Low bandwidth — each signal carries at most one word of data (with sigqueue). Pipes, sockets, and shared memory can transfer megabytes. (3) Asynchronous complexity — handlers must be async-signal-safe, you cannot use most standard library functions in them, and race conditions with global state are difficult to avoid. Signals are suitable for process synchronization and event notification, but not for data transfer.

Q2. What signals are conventionally used to control a daemon process?
Answer:

SIGHUP is traditionally sent to a daemon to re-read its configuration file (reload without restart). SIGTERM is for a graceful shutdown (clean up and exit). SIGINT is for interactive termination. SIGUSR1 and SIGUSR2 are application-defined — commonly used for toggling debug logging, rotating log files, or other daemon-specific controls. Most daemons handle these signals by setting flags in the handler and acting on them in the main loop.

Q3. What is the main structural difference between the BSD sigvec and POSIX sigaction?
Answer:

The sigvec structure’s sv_mask field is an integer (bitmask), not a sigset_t. On 32-bit architectures this limits the mask to 31 signals. POSIX sigaction uses sigset_t which supports the full range of signals. The other difference is sv_flags: BSD used SV_INTERRUPT to mean “do NOT restart interrupted system calls” (because restarting was the BSD default), whereas POSIX sigaction requires SA_RESTART to enable restarting (no restart is the default).

Q4. What does sigblock() do and what is its POSIX equivalent?
Answer:

BSD sigblock(mask) adds the signals specified in the integer bitmask to the process signal mask. It returns the previous mask as an integer. The POSIX equivalent is sigprocmask(SIG_BLOCK, &new_mask, &old_mask) where new_mask is a sigset_t. The BSD sigmask(sig) macro converts a signal number to the corresponding bit in the integer mask — POSIX uses sigset_t + sigaddset() for the same purpose.

Q5. You see sigset() in old code. What does it do differently from the standard signal() function?
Answer:

sigset() is the System V reliable signal handler installer. Unlike old signal(), it provides reliable semantics: the signal handler is NOT reset on entry, the signal IS blocked during handler execution. It also accepts SIG_HOLD as the handler argument — which adds the signal to the process mask without changing the current handler. sigset() is marked obsolete in SUSv4 and should be replaced with sigaction() in new code.

Chapter 22 — Complete Summary

Chapter 22 Key Takeaways
Topic Key Point
Core dumps Created on certain fatal signals. Controlled by ulimit -c and /proc/sys/kernel/core_pattern. Suppressed for set-UID programs.
SIGKILL/SIGSTOP Cannot be caught, blocked, or ignored. Always work as last resort.
SIGCONT Always resumes stopped process even if blocked/ignored. Discards pending stop signals.
Sleep states TASK_INTERRUPTIBLE (S) = signal wakes it. TASK_UNINTERRUPTIBLE (D) = SIGKILL can’t kill it.
Hardware signals SIGSEGV, SIGFPE etc. Never return from handler normally — use siglongjmp() or accept termination.
Sync vs async Synchronous = generated by the process itself, delivered immediately. Async = external, slight delay.
Signal order Linux: ascending number. SUSv3 says implementation-defined for standard signals.
signal() vs sigaction() Always use sigaction(). signal() has inconsistent semantics across UNIX versions.
Realtime signals Queued, ordered, carry data. Send with sigqueue(), receive with SA_SIGINFO handler.
sigsuspend() Atomically unblock + wait. Solves the sigprocmask+pause race condition.
sigwaitinfo() Synchronously wait without a handler. Faster than sigsuspend+handler.
signalfd() Read signals as file descriptor. Perfect for epoll-based event loops.
Signals as IPC Poor choice — not queued, low bandwidth, async complexity. Use for process sync and event notification only.
Legacy APIs System V: sigset/sighold/sigrelse. BSD: sigvec/sigblock. Use POSIX equivalents in new code.

Chapter 22 Complete!

You have covered all advanced signal topics. Revisit any section or go back to the index.

← Back to Chapter Index Review Realtime Signals

Leave a Reply

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