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