Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Introduction to Linux Process Signals

Tech 1

Signals in Daily Life

In our daily lives, signals are everywhere, such as:

  • Traffic lights
  • School bells
  • Starting pistols
  • ...

Because we have received education (we can recognize signals), when these signals are generated, we immediately think of the corresponding action. However, we might not process them immediately because we could be doing something more important, until an appropriate time to handle them arrives. For example: when the alarm rings, you are supposed to get up and study, but the bed is too comfortable to leave. Therefore, between the generation of the signal and its processing, the signal must be remembered (saved).

Understanding Process Signals

  1. Similarly, a process must also have the ability to recognize and handle signals. Programmers have built a set of instructions into the operating system; an instruction represents a special action, and these instructions are signals (process signals).
  2. When a process receives a specific signal, it may not process it immediately, but rather wait until an appropriate time.
  3. A process must save the signal from the time it is generated until it is processed, and handle it at an appropriate time.

In Linux, you can view the list of signals in the current system using the following command:

kill -l

There are a total of 62 process signals in the system. Signals 1-31 are standard signals (the primary focus); signals 34-64 are real-time signals (not covered here).

  • Standard Signals: Scheduled fairly based on time slices, suitable for personal computers. (They do not need to be processed immediately upon recognition.)
  • Real-time Signals: High responsiveness, suitable for platforms with fewer tasks that require fast processing, such as car infotainment systems or rocket launch consoles. (They must be processed immediately upon recognition.)

Note: In Linux, a signal is essentially just a number. Names like SIGKILL and SIGINT are actually macros.

The 31 Standard Signals

Signal Number Signal Name Description
1 SIGHUP If a terminal interface detects a disconnection, the default action is to terminate the process.
2 SIGINT Stands for Interrupt. This signal is typically sent by the user via the keyboard (Ctrl + c), and the default action is to interrupt the current process.
3 SIGQUIT When the user presses the key combination (usually Ctrl + \), this signal not only terminates the foreground proces group but also generates a core file.
4 SIGILL Indicates that the process has executed an illegal instruction. The default action is to terminate the process and generate a core file.
5 SIGTRAP Generated by a breakpoint instruction or other trap instructions. The default action is to terminate the process and generate a core file.
6 SIGABRT Generated by calling the abort function. The process terminates abnormally, and a core file is generated.
7 SIGBUS Often generated when certain types of memory faults occur. The default action is to terminate the process and generate a core file.
8 SIGFPE Indicates an arithmetic operation exception, such as division by zero or floating-point overflow. The default action is to terminate the process and generate a core file.
9 SIGKILL This signal cannot be caught or ignored. It provides a reliable method for system administrators to kill any process.
10 SIGUSR1 A user-defined signal. Programmers can define and use this signal in their programs. The default action is to terminate the process.
11 SIGSEGV Indicates that the process made an invalid memory access (e.g., accessing an uninitialized pointer). The default action is to terminate the process and generate a core file.
12 SIGUSR2 Another user-defined signal, similar to SIGUSR1. The default action is to terminate the process.
13 SIGPIPE Received if a write operation is performed on a pipe whose read process has terminated. The default action is to terminate the process.
14 SIGALRM Generated when a timer set by the alarm function expires, or when the interval set by the setitimer function expires.
15 SIGTERM This signal is caught by applications, giving the program a chance to perform cleanup before exiting. Unlike SIGKILL, it can be caught or ignored, and is typically used to indicate a normal program exit.
16 SIGSTKFLT Indicates a stack fault on the coprocessor (unused). The default action is to terminate the process.
17 SIGCHLD Sent to a parent process when a child process terminates or stops. By default, this signal is ignored. If the parent process wishes to be notified of such state changes in its child, it should catch this signal. The signal handler typically calls a wait function to obtain the child process ID and its termination status.
18 SIGCONT Can be sent to make a stopped process continue running.
19 SIGSTOP A job control signal used to stop a process, similar to the interactive stop signal (SIGTSTP), but it cannot be caught or ignored.
20 SIGTSTP Interactive stop signal. Generated by the terminal driver and sent to every process in the foreground process group when the user presses the key combination (usually Ctrl + Z).
21 SIGTTIN Generated by the terminal driver and sent to a background process when it attempts to read from the terminal console. The default action is to pause the process.
22 SIGTTOU Generated by the terminal driver and sent to a background process when it attempts to output data to the terminal console. The default action is to pause the process.
23 SIGURG Sent to the currently running process when urgent data arrives on a socket, reporting that urgent data has arrived. The default action is to ignore.
24 SIGXCPU Generated and sent to the process by the system when the process execution time exceeds the allocated CPU time. The default action is to terminate the process and generate a core file.
25 SIGXFSZ Received if a process writes to a file exceeding the maximum file size limit. The default action is to terminate the process and generate a core file.
26 SIGVTALRM Generated when a virtual timer expires. Similar to SIGALRM, but it only counts the CPU time used by the process in user mode. The default action is to terminate the process.
27 SIGPROF Similar to SIGVTALRM, but it includes both the CPU time used by the process and the time spent executing system calls. The default action is to terminate the process.
28 SIGWINCH Sent by the kernel to the foreground process group when the terminal window size changes. The default action is to ignore.
29 SIGIO Indicates an asynchronous I/O event. The default action is to terminate the process.
30 SIGPWR Power failure. The default action is to terminate the process.
31 SIGSYS Indicates an invalid system call. The default action is to terminate the process and generate a core file.

Phenomena When a Process Receives a Signal

Foreground and Background Processes

  • Foreground Process: By default, processes run in the foreground, sending output directly to the terminal. Typically, the process currently interacting with the user is the foreground process. Users can terminate a foreground process by pressing Ctrl + c in the terminal or by using the kill command to send signal 9 (SIGKILL).
kill -9 <pid>
  • Background Process: When running in the background, a process does not occupy the terminal and will not block user input/output. Users can place a process in the background using the following command. However, to kill it, you must use the kill command (as shown above).
./myprocess &

Notes:

  1. In Linux, a terminal is paired with a command-line interpreter (bash), and a terminal only allows one foreground process, but multiple background processes.
  2. By default, bash is the foreground process. When a foreground process starts, bash becomes a background process.
  3. Keyboard input is received by the foreground process first.
  4. Ctrl + c does not terminate the bash process because bash handles it specially.

The Ctrl + c Hotkey

Consider an infinite loop program like this:

#include <iostream>

int main() {
    for (;;) {
        std::cout << "Process executing an infinite loop..." << std::endl;
    }
    return 0;
}

When this program runs, it will rapidly print to the screen. As commonly taught, pressing Ctrl + c will terminate the program. Why does this happen?

When the infinite loop program runs, it becomes the foreground process, and bash becomes the background process. Since keyboard input is received by the foreground process first, Ctrl + c is essentially interpreted by the foreground process as receiving a specific signal.

Actually, this triggers Signal 2: SIGINT.

There are three ways to handle a process signal:

  1. System default action
  2. Ignore the signal (no action)
  3. User-defined handling (signal catching)

By default, the action for Ctrl + c is to terminate the process. Can we customize the signal handling action? Yes, we can.

The signal system call is used to set a custom signal handler. Its prototype is:

#include <signal.h>

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler);

Explanation:

  • signum: The number of the signal to be handled. Passing the signal name is also acceptable because the name is a macro for the number.
  • handler: A function pointer (function address).
void terminationHandler(int receivedSignal) {
    // Custom handling logic
}

Clearly, the signal function acts as a callback. When a specific signal is caught, it invokes the corresponding function to execute the action. The signum is passed as the argument to the handler function.

Notes:

  1. The signal function can be set anywhere, but it is generally recommended to set it at the beginning of the program.
  2. Once set via signal, it remains valid for the entire lifecycle of the process.
  3. The signal function itself is not triggered by its declaration; it is triggered during subsequent code execution (the handler is only called when the process receives signum).
  4. Not all signals can be custom-handled. For example, SIGKILL and SIGSTOP cannot be caught or ignored.
  5. If the handler function body is empty, the signal handling might fall back to the system default depending on implementation, but typically an empty handler means the signal is ignored upon receipt.

For instance, we can modify the handling of Ctrl + c. The custom action will be: print the received signal number and then exit the process.

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <cstdlib>

void terminationHandler(int receivedSignal) {
    std::cout << "Received signal: " << receivedSignal << std::endl;
    std::exit(EXIT_FAILURE);
}

int main() {
    // Register the custom signal handler at the beginning
    std::signal(SIGINT, terminationHandler);

    for (;;) {
        std::cout << "Process is running in a continuous loop..." << std::endl;
        sleep(1);
    }
    return 0;
}

Hardware Interrupts

How does keyboard data (Ctrl + c) get input to the operating system? How does the OS know when there is data from the keyboard?

  • At the data level, the CPU does not fetch data directly from peripherals; it fetches from memory (because memory access is much faster).
  • At the control level, the CPU can read the status of peripherals by communicating with external device controllers. These controllers manage interaction with peripherals, including data transmission and status monitoring.
  • When the keyboard has data, the OS is notified first through a hardware interrupt mechanism. Input devices communicate with the CPU and OS via interrupts. When a peripheral has data, the OS uses an interrupt to notify the CPU of an important event, which includes a hardware interrupt number (an identifier used by the CPU to distinguish interrupt types, stored in CPU registers).
  • Upon receiving an interrupt request, the CPU pauses its current task and looks up the corresponding entry in the Interrupt Vector Table (essentially an array) based on the interrupt number. The Interrupt Vector Table is a data structure stored in memory and maintained by the OS. Its entries are addresses of methods that call hardware device drivers, facilitating hardware interaction. For example, a hard disk interrupt service routine might call a hard disk driver function to read or write data.
  • The interrupt handler calls the appropriate hardware method to process the input data and stores it in the memory's input buffer. Finally, it is transferred to the corresponding upper-layer application via file descriptors. For example, a terminal program reads user commands and operations from the OS via file descriptors.

Therefore, the process signals we learn about are essentially simulated using hardware interrupts.

How does Ctrl + c become a signal?

  • In the OS, keyboard input can consist of regular characters (like letters) and special control characters (like Ctrl + c). The former can be echoed to the screen, while the latter are not echoed.
  • The OS first determines the type of character input. Regular characters are copied directly from the keyboard file to the kernel buffer; if it's a special control character, the OS interprets it as a specific signal and sends it to the foreground process.

Concept of Signals

Signal generation and the execution of your own code are asynchronous. Signals belong to the category of software interrupts.

  • Asynchronous: The program does not need to execute in a strict sequential order. For example, a teacher sends a student to the office to fetch a microphone and continues teaching without waiting for the student to return.
  • Synchronous: The program executes in a predetermined sequence, where each step must wait for the previous one to complete before executing. For example, a teacher sends a student to fetch a microphone and waits for them to return before starting the lesson.
  • In other words, we do not know exactly when a signal will be generated, but we just continue running our code, waiting for the signal to arrive at some future moment.

Note: Signals are always sent to a process by the Operating System (the manager).

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.