System Calls vs Signals

System Calls (syscalls)

A system call (syscall) is the programmatic way in which a computer program requests a service from the operating system[a] on which it is executed.

Program → Kernel
Purpose: Request services from the kernel

Examples:

// File operations
int fd = open("/tmp/file.txt", O_RDONLY);  // syscall
read(fd, buffer, size);                     // syscall
close(fd);                                  // syscall

// Process operations
pid_t pid = fork();                         // syscall
exit(0);                                    // syscall

// Memory
void* ptr = mmap(...);                      // syscall

// Network
int sock = socket(AF_INET, SOCK_STREAM, 0); // syscall

How it works:

// User space code
write(1, "Hello", 5);
    ↓
// Triggers software interrupt/trap
syscall instruction (x86-64)
    ↓
// Switch to kernel mode
Kernel handles the request
    ↓
// Return to user space
Result returned to program


Signals

Direction: Kernel → Program (or Program → Program via kernel)

Purpose: Notify program of events (interrupts, errors, user actions)

Common Signals:

SIGINT  (2)   // Ctrl+C - Interrupt
SIGTERM (15)  // Termination request (kill command default)
SIGKILL (9)   // Force kill (cannot be caught!)
SIGSEGV (11)  // Segmentation fault
SIGCHLD (17)  // Child process terminated
SIGSTOP (19)  // Stop process (cannot be caught)
SIGALRM (14)  // Timer expired
SIGUSR1 (10)  // User-defined signal 1
SIGUSR2 (12)  // User-defined signal 2

How Ctrl+C Works:

User presses Ctrl+C
    ↓
Terminal driver detects it
    ↓
Terminal sends SIGINT to foreground process
    ↓
Kernel delivers signal to process
    ↓
Process handles it (or terminates by default)


Key Differences

Aspect System Calls Signals
Direction Program → Kernel Kernel → Program
Timing Synchronous Asynchronous
Initiation Program requests External event
Purpose Get kernel services Notify of events
Control flow Blocking/waiting Interrupts execution
Return Returns value No return value

💡 System programs can use system calls (syscalls) to invoke kernel functions from user space.

💡 Signals are used as a one-way asynchronous notification mechanism for a process.
The Ctrl-C handler is an example of a type of communication mechanism in Linux


Signal Handling

Custom Handler:

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

void handle_sigint(int sig) {
    printf("\\nCaught SIGINT (Ctrl+C)! Signal: %d\\n", sig);
    // Don't exit - keep running
}

int main() {
    // Register signal handler
    signal(SIGINT, handle_sigint);
    // or more portable:
    struct sigaction sa;
    sa.sa_handler = handle_sigint;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    sigaction(SIGINT, &sa, NULL);

    while(1) {
        printf("Running... (Press Ctrl+C)\\n");
        sleep(1);
    }
    return 0;
}

Ignoring Signals:

signal(SIGINT, SIG_IGN);  // Ignore Ctrl+C
signal(SIGTERM, SIG_DFL); // Restore default behavior

Blocking Signals:

sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGINT);

// Block SIGINT
sigprocmask(SIG_BLOCK, &set, NULL);

// Critical section - SIGINT won't interrupt

// Unblock
sigprocmask(SIG_UNBLOCK, &set, NULL);


Sending Signals

From Command Line:

# Send SIGTERM (polite termination request)
kill 1234
kill -15 1234
kill -SIGTERM 1234

# Send SIGKILL (force kill - cannot be caught)
kill -9 1234
kill -SIGKILL 1234

# Send to all processes in group
killall process_name

# Ctrl+C sends SIGINT
# Ctrl+Z sends SIGTSTP (stop)
# Ctrl+\\ sends SIGQUIT

From Code:

#include <signal.h>
#include <sys/types.h>

// Send signal to specific process
kill(pid, SIGTERM);

// Send to process group
kill(-pgid, SIGINT);

// Send to self
raise(SIGUSR1);

// Send to child processes
kill(0, SIGCHLD);  // All in current process group


Signal Safety

Problem: Signal handlers can interrupt code at any point!

// UNSAFE! malloc is not signal-safe
void unsafe_handler(int sig) {
    char* buf = malloc(100);  // DANGEROUS!
    printf("Signal!\\n");       // Also unsafe
    free(buf);
}

Signal-Safe Functions:

Only these can be used in signal handlers:

// SAFE signal handler
void safe_handler(int sig) {
    const char msg[] = "Signal received\\n";
    write(STDERR_FILENO, msg, sizeof(msg)-1);
    _exit(1);  // Not exit()!
}


Real-World Example: Graceful Shutdown

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

volatile sig_atomic_t shutdown_requested = 0;

void handle_shutdown(int sig) {
    // Set flag (signal-safe operation)
    shutdown_requested = 1;
}

int main() {
    // Setup signal handlers
    signal(SIGINT, handle_shutdown);   // Ctrl+C
    signal(SIGTERM, handle_shutdown);  // kill command

    printf("Server running. Press Ctrl+C to shutdown gracefully.\\n");

    while (!shutdown_requested) {
        // Do work
        printf("Working...\\n");
        sleep(1);

        // Use syscalls for actual work
        // write(), read(), etc.
    }

    printf("\\nShutting down gracefully...\\n");
    // Cleanup: close files, flush buffers, etc.

    return 0;
}