进程卡在 D 状态 [kthreadd] / [kswapd0] 的深层原因定位

5次阅读

kthreadd 和 kswapd0 卡在 D 状态是因等待不可中断资源:kthreadd 通常因子线程初始化挂起,kswapd0 则多因内存压力下 I/O 或锁阻塞;需查 /proc/pid/stack 定位具体等待点,并结合 vmstat、slabtop 与 cgroup 配置综合分析。

进程卡在 D 状态 [kthreadd] / [kswapd0] 的深层原因定位

为什么 kthreaddkswapd0 会卡在 D 状态?

D 状态(Uninterruptible Sleep)不是“卡死”,而是内核线程正在等待不可被信号中断的底层资源,比如磁盘 I/O 完成或内存页回收锁。对 kthreadd 来说,它本身是内核线程的父进程,几乎不直接执行耗时操作——真正卡住的往往是它派生出的子线程(如 kswapd0khugepaged)。而 kswapd0 卡在 D 状态,90% 以上指向内存压力 + 回收路径阻塞,常见于:慢速存储(如 NFS、iSCSI 后端卡顿)、ext4 的 journal 提交延迟、或 cgroup v1 下 memory.limit_in_bytes 触发的强制同步回收。

/proc/[pid]/stack 看清到底卡在哪一行

别只看 pstop,它们只能告诉你状态是 D,但不知道等什么。直接读内核

cat /proc/$(pgrep kswapd0)/stack

典型输出中若出现:

  • __rwsem_down_read_failed → 表示在等某个读写信号量(比如 shrinker 链表被其他 CPU 持有)
  • wait_on_page_bit_common → 正在等某页的 PG_locked 标志清除,常见于该页正被 writeback 或 swapout
  • ext4_writepagesnfs_updatepage → 存储后端响应超时,I/O 请求挂在队列里没返回

注意:kthreadd 自身通常很短(只有 kthreadd 函数调用),如果它也显示 D,大概率是它刚 fork 出子线程后,子线程还没完成初始化就被调度器挂起——这时应优先查子线程(如 kswapd0)的栈。

vmstat 1slabtop 联合判断回收瓶颈类型

D 状态持续时间长 ≠ 内存不足,可能是回收效率崩溃。观察关键指标:

  • vmstat 1si(swap-in)持续 > 0,但 so(swap-out)极低 → kswapd0 在反复尝试回收却失败(如所有可回收页都被 mlock() 锁住)
  • free 列稳定但 buff/cache 不降,且 slabtop 显示 dentryinode_cache 占用飙升 → shrinker 未及时触发,或 nr_shrinker_deferred 非零(说明 shrinker 被跳过)
  • pgpgin/pgpgout 值极小,但 pgmajfault 暴涨 → 进程频繁缺页,而 kswapd0 无法及时分配新页,可能因 zone watermark 设置过严(/proc/sys/vm/lowmem_reserve_ratio 异常)

cgroup v1 下 memory.limit_in_bytes 是隐形杀手

在 cgroup v1 环境中,一旦容器内存接近 memory.limit_in_bytes,内核会强制走同步回收路径(try_to_free_pages),此时 kswapd0 会被绕过,由触发缺页的用户进程自己调用回收逻辑——但若该进程又依赖其他被锁资源(如 ext4 的 journal_lock),就会导致整个回收链路卡在 D 状态,且栈中看不到 kswapd0,反而看到用户进程卡在 do_swap_page。验证方法:

grep -r "limit_in_bytes" /sys/fs/cgroup/memory/ | xargs -n1 cat 2>/dev/null | grep -v "^0$"

只要非零值存在,就需检查对应 cgroup 的 memory.usage_in_bytes 是否长期 > 90% limit,并确认是否启用了 memory.swappiness=0(这会让内核拒绝 swap,加剧直接回收压力)。

这类问题最难排查,因为表象是 kswapd0 无响应,实际根因在 cgroup 配置和 swappiness 的组合效应上——而 /proc/[pid]/stack 里根本看不到 cgroup 相关函数名。

text=ZqhQzanResources