CPU 70% idle 但 load average 84?D 状态进程不可中断睡眠的深度排查

搬运并适配自国外社区真实案例。原文出处见文末。

TL;DR

top 显示 CPU 70% 以上空闲,但 load average 却飙升到 20+ 甚至 80+,问题几乎确定出在 D 状态(不可中断睡眠,TASK_UNINTERRUPTIBLE)进程 。Linux 内核将 load average 计算为「可运行进程数(R 状态)+ 不可中断睡眠进程数(D 状态)」的指数移动平均。这意味着每个卡在磁盘 I/O、NFS 挂载、或内核锁等待上的进程都会直接推高 load average。本文以 ServerFault 上一位运维人员的真实案例(8 核服务器、load average 84、ps 命令自己都 D 住了)为入口,结合 Red Hat KB 中 NFS4.0 冻结案例(vmcore 分析显示 4454 个进程卡在 rpc_wait_bit_killable)、Brendan Gregg 对 1993 年 Linux 内核历史补丁的考古挖掘,以及 SUSE KB 中「Analysising Processes in D State」的系统化方法,带你从 /proc/PID/stacktask_struct.stateinclude/linux/sched.h 一路深挖到 kernel/sched/core.c__schedule() 函数,理解为什么你的 kill -9 对 D 状态进程完全无效。

现象

2023 年 1 月,ServerFault 用户 Brian 发出求助帖:

yaml 复制代码
$ top -o S
top - 14:44:51 up 298 days, 4:47, 1 user,
load average: 84.54, 85.11, 85.33
Tasks: 622 total, 1 running, 621 sleeping, 0 stopped, 0 zombie
%Cpu(s): 11.5 us, 2.0 sy, 0.0 ni, 85.6 id, 0.5 wa, 0.0 hi, 0.3 si, 0.0 st

# 大量进程状态为 D
2153 root  20 0 22904 1156 944 D  0.0  0.0  0:00.01 ps
2425 root  20 0  4372  712 532 D  0.0  0.0  0:00.01 pidof
6608 user1 20 0 14504  844 724 D  0.0  0.0  0:00.00 ps
7103 user1 20 0 14504  828 724 D  0.0  0.0  0:00.01 ps
# ... 十几条 ps/pidof 全部 D 状态
  • CPU:85.6% idle、iowait 仅 0.5%------CPU 资源充裕
  • load average:84+,8 核机器,load/CPU 比为 10.5
  • 关键线索 :就连 ps 命令本身也在 D 状态!说明执行进程查看这个操作本身就被阻塞了
  • 系统版本:Ubuntu 14.04.5、内核 3.13.0-123、8 核 CPU

这完全违背直觉:CPU 几乎全空,为什么系统比过载还卡?load average 凭什么这么高?

排查过程

第一回合:常规三板斧------top / iostat / vmstat

bash 复制代码
$ iostat
avg-cpu:  %user   %nice %system %iowait  %steal   %idle
           8.80    0.00    1.90    0.64    0.00   88.66

Device:   tps   kB_read/s  kB_wrtn/s
sda     37.57    164.32    1690.01
sdb     14.96    239.74     212.36

分析

  • iowait 仅 0.64%------磁盘 I/O 看起来在正常范围内
  • 没有明显的磁盘瓶颈
  • 但这是「平均值」的陷阱:iowait 统计的是 CPU 在等待 I/O 的时间百分比。如果大量进程卡在 D 状态但根本没有消耗 CPU 周期(纯在内核态等待),iowait 不会涨
  • 结论:iowait 低 ≠ 没有 I/O 阻塞。这是很多运维人员的第一个误判点

第二回合:定位 D 状态进程的阻塞点------/proc/PID/stack

Brian 从输出中注意到连 ps 命令本身都卡住了。他找了一个 D 状态 ps 进程(PID 2153),查看内核调用栈:

bash 复制代码
$ cat /proc/2153/stack
[<ffffffff8119d3e4>] call_rwsem_down_read_failed+0x14/0x30
[<ffffffff8119c742>] __access_remote_vm+0x42/0x1d0
[<ffffffff8119c920>] access_process_vm+0x50/0x70
[<ffffffff8126dbc0>] proc_pid_cmdline+0x8a/0x120
[<ffffffff8126de7f>] proc_info_read+0x9f/0xf0
[<ffffffff8117a1e5>] vfs_read+0x95/0x160
[<ffffffff8117abd9>] SyS_read+0x49/0xa0

分析

  1. ps 命令要读取 /proc/PID/cmdline 来显示进程的命令行
  2. 这个过程调用 proc_pid_cmdline()access_process_vm()__access_remote_vm()
  3. __access_remote_vm() 需要获取目标进程的 mm->mmap_lock(读信号量)
  4. call_rwsem_down_read_failed获取读锁失败,阻塞在信号量上
  5. 为什么读锁获取会失败?因为某个写者持有锁------而且那个写者本身可能也卡在 D 状态

这是 D 状态的一个隐蔽特性 :它不只是「等磁盘」或「等 NFS」,也经常是「等内核锁」。Brendan Gregg 在 2017 年的博客中指出:Linux 0.99.14 中只有 13 个代码路径设置 TASK_UNINTERRUPTIBLE,但到了 Linux 4.12,有近 400 个路径。这些新增的路径大量涉及内核锁原语------mutex_lock()down()(信号量)、rwsem。这些锁等待也被计入 load average。

第三回合:更系统化的 D 状态分析------SUSE KB 方法论

SUSE 的知识库文章「Analysing processes in D state」提供了一套完整的排查框架,比零散地看个别 /proc/PID/stack 更加系统化:

bash 复制代码
# 步骤 1:按 wchan(阻塞函数)统计所有 D 状态进程
$ ps -eo pid,state,wchan,comm | awk '$2=="D"' | awk '{print $3}' | sort | uniq -c | sort -rn
     48 rpc_wait_bit_killable
     17 call_rwsem_down_read
     12 wait_on_page_writeback
      8 jbd2_log_wait_commit
      3 xfs_log_force

分析

  • 48 个进程等 NFS RPC → 问题在 NFS 服务端或网络
  • 17 个进程等读写信号量 → 可能有进程持有 mmap_lock 不释放
  • 12 个等页面写回 → 磁盘写入慢
  • 这不是个别进程的问题,而是系统级阻塞模式
bash 复制代码
# 步骤 2:查看具体进程的内核栈(前 5 个 wchan 类型的代表进程)
$ for state_type in rpc_wait call_rwsem wait_on_page; do
    echo "==== $state_type ===="
    ps -eo pid,wchan | grep "$state_type" | head -3 | awk '{print $1}' \
      | xargs -I{} sh -c 'echo "PID {}:"; cat /proc/{}/stack 2>/dev/null; echo'
done
bash 复制代码
# 步骤 3:检查 I/O 子系统------iostat 深度分析
$ iostat -x 1 5

关注这些指标而非平均 tps:

  • avgqu-sz(平均队列长度):超过 1 表示 I/O 堆积
  • await(平均等待时间):超过 20ms 表示磁盘可能成为瓶颈
  • %util:接近 100% 表示设备饱和

第四回合:hung_task 时间炸弹------600 秒超时与 dmesg 警告

当 D 状态进程卡住超过 120 秒(可配置,默认 kernel.hung_task_timeout_secs=120),内核的 hung_task 检测器会在 dmesg 中打印警告。ServerFault 上另一位用户在 CentOS 5.5 上遇到更严重的情况------进程卡了超过 600 秒,最终触发了 HP ASR 看门狗重启:

ini 复制代码
$ dmesg | grep -A20 "blocked for"
INFO: task in.telnetd:12210 blocked for more than 600 seconds.
"echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.
in.telnetd D ffff81000102e4a0  0 12210   8899  16297   12762 (L-TLB)
Call Trace:
 [<ffffffff80063c6f>] __mutex_lock_slowpath+0x60/0x9b
 [<ffffffff80063cb9>] .text.lock.mutex+0xf/0x14
 [<ffffffff8009dc66>] flush_workqueue+0x3f/0x87
 [<ffffffff801a96ee>] release_dev+0x503/0x67b
 [<ffffffff800537af>] tty_release+0x11/0x1a
 [<ffffffff80012ad9>] __fput+0xd3/0x1bd
 [<ffffffff80023c39>] filp_close+0x5c/0x64

INFO: task dbc:9054 blocked for more than 600 seconds.
dbc D ffff810001036b20  0  9054      1   3272    1795 (L-TLB)
Call Trace:
 [<ffffffff80063c6f>] __mutex_lock_slowpath+0x60/0x9b
 [<ffffffff8009dc66>] flush_workqueue+0x3f/0x87
 [<ffffffff801a96ee>] release_dev+0x503/0x67b
 [<ffffffff8837dd1d>] :xfs:xfs_free_eofblocks+0x9d/0x1fe
 [<ffffffff80012ad9>] __fput+0xd3/0x1bd

两个进程都卡在 __mutex_lock_slowpath 。其中 dbc 进程调用链从 xfs_free_eofblocks(XFS 文件系统释放 EOF 块)一路到 flush_workqueue__mutex_lock_slowpath。这是典型的文件系统层 mutex 死锁:一个进程在释放 tty 设备时尝试 flush workqueue,但同时 XFS 的 EOF 块清理也在等同一个 mutex。

这个案例的关键教训:不是所有 D 状态都来自磁盘或 NFS。文件系统的内部 mutex 也会导致 D 状态,而且这些 mutex 死锁往往自我强化------持有锁的进程也在 D 状态等别的东西。

第五回合:关联分析------谁产生了这些 D 状态进程

Red Hat KB 2142081 记录了一个更极端的案例。某企业 NFS4.0 客户端出现高 load average 但 CPU 空闲的故障,系统最终完全冻结。通过 crash 分析 vmcore:

yaml 复制代码
crash> sys | grep LOAD
LOAD AVERAGE: 4462.05, 3818.55, 2068.32

crash> ps -S
RU: 9          # 可运行进程:仅 9 个!
IN: 723        # 可中断睡眠:723 个
UN: 4454       # 不可中断睡眠:4454 个!!!

crash> foreach UN bt | awk '/#1 / {print $3,$5}' | sort | uniq -c | sort -nr
4454 rpc_wait_bit_killable ffffffffa01a7422

4454 个进程全部卡在同一个内核函数rpc_wait_bit_killable()。内核栈追溯显示:

bash 复制代码
PID: 6181  TASK: ffff880437037520  COMMAND: "httpd"
#0  schedule
#1  rpc_wait_bit_killable         [sunrpc]
#2  __wait_on_bit
#3  __rpc_wait_for_completion_task [sunrpc]
#4  nfs4_run_open_task            [nfs]
#5  nfs4_do_open                  [nfs]
#6  nfs4_atomic_open              [nfs]
#7  nfs_open_revalidate           [nfs]
#8  do_lookup
#9  __link_path_walk
#10 path_walk
#11 do_filp_open
#12 do_sys_open

调用链解读

  1. httpd 尝试 open() 一个文件
  2. 文件在 NFS 挂载点上 → nfs4_atomic_open()
  3. NFS 客户端发出 OPEN RPC → rpc_run_task()__rpc_wait_for_completion_task()
  4. NFS 服务端不响应 → 进程无限期卡在 rpc_wait_bit_killable
  5. 函数名中虽然有 "killable",但这个等待仍被标记为 TASK_UNINTERRUPTIBLE
  6. 每个来访 HTTP 请求会 fork 新的 httpd → 新建进程 → 同样卡住 → 滚雪球

这就是 load average 雪崩的经典模式:慢 I/O 源 → 每次请求 fork 进程 → 新进程同样卡住 → D 状态进程数暴增 → load average 爆炸。这时 CPU 完全空闲,因为根本没人用------全在等。

根因:内核代码级解析

1. include/linux/sched.h:TASK_UNINTERRUPTIBLE 的定义

c 复制代码
/* include/linux/sched.h */
#define TASK_RUNNING            0x0000
#define TASK_INTERRUPTIBLE      0x0001
#define TASK_UNINTERRUPTIBLE    0x0002
#define __TASK_STOPPED          0x0004
#define __TASK_TRACED           0x0008

/* 组合状态 */
#define TASK_KILLABLE           (TASK_WAKEKILL | TASK_UNINTERRUPTIBLE)
#define TASK_IDLE               (TASK_UNINTERRUPTIBLE | TASK_NOLOAD)
  • TASK_INTERRUPTIBLE(0x0001):进程在睡眠,但可以被信号(signal)唤醒。kill 发的 SIGTERM/SIGKILL 可以打断这种睡眠
  • TASK_UNINTERRUPTIBLE(0x0002):进程在睡眠,信号无法唤醒kill -9 也杀不死------信号被内核「持有」直到进程出 D 状态才递送
  • TASK_KILLABLE:2.6.25 引入的折中方案------D 状态但可被致命信号(fatal signal)杀死

2. 1993 年历史补丁:为什么把 D 状态计入 load average?

Brendan Gregg 在 2017 年做了一次令人叹服的内核考古------他翻遍了 1993 年的邮件列表压缩包、98000 封邮件、6000+ 邮件摘要,最终在 oldlinux.org 的一个压缩邮箱文件中找到了原始补丁。那是 1993 年 10 月 29 日,Matthias Urlichs 的邮件:

diff 复制代码
From: Matthias Urlichs <urlichs@...>
Subject: Load average broken ?
Date: Fri, 29 Oct 1993 11:37:23 +0200

The kernel only counts "runnable" processes when computing the load
average. I don't like that; the problem is that processes which are
swapping or waiting on "fast", i.e. noninterruptible, I/O, also
consume resources. It seems somewhat nonintuitive that the load
average goes down when you replace your fast swap disk with a
slow swap disk...

--- kernel/sched.c.orig  Fri Oct 29 10:31:11 1993
+++ kernel/sched.c       Fri Oct 29 10:32:51 1993
@@ -414,7 +414,9 @@
        unsigned long nr = 0;
        for(p = &LAST_TASK; p > &FIRST_TASK; --p)
-               if (*p && (*p)->state == TASK_RUNNING)
+               if (*p && ((*p)->state == TASK_RUNNING) ||
+                   (*p)->state == TASK_UNINTERRUPTIBLE) ||
+                   (*p)->state == TASK_SWAPPING))
                nr += FIXED_1;
        return nr;
}

三行代码,改变了 Linux 运维 30 年 。Matthias 的逻辑是:如果你把快速交换磁盘换成慢速的,系统明显更慢、用户感知更差,但 load average 反而下降(因为进程卡在交换等待而不是 CPU 就绪队列),这「不直觉」。加入 TASK_UNINTERRUPTIBLETASK_SWAPPING 后,load average 从「CPU 负载」变成了「系统负载」。

24 年后,Matthias 在被 Brendan Gregg 追问时回应:

"The point of 'load average' is to arrive at a number relating how busy the system is from a human point of view. TASK_UNINTERRUPTIBLE means (meant?) that the process is waiting for something like a disk read which contributes to system load. A heavily disk-bound system might be extremely sluggish but only have a TASK_RUNNING average of 0.1, which doesn't help anybody."

3. 为什么 kill -9 对 D 状态进程无效?

__schedule() 中的状态检查逻辑决定:

c 复制代码
/* kernel/sched/core.c */
static void __sched notrace __schedule(unsigned int sched_mode)
{
    // ...
    if (prev_state & TASK_INTERRUPTIBLE) {
        // 检查是否有挂起信号
        if (signal_pending_state(prev_state, prev)) {
            // 有信号 → 唤醒,不睡眠
            prev_state = TASK_RUNNING;
        } else {
            // 无信号 → 继续睡眠
            deactivate_task(rq, prev, DEQUEUE_SLEEP);
        }
    }
    // TASK_UNINTERRUPTIBLE 的分支:无条件睡眠,完全不管信号!
    // ...
    context_switch(rq, prev, next);
}

关键逻辑

  • TASK_INTERRUPTIBLEsignal_pending_state() 检查 → 有信号就唤醒
  • TASK_UNINTERRUPTIBLE不进入信号检查分支 → 直接 deactivate_task → 进程从运行队列移除 → 上下文切换

这就是为什么 D 状态进程成为「僵尸克星」------在内核调度层就决定了信号对它无效

3. kernel/sched/loadavg.c:load average 如何计入 D 状态

c 复制代码
/* kernel/sched/loadavg.c - calc_global_load_tick() 调用的核心逻辑 */
/*
 * 采样函数 compute the active task count:
 *   nr_active = nr_running() + nr_uninterruptible()
 *
 * 关键:nr_uninterruptible() 遍历所有 CPU 的 runqueue,
 * 累计 TASK_UNINTERRUPTIBLE 状态的任务数。
 */
long calc_load_fold_active(struct rq *this_rq, long adjust)
{
    long nr_active, delta = 0;

    nr_active = this_rq->nr_running - adjust;
    nr_active += (long)this_rq->nr_uninterruptible;

    if (nr_active != this_rq->calc_load_active) {
        delta = nr_active - this_rq->calc_load_active;
        this_rq->calc_load_active = nr_active;
    }

    return delta;
}

每个 D 状态进程都被计入 load average。这就是为什么 8 核机器上 84 的 load average 合理------如果有 80+ 个进程在 D 状态,load average 就是 80+。

4. fs/proc/array.c:ps/top 如何显示 D 状态

c 复制代码
/* fs/proc/array.c */
static const char * const task_state_array[] = {
    "R (running)",       /*  0x00 */
    "S (sleeping)",      /*  0x01 */
    "D (disk sleep)",    /*  0x02 */    /* ← TASK_UNINTERRUPTIBLE */
    "T (stopped)",       /*  0x04 */
    "t (tracing stop)",  /*  0x08 */
    "X (dead)",          /*  0x10 */
    "Z (zombie)",        /*  0x20 */
    "P (parked)",        /*  0x40 */
    "I (idle)",          /*  0x80 */
};

static inline const char *get_task_state(struct task_struct *tsk)
{
    unsigned int state = READ_ONCE(tsk->__state);
    // ... 掩码处理 ...
    return task_state_array[task_state_index(tsk, state)];
}

pstop 通过读取 /proc/PID/status 获取状态,最终调用 get_task_state()。状态索引 2 返回 "D (disk sleep)"------UNIX 传统称法,但在现代内核中 D 状态早已不限于磁盘 I/O。

5. 进程状态转换全图

Linux 内核有 7 种主要进程状态,它们之间的转换由 __schedule()try_to_wake_up() 驱动:

状态 宏定义 ps 显示 可被信号唤醒 计入 load
RUNNING TASK_RUNNING (0x00) R N/A
INTERRUPTIBLE TASK_INTERRUPTIBLE (0x01) S
UNINTERRUPTIBLE TASK_UNINTERRUPTIBLE (0x02) D
STOPPED __TASK_STOPPED (0x04) T
ZOMBIE EXIT_ZOMBIE (0x20) Z
KILLABLE TASK_KILLABLE D 仅致命信号
IDLE TASK_IDLE I

从上述表格可以总结出两条核心铁律

  • 在 load average 中:R 状态 + D 状态 = load average 采样值
  • 在信号处理上:只有 S 状态(TASK_INTERRUPTIBLE)会被信号打断;D 状态完全免疫

当你发现 load average 很高但 CPU 很闲时,100% 是 D 状态进程在「撑」load average

解决方案

方案一:识别阻塞源(治本)

bash 复制代码
# 1. 找出所有 D 状态进程
ps -eo pid,state,wchan,comm | awk '$2=="D"'

# 2. 按阻塞函数分组统计(定位热点)
for pid in $(ps -eo pid,state | awk '/ D /{print $1}'); do
    cat /proc/$pid/wchan 2>/dev/null
done | sort | uniq -c | sort -rn

# 输出示例:
#    200 rpc_wait_bit_killable    → NFS 问题
#    150 call_rwsem_down_read     → 内核锁争用
#     50 wait_on_page_writeback   → 磁盘写回慢

方案二:常见 D 状态风暴的针对性处理

阻塞函数(wchan) 可能原因 处理方式
rpc_wait_bit_killable NFS 服务端无响应 恢复 NFS 服务端;临时 umount -f -l 强制卸载;加 soft 选项避免无限重试
call_rwsem_down_read/write 内核锁争用(常与 /proc 访问有关) 减少对 /proc 的并发扫描;升级内核(新版本 rwsem 有乐观自旋优化)
wait_on_page_writeback 磁盘写入慢 iostat -x 1 确认;调整 vm.dirty_ratiovm.dirty_background_ratio
jbd2_log_wait_commit ext4 日志提交阻塞 检查磁盘延迟;考虑 data=writeback 挂载选项
xfs_log_force XFS 日志压力大 检查磁盘 IOPS;考虑 SSD 或增大日志大小
__mutex_lock_slowpath 文件系统/设备层 mutex 检查对应文件系统健康状态;看关联进程的完整 Call Trace

方案三:Brendan Gregg 的 eBPF off-CPU 火焰图(进阶诊断)

当你需要可视化 D 状态进程的内核调用路径时,bcc 的 offcputime 工具可以生成 off-CPU 火焰图:

bash 复制代码
# 采样 60 秒,只抓 TASK_UNINTERRUPTIBLE(状态值 2)的调用栈
# 需要 Linux 4.8+ 和 bcc 工具集
$ /usr/share/bcc/tools/offcputime -K --state 2 -f 60 > out.stacks
$ awk '{ print $1, $2 / 1000 }' out.stacks \
    | flamegraph.pl --color=io --countname=ms > offcpu_unint.svg

这个火焰图的横轴宽度表示进程在各种不可中断等待中花费的时间。Brendan Gregg 在文章中使用这个技术量化了 D 状态对 load average 的贡献------例如他发现在一个生产服务器上,systemd-journalproc_pid_cmdline_read() 中阻塞了 0.07 个 load 单位,而 rwsem 锁等待贡献了 0.23 个单位。

方案四:紧急止损------重启是最后手段

D 状态进程无法被 kill -9 杀死。以下手段按推荐程度排列:

  1. 恢复 I/O 源(首选)------恢复 NFS 服务端/更换磁盘/降低 IO 负载
  2. 强制卸载挂载点 ------umount -f -l /mnt/nfs 可以让 NFS 卡住的进程脱离等待
  3. 重启相关服务------如果阻塞源是某个可控服务,重启它
  4. echo b > /proc/sysrq-trigger------最后的强制重启(sysrq 强制重启)

⚠️ 警告reboot 命令可能也会卡住,因为 shutdown 脚本需要遍历所有进程并发送信号------D 状态进程不响应,导致关机流程阻塞。

如何自检你是否中招

bash 复制代码
#!/bin/bash
# D 状态进程自检脚本
# 用于 Zabbix/Nagios/crontab 持续监控

THRESHOLD=10  # D 状态进程超过此数告警

D_COUNT=$(ps -eo state | grep -c '^D')
LOAD1=$(awk '{print $1}' /proc/loadavg)
CPUS=$(nproc)
LOAD_RATIO=$(echo "scale=2; $LOAD1 / $CPUS" | bc)

echo "D-state processes: $D_COUNT | Load 1min: $LOAD1 | CPUs: $CPUS | Ratio: $LOAD_RATIO"

# CPU 空闲但 D 状态进程多 → 典型 D 状态风暴
CPU_IDLE=$(top -bn1 | grep 'Cpu' | awk '{print $8}' | cut -d'%' -f1)
if [ "$D_COUNT" -gt "$THRESHOLD" ] && [ "$(echo "$CPU_IDLE > 50" | bc)" -eq 1 ]; then
    echo "⚠️  告警:CPU空闲${CPU_IDLE}% 但 $D_COUNT 个进程在 D 状态!"
    echo "Top D-state wchan:"
    for pid in $(ps -eo pid,state | awk '/ D /{print $1}' | head -20); do
        printf "  PID %-6s → %s\n" "$pid" "$(cat /proc/$pid/wchan 2>/dev/null)"
    done
fi

部署方式

bash 复制代码
# 每 5 分钟检查一次
*/5 * * * * /usr/local/bin/check_dstate.sh >> /var/log/dstate.log 2>&1

启示

  1. load average 是系统负载,不是 CPU 负载 Linux 的 load average 自 1993 年 patch(Matthias Urlichs, 0.99.14)起就包含了 TASK_UNINTERRUPTIBLE。它的设计初衷是反映「系统的主观繁忙度」,而不只是 CPU 繁忙度。把 load average 除以 CPU 核数来判断系统是否过载是一种误导------必须同时看 CPU idle 和进程状态分布。

  2. iowait 低 ≠ 没有 I/O 阻塞 iowait 统计的是 CPU 在 idle 状态下等待 I/O 的时间百分比。如果大量进程卡在 D 状态但根本没在 CPU 上跑,iowait 不会涨。正确的做法是直接数 D 状态进程数量:ps -eo state | grep -c '^D'

  3. /proc/PID/stack 是 D 状态排查的银弹 不需要 eBPF、不需要 crash dump、不需要 root 的额外权限(/proc/PID/stack 可读性由 ptrace 权限控制)。一行命令就能看到进程卡在内核的哪个函数、哪行代码。这是 Brendan Gregg 和 SUSE KB 都提及的第一步排查动作。

  4. D 状态进程会自我繁殖 在 NFS 冻结案例中,每个 HTTP 请求都会 fork 新的 httpd → open NFS 文件 → 也卡住 → D 进程越来越多 → load average 指数上升。这种正反馈循环比 CPU 过载更危险,因为它无法通过 OOM killer 自动止损。

  5. TASK_KILLABLE 是内核社区给出的「不完全解决方案」 2.6.25 引入的 TASK_KILLABLE 允许 D 状态进程响应致命信号,但需要内核文件系统/NFS 代码显式使用。大量旧的代码路径仍使用纯 TASK_UNINTERRUPTIBLE。Peter Zijlstra 曾提议用 task_struct->in_iowait 代替 TASK_UNINTERRUPTIBLE 来计算 load average,但至今未落地------因为「等待内核锁」的线程是否应该计入系统负载,社区仍有争议。


原始出处


本文首发于 CSDN 专栏《运维漏洞指南》,系列第 9 篇。该系列旨在用「可观测症状 → 排查回合 → 内核源码」的三步法,将国外高质量社区案例(ServerFault / LWN / Red Hat KB)搬运并适配到中文运维场景。

相关推荐
界面开发小八哥3 天前
界面控件DevExtreme v26.1新版亮点——支持Angular 22
前端·javascript·angular.js·devexpress·ui开发·devextreme
巴勒个啦7 天前
Vue 3.6 Vapor Mode 实战:我把一个 Vue3 项目的渲染性能提升了 4 倍
前端·angular.js
shmily麻瓜小菜鸡10 天前
在 Angular 项目中实现国际化(i18n)和翻译 使用ngx-translate
前端·javascript·angular.js
starrysky81015 天前
一个 JSON 解析错误,搞垮了整个进程池?——requests JSONDecodeError + BrokenProcessPool 排障实录
angular.js
starrysky81015 天前
小模型反而爆显存?Qwen2.5-VL 3B vs 7B 的 vLLM 显存真相——多模态 Profiling 机制深度拆解
angular.js
starrysky81019 天前
THP 透明大页导致数据库延迟从 2ms 飙升到 2000ms——khugepaged 内存压缩风暴排查实录
angular.js
starrysky81022 天前
多Agent通信架构实战:从NATS消息总线到五大编排模式的生产落地——Agent通信协议篇
angular.js
starrysky81022 天前
RecursionError: maximum recursion depth exceeded —— 你的函数调用链,踩穿了 CPython 的安全气囊
angular.js
starrysky81024 天前
MemAvailable 还有 29GB,系统却报内存压力?——Ubuntu 24.04 CIFS 内核 Page Cache 泄漏排查实录
angular.js