第53课:kill 与进程信号
学习目标
- 理解 Linux 进程信号的作用和传递机制
- 掌握使用
kill命令向进程发送信号的方法 - 熟悉常见的信号(如
SIGTERM,SIGKILL,SIGHUP)及其含义 - 学会根据进程名查找并发送信号,以及如何安全地终止“顽固”进程
核心概念
在 Linux 中,进程之间的通信除了通过文件、网络等,还有一个非常重要的机制——信号 (Signal)。信号是进程间通信的一种简单形式,它是一个发送给进程的异步通知,告诉进程发生了某个特定事件。
你可以把信号想象成操作系统或另一个进程对你正在运行的程序“喊话”。比如,你正在运行一个程序,突然想退出,通常可以按 Ctrl+C。这实际上就是终端驱动程序向你的前台进程组发送了一个 SIGINT(中断)信号,请求进程终止。
kill 命令的名字可能有些误导,它并非总是用于“杀死”进程。更准确地说,它是用来向一个或多个进程发送信号的工具。默认情况下,它发送的是 SIGTERM (15) 信号,这是一个“礼貌”的终止请求。进程可以捕获这个信号,执行清理操作(如保存文件、释放资源)后再退出。
然而,有些进程可能会忽略 SIGTERM 信号,或者陷入无法响应的状态。这时,我们可以使用“最终手段”——发送 SIGKILL (9) 信号。这是一个由内核直接强制终止进程的信号,进程无法捕获、忽略或阻塞它。这是真正的“杀死”进程,但也意味着进程没有机会进行任何清理。
代码示例
1. 理解信号处理(C语言示例)
这个程序演示了如何捕获 SIGINT 信号,并在退出前执行一些操作。
// 文件名:signal_catch.c
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
// 信号处理函数
void handle_sigint(int sig) {
printf("\n捕获到信号 %d (SIGINT)!\n", sig);
printf("正在执行清理工作...\n");
// 模拟清理,比如关闭文件、释放内存
sleep(1);
printf("清理完成,程序即将退出。\n");
exit(0);
}
int main() {
// 注册信号处理函数,当收到 SIGINT 时调用 handle_sigint
signal(SIGINT, handle_sigint);
printf("程序启动,PID: %d\n", getpid());
printf("请尝试按 Ctrl+C 或在另一个终端用 `kill -SIGINT %d` 发送信号\n", getpid());
// 无限循环,等待信号
while(1) {
sleep(1);
}
return 0;
}
编译和运行:
gcc signal_catch.c -o signal_catch
./signal_catch
在另一个终端窗口,使用 kill 命令测试:
# 先找到进程号(假设输出为 12345)
ps aux | grep signal_catch
# 发送 SIGINT 信号(2号信号)
kill -2 12345
# 或者使用信号名称
kill -SIGINT 12345
2. 使用 kill 命令管理进程
# 查看所有信号及其编号
kill -l
# 启动一个简单的后台进程(例如 `top` 或 `sleep`)
sleep 300 &
# 记下它的进程号(PID),假设为 23456
# 发送默认的 SIGTERM 信号(15),请求进程优雅退出
kill 23456
# 如果进程没退出,发送 SIGKILL 信号(9)强制终止
kill -9 23456
# 根据进程名查找并终止进程(更常用)
# 1. 使用 pkill(按模式匹配)
pkill -f "sleep 300"
# 2. 使用 killall(按精确进程名)
killall sleep
实践练习
练习1:基本信号发送
- 在终端A中,运行命令
sleep 1000,使其在后台执行。 - 记下它的PID。
- 在终端B中,先尝试用
kill PID(默认信号) 终止它。观察现象。 - 如果需要,使用
kill -9 PID强制终止。 - 思考:如果
sleep进程在前台运行,直接按Ctrl+C发送了什么信号?
预期输出:
- 发送
SIGTERM后,sleep进程会正常退出。 - 使用
ps命令确认进程已消失。
练习2:按名称管理进程
- 同时启动两个
sleep进程:sleep 200和sleep 300。 - 使用
pkill命令,只终止参数为 “300” 的那个sleep进程。 - 确认另一个
sleep 200进程仍然在运行。 - 最后清理所有
sleep进程。
预期命令与输出:
# 启动
sleep 200 &
sleep 300 &
# 精准终止
pkill -f "sleep 300"
# 验证
ps aux | grep sleep
# 清理
pkill -f "sleep 200"
练习3:在脚本中处理信号(进阶)
编写一个简单的Shell脚本 monitor.sh,它每隔5秒打印一次时间。当收到 SIGTERM (15) 信号时,它应打印“收到终止信号,脚本退出”后正常退出;当收到 SIGUSR1 (10) 信号时,应打印“收到用户自定义信号1”。
#!/bin/bash
# 用 trap 命令捕获信号并定义处理动作
trap 'echo "收到终止信号,脚本退出"; exit 0' SIGTERM
trap 'echo "收到用户自定义信号1"' SIGUSR1
echo "监控脚本启动,PID: $$"
while true; do
echo "$(date)"
sleep 5
done
运行后,在另一个终端测试:
kill -SIGUSR1 <PID>
kill <PID> # 默认SIGTERM
常见错误
- 混淆
SIGTERM和SIGKILL:新手倾向于直接用kill -9,这是不好的习惯。应该先尝试kill PID或kill -15 PID,给进程一个自我清理的机会。只在进程无响应时才使用-9。 - 误杀关键进程:使用
pkill或killall时,务必仔细检查匹配模式。例如,pkill ssh可能会杀掉你的远程连接会话本身,导致断连。使用pgrep -a ssh先预览匹配结果更安全。 - 信号数字记错:不要死记硬背所有信号编号。常用的就几个(1, 2, 3, 9, 15, 19)。使用
kill -l查看列表,或在命令中直接使用信号名称(如kill -SIGKILL PID),这样更清晰。 - 忽略进程状态:当进程处于 D (Uninterruptible Sleep) 状态(通常是等待I/O)时,即使
SIGKILL信号也无法立即生效。这时只能等待其I/O操作完成,或重启系统。Z (Zombie) 状态的进程已经结束,只需回收其资源,kill对其无效。
小结
- 信号是 Linux 进程通信的基石,用于通知进程异步事件。
kill命令的主要用途是发送信号,而不仅仅是终止进程。默认信号是SIGTERM(15)。- 优雅终止优先于强制终止:首先尝试
SIGTERM,让进程有机会清理。仅在必要时使用SIGKILL(9)。 - 善用进程名管理工具:
pkill和killall比手动查找PID更高效,但使用时需谨慎,避免误操作。 - 高级应用:在脚本和程序中,你可以使用
trap(Shell) 或signal()(C语言) 来捕获并自定义信号处理逻辑,使你的程序更加健壮和可控。
理解进程信号,是进行高效 Linux 系统管理和故障排查的关键一步。它能让你在进程“失控”时,拥有清晰而有力的控制手段。