From the 408 OS Exam to Production: Why Is the Signal-Sending Function Called kill Instead of send?
AI translation notice: This English version was translated from the original Chinese article with AI assistance. Some nuances may differ from the original.
Recently, while reviewing the “Process” chapter of the 408 operating systems course, I got to the signal mechanism of inter-process communication (IPC). Since this round was the intensive review phase, I was reading the textbook very carefully.
I froze for a moment: “Wait—sending a signal between processes, and the system call is called kill()?”
That cannot be right. Is all inter-process communication meant to kill the other party? To use an analogy: you send a friend a message saying “come downstairs and hang out,” and someone calls that operation execute_friend(best_buddy). That is absurd, and genuinely counterintuitive, so I dug in a little deeper to find out what was going on.
Why did the UNIX forefathers choose such a misleading name? Today let us dig into this “legacy naming” that has stayed with us for decades, and take a look at what it actually means in everyday development and in interviews.
The “Force Kill” the Name Misleads You Into
When I first saw the kill() system call, or the kill command in a terminal, I formed an obvious assumption: its only job is to “take out a process.”
When troubleshooting a production incident or debugging a program, we habitually type kill <pid>, or the more brutal kill -9 <pid>, into a terminal. As the process vanishes instantly, I naturally equated that operation with the way a program ends.
But in fact, that intuition of mine concealed a major technical misunderstanding.
The essence of kill() is to deliver a signal to a specified process, not to “kill” it in the literal sense. Terminating the process is merely the default behavior of a process on receiving a particular signal, not a functional limit of the system call itself. In other words, kill is nothing more than an asynchronous communication mechanism for notifications, and “killing” is just one default result among many ways of handling signals.
What Does kill(pid, sig) Actually Do?
Let us first look at how kill() is normally used.
In C, the prototype of the kill() system call is very simple:
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
pid: the ID card number of the target process.sig: the number of the signal you want to send (such asSIGTERM,SIGUSR1, and so on). If you pass0forsig(known as the null signal),kill()does not send a signal at all; it is used to probe whether the process exists and whether the current user has permission to send it signals (commonly used for liveness checks when writing daemons).
A Simple C Example
The code below shows how to send a custom signal (SIGUSR1) to a specified process, and how to check the process's status:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
int main(int argc, char *argv[]){
if (argc != 2) {
fprintf(stderr, "Usage: %s <pid>\n", argv[0]);
exit(1);
}
pid_t target_pid = atoi(argv[1]);
// 1. 探测进程是否存在
if (kill(target_pid, 0) == 0) {
printf("进程 %d 存活,正在发送自定义信号 SIGUSR1...\n", target_pid);
// 2. 发送 SIGUSR1 信号
kill(target_pid, SIGUSR1);
} else {
perror("进程不存在或无权限");
}
return 0;
}
Here, kill() merely delivers the signal into the kernel's process control block (PCB), completing a lightweight event notification.
The Historical Puzzle: Why kill and Not send?
Since its essence is sending a signal, why is it not called send_signal(), or simply send()? That would seem more intuitive.
Answering that means rewinding to the wild early days of UNIX in the 1970s.
- Historical constraints and original intent: In the earliest UNIX designs, inter-process communication was nowhere near as complex as it is today. There were very few kinds of signals, and the one purpose, the core scenario, they were designed for was—terminating processes that had run out of control, were stuck in an infinite loop, or were hogging resources. So the intuitive mission of this system call at the time was “take it out,” and the name
killwas extremely apt. - Path dependence in the POSIX standard: As systems evolved, people found this “asynchronous notification mechanism” too useful to confine to killing processes; it could also announce state changes, reload configuration, report that a child process had exited, and more. Signals multiplied, and the feature expanded into general-purpose IPC. But by then the name
kill()had already taken root in countless system source files and early books. To maintain backward compatibility, the POSIX standard ultimately kept the name. - Distinction from data-transfer APIs: Names like
send()andsendto()usually accompany the transfer of large byte streams in network programming and pipes. Traditional UNIX signals, by contrast, are extremely lightweight: they carry no extra data payload (modernsigqueuecan pass a small amount of data, but that is an extension), and convey only a pure event notification that “something happened.” So avoidingsendalso avoids confusion with data transfer.
Common Signals at a Glance
During development and in interviews, a handful of high-frequency signals are worth knowing. Here I had AI summarize them for me, which also makes them handy for later use:
| Signal Name | Default Number | Default Behavior | Typical Use Case / Meaning |
|---|---|---|---|
| SIGHUP | 1 | Terminates the process | Sent when the terminal disconnects; modern servers often repurpose it as Nginx/daemon “reload configuration” |
| SIGINT | 2 | Terminates the process | Ctrl + C, interrupts the foreground process |
| SIGQUIT | 3 | Core dump and terminate | Ctrl + \ (backslash), which not only quits but leaves a core dump file for debugging |
| SIGKILL | 9 | Forcibly kills the process | Cannot be caught or ignored. The kernel reclaims it directly; the program does not even get a chance to say goodbye |
| SIGSEGV | 11 | Core dump and terminate | Segmentation fault (illegal memory access, such as a wild pointer or out-of-bounds access) |
| SIGPIPE | 13 | Terminates the process | Triggered by continuing to write to a pipe/socket with no read end |
| SIGALRM | 14 | Terminates the process | Timer expiration (triggered by the alarm() function) |
| SIGTERM | 15 | Terminates the process | Software termination (what the default kill command sends). Can be caught, and is the first choice for graceful shutdown |
| SIGSTOP | 19 | Suspends the process | Cannot be caught or ignored. Suspends execution (like pressing pause) |
| SIGCONT | 18 | Resumes the process | Resumes a process suspended by SIGSTOP or for some other reason |
| SIGUSR1/2 | 30/31 | Terminates the process | User-defined signals; developers can assign them whatever business meaning they like |
AI also went ahead and wrote an extra exam question all on its own, so I will record it here too:
💡 A high-frequency interview question:
Why can
SIGKILL(9) andSIGSTOP(19) not be caught or ignored?Answer: Because they are the kernel's “absolute control” reserved for the administrator. If user code were allowed to catch
SIGKILL, and someone happened to write an infinite loop that catches it and does nothing, the system could never again kill that rogue process by conventional means, and the whole system would spin completely out of control.
Real-World Use Cases
Where does the kill command actually get used day to day? Again, I had AI give me a quick summary:
1. Daily Operations in the Shell
kill <pid>: sends SIGTERM (15) by default. It gives the process a polite hint: “Buddy, that is about enough—wrap up what you are doing and head out.”kill -9 <pid>: sends SIGKILL (9). “No more talk—vanish this instant!” (Note: using9all the time easily leads to disasters such as uncommitted database transactions, temp files left behind, and unreleased locks.)kill -HUP <pid>: sends SIGHUP (1). In servers such as Nginx, it is commonly used for hot reloading: on receiving it, the master process reloads its configuration and gracefully restarts the worker processes, with zero service interruption.
2. Backend Development and Graceful Shutdown
When writing web services in Go, Java, or Node.js, if we stop a container with Docker (which sends SIGTERM) or press Ctrl+C in a terminal (which sends SIGINT), the program cannot simply drop dead.
A competent backend framework will listen for these termination signals:
- After receiving
SIGTERM, stop accepting new HTTP requests. - Finish the requests currently in flight and write them to the database.
- Disconnect from Redis and MySQL, release resources safely, and then exit gracefully.
3. Simple Inter-Process Communication (IPC)
In some multi-process daemons (such as the Master-Worker pattern), the master process can use kill(worker_pid, SIGUSR1) to tell a child process: “The log file has been rotated, so please reopen the log file handle.”
What Should Ordinary Developers Watch Out For?
Even if you usually write high-level business code and never call the system-level kill() directly, you have definitely hit—or will sooner or later hit—the following signal-related “reefs”:
- The power of Ctrl+C: You run a long script in a terminal, your hand slips, and you press
Ctrl+C. In essence, the terminal driver has just sentSIGINTto the foreground process group. - Zombie processes and
SIGCHLD: A parent process forks a child; if the child dies and the parent never reaps its status throughwait(), the child becomes a zombie process. And when the child exits, the kernel sends the parent aSIGCHLDsignal. Many high-performance frameworks reap child processes efficiently precisely by catchingSIGCHLDasynchronously. - Interrupted system calls (
EINTR): If your program is in the middle of a blocking I/O operation (such as areadon a socket), and a signal arrives and its handler runs, thatreadmay be interrupted and return1, witherrnoset toEINTR. When writing low-level network libraries, you must explicitly handle this “interrupted by a signal” case (usually by wrapping it in an outerwhileloop and retrying). - Async-signal safety (Async-Signal-Safe): When writing a signal handler, what you can do inside it is extremely limited. Never call non-thread-safe or non-async-signal-safe functions such as
printf()ormalloc()inside a signal handler, or you may easily cause deadlocks or memory corruption that are very hard to track down. - Signals do not queue (non-real-time signals get lost): Traditional standard signals (1–31) do not queue. If the same signal is sent to a process several times in quick succession, the process may only perceive it once. If your business logic absolutely cannot drop a single event, standard signals will not do the job (you need real-time signals, or RT signals).
Conclusion
After going through all of this, I finally truly digested the inter-process communication function, and a little extra reading also gave me a deeper understanding of IPC.
But honestly, this kind of misunderstanding is scary. Without AI helping me summarize things and answer my questions, I probably would not have wanted to research on my own why the IPC function is called kill() for quite a while; I might have just assumed that inter-process communication exists entirely to finish the other party off. I also wonder how much more historical debt like this is out there...
I hope this note can also help developers who are grinding through 408 operating systems, or whose scalps are tingling from wrestling with Linux signals, finally sort out the technical truth behind this historical baggage.
- Author:Mark Xu
- Copyright:All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating otherwise.
