Chapter 64: Sending Signals: kill() and raise()

Chapter Objectives

By the end of this chapter, you will be able to:

  • Understand the fundamental role of signals as an Inter-Process Communication (IPC) mechanism in Linux.
  • Explain the operational differences and use cases for the kill() and raise() system calls.
  • Implement C programs that programmatically send signals to other processes using their Process IDs (PIDs).
  • Configure and write applications that manage process groups and send signals to entire groups of related processes.
  • Debug common permission errors and logical flaws in signal-sending applications.
  • Apply signal-sending techniques to control and manage multi-process applications on an embedded Raspberry Pi 5 system.

Introduction

In the intricate dance of a modern Linux system, processes rarely operate in isolation. They must communicate, coordinate, and control one another to perform complex tasks. While previous chapters may have explored more structured forms of Inter-Process Communication (IPC) like pipes or shared memory, this chapter delves into one of the oldest and most fundamental mechanisms: signals. Signals are the system’s lightweight, asynchronous notifications, serving as the software equivalent of a hardware interrupt. They report exceptional events, from user actions like pressing Ctrl+C to catastrophic errors like an illegal memory access.

Understanding how to programmatically send signals is not merely an academic exercise; it is a critical skill for any embedded Linux developer. In an embedded environment, robust process management is paramount. Imagine a system with a central management process responsible for monitoring the health of various peripheral drivers or application services. If a service becomes unresponsive, the manager must be able to terminate it cleanly and perhaps restart it. This is often accomplished by sending signals. From shutting down a data logging service gracefully with SIGTERM to forcing a misbehaving driver to stop with SIGKILL, the ability to send signals gives your applications the power to act as supervisors, ensuring system stability and reliability. This chapter will equip you with the knowledge to wield this power effectively using the standard kill() and raise() system calls on your Raspberry Pi 5.

Technical Background

The Nature of Signals in UNIX and Linux

To truly appreciate the function of kill() and raise(), one must first understand the nature of signals themselves. Signals are a form of asynchronous Inter-Process Communication (IPC) in UNIX-like operating systems, including Linux. Their history dates back to the earliest days of UNIX research at Bell Labs. They were designed as a simple, efficient way for the kernel to notify a process that a specific event has occurred. This event could be a hardware exception, such as a division-by-zero error, or a software-initiated event, such as a user pressing an interrupt key or one process needing to communicate with another.

A signal is often described as a “software interrupt” because it alters the receiving process’s flow of execution. When a signal is delivered to a process, the kernel interrupts the process’s normal execution. The process must then handle the signal. For every signal, a process can take one of three possible actions:

  1. Default Action: Every signal has a default action associated with it. For many signals, like SIGSEGV (segmentation fault), the default action is to terminate the process. For others, like SIGCHLD (a child process has terminated), the default action is to be ignored.
  2. Catch the Signal: A process can register a custom function, known as a signal handler, to be executed when the signal is delivered. This allows the application to perform custom logic, such as cleaning up resources before exiting.
  3. Ignore the Signal: A process can explicitly choose to ignore a signal.

However, not all signals can be caught or ignored. The SIGKILL and SIGSTOP signals are special; they are handled directly by the kernel and cannot be intercepted by the process. This provides a guaranteed mechanism for the system administrator (and the kernel) to terminate or stop any non-essential process.

Signal Name Number Default Action Description
SIGHUP 1 Terminate Hangup detected on controlling terminal or death of controlling process. Often used to signal daemons to reload configuration.
SIGINT 2 Terminate Interrupt from keyboard (e.g., Ctrl+C).
SIGQUIT 3 Terminate + Core Dump Quit from keyboard (e.g., Ctrl+\).
SIGKILL 9 Terminate Forced termination signal. Cannot be caught or ignored.
SIGUSR1 10 Terminate User-defined signal 1. Available for application-specific purposes.
SIGSEGV 11 Terminate + Core Dump Segmentation fault: invalid memory reference.
SIGTERM 15 Terminate Standard termination signal. The “polite” way to ask a program to exit. Can be caught to perform cleanup.
SIGCHLD 17 Ignore A child process has terminated, stopped, or continued.

Signals are identified by small, positive integers, but in programming, we always refer to them by their symbolic names defined in the <signal.h> header, such as SIGHUPSIGINTSIGTERM, and so on. The command kill -l on a Linux system will list all the available signals.

Sending Signals: The Role of kill()

While the kernel sends signals to report hardware events, and the terminal driver sends signals like SIGINT in response to user input, the most common way for one process to send a signal to another is via the kill() system call. The name is somewhat of a misnomer; while it is frequently used to terminate processes, its actual function is simply to send any signal to a process or a group of processes.

The prototype for the kill() function is defined in <sys/types.h> and <signal.h>:

C
int kill(pid_t pid, int sig);

The function takes two arguments. The sig argument is the signal number (e.g., SIGTERMSIGUSR1) to be sent. The pid argument determines the target of the signal, and its interpretation is crucial:

  • pid > 0: The signal is sent to the process whose Process ID (PID) is equal to pid. This is the most straightforward use case: targeting a single, specific process.
  • pid == 0: The signal is sent to every process in the process group of the calling process. A process group is a collection of related processes, often a shell and the commands it has launched. This is useful for tasks like ensuring all related background jobs are terminated together.
  • pid == -1: The signal is sent to every process for which the calling process has permission to send signals, except for process 1 (init) and the calling process itself. This is a powerful but potentially dangerous option, often used by system shutdown scripts.
  • pid < -1: The signal is sent to every process in the process group whose ID is -pid (the absolute value of pid). This allows a process to signal a specific process group other than its own.
flowchart TD
    subgraph "kill(pid, sig) Logic"
        direction TB
        A["Start: kill(pid, sig) called"]:::primary
        A --> B{Check value of 'pid'}:::decision

        B --> C[pid > 0]
        C --> D["Send 'sig' to the process<br>with PID == <b>pid</b>"]:::process

        B --> E[pid == 0]
        E --> F["Send 'sig' to every process in the<br>caller's process group"]:::process

        B --> G[pid < -1]
        G --> H["Send 'sig' to every process in the<br>process group with PGID == <b>-pid</b>"]:::process
        
        B --> I[pid == -1]
        I --> J["Send 'sig' to all processes the<br>caller has permission for<br>(except init and self)"]:::process
    end

    D --> K[End]:::success
    F --> K
    H --> K
    J --> K

    classDef primary fill:#1e3a8a,stroke:#1e3a8a,stroke-width:2px,color:#ffffff;
    classDef success fill:#10b981,stroke:#10b981,stroke-width:2px,color:#ffffff;
    classDef decision fill:#f59e0b,stroke:#f59e0b,stroke-width:1px,color:#ffffff;
    classDef process fill:#0d9488,stroke:#0d9488,stroke-width:1px,color:#ffffff;

For a call to kill() to succeed, the sending process must have the necessary permissions. The general rule is that a process can send a signal to another process if the real or effective user ID of the sender matches the real or effective user ID of the receiver. The exception is the superuser (root), which can send signals to any process. If the sender lacks permission, the call fails, and errno is set to EPERM. If the target PID does not exist, the call fails, and errno is set to ESRCH (No such process).

Self-Signaling: The raise() Function

Sometimes, a process needs to send a signal to itself. This might seem strange, but it’s a useful technique for invoking the process’s own signal handling logic. For example, a complex application might detect an internal, non-recoverable error and could raise(SIGTERM) to initiate its own graceful shutdown procedure, which is defined in its SIGTERM handler. This centralizes the shutdown logic, ensuring it’s executed regardless of whether the termination request came from an external process or from the application itself.

While a process could achieve this with kill(getpid(), sig), the C standard library provides a more direct and portable function for this purpose: raise().

The raise() function is defined in <signal.h>:

C
int raise(int sig);

It takes a single argument: the signal to be sent. The call raise(sig) is functionally equivalent to kill(getpid(), sig). It sends the specified signal to the calling process. Its primary benefit is simplicity and clarity of intent. When you see raise() in code, you know immediately that the process is signaling itself, which makes the code more self-documenting.

sequenceDiagram
    participant P as Process (main loop)
    participant H as Signal Handler (handle_sigterm)
    
    P->>P: 1. Application logic runs...
    
    Note right of P: An internal error is detected<br>or a shutdown condition is met.
    
    P->>P: 2. Calls raise(SIGTERM)
    
    activate P
    Note over P: Kernel delivers SIGTERM<br>to the process itself.
    P-->>H: 3. Execution jumps to signal handler
    deactivate P
    
    activate H
    H->>P: 4. Sets global flag:<br>shutdown_requested = 1
    H-->>P: 5. Handler returns
    deactivate H
    
    activate P
    P->>P: 6. Main code resumes after raise() call
    
    P->>P: 7. Checks flag: if (shutdown_requested)
    P->>P: 8. Calls cleanup_resources()
    deactivate P
    
    Note right of P: Process exits gracefully.

Comparison: kill() vs. raise()

Feature kill() raise()
Primary Target Any process or process group. Only the calling process itself.
Syntax int kill(pid_t pid, int sig); int raise(int sig);
Required Information The Process ID (PID) or Process Group ID (PGID) of the target. No PID is needed; it’s implicit.
Common Use Case Inter-process control (e.g., a manager terminating a worker). Triggering a process’s own signal handler for internal cleanup or state change.
Portability Defined by POSIX. Defined by the C Standard Library (ISO C), making it slightly more portable outside of POSIX systems.
Self-Signaling Equivalence kill(getpid(), sig); raise(sig);

Process and Process Group IDs

A firm grasp of Process IDs (PIDs) and Process Group IDs (PGIDs) is essential for using kill() effectively. Every process in a Linux system is assigned a unique PID when it is created. A process can get its own PID using the getpid() system call and its parent’s PID using getppid().

Processes are also organized into process groups. Each process group has a unique Process Group ID (PGID). The PGID is always the PID of a specific process, known as the process group leader. By default, when a new process is created via fork(), it becomes a member of its parent’s process group. A process can find its PGID using the getpgrp() call.

Process groups are the basis for job control in shells. When you run a command pipeline like cat data.txt | grep "error" | wc -l, the shell typically creates a new process group for this pipeline. The PGID for this group would be the PID of the cat process. This organization allows the shell to manage the entire pipeline as a single “job.” If you then press Ctrl+C, the shell sends a SIGINT signal to the entire process group, ensuring that catgrep, and wc all terminate. This is an example of kill() being used with a negative pid argument to target a whole group.

In embedded systems, you might design a multi-process application where a primary “manager” process forks several “worker” processes. By placing all these workers in the same process group, the manager can efficiently signal all of them at once—for instance, sending a SIGUSR1 to tell them all to reload their configuration or a SIGTERM to initiate a coordinated shutdown.

graph TD
    subgraph Terminal
        A(Shell Process: bash)
    end

    subgraph "Job: Pipeline (PGID = 1001)"
        direction LR
        B[Process 1001<br><b>cat data.txt</b>]:::process
        C["Process 1002<br><b>grep <i>error</i></b>"]:::process
        D[Process 1003<br><b>wc -l</b>]:::process
        B -- "pipe" --> C -- "pipe" --> D
    end

    subgraph Kernel
        E{User presses Ctrl+C}:::decision
    end
    
    E --> A
    A -- "Sends kill(-1001, SIGINT)" --> B
    A -- " " --> C
    A -- " " --> D

    style B fill:#8b5cf6,stroke:#8b5cf6,stroke-width:1px,color:#ffffff
    style C fill:#8b5cf6,stroke:#8b5cf6,stroke-width:1px,color:#ffffff
    style D fill:#8b5cf6,stroke:#8b5cf6,stroke-width:1px,color:#ffffff
    classDef decision fill:#f59e0b,stroke:#f59e0b,stroke-width:1px,color:#ffffff;
    classDef process fill:#0d9488,stroke:#0d9488,stroke-width:1px,color:#ffffff;

Practical Examples

This section provides hands-on examples for the Raspberry Pi 5. We will assume you have a standard Raspberry Pi OS (or a similar Debian-based distribution) running and are comfortable with compiling and running C programs from the command line.

Example 1: Basic Signal Sending with kill()

Our first example demonstrates the fundamental use of kill(): one process sending a signal to another. We will create two programs: a “target” process that waits for a signal and a “sender” process that sends it.

The Target Program (target.c)

The target program will simply print its PID and then enter an infinite loop, pausing execution with the pause() system call. The pause() function causes the process to sleep until any signal is caught. We will also set up a simple signal handler for SIGUSR1 to confirm that our signal was received.

C
// target.c: A process that waits to receive a signal.

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

// A simple signal handler for SIGUSR1
void handle_sigusr1(int sig) {
    // Note: printf is not async-signal-safe, but used here for simple demonstration.
    // In production code, use write() or other safe functions.
    printf("\nTarget: Received SIGUSR1! Resuming work...\n");
}

int main() {
    // Register the signal handler for SIGUSR1
    signal(SIGUSR1, handle_sigusr1);

    // Get and print the Process ID (PID)
    pid_t my_pid = getpid();
    printf("Target process running with PID: %d\n", my_pid);
    printf("Target: Waiting for a signal...\n");

    // Loop indefinitely, pausing to wait for signals
    while(1) {
        pause(); // Suspend execution until a signal is caught
        printf("Target: Woke up from pause(). Waiting again.\n");
    }

    // This part is unreachable in this simple example
    return 0;
}

Code Explanation:

  • handle_sigusr1: This function is our custom signal handler. When the process receives SIGUSR1, this function will execute.
  • signal(SIGUSR1, handle_sigusr1): This line registers handle_sigusr1 as the handler for the SIGUSR1 signal.
  • getpid(): This call retrieves the current process’s PID, which we need to provide to the sender program.
  • pause(): This is the key to our waiting mechanism. The process will block on this line until a signal handler has been executed.

The Sender Program (sender.c)

The sender program will take a PID and a signal number as command-line arguments and use kill() to send the specified signal to the target process.

C
// sender.c: A process that sends a signal to another process.

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
#include <errno.h>

int main(int argc, char *argv[]) {
    if (argc != 3) {
        fprintf(stderr, "Usage: %s <pid> <signal_number>\n", argv[0]);
        return 1;
    }

    // Convert command-line arguments to integers
    pid_t target_pid = atoi(argv[1]);
    int signal_to_send = atoi(argv[2]);

    printf("Sender: Attempting to send signal %d to PID %d\n", signal_to_send, target_pid);

    // Use kill() to send the signal
    if (kill(target_pid, signal_to_send) == -1) {
        // Handle potential errors
        perror("Sender: kill failed");
        if (errno == ESRCH) {
            fprintf(stderr, "Sender: Error - No such process with PID %d.\n", target_pid);
        } else if (errno == EPERM) {
            fprintf(stderr, "Sender: Error - Permission denied. Are you running as the same user?\n");
        }
        return 1;
    }

    printf("Sender: Signal sent successfully!\n");

    return 0;
}

Code Explanation:

  • argcargv: We use standard command-line arguments to get the target PID and signal number.
  • atoi(): This function converts the string arguments from the command line into integers.
  • kill(target_pid, signal_to_send): This is the core of the program. It sends the signal.
  • Error Handling: We check the return value of kill(). If it is -1, an error occurred. We use perror() to print a system error message and also check errno to provide more specific feedback to the user.

Build and Execution Steps

1. Open two terminals on your Raspberry Pi 5. One will be for the target, the other for the sender.

2. Compile the programs in each terminal:

Bash
# In Terminal 1 
gcc -o target target.c 
# In Terminal 2 
gcc -o sender sender.c

3. Run the target program in Terminal 1:

Bash
# In Terminal 1 
./target


The program will print its PID and wait. Note the PID. For example:

Plaintext
Target process running with PID: 12345
Target: Waiting for a signal...

4. Run the sender program in Terminal 2. Use the PID from the target and send signal 10 (which corresponds to SIGUSR1 on most Linux systems).

Bash
# In Terminal 2 (replace 12345 with the actual PID)
./sender 12345 10


The sender’s output will be:

Plaintext
Sender: Attempting to send signal 10 to PID 12345
Sender: Signal sent successfully!

5. Observe the output in Terminal 1. The target will wake up, execute its signal handler, and then go back to waiting.

Plaintext
Target process running with PID: 12345
Target: Waiting for a signal...

Target: Received SIGUSR1! Resuming work...
Target: Woke up from pause(). Waiting again.

6. Experiment further. Try sending other signals. For example, send SIGTERM (signal 15) to terminate the target gracefully.

Bash
# In Terminal 2
./sender 12345 15


The target process in Terminal 1 will now terminate and the shell prompt will return.

Example 2: Self-Signaling with raise() for Graceful Shutdown

This example demonstrates how a process can use raise() to trigger its own cleanup logic. This is a common pattern in embedded applications that need to shut down gracefully.

The Self-Terminating Program (self_term.c)

C
// self_term.c: A program that uses raise() to trigger its own shutdown.

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

// A flag to control the main loop, modified by the signal handler
volatile sig_atomic_t shutdown_requested = 0;

// Signal handler for SIGTERM
void handle_sigterm(int sig) {
    // This handler will be triggered by raise()
    printf("\nSIGTERM caught! Initiating graceful shutdown...\n");
    shutdown_requested = 1;
}

void cleanup_resources() {
    printf("Cleaning up resources (e.g., closing files, releasing hardware)...\n");
    // In a real application, you would close file descriptors,
    // deallocate memory, shut down hardware interfaces, etc.
    printf("Cleanup complete.\n");
}

int main() {
    // Register the handler for SIGTERM
    signal(SIGTERM, handle_sigterm);

    printf("Application running with PID: %d\n", getpid());
    printf("Simulating work for 5 seconds before triggering shutdown.\n");

    // Simulate some work
    sleep(5);

    // An internal condition has been met, time to shut down.
    printf("\nInternal condition met. Raising SIGTERM to self.\n");
    if (raise(SIGTERM) != 0) {
        perror("raise failed");
        exit(1);
    }

    // The signal handler will execute here, setting shutdown_requested to 1.
    // The program flow continues after the signal handler returns.

    // Check the flag set by the signal handler
    if (shutdown_requested) {
        cleanup_resources();
    }

    printf("Exiting gracefully.\n");
    return 0;
}

Code Explanation:

  • volatile sig_atomic_t shutdown_requested: This global variable acts as a flag. It is declared volatile to prevent the compiler from making optimizations that might break its usage in a signal handler. sig_atomic_t guarantees that reads and writes to it are atomic, which is essential for variables shared between a signal handler and the main program logic.
  • handle_sigterm: This handler simply sets the shutdown_requested flag to 1. It avoids doing complex work inside the handler itself, which is a best practice.
  • raise(SIGTERM): After a simulated work period, the program calls raise() to send SIGTERM to itself. This immediately invokes handle_sigterm.
  • cleanup_resources(): After the signal handler returns, the main program logic checks the flag and, if it’s set, proceeds to call the main cleanup function. This pattern separates the immediate signal notification from the more complex shutdown logic.

Build and Execution Steps

1. Compile the program:

Bash
gcc -o self_term self_term.c

2. Run the program:

Bash
./self_term

3. Observe the output: The program will run for 5 seconds, then print messages indicating it is raising the signal, handling it, cleaning up, and exiting.

Plaintext
Application running with PID: 1543534
Simulating work for 5 seconds before triggering shutdown.

Internal condition met. Raising SIGTERM to self.

SIGTERM caught! Initiating graceful shutdown...
Cleaning up resources (e.g., closing files, releasing hardware)...
Cleanup complete.
Exiting gracefully.

This example clearly shows how raise() can be used to cleanly integrate signal handling logic into a program’s own control flow, a powerful technique for creating robust, self-managing applications.

Common Mistakes & Troubleshooting

When working with signals, developers often encounter a few common pitfalls. Understanding these can save significant debugging time.

Mistake / Issue Symptom(s) Troubleshooting / Solution
Permission Denied kill() fails, and perror prints “Permission denied”. errno is set to EPERM. The sending and receiving processes must be owned by the same user. To signal a process owned by another user (e.g., root), the sender must have superuser privileges. Run the sender with sudo.
No Such Process kill() fails, and perror prints “No such process”. errno is set to ESRCH. This is a race condition where the target PID has already exited. Before sending a real signal, check if the process exists with kill(pid, 0). If this call fails with ESRCH, the process is gone.
Unsafe Functions in Handler The program crashes unpredictably, deadlocks, or exhibits corrupted data when a signal is received. Signal handlers must only call async-signal-safe functions (e.g., write(), _exit()). Avoid printf(), malloc(), etc. The safest pattern is to set a volatile sig_atomic_t flag in the handler and handle the logic in the main loop.
Ignoring Signal Return Value The signal appears to have no effect, and there’s no error message. The target process continues running as if nothing happened. Always check the return value of kill() and raise(). If it returns -1, an error occurred. Use perror() to print a descriptive error message to understand why it failed.
Incorrect PID for Group Signal Sending a signal to a process group using kill(-pid, sig) affects the wrong processes or no processes at all. To signal a process group, the first argument must be the negative of the Process Group ID (PGID), not the PID of a random process in the group. Use getpgrp() or getpgid(pid) to get the correct PGID.

Exercises

  • The Watchdog and the Worker:
    • Objective: Create a two-process system. A worker process creates a file /tmp/worker.pid containing its PID and then enters a loop, printing a “working…” message every second. A watchdog process reads the PID from the file and, after 10 seconds, sends a SIGTERM signal to the worker to terminate it.
    • Guidance: The worker should install a SIGTERM handler that prints “SIGTERM received, shutting down…” and then calls exit(). The watchdog will need to read the PID from the file, sleep(10), and then use kill().
    • Verification: When you run the worker, it should start printing messages. When you run the watchdog, the worker should stop after 10 seconds, printing its shutdown message before it exits.
sequenceDiagram
    participant W as Worker Process
    participant FS as Filesystem (/tmp/worker.pid)
    participant D as Watchdog Process

    W->>W: 1. Starts up, gets own PID
    W->>FS: 2. Creates & writes PID to file
    activate FS
    FS-->>W: 3. File created
    deactivate FS
    
    loop Work Loop
        W->>W: 4. Prints "working..."
        W->>W: 5. sleep(1)
    end
    
    D->>D: 6. Starts up
    D->>FS: 7. Reads PID from file
    activate FS
    FS-->>D: 8. Returns PID of Worker
    deactivate FS
    
    D->>D: 9. sleep(10)
    
    Note over D,W: Watchdog sends SIGTERM
    D-->>W: 10. kill(worker_pid, SIGTERM)
    
    W->>W: 11. Catches SIGTERM, runs handler
    W->>W: 12. Prints "shutting down..."
    W-->>D: 13. Exits gracefully
  • Configuration Reload:
    • Objective: Modify the target.c program from our first example. In addition to handling SIGUSR1, make it also handle SIGHUP. The SIGHUP handler should print a message like “SIGHUP received! Reloading configuration…”.
    • Guidance: You will need to register a second signal handler for SIGHUP. Use the sender program to send both SIGUSR1 (10) and SIGHUP (1) to the target and observe the different outputs.
    • Verification: The target process should respond differently depending on which signal is sent, demonstrating the ability to handle multiple, distinct commands via signals.
  • Process Group Termination:
    • Objective: Write a program that forks three child processes. Each child should print its PID and PGID and then enter an infinite sleep() loop. The parent process should wait for 5 seconds and then send a SIGKILL signal to the entire process group.
    • Guidance: Use fork() three times in the parent. The children can use getpid() and getpgrp() to print their information. The parent can get the process group ID with getpgrp() and then use kill(-pgid, SIGKILL) to signal the group.
    • Verification: The parent and all three children should start. After 5 seconds, all three children should be terminated simultaneously by the single kill() call from the parent.
  • Error Handling Robustness:
    • Objective: Modify the sender.c program to be more robust. It should first check if the target PID exists before trying to send a signal.
    • Guidance: A common way to check if a process exists is to use kill(pid, 0). This special call performs error checking (permissions, existence) but doesn’t actually send a signal. If this call succeeds, the process exists. If it fails with ESRCH, the process does not exist.
    • Verification: Run the modified sender with a non-existent PID. It should print a clear error message “Process does not exist” instead of the more generic one from perror. Then run it with a valid PID to ensure it still works correctly.

Summary

  • Signals are Asynchronous Notifications: They are a fundamental IPC mechanism in Linux for notifying processes of hardware or software events.
  • kill() Sends Signals: The kill(pid, sig) system call is the primary tool for a process to send a signal to another process or process group. The interpretation of the pid argument allows for precise targeting.
  • raise() Signals Self: The raise(sig) function is a convenient and portable way for a process to send a signal to itself, which is useful for triggering its own signal handlers.
  • Permissions are Key: A process must have appropriate permissions (typically, matching user IDs) to send a signal to another process. The superuser is the exception.
  • Process Groups Enable Job Control: Organizing processes into groups allows signals to be sent to multiple related processes at once, a crucial feature for managing complex applications and shell jobs.
  • Signal Handlers Must Be Safe: Functions called from within a signal handler must be async-signal-safe. The best practice is to keep handlers minimal, often just setting a flag for the main program loop to act upon.

Further Reading

  1. The Linux Programming Interface by Michael Kerrisk – Chapters 20-22 provide an exhaustive and authoritative treatment of signals.
  2. Advanced Programming in the UNIX Environment by W. Richard Stevens and Stephen A. Rago – A classic text with deep insights into process control and signals.
  3. signal(7) Linux Manual Page – Provides a comprehensive overview of Linux signals. Access via man 7 signal.
  4. kill(2) Linux Manual Page – The definitive documentation for the kill system call. Access via man 2 kill.
  5. Raspberry Pi Foundation Documentation – Official hardware and software documentation for the Raspberry Pi platform. https://www.raspberrypi.com/documentation/
  6. GNU C Library (glibc) Manual – Section on Signal Handling provides details on the C library’s implementation and wrappers. https://www.gnu.org/software/libc/manual/html_node/Signal-Handling.html

Leave a Comment

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

Scroll to Top