kthreadd 和 kswapd0 卡在 D 状态是因等待不可中断资源:kthreadd 通常因子线程初始化挂起,kswapd0 则多因内存压力下 I/O 或锁阻塞;需查 /proc/pid/stack 定位具体等待点,并结合 vmstat、slabtop 与 cgroup 配置综合分析。
![进程卡在 d 状态 [kthreadd] / [kswapd0] 的深层原因定位](https://img.php.cn/upload/article/001/242/473/176977692232982.jpeg)
为什么 kthreadd 和 kswapd0 会卡在 D 状态?
D 状态(Uninterruptible Sleep)不是“卡死”,而是内核线程正在等待不可被信号中断的底层资源,比如磁盘 I/O 完成或内存页回收锁。对 kthreadd 来说,它本身是内核线程的父进程,几乎不直接执行耗时操作——真正卡住的往往是它派生出的子线程(如 kswapd0、khugepaged)。而 kswapd0 卡在 D 状态,90% 以上指向内存压力 + 回收路径阻塞,常见于:慢速存储(如 NFS、iSCSI 后端卡顿)、ext4 的 journal 提交延迟、或 cgroup v1 下 memory.limit_in_bytes 触发的强制同步回收。
用 /proc/[pid]/stack 看清到底卡在哪一行
别只看 ps 或 top,它们只能告诉你状态是 D,但不知道等什么。直接读内核栈:
cat /proc/$(pgrep kswapd0)/stack
典型输出中若出现:
-
__rwsem_down_read_failed→ 表示在等某个读写信号量(比如shrinker链表被其他 CPU 持有) -
wait_on_page_bit_common→ 正在等某页的 PG_locked 标志清除,常见于该页正被 writeback 或 swapout -
ext4_writepages或nfs_updatepage→ 存储后端响应超时,I/O 请求挂在队列里没返回
注意:kthreadd 自身栈通常很短(只有 kthreadd 函数调用),如果它也显示 D,大概率是它刚 fork 出子线程后,子线程还没完成初始化就被调度器挂起——这时应优先查子线程(如 kswapd0)的栈。
vmstat 1 和 slabtop 联合判断回收瓶颈类型
D 状态持续时间长 ≠ 内存不足,可能是回收效率崩溃。观察关键指标:
- 若
vmstat 1中si(swap-in)持续 > 0,但so(swap-out)极低 →kswapd0在反复尝试回收却失败(如所有可回收页都被mlock()锁住) - 若
free列稳定但buff/cache不降,且slabtop显示dentry或inode_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 相关函数名。










