资讯动态

Linux 内存回收守护进程 kswapd:从周期唤醒到紧迫平衡全流程

发布时间:2026/10/8 23:18:00 来源:尧图企业网站定制
在生产环境维护高吞吐数据库或大型中间件集群时几乎所有运维与系统研发都经历过“kswapd0 突发打满单核 CPU 100%”的惊魂时刻。监控告警频发系统平均负载Load Average瞬间拉升数倍关键服务的 P99 时延出现断崖式劣化。许多初入工业界的工程师在面对这一现象时往往下意识地祭出“关闭 Swap 分区”的祖传秘方执行swapoff -a。然而这往往将系统推向更危险的深渊失去了匿名页换出缓冲区的保护系统在遭遇突发内存压力时会跳过平滑调谐瞬间触发应用线程的直接内存回收Direct Reclaim甚至毫无征兆地引爆 OOM-Killer 强杀核心数据库进程。kswapd是 Linux 虚拟内存管理器VMM中最核心的后台异步平衡守护进程。它绝非一个简单的“内存清理工”而是背负着精密的 NUMA 拓扑平衡、页面阶数Order碎片整理以及 LRU 双向链表扫描的多维状态机。kswapd 执行骨架与 pgdat 平衡状态机Linux 系统中的每一个 NUMA 内存节点Node都会在系统引导时绑定一个名为kswapd[N]的独立内核线程如单路 CPU 通常只有kswapd0双路服务器则存在kswapd0与kswapd1。该线程常年驻留在内核的wait_queue_head_t kswapd_wait等待队列中。其生命周期由伙伴系统的内存水线驱动并在核心函数balance_pgdat()中完成极为严苛的水位推演与紧迫回收。------------------------------------------------------------------------- | kswapd Kernel Daemon Execution Pipeline | ------------------------------------------------------------------------- Deep Sleep State | | [Alloc Path: Zone Free WMARK_LOW] v wakeup_kswapd(pgdat) | v ------------------------------------------- | kswapd() Core Routine | ------------------------------------------- | v balance_pgdat(pgdat, order) | ------------------------------------------------ | | v v [1. Scan LRU Lists] [2. Shrink Slab Caches] - Loop priority from 12 down to 0 - Shrink dentry inode caches - Check vm.swappiness (Anon vs File pages) - Call filesystem shrinkers - File clean: Drop instantly - File dirty: Queue to flusher threads - Anonymous: Compress / Page out to Swap | | ------------------------------------------------ | v Is Node Balanced for Target Order? (All Zones Free WMARK_HIGH Boost) / \ No / \ Yes / \ v v Lower priority (intensify) pgdat_balanced true Continue scanning loop Break loop return to sleep1. 扫描力度的渐进升级Reclaim Priority当balance_pgdat()开始回收时它维护着一个名为priority的循环变量从内核预设的默认值DEF_PRIORITY通常为 12逐级递减到 0。在priority 12的初始阶段内核仅扫描总链表长度的 $1/4096$即 $1/2^{12}$如果经过这一轮扫描后各 Zone 的水位仍然未能回升到WMARK_HIGHpriority便会递减扫描比例依次翻倍扫描力度与时间呈指数级上升一旦priority跌入低区间如 0 到 3说明内存赤字极其危急kswapd会动用高开销的锁争抢与同步写盘逻辑这就是 CPU 利用率瞬间冲向 100% 的底层原因。2. 匿名页与文件页的博弈平衡在扫描 Active 与 Inactive 链表时内核必须决定究竟回收文件缓存页File-backed Page Cache还是进程私有匿名页Anonymous Memory。这一决策由全局参数vm.swappiness精确控制。其数学本质绝不是“内存剩余百分之几才用 swap”而是参与扫描比例公式的相对权重因子$$\frac{\text{Scan(Anon)}}{\text{Scan(File)}} \approx \frac{\text{swappiness}}{200 - \text{swappiness}}$$当swappiness 60时系统明显偏向于丢弃文件缓存只有当swappiness 100时匿名页与文件页才具有完全均等的回收权重。工业级 C23kswapd 活跃度与系统回收效率监测器运维不能等到系统卡死才被动感知。通过周期性解析/proc/vmstat我们可以准确捕获kswapd的扫描次数pgscan_kswapd、实际偷窃回收页数pgsteal_kswapd以及灾难性的直接内存回收事件pgscan_direct从而推导出当前内存回收的“吞吐健康度”// kswapd_efficiency_monitor.c #include stdio.h #include stdlib.h #include string.h #include unistd.h constexpr size_t BUF_SIZE 256; constexpr const char *VMSTAT_PATH /proc/vmstat; typedef struct { unsigned long pgscan_kswapd; unsigned long pgsteal_kswapd; unsigned long pgscan_direct; unsigned long pgsteal_direct; unsigned long pageoutrun; // kswapd 被唤醒的次数 } VmReclaimStats; static int parse_vmstat(VmReclaimStats *stats) { FILE *fp fopen(VMSTAT_PATH, r); if (!fp) return -1; char line[BUF_SIZE]; while (fgets(line, sizeof(line), fp) ! nullptr) { if (strncmp(line, pgscan_kswapd, 13) 0) { sscanf(line, %*s %lu, stats-pgscan_kswapd); } else if (strncmp(line, pgsteal_kswapd, 14) 0) { sscanf(line, %*s %lu, stats-pgsteal_kswapd); } else if (strncmp(line, pgscan_direct, 13) 0) { sscanf(line, %*s %lu, stats-pgscan_direct); } else if (strncmp(line, pgsteal_direct, 14) 0) { sscanf(line, %*s %lu, stats-pgsteal_direct); } else if (strncmp(line, pageoutrun, 10) 0) { sscanf(line, %*s %lu, stats-pageoutrun); } } fclose(fp); return 0; } int main(void) { VmReclaimStats prev {0}, curr {0}; if (parse_vmstat(prev) 0) { perror(Failed to parse /proc/vmstat); return 1; } printf(Starting kswapd performance tracking (sampling every 2s)...\n); while (1) { sleep(2); if (parse_vmstat(curr) 0) break; unsigned long kscan_diff curr.pgscan_kswapd - prev.pgscan_kswapd; unsigned long ksteal_diff curr.pgsteal_kswapd - prev.pgsteal_kswapd; unsigned long dscan_diff curr.pgscan_direct - prev.pgscan_direct; // 计算 kswapd 回收效率比偷取页 / 扫描页 double efficiency (kscan_diff 0) ? ((double)ksteal_diff / (double)kscan_diff) * 100.0 : 100.0; printf(\n[Metric 2s Window]\n); printf( kswapd Scanned : %-8lu pages (%lu MB)\n, kscan_diff, (kscan_diff * 4) / 1024); printf( kswapd Reclaimed: %-8lu pages (Efficiency: %.2f%%)\n, ksteal_diff, efficiency); if (kscan_diff 10000 efficiency 15.0) { printf( \033[1;33m[WARNING] kswapd struggling! Scanning heavily with extremely low yield.\033[0m\n); } if (dscan_diff 0) { printf( \033[1;31m[CRITICAL] Direct Reclaim detected (%lu pages)! kswapd fell behind.\033[0m\n, dscan_diff); } prev curr; } return 0; }生产硬核排障kswapd 假死与 100% 空转深度归因当定位到kswapd持续霸占 CPU 时必须按优先级排查以下三大物理陷阱1. 高阶内存High-Order碎片化引发的永不平衡死循环这是最常见也最凶险的场景。当大流量网卡驱动需要连续物理内存分配成包 SKB或应用程序尝试申请大内存页如order 3即 32KB 连续物理页而物理内存高度碎片化时分配器唤醒kswapd(order3)kswapd只能通过回收单页来凑高阶连续空间但在严重的碎片化状态下即使它把水位推到了WMARK_HIGH依然由于没有足够连续的物理页块导致当前 Node 依然被判定为“Unbalanced”kswapd陷入疯狂的重复扫描而系统整体空闲内存却居高不下。规避措施限制透明大页Transparent Huge Pages, THP的激进分配将其设为madvise避免全系统高阶申请诱发kswapd狂暴echo madvise /sys/kernel/mm/transparent_hugepage/enabled echo madvise /sys/kernel/mm/transparent_hugepage/defrag2. VFS 元数据泄漏导致 shrink_slab 耗时失控当系统内存在大量极细小文件的创建与删除如某些未清理的日志目录或临时缓存时内核会堆积数千万个dentry与inode缓存。在扫描过程中kswapd会调用各个挂载文件系统的shrink_slab()。由于很多文件系统的元数据锁粒度较粗成千上万个 inode 链表的释放会引发可怕的自旋锁争抢导致kswapd长期卡在内核自旋锁上。调优建议通过调大vm.vfs_cache_pressure默认 100可调至 150促使内核在回收时以更高权重清理目录项与 inode 缓存杜绝元数据过度膨胀。3. Swappiness 配置的极端主义错误许多运维盲目将vm.swappiness设置为0。在现代内核中0意味着“只要可能绝不使用 Swap直到出现极端内存紧迫”。这剥夺了kswapd平滑换出常年不访问的冷匿名内存的权力。当内存突发紧张时系统只能拼命丢弃正在被频繁读取的 Page Cache导致物理磁盘 IOPS 瞬间打满随后立即崩塌并触发直接回收。工业基线推荐在搭载 NVMe 高速磁盘的服务器上将vm.swappiness设为10至30既能提供必要的缓冲垫又能防止机械硬盘时代的随机写盘抖动。kswapd是内核守护物理内存长治久安的核心卫士。唯有理解其在阶数碎片、回收效率比与元数据锁之间的细微权衡才能彻底平息生产系统中的 CPU 狂飙与时延毛刺。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑