kill -9 也杀不掉的进程:一次 Linux D 状态不可中断睡眠排查复盘

一个进程卡死,以 root 身份连发十几个 kill -9 都杀不掉,而每一次 kill 命令都干净返回退出码 0 没有任何报错。一个我用最强信号连发十几次都杀不掉、却又声称每次都成功的进程。排查梳理:kill 不是去把进程杀掉,它做的只是给进程投递一个信号放进信箱,真正处理信号是进程自己被调度时去做的;kill 返回 0 只代表信号成功投递,完全不保证进程已死;kill 真正报错只有 No such process 和 Operation not permitted 两种;SIGKILL -9 无法被捕获忽略但它的必杀由内核执行,进程卡在内核没法介入的状态时 -9 也得排队;进程不是活死二元有 R/S/D/Z/T 多种状态,ps 的 STAT 列第一个字母就是状态;D 是不可中断睡眠进程卡在一次内核态 I/O 里这期间任何信号包括 SIGKILL 都得排队;S 可中断睡眠信号一来立刻醒能杀,D 睡得死信号唤不醒;信号要在进程从内核态返回用户态那一刻才被检查,D 状态根本走不到那一步;D 状态是保护一次不能半途而废的 I/O 完整性的有意设计正常一闪而过;wchan 和 /proc/PID/stack 能看出进程卡在哪个内核函数,nfs 开头就是卡在 NFS;D 进程不是病根是受害者根子是一次完不成的 I/O,先 dmesg 找 NFS 失联或磁盘 I/O error;dmesg 里 not responding still trying 是 NFS,task blocked for more than 120 seconds 是内核报有进程卡死 I/O;NFS 默认 hard 挂载服务端失联会让进程无限重试永久卡 D,soft 挂载会返回错误让进程解脱;NFS 卡死可 umount -l 把挂载点从目录树摘掉,I/O 一旦有结果堆积的 kill 信号立刻生效;正确解法是认清 D 进程 kill 无用、去治 I/O 的根、监控 D 进程数,以及一套杀不掉的进程排查纪律。

2022 年,我遇到一个把我对 kill 的全部信任都打碎了的故障。一台服务器上有个进程卡死了,不响应了。这种事我处理过无数次,流程烂熟于心:kill 它,不行就 kill -9,-9 是"最高权限的处决令",我一直深信,世上没有 kill -9 杀不掉的进程。我敲下 kill -9 8742,回车,命令安安静静地返回了,没有任何报错。我 ps 一看——它还在。我以为是我手快,系统还没反应过来,等了几秒,再 ps——还在。我又 kill -9 了一次,再一次,连着发了十几个 -9,每一次命令都干干净净地返回,没有一句抱怨。可那个进程,PID 8742,稳稳地待在 ps 的输出里,纹丝不动。我开始怀疑是不是权限不够,sudo kill -9,还是杀不掉。一个我以 root 身份、用最强的 -9 信号、连发了十几次都杀不掉的进程——而每一次 kill 命令,都明明白白告诉我"我执行成功了"。kill 说它成功了,进程却还活着。这两件事就这么荒唐地同时成立。我盯着那个杀不死的 PID,第一次开始怀疑:kill -9 这个"处决令",它到底是怎么"处决"一个进程的?这件事逼着我把进程状态、不可中断睡眠、信号投递这一整套彻底理清了。本文复盘这次实战。

问题背景

环境:CentOS 7,一台挂载了 NFS 网络存储的应用服务器
事故现象:
- 一个进程卡死,kill -9 连发十几次都杀不掉
- ★ 每次 kill -9 命令都正常返回,没有任何报错
- ★ sudo kill -9 也杀不掉

现场排查:
# 1. kill -9 干净返回,进程却还在
$ kill -9 8742
$ echo $?
0                                        # ★ 退出码 0,kill"成功"了
$ ps -p 8742
  PID TTY   TIME CMD
 8742 ?     0:03 myapp                   # ★ 它还在!

# 2. ★ 看这个进程的状态(STAT 列)
$ ps -eo pid,stat,comm | grep 8742
 8742 D     myapp
#      ^ ★ 状态是 D —— 不是 R 也不是 S,是 D

# 3. ★ D 是什么意思?看进程卡在哪个内核函数
$ ps -eo pid,stat,wchan:32,comm | grep 8742
 8742 D    nfs_wait_on_request           myapp
#     ^^^   ^^^^^^^^^^^^^^^^^^^ ★ 卡在一个 nfs 开头的内核函数里

# 4. ★ 看进程的内核调用栈,进一步确认
$ cat /proc/8742/stack
[<...>] nfs_wait_on_request+0x...
[<...>] nfs_updatepage+0x...
[<...>] nfs_write_end+0x...
# ★ 一连串 nfs_ 开头 —— 它死死卡在一次 NFS 写操作上

# 5. ★ 看系统日志,NFS 出事了
$ dmesg | tail
nfs: server 10.0.0.200 not responding, still trying
#    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ★ NFS 服务器失联了!

# 6. 确认那个 NFS 挂载
$ mount | grep nfs
10.0.0.200:/data on /mnt/nfs type nfs (rw,hard,...)
#                                         ^^^^ ★ hard 挂载

根因(后来想清楚的):
1. ★ kill 不是"我去把进程杀了"。kill 做的事只是
   【投递一个信号】给进程 —— 把信号放进进程的
   "信箱"。真正的"处理这个信号(比如自杀)",
   是【进程自己】在被内核调度运行时去做的。
2. ★ 进程有好几种状态。其中 D 状态叫"不可中断
   睡眠":进程正卡在一次【内核态的 I/O】里等结果,
   这期间它【不会被调度、不响应任何信号】 ——
   连 SIGKILL(-9)也不行。
3. 我那个进程,state=D,卡在一次 NFS 写操作里。
   NFS 服务器 10.0.0.200 失联了,这次写操作
   永远完不成 -> 进程永远停在 D 状态。
4. ★ 于是:我每次 kill -9,信号确实成功投递进了
   它的信箱(所以 kill 返回 0)。但进程在 D 状态
   里"睡死"了,根本没机会醒来去查看信箱、执行
   那个"自杀"。信号就一直堆在信箱里没人处理。
5. ★ 真正的根子不在进程,在 NFS。进程是被一个
   完不成的 I/O 卡住的"受害者"。
不是 kill 没用,是进程在 D 状态里根本没机会响应它。

修复 1:kill -9 失败,不代表 kill 命令出错

# === ★ 先纠正一个根深蒂固的误解:kill 到底干了什么 ===

# === ★ kill 不是"杀",是"投递一个信号" ===
# "kill" 这个名字,误导了无数人。它真正做的事是:
#   ★ 给目标进程【发送一个信号】 —— 仅此而已。
# 信号是一个"通知"。kill 把这个通知投递进进程的
#   "信箱",kill 的工作就【结束】了。
# ★ 至于进程收到信号后做什么 —— 那是【进程那边】
#   的事,和 kill 命令本身没关系了。

# === ★ kill 返回 0,只代表"信号投递成功" ===
$ kill -9 8742
$ echo $?
0
# ★ 这个 0 的含义,精确地说是:
#   "信号已经成功放进了 8742 的信箱。"
# ★ 它【完全不保证】:进程已经读到信号了、
#   进程已经因此退出了。投递成功 ≠ 进程已死。

# === ★ kill 真正会"报错"的几种情况 ===
$ kill -9 99999
-bash: kill: (99999) - No such process       # 进程不存在
$ kill -9 1234
-bash: kill: (1234) - Operation not permitted # 权限不够
# ★ 只有这类情况 kill 才非 0。如果 kill 返回了 0,
#   说明"投递"这一步没问题 —— 进程没死,问题
#   出在【进程收到信号之后】,不在 kill。

# === ★ SIGKILL(-9)的特殊之处,和它的局限 ===
# 普通信号(如 -15 SIGTERM),进程可以"捕获"它,
#   自己写代码决定怎么处理,甚至忽略。
# ★ SIGKILL(-9)和 SIGSTOP 不一样:进程【无权】
#   捕获、无权忽略 -9。所以大家说 -9 是"必杀"。
# ★ 但这里有个大前提常被忽略:-9 的"必杀",是
#   【内核】来执行的 —— 内核要在某个时机,把这个
#   进程清理掉。而如果进程正卡在一种【内核根本
#   没法介入清理】的状态里,-9 就也得【排队等】。
#   这就是本文这次。

# === 认知 ===
# ★ kill 返回 0,只说明"信号送到了信箱"。进程没死,
#   不是 kill 失败,而是这个信号【还没被处理】 ——
#   接下来该查的是"进程为什么没处理它",而不是
#   "kill 为什么没用"。

修复 2:看懂进程状态——ps 的 STAT 列

# === ★ 进程不只有"活着/死了",它有好几种状态 ===

# === ★ ps 的 STAT 列,就是进程的当前状态 ===
$ ps -eo pid,stat,comm
  PID STAT COMMAND
 1234 S    sshd
 5678 R    top
 8742 D    myapp                          # ★ 我们的"问题进程"
 9001 Z    defunct                        # 僵尸
# ★ STAT 第一个字母,就是进程状态。必须看懂它。

# === ★ 几种核心状态 ===
# R (Running)  -> 正在运行,或在运行队列里等 CPU。
# S (Sleeping) -> ★ 可中断睡眠。最常见的状态:
#                 进程在等点什么(等网络、等输入),
#                 但它【能被信号唤醒】。kill 它,
#                 它立刻就能醒来响应 —— 能杀掉。
# D (Disk sleep)-> ★ 不可中断睡眠。进程在等一次
#                 内核态 I/O,这期间【信号都得排队】,
#                 包括 kill -9。本文这次就是它。
# Z (Zombie)   -> 僵尸:进程已死,但父进程还没回收
#                 它的退出状态。kill 僵尸没用
#                 (它已经死了)。
# T (Stopped)  -> 被暂停了(如按了 Ctrl+Z,或收到
#                 SIGSTOP)。

# === ★ 这次的关键:S 和 D 的区别 ===
# S 和 D 都是"在睡觉、在等待",但有本质区别:
#  - S(可中断):睡得浅。信号一来,立刻惊醒,
#    放下手头的等待,转去处理信号。-> kill 有效。
#  - D(不可中断):睡得"死"。它在等一件【绝对
#    不能被打断】的事(一次内核 I/O),在这件事
#    完成之前,任何信号都【唤不醒】它。-> kill
#    会被无限期推迟。
# ★ "kill -9 杀不掉",几乎可以直接翻译成:
#   "这个进程处于 D 状态"。

# === ★ STAT 后面还可能跟附加字母 ===
$ ps -eo pid,stat,comm | grep myapp
 8742 D<   myapp
#       ^ <:高优先级;N:低优先级;l:多线程;
#         s:会话首进程;+:在前台进程组。
# ★ 排查时,看【第一个字母】就够了。

# === ★ 一眼揪出所有 D 状态进程 ===
$ ps -eo pid,stat,comm | awk '$2 ~ /^D/'
 8742 D    myapp
# ★ 系统卡、负载高时,这一条很有用 —— 一片 D
#   进程,基本就是 I/O 出事了。

# === 认知 ===
# ★ 进程不是"活/死"二元的,它有 R/S/D/Z/T 等状态。
#   kill -9 对 S 状态立竿见影,对 D 状态却得排队 ——
#   排查"杀不掉的进程",第一件事就是看 STAT 列。

修复 3:D 状态到底是什么——不可中断睡眠

# === ★ 理解 D 状态:为什么连 -9 都奈何不了它 ===

# === ★ 信号是怎么"被处理"的 ===
# 信号不会"主动冲进进程里执行"。流程是这样:
#  1. kill 把信号放进进程的信箱(pending 信号集)。
#  2. ★ 进程要在某个时机"检查信箱" —— 这个时机,
#     通常是它【从内核态返回用户态】的那一刻。
#  3. 检查到有信号,才去处理它(比如执行退出)。
# ★ 关键:第 2 步必须发生,信号才会被处理。

# === ★ D 状态:进程"卡在内核态里出不来" ===
# 当进程发起一次 I/O(读写磁盘、读写网络存储),
#   它会【陷入内核态】,然后睡眠等待 I/O 结果。
# ★ 如果这个睡眠是 D(不可中断)类型的,那么在
#   I/O 完成之前,进程就【一直卡在内核态里】,
#   它【根本没有机会】走到"返回用户态、检查信箱"
#   那一步。
# ★ 所以信号(哪怕是 -9)就一直堆在信箱里 ——
#   不是内核不想杀它,是这个进程此刻【不在能被
#   杀的位置上】。它得先把那次 I/O 等完、从内核
#   态出来,才轮得到处理信号。

# === ★ 为什么要设计这种"杀不掉"的状态 ===
# 这不是 bug,是【有意的设计】。想象进程正在往
#   磁盘写一块数据,写到一半:
#  - 如果这时允许信号打断它、让它立刻退出 ——
#    那块数据就写坏了,文件系统可能损坏。
# ★ D 状态的意义:保护一次【关键的、不能半途而废
#   的 I/O 操作】的完整性。代价是:这期间进程
#   "刀枪不入"。
# ★ 正常情况,一次 I/O 几毫秒就完事,D 状态一闪
#   而过,你根本注意不到。只有当 I/O【永远完不成】
#   时,进程才会"永久卡在 D" —— 那才是故障。

# === ★ 看进程到底卡在哪个内核函数:wchan ===
$ ps -eo pid,stat,wchan:40,comm | grep 8742
 8742 D    nfs_wait_on_request            myapp
#          ^^^^^^^^^^^^^^^^^^^ ★ wchan:进程睡在哪个内核函数
# ★ wchan(wait channel)告诉你"它在等什么"。
#   nfs_ 开头 -> 等 NFS;io_schedule -> 等磁盘 I/O。

# === ★ 看完整的内核调用栈 ===
$ cat /proc/8742/stack
[<0>] nfs_wait_on_request+0x...
[<0>] nfs_updatepage+0x...
[<0>] nfs_write_end+0x...
# ★ 一连串 nfs_,实锤:它卡在一次写 NFS 的操作上。

# === 认知 ===
# ★ D 状态是进程"卡在一次内核 I/O 里、还没机会
#   回到用户态检查信箱"的状态。这是保护 I/O 完整性
#   的有意设计。kill -9 不是无效,是【还没轮到】 ——
#   I/O 不结束,它就一直没机会被执行。

修复 4:D 进程从哪来——卡死的 I/O

# === ★ 进程为什么会"永久"卡在 D —— 找那个完不成的 I/O ===

# === ★ D 进程的根子:一次永远完不成的 I/O ===
# 进程卡在 D 出不来,唯一的原因就是:它等的那次
#   I/O,迟迟不返回结果。常见的"永不返回的 I/O":
#  1. ★ NFS / 网络存储 失联(本文这次):远端的
#     存储服务器宕了、网络断了,读写请求石沉大海。
#  2. ★ 物理磁盘故障:盘坏了、坏道,读写卡死。
#  3. 磁盘极度繁忙:I/O 被压满,请求排长队(这种
#     多半会自己缓过来,不是永久卡死)。
#  4. 底层块设备问题:比如某些云盘、iSCSI 掉线。

# === ★ 第一现场:看内核日志 dmesg ===
$ dmesg -T | tail -20
[...] nfs: server 10.0.0.200 not responding, still trying
[...] nfs: server 10.0.0.200 not responding, still trying
# ★ "not responding, still trying" —— NFS 服务器
#   失联了,内核还在一遍遍重试。这就是 D 进程的根。
# 磁盘故障会是别的关键词:
#   "I/O error", "blk_update_request: I/O error",
#   "task XXX blocked for more than 120 seconds"

# === ★ "blocked for more than 120 seconds" 这条很关键 ===
# 内核有个机制:一个任务在 D 状态卡了超过 120 秒,
#   内核就会打印一条 "hung task" 警告:
$ dmesg | grep -i 'blocked for more than'
INFO: task myapp:8742 blocked for more than 120 seconds.
# ★ 看到这条,就是明确的"有进程卡死在 I/O"信号。

# === ★ 看是不是磁盘 I/O 出了问题 ===
$ iostat -x 2 3
# ★ 看某块盘:%util 长期 100%、await(响应时间)
#   高得离谱 -> 这块盘有问题或被压垮。

# === ★ 看 NFS 挂载的情况 ===
$ mount | grep nfs
10.0.0.200:/data on /mnt/nfs type nfs (rw,hard,intr,...)
# ★ 注意挂载选项里的 hard / soft:
#  - hard(默认):NFS 服务器没响应,客户端【无限
#    重试】 —— 进程就【永久】卡在 D。
#  - soft:重试几次就放弃、返回一个错误 —— 进程
#    能拿到错误、从 D 里出来(但可能丢数据)。
# ★ 我这次是 hard 挂载,所以进程永久卡死。

# === ★ 一个常见的"连环卡死" ===
# NFS 一挂,常常【一连串】进程跟着卡 D:任何一个
#   去碰那个 NFS 目录的进程(哪怕只是 ls、df),
#   都会卡进去。所以你会看到一大片 D 进程。
$ ps -eo pid,stat,comm | awk '$2 ~ /^D/'   # 一看一大片

# === 认知 ===
# ★ D 进程自己不是病根,它是"受害者" —— 真正的
#   病根是某个【完不成的 I/O】:NFS 失联、磁盘故障。
#   先 dmesg 找到那个 I/O 源头,别盯着进程使劲 kill。

修复 5:正确解法——治 I/O 的根,别只盯着进程

# === ★ 解法:D 进程杀不掉,要去解决它等的那个 I/O ===

# === ★ 解法 1:认清——D 进程,kill 是没用的 ===
# 先接受现实:进程在 D 状态,你 kill 多少次、
#   用多大的信号,都【没用】。它要等 I/O 结束、
#   从内核态出来,才会处理信号。
# ★ 别再连发 kill -9 了 —— 把精力转向"I/O"。
# ★ 唯一例外:I/O 一旦"恢复"或"彻底失败返回",
#   进程立刻就会出 D 状态,之前堆积的 kill 信号
#   会【马上生效】,进程瞬间被清理掉。所以方向是:
#   让那个 I/O"有个结果"。

# === ★ 解法 2:NFS 卡死 —— 恢复 NFS 服务端 ===
# 如果根因是 NFS 服务器失联(本文这次):
#  - ★ 首选:把 NFS 服务器/网络修复。一旦 NFS
#    恢复响应,客户端那些卡住的 I/O 就完成了,
#    D 进程立刻解脱、堆积的 kill 立刻生效。
$ ping 10.0.0.200                    # 先确认 NFS 服务器通不通
$ showmount -e 10.0.0.200            # 确认 NFS 导出恢复

# === ★ 解法 3:NFS 服务端救不回 —— 强制卸载挂载点 ===
# 如果 NFS 服务器短期回不来,要让客户端"死心":
$ umount -f /mnt/nfs                 # -f:强制卸载
$ umount -l /mnt/nfs                 # -l:lazy,先从目录树摘掉
# ★ -l(lazy umount)很实用:它立刻把挂载点从
#   目录树上摘除,让【新的】访问不再卡进去;已经
#   卡住的旧进程,等内核处理完会陆续释放。
# ★ 卸载后,卡死的 I/O 会失败返回,D 进程随之解脱。

# === ★ 解法 4:预防——NFS 挂载用 soft + 超时 ===
# 从源头降低"永久卡死"的风险,挂载选项里:
$ mount -t nfs -o soft,timeo=30,retrans=3 10.0.0.200:/data /mnt/nfs
# ★ soft:重试几次失败就【返回错误】,而不是无限
#   重试 —— 进程能拿到错误、从 D 里出来,不会永久卡。
# ⚠ 代价:soft 在写操作时可能丢数据。要紧数据的
#   场景,业界仍倾向 hard,但要配好监控、能快速
#   发现 NFS 异常。按业务取舍。

# === ★ 解法 5:磁盘故障引起的 D —— 换盘/隔离 ===
# 如果 dmesg 里是 "I/O error"、坏盘:
#  - 这种 D 进程,基本要等到【那块盘被处理掉】
#    (换盘、从 RAID 剔除、卸载)才会解脱。
#  - 实在不行,卡死的进程可能要靠【重启机器】
#    才能彻底清掉 —— 因为 D 进程,真的连 root
#    都杀不动。
# ★ 这也说明:D 进程是很严重的信号,别等它自己好。

# === ★ 解法 6:监控里盯住 D 状态进程数 ===
# D 进程是 I/O 子系统出事的【强烈预警】。把它纳入监控:
$ ps -eo stat | grep -c '^D'         # 当前 D 进程数量
# ★ D 进程数突然变多,几乎等同于"存储/磁盘要出事"。
#   配合 dmesg 的 "hung task" 告警一起看。

# === 验证 ===
$ dmesg | tail                       # ★ 不再刷 NFS not responding
$ ps -eo pid,stat,comm | awk '$2~/^D/'  # ★ 没有 D 进程了
$ ps -p 8742                          # ★ 进程已消失(信号生效了)
# ★ I/O 源头恢复 + 没有 D 进程残留 —— 才算真修好。

口诀放进脑子:kill -9 杀不掉先看 STAT,是 D 就去查 I/O,别再 kill 进程。

修复 6:杀不掉的进程排查纪律

# === 这次事故暴露的认知盲区,定几条纪律 ===

# === 1. ★ kill 不是"杀",是"投递一个信号",返回 0 只代表信号送进了信箱 ===

# === 2. ★ kill -9 杀不掉、命令还返回 0,第一件事是看进程 STAT 列 ===
$ ps -eo pid,stat,comm | grep PID

# === 3. ★ STAT 是 D(不可中断睡眠),就是 kill -9 也得排队的根本原因 ===

# === 4. D 状态是进程卡在一次内核 I/O 里,没机会回用户态检查信箱处理信号 ===

# === 5. ★ 看 wchan 和 /proc/PID/stack,知道进程到底卡在哪个内核操作上 ===
$ cat /proc/PID/stack

# === 6. ★ D 进程的根子是一次完不成的 I/O:NFS 失联、磁盘故障,先 dmesg ===
$ dmesg -T | tail

# === 7. dmesg 里 "not responding still trying" 是 NFS,"I/O error" 是坏盘 ===

# === 8. ★ "task blocked for more than 120 seconds" 是内核报"有进程卡死 I/O" ===

# === 9. ★ NFS 卡死可 umount -l 摘掉挂载点,挂载用 soft 可避免永久卡 ===

# === 10. 排查"进程杀不掉"的步骤链 ===
$ ps -eo pid,stat,wchan,comm | grep PID   # ① 看状态,是不是 D
$ cat /proc/PID/stack                     # ② 卡在哪个内核操作
$ dmesg -T | tail                         # ③ 找完不成的 I/O 源头
$ 修复 NFS/磁盘 或 umount -l               # ④ 治 I/O 的根
$ I/O 一结束 -> 进程自动解脱,信号生效      # ⑤ 验证
# 按这个顺序,"杀不掉的进程"基本能定位、能根治。

命令速查

需求                        命令
=============================================================
看进程状态                  ps -eo pid,stat,comm | grep PID
看进程卡在哪个内核函数      ps -eo pid,stat,wchan:40,comm | grep PID
看进程内核调用栈            cat /proc/PID/stack
揪出所有 D 状态进程         ps -eo pid,stat,comm | awk '$2 ~ /^D/'
数 D 进程数量               ps -eo stat | grep -c '^D'
看内核日志找 I/O 故障       dmesg -T | tail -20
找卡死超 120 秒的任务       dmesg | grep 'blocked for more than'
看磁盘 I/O 压力             iostat -x 2 3
看 NFS 挂载                 mount | grep nfs
强制/lazy 卸载挂载点        umount -f 挂载点  /  umount -l 挂载点
看进程打开的文件            ls -l /proc/PID/fd

口诀:kill -9 杀不掉先 ps 看 STAT,是 D 状态就别再 kill 了
      D 进程卡在 I/O,根子是 NFS 失联或坏盘,治 I/O 的根进程才会解脱

避坑清单

  1. kill 不是把进程杀掉,它只是给进程投递一个信号,返回 0 只代表信号成功放进了信箱
  2. kill -9 杀不掉进程而命令还返回 0,第一件事是 ps 看进程的 STAT 状态列,别狂发信号
  3. 进程状态有 R 运行 S 可中断睡眠 D 不可中断睡眠 Z 僵尸 T 暂停,要看懂 STAT 第一个字母
  4. S 状态进程信号一来立刻醒来响应能杀掉,D 状态进程连 kill -9 也得排队等
  5. D 状态是进程卡在一次内核态 I/O 里,没机会返回用户态去检查信箱处理信号
  6. D 状态是保护 I/O 完整性的有意设计,正常一闪而过,永久卡 D 才是故障
  7. 看 wchan 和 /proc/PID/stack 能知道进程卡在哪个内核操作,nfs 开头就是卡在 NFS
  8. D 进程不是病根是受害者,根子是完不成的 I/O,先 dmesg 找 NFS 失联或磁盘 I/O error
  9. NFS 默认 hard 挂载服务端失联会让进程无限重试永久卡 D,soft 挂载会返回错误让进程解脱
  10. NFS 卡死可以 umount -l 把挂载点从目录树摘掉,I/O 一旦有结果堆积的 kill 信号立刻生效

总结

这次"用 root 身份连发十几个 kill -9 都杀不掉一个进程"的事故,纠正了我一个关于"权力"的、近乎天真的想象。在我心里,kill -9 一直是一道至高无上的"处决令"。我以为我和进程之间的关系是:我是握有生杀大权的执法者,进程是待决的犯人;我签发 kill -9 这道令,就等于亲手把进程【按住、处决】了——这个动作是我发出的,效果是即时的、必然的,中间不存在任何变数。-9 这个数字,在我心里就是"不可抗拒"的同义词。正因为抱着这个想象,这次的现象才会彻底击溃我:我以最高的身份,签发了十几道最强的处决令,每一道令都被系统"受理"了(kill 都返回 0),可那个犯人,稳稳地站在那里,毫发无伤。一个被我"处决"了十几次的进程还活着——在我那个"签令即处决"的想象里,这是绝无可能发生的事。复盘到根上,我才明白,我从一开始就把这件事的机制想反了。kill -9 根本不是我"亲手处决"进程。我做的事,远没有那么有力——我只是写了一张纸条,上面写着"请你自尽",然后把这张纸条,投进了进程的信箱。我的权力,到"投递"这一步就【结束】了。真正执行"处决"的,从来不是我,而是进程自己——它得在某个时刻,走到信箱前,取出这张纸条,然后由内核协助它了结自己。整个链条里,我只负责"投递",而"取出并执行",是另一个我完全无法控制的环节。平时,进程取信非常勤快,投递和执行几乎同时发生,这让我产生了一个持续多年的错觉,以为"投递"就等于"处决"。而这一次,那个进程陷在了 D 状态里——它正卡在一次永远完不成的 NFS 写操作上,死死地待在内核态出不来,它根本【没有机会】走到信箱前。我的十几张纸条,确确实实都投进了它的信箱,一张不少地堆在那里;可那个该来取信的"人",被另一件事困住了,永远没能赴约。我一直以为我在和"进程"较劲,其实我对进程做的事(投递)早就完美完成了;真正卡住的,是进程和它脚下那次 I/O 之间的关系——而那个,根本不在我 kill 命令的射程之内。这次最大的收获,是我对"我的动作"和"我想要的结果"之间的距离,有了一份清醒。我习惯于把"我发出了一个指令"和"那个指令达成了效果"这两件事,在脑子里划上等号——我命令了,所以它就该发生。可这次让我看清:我的指令,往往只是一根链条的【第一环】;它要真正变成我想要的结果,中间还得经过好几个不由我掌控的环节。如果其中某一环断了——比如那个该"取信执行"的进程自己瘫了——那么我这一环做得再用力、再标准、重复再多次,都到不了终点。错误不在我的动作上,我的动作完成得无可挑剔;错误在于我误以为"完成了我这一环",就等于"完成了整件事"。所以下一次,当我发出一个指令、它却没有产生我预期的效果时,我不会再机械地、更用力地重复那个指令了——我已经做到位的事,重复一百遍也还是只到那个位置。我会转过身,沿着指令之后的那条链条,一环一环地往下查:我的指令交出去之后,接下来该由谁接手?那个接手的人,此刻还有能力履行它的职责吗?——很多时候,事情卡住,不是因为我们下令下得不够狠,而是因为在我们的命令和我们想要的结果之间,还隔着一段我们从不曾留意、此刻却已经断裂了的路。

—— 别看了 · 2026
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理 邮箱1846861578@qq.com。
Linux教程

ping 通了 scp 大文件却卡死:一次 Linux MTU 路径黑洞排查复盘

2026-5-20 23:01:12

Linux教程

脚本中间步骤失败了还在往下跑:一次 Linux Bash set -e 缺失的部署事故复盘

2026-5-20 23:11:32

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索