这是《Linux 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 信号是 Linux 向进程传递异步事件的机制。优雅停机、配置重载、故障转储、脚本中断,都依赖信号。理解信号的发送、阻塞、默认行为和继承规则,才能设计可靠的服务生命周期。
8.1 信号是什么
kill / kernel / terminal
|
v
signal
|
v
target process
|
+-- default action
+-- ignore
+-- custom handler
查看信号:
kill -l
man 7 signal
常用信号:
| 信号 | 默认行为 | 常见用途 |
|---|---|---|
| SIGHUP | 终止 | 终端断开;很多守护进程用它重载配置 |
| SIGINT | 终止 | Ctrl+C |
| SIGQUIT | 终止并 core | 调试转储 |
| SIGILL | 终止并 core | 非法指令 |
| SIGABRT | 终止并 core | abort |
| SIGFPE | 终止并 core | 算术错误 |
| SIGKILL | 终止 | 强制杀死 |
| SIGSEGV | 终止并 core | 非法内存访问 |
| SIGPIPE | 终止 | 写已关闭管道或 socket |
| SIGALRM | 终止 | 定时器 |
| SIGTERM | 终止 | 优雅终止 |
| SIGUSR1 | 终止 | 应用自定义 |
| SIGUSR2 | 终止 | 应用自定义 |
| SIGCHLD | 忽略 | 子进程状态变化 |
| SIGCONT | 继续 | 恢复执行 |
| SIGSTOP | 暂停 | 强制暂停 |
| SIGTSTP | 暂停 | Ctrl+Z |
| SIGWINCH | 忽略 | 终端窗口变化 |
8.2 发送信号
按 PID:
kill <pid>
kill -TERM <pid>
kill -15 <pid>
kill -KILL <pid>
按名称或命令行:
pkill -TERM nginx
pkill -TERM -f 'java -jar order.jar'
killall -TERM nginx
给进程组发送:
kill -TERM -<pgid>
负号表示进程组 ID。使用前必须确认范围,避免误杀其他服务。
查看进程组:
ps -eo pid,pgid,sid,stat,cmd
ps -o pid,pgid,sid,cmd -p <pid>
8.3 默认行为与处理
进程可以选择:
1. 执行默认动作
2. 忽略信号
3. 捕获信号并执行处理函数
SIGKILL 和 SIGSTOP 不能被捕获、阻塞或忽略,这是内核保底能力。
C 示例:
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
volatile sig_atomic_t running = 1;
void handle_term(int signo) {
running = 0;
}
int main(void) {
struct sigaction sa;
sa.sa_handler = handle_term;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if (sigaction(SIGTERM, &sa, NULL) != 0) {
perror("sigaction");
return 1;
}
while (running) {
pause();
}
puts("graceful shutdown");
return 0;
}
信号处理函数中应尽量避免调用非异步信号安全函数。复杂清理应设置标志,由主循环处理。
8.4 优雅停机
理想流程:
1. 负载均衡摘除实例
2. 停止接收新请求
3. 发送 SIGTERM
4. 等待存量请求完成
5. 关闭资源:连接、文件、锁、队列
6. 输出退出原因
7. 超时后由编排系统 SIGKILL
Nginx:
sudo nginx -s reload
sudo nginx -s quit
reload 通常重读配置并平滑拉起 worker,quit 通常等待请求结束后退出。具体行为以当前版本文档为准。
systemd 停止:
sudo systemctl stop app
sudo systemctl kill -s SIGTERM app
超时配置:
[Service]
TimeoutStopSec=30s
KillSignal=SIGTERM
KillMode=mixed
8.5 SIGPIPE 问题
进程向已关闭的管道或 socket 写数据时可能收到 SIGPIPE。很多网络服务会忽略 SIGPIPE,并在写调用处处理 EPIPE 错误。
处理原则:
- 不要让连接关闭直接杀死服务;
- 写失败要记录上下文;
- 关闭对应连接;
- 不掩盖真实 bug;
- 按语言和框架的推荐方式处理。
Java 中对已关闭 socket 的异常处理与 C 不同,应结合具体运行时分析。
8.6 Core Dump
Core dump 是进程异常退出时的内存镜像,用于定位崩溃。
查看限制:
ulimit -c
cat /proc/sys/kernel/core_pattern
临时开启:
ulimit -c unlimited
core 文件路径由 core_pattern 控制,可能由 systemd-coredump、apport 或 abrt 接管,发行版差异较大。
systemd:
coredumpctl list
coredumpctl info <pid>
coredumpctl gdb <pid>
生产注意:
- core 可能包含敏感数据;
- 大内存进程文件很大;
- 存储位置要有空间;
- 需要保留二进制和符号;
- 采集流程要受控。
8.7 信号与 Shell 脚本
捕获信号:
#!/usr/bin/env bash
set -euo pipefail
tmpfile=$(mktemp)
cleanup() {
rm -f "$tmpfile"
}
trap cleanup EXIT INT TERM
echo data > "$tmpfile"
sleep 30
trap EXIT 会在脚本退出时执行。多个信号的处理顺序和脚本当前命令有关,退出前要保证清理逻辑幂等。
忽略 Ctrl+C:
trap '' INT
恢复默认:
trap - INT
关键任务不要随意忽略终止信号,应实现可中断的检查点。
8.8 信号常见问题
kill 没反应
ps -o pid,stat,cmd -p <pid>
cat /proc/<pid>/status | grep State
dmesg -T | tail -100
常见原因:
- 进程处于 D 状态,等待 IO 返回;
- 僵尸进程,等待父进程回收;
- PID 1 实现特殊;
- 命令拼写或目标错误;
- 权限不足;
- 内核或存储异常。
重载配置无效
nginx -t
sudo systemctl reload nginx
journalctl -u nginx -n 100
检查:
- 配置文件路径;
- 是否 reload 到主进程;
- 是否还有独立 worker 未退出;
- 配置是否实际命中当前 server 块;
- 是否需要重启而非 reload。
容器停止很慢
SIGTERM 未被应用处理
-> 编排等待 termination grace period
-> 超时后 SIGKILL
检查应用是否监听 SIGTERM,以及入口脚本是否会转发信号。
本章小结
信号是进程生命周期控制的基础。生产服务要正确处理 SIGTERM,实现摘流、停止接流、处理存量请求和释放资源。SIGKILL 是保底手段,不是日常操作。Core dump 有助于崩溃定位,但必须治理大小、安全和留存。
思考题
- 为什么 SIGKILL 不能被捕获?
- 设计一个 Java 服务的优雅停机流程。
KillMode=control-group和mixed有什么差异?- 哪些信号适合用于重载配置?
- 脚本中如何保证临时文件被清理?