Fundamentals 28 min read

Unlocking Linux Signal Stacks: Why They Exist and How to Use Them Safely

This article explains the purpose of Linux's separate signal stack, how it differs from the regular process stack, the mechanics of signal delivery, practical code examples for enabling and using it, and why it matters for stability, real‑time processing, multithreading, and performance optimization.

Deepin Linux
Deepin Linux
Deepin Linux
Unlocking Linux Signal Stacks: Why They Exist and How to Use Them Safely

Overview of the Linux Signal Stack

A signal stack is a dedicated temporary stack that the kernel switches to when a signal handler runs. It isolates signal handling from the main user stack, allowing the handler to execute safely even if the main stack is corrupted or exhausted.

Reasons for a Separate Stack

If the main stack overflows, the handler would have no safe space to run.

Signal handlers may need more stack space than the main stack provides.

Using a separate stack prevents interference between normal program flow and signal handling.

Enabling and Configuring a Signal Stack

The signal stack is disabled by default. It can be enabled with the sigaltstack() system call, which takes a stack_t structure describing the stack address, size, and flags.

#include <signal.h>
int sigaltstack(const stack_t *ss, stack_t *oldss);

typedef struct {
    void   *ss_sp;    /* start address */
    int     ss_flags; /* SS_DISABLE or SS_ONSTACK */
    size_t  ss_size;  /* size in bytes */
} stack_t;

Typical Allocation and Registration

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

int main() {
    stack_t ss;
    ss.ss_sp = malloc(SIGSTKSZ);
    if (!ss.ss_sp) { perror("malloc"); return 1; }
    ss.ss_size = SIGSTKSZ;
    ss.ss_flags = 0;               /* enable the stack */
    if (sigaltstack(&ss, NULL) == -1) {
        perror("sigaltstack");
        free(ss.ss_sp);
        return 1;
    }
    /* install a handler that uses SA_ONSTACK */
    struct sigaction sa = {0};
    sa.sa_handler = SIG_DFL;      /* replace with real handler */
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_ONSTACK;
    sigaction(SIGSEGV, &sa, NULL);
    /* ... program logic ... */
    free(ss.ss_sp);
    return 0;
}

Common Use Cases

Stack‑Overflow Protection

When a program crashes with SIGSEGV due to a stack overflow, a handler installed on a signal stack can still run, log the error, and exit cleanly.

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

void segv_handler(int signum) {
    printf("Caught SIGSEGV – stack overflow detected.
");
    exit(1);
}

int main() {
    stack_t ss;
    ss.ss_sp = malloc(SIGSTKSZ);
    ss.ss_size = SIGSTKSZ;
    ss.ss_flags = 0;
    if (sigaltstack(&ss, NULL) == -1) { perror("sigaltstack"); return 1; }

    struct sigaction sa = {0};
    sa.sa_handler = segv_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_ONSTACK;
    if (sigaction(SIGSEGV, &sa, NULL) == -1) { perror("sigaction"); return 1; }

    /* provoke overflow */
    int *p = NULL; *p = 1;   /* generates SIGSEGV */
    free(ss.ss_sp);
    return 0;
}

Multithreaded Signal Handling

Each thread can allocate its own signal stack, allowing independent handling of signals such as SIGUSR1 without cross‑thread interference.

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

void *thread_func(void *arg) {
    stack_t ss;
    ss.ss_sp = malloc(SIGSTKSZ);
    ss.ss_size = SIGSTKSZ;
    ss.ss_flags = 0;
    if (sigaltstack(&ss, NULL) == -1) { perror("sigaltstack"); pthread_exit(NULL); }

    void handler(int s) {
        printf("Thread %lu received signal %d
", (unsigned long)pthread_self(), s);
    }
    struct sigaction sa = {0};
    sa.sa_handler = handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_ONSTACK;
    sigaction(SIGUSR1, &sa, NULL);

    while (1) { sleep(1); }
    return NULL; /* never reached */
}

int main() {
    pthread_t t1, t2;
    pthread_create(&t1, NULL, thread_func, NULL);
    pthread_create(&t2, NULL, thread_func, NULL);
    sleep(3); pthread_kill(t1, SIGUSR1);
    sleep(3); pthread_kill(t2, SIGUSR1);
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    return 0;
}

Debugging Tips

Always check the return values of malloc, sigaltstack, and sigaction.

Use gdb to set breakpoints at the signal handler, sigaltstack, and sigaction to observe stack switches.

Inspect the process's signal mask with info signals to ensure signals are not blocked.

For multithreaded programs, info threads and thread <id> help isolate issues.

Build Instructions

Install the GNU toolchain (e.g., sudo apt update && sudo apt install build-essential), then compile with: gcc -Wall -g -o signal_demo signal_demo.c Running the binary will demonstrate the signal stack in action: a generated SIGSEGV is caught by the handler running on the alternate stack, printing a message before the program exits.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

LinuxC programmingsystem callsSignal StackSIGSTKSZ
Deepin Linux
Written by

Deepin Linux

Research areas: Windows & Linux platforms, C/C++ backend development, embedded systems and Linux kernel, etc.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.