kill -9 是很多人的肌肉记忆——进程不听话,直接 -9 送走。但 kill 这个命令名其实起得有误导性:它默认发的信号根本不是”杀死”,而是”请求退出”。这两者的区别,决定了进程退出前有没有机会保存数据、释放资源。

这是 Linux 命令系列的一篇,承接 Linux 常见命令与工具地图服务器排查那篇 讲了用 ps/top 找进程,这篇讲找到之后怎么正确地”管”它。

信号:内核给进程递的通知条

信号(signal)是内核发给进程的一种异步通知。大部分信号进程可以选择怎么响应——捕获它执行自定义逻辑、忽略它、或者按默认行为处理;只有极少数信号(比如 SIGKILL)是强制的,进程没有任何选择余地。

1
2
3
kill -l
# 列出系统支持的全部信号,有几十个
# 日常真正需要认识的只有三四个:TERM、KILL、HUP

kill 命令的作用就是给指定 PID 的进程发信号——发什么信号由参数决定,不指定就发默认的。

SIGTERM(15):kill 的默认信号,请求优雅退出

1
2
3
4
5
6
7
8
kill 12345
# 不带任何信号参数,默认发 SIGTERM(15)
# 语义是"请你退出"——进程可以捕获这个信号,先做完清理再退出:
# 保存未写盘的数据、关闭数据库连接、写完最后的日志

kill -15 12345
kill -TERM 12345
# 和上面完全等价,显式写出信号编号或名字

绝大多数正经写的服务都注册了 SIGTERM 处理逻辑(Go 里的 signal.Notify、容器里的优雅停机钩子都是响应它),收到后会体面地收尾。这也是 systemctl stopdocker stop 停服务时先发的信号。

SIGKILL(9):最后手段,不是首选

1
2
3
kill -9 12345
# SIGKILL 由内核直接终结进程,进程无法捕获、无法忽略——
# 也就没有任何清理机会:没保存的数据直接丢、锁文件残留、连接不释放

-9 的问题不在”杀不死”,恰恰在”杀得太死”:进程的清理逻辑一行都不会执行。数据库进程被 -9 可能留下损坏的数据文件,持锁的进程被 -9 可能让别的进程永远等锁。正确的顺序是:

1
2
3
4
5
6
kill 12345
# 第 1 步:先发 SIGTERM,给进程清理的机会

sleep 5 && kill -0 12345 2>/dev/null && kill -9 12345
# 第 2 步:等几秒,确认还没退出(kill -0 只检测进程存在,不发实际信号),
# 这时候再上 -9 强制终结

-9 当默认选项,等于放弃了所有进程善后的机会——它应该是 SIGTERM 无效之后的最后手段。

SIGHUP(1):重载配置的约定俗成

SIGHUP 历史上表示”终端挂断”(hangup,拨号上网时代的遗产),现在大量守护进程把它重新定义成”重新加载配置文件,但不重启”:

1
2
3
kill -HUP $(cat /var/run/nginx.pid)
# 给 nginx 主进程发 SIGHUP,nginx 会重读配置并平滑应用,服务不中断
# nginx -s reload 底层做的就是这件事

不是所有程序都遵守这个约定,用之前查一下目标服务的文档;但见到 kill -HUP 时要知道,它的意图大概率是”重载配置”,不是杀进程。

按场景补全:还有几个迟早会用到的信号

前面 TERM/KILL/HUP 是服务进程最常打交道的三个,但信号还分布在别的几类常见场景里,按场景归类一次,以后碰到能直接搜到。

终端交互:Ctrl-C / Ctrl-\ / Ctrl-Z 发的都是信号

1
2
3
4
# 前台跑一个程序时按下的这几个组合键,本质都是终端驱动发信号给前台进程组:
# Ctrl-C → SIGINT(2) 请求中断,默认行为是终止进程,程序可以捕获自己处理
# Ctrl-\ → SIGQUIT(3) 请求退出并生成 core dump,比 Ctrl-C 更"重",调试卡死的进程时有时会用
# Ctrl-Z → SIGTSTP(20)请求挂起到后台,不是终止,配合 fg/bg 恢复运行

kill -INT 12345 和终端按 Ctrl-C 发的是同一个信号,只是来源不同(终端驱动 vs 手动 kill)。很多程序对 SIGINT 和 SIGTERM 的处理逻辑是一样的(收到任何一个都触发优雅退出),这也是为什么很多语言的信号捕获写法会把这两个一起注册。

进程恢复:SIGSTOP 和 SIGCONT

1
2
3
4
5
kill -STOP 12345
# SIGSTOP:强制暂停进程,和 SIGKILL 一样不可被捕获、不可被忽略

kill -CONT 12345
# SIGCONT:唤醒一个被暂停的进程(不管是被 SIGSTOP 还是 Ctrl-Z/SIGTSTP 暂停的),继续运行

fg/bg 命令背后做的就是发 SIGCONTSIGSTOP 常用来临时冻结一个占用资源的进程做诊断,而不用直接杀掉它,诊断完 SIGCONT 恢复运行。

自定义运行时行为:SIGUSR1 / SIGUSR2

1
2
3
4
kill -USR1 $(cat /var/run/nginx.pid)
# SIGUSR1/SIGUSR2 内核不规定具体语义,做什么完全由程序自己约定
# nginx 用 SIGUSR1 触发"重新打开日志文件"——配合 logrotate 切割日志后,
# 不用重启进程就能让 nginx 把新日志写进新文件

见到某个服务的文档里提到”发 SIGUSR1 触发 xxx”,说明这是这个程序自己约定的行为,跟 nginx 的用法未必是一回事,用之前查该程序自己的文档。

子进程与管道:SIGCHLD、SIGPIPE 不是手动发的

1
2
3
4
5
6
# SIGCHLD:子进程退出或状态改变时,内核自动发给父进程,
# 父进程靠它触发 wait(),回收子进程资源,避免留下僵尸进程

# SIGPIPE:往一个读端已经关闭的管道或 socket 写数据时触发,
# 默认行为是终止当前进程——这也是很多网络服务要显式忽略 SIGPIPE 的原因,
# 否则对端一断开连接,写操作就可能把整个进程杀死

这两个不是靠 kill 手动发的,是内核在特定事件下自动触发,但排查”进程为什么莫名其妙退出了”这类问题时,经常需要认出它们。

pkill / pgrep:按名字找和杀,跳过查 PID

pgrep 按进程名查 PID,pkill 按进程名直接发信号——省掉 ps aux | grep xxx 再手动复制 PID 的三步操作:

1
2
3
4
5
6
7
8
9
pgrep -l nginx
# -l:连同进程名一起列出所有匹配 "nginx" 的进程 PID

pkill nginx
# 给所有名字匹配 "nginx" 的进程发 SIGTERM(默认信号和 kill 一样)

pkill -9 -f "python.*worker"
# -f:匹配完整命令行而不只是进程名——适合按启动参数区分同名进程
# 注意 -f 匹配范围大,先用 pgrep -f 确认匹配到的是哪些进程再杀,避免误伤

pkill -f 是双刃剑:模式写宽了会把不相干的进程一起杀掉。安全的习惯是先跑一遍同参数的 pgrep -fl,看清楚列表再动手。

nice / renice:CPU 调度优先级

nice 值决定进程在 CPU 竞争中的优先级,范围 -20 到 19——数值越低优先级越高,这个方向和直觉相反:可以理解成”nice 值越高,进程越’客气’,越愿意把 CPU 让给别人”。普通用户只能调高 nice 值(降低优先级),设负值需要 root。

1
2
3
4
5
6
7
nice -n 19 tar -czf backup.tar.gz /data
# -n:指定 nice 值增量,19 是最低优先级
# 跑大型备份、压缩这类不着急的任务时降低优先级,避免拖慢正常服务

renice -n 10 -p 12345
# renice 调整已经在运行的进程,-p 指定 PID
# 发现某个后台任务吃 CPU 影响了在线服务,不用重启它,直接调低优先级

常见场景对应的信号速查表

场景 信号(编号) 作用
请求优雅退出 SIGTERM(15) kill 默认信号,进程可捕获自己清理后退出
强制终结 SIGKILL(9) 内核直接终结,不可捕获不可忽略,无清理机会,最后手段
重载配置 SIGHUP(1) 多数守护进程约定为”重读配置不重启”(如 nginx -s reload
终端按 Ctrl-C SIGINT(2) 请求中断,默认终止,可捕获自己处理,语义常和 SIGTERM 一起注册
终端按 Ctrl-\ SIGQUIT(3) 请求退出并生成 core dump,比 Ctrl-C 更”重”
终端按 Ctrl-Z SIGTSTP(20) 请求挂起到后台,不是终止,配合 fg/bg 恢复
强制暂停 SIGSTOP(19) 不可捕获不可忽略地冻结进程,常用于诊断
恢复运行 SIGCONT(18) 唤醒被 SIGSTOP 或 Ctrl-Z 暂停的进程,fg/bg 背后就是它
自定义运行时行为 SIGUSR1 / SIGUSR2(10 / 12) 内核不规定语义,由程序自己约定(nginx 用 SIGUSR1 切割日志)
子进程状态变化(内核自动发) SIGCHLD(17) 子进程退出/停止时发给父进程,触发 wait() 回收,避免僵尸进程
写入已断开的管道/socket(内核自动发) SIGPIPE(13) 默认终止当前进程,网络服务常显式忽略它
只探测不发送 kill -0 不发实际信号,只检测进程是否存在
列出全部信号 kill -l 查看系统支持的完整信号列表
命令 作用
pgrep -l 名字 按进程名查 PID
pkill 名字 按进程名发 SIGTERM
pkill -f 模式 按完整命令行匹配,先用 pgrep -fl 确认再杀
nice -n N 命令 以指定优先级启动,-20 最高、19 最低,越低越优先
renice -n N -p PID 调整运行中进程的优先级,负值需要 root

写到这里

kill 家族的正确打开方式:默认 SIGTERM 给进程体面退出的机会,几秒后还赖着不走再 -9;重载配置认准 SIGHUP;批量操作用 pkill 但先 pgrep 确认目标。至于 nice,记住”值越低越优先”这个反直觉设定,剩下的就是两条命令的事。