53·进程管理进阶

kill 与进程信号

killsignals

第53课:kill 与进程信号

学习目标

  1. 理解 Linux 进程信号的作用和传递机制
  2. 掌握使用 kill 命令向进程发送信号的方法
  3. 熟悉常见的信号(如 SIGTERM, SIGKILL, SIGHUP)及其含义
  4. 学会根据进程名查找并发送信号,以及如何安全地终止“顽固”进程

核心概念

在 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:基本信号发送

  1. 在终端A中,运行命令 sleep 1000,使其在后台执行。
  2. 记下它的PID。
  3. 在终端B中,先尝试用 kill PID (默认信号) 终止它。观察现象。
  4. 如果需要,使用 kill -9 PID 强制终止。
  5. 思考:如果 sleep 进程在前台运行,直接按 Ctrl+C 发送了什么信号?

预期输出:

  • 发送 SIGTERM 后,sleep 进程会正常退出。
  • 使用 ps 命令确认进程已消失。

练习2:按名称管理进程

  1. 同时启动两个 sleep 进程:sleep 200sleep 300
  2. 使用 pkill 命令,只终止参数为 “300” 的那个 sleep 进程
  3. 确认另一个 sleep 200 进程仍然在运行。
  4. 最后清理所有 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

常见错误

  1. 混淆 SIGTERMSIGKILL:新手倾向于直接用 kill -9,这是不好的习惯。应该先尝试 kill PIDkill -15 PID,给进程一个自我清理的机会。只在进程无响应时才使用 -9
  2. 误杀关键进程:使用 pkillkillall 时,务必仔细检查匹配模式。例如,pkill ssh 可能会杀掉你的远程连接会话本身,导致断连。使用 pgrep -a ssh 先预览匹配结果更安全。
  3. 信号数字记错:不要死记硬背所有信号编号。常用的就几个(1, 2, 3, 9, 15, 19)。使用 kill -l 查看列表,或在命令中直接使用信号名称(如 kill -SIGKILL PID),这样更清晰。
  4. 忽略进程状态:当进程处于 D (Uninterruptible Sleep) 状态(通常是等待I/O)时,即使 SIGKILL 信号也无法立即生效。这时只能等待其I/O操作完成,或重启系统。Z (Zombie) 状态的进程已经结束,只需回收其资源,kill 对其无效。

小结

  • 信号是 Linux 进程通信的基石,用于通知进程异步事件。
  • kill 命令的主要用途是发送信号,而不仅仅是终止进程。默认信号是 SIGTERM (15)。
  • 优雅终止优先于强制终止:首先尝试 SIGTERM,让进程有机会清理。仅在必要时使用 SIGKILL (9)。
  • 善用进程名管理工具pkillkillall 比手动查找PID更高效,但使用时需谨慎,避免误操作。
  • 高级应用:在脚本和程序中,你可以使用 trap (Shell) 或 signal() (C语言) 来捕获并自定义信号处理逻辑,使你的程序更加健壮和可控。

理解进程信号,是进行高效 Linux 系统管理和故障排查的关键一步。它能让你在进程“失控”时,拥有清晰而有力的控制手段。

练习编辑器

rust
Loading...

继续学习

完成本课后,建议继续学习下一课「nice 与 renice 进程优先级」 以巩固所学知识。