ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Linux内核kswapd内存回收机制深度解析

Linux内核kswapd内存回收机制深度解析 1. 什么是kswapd它不是“ swap 的守护神”而是内存压力下的冷静调度员很多人第一次看到kswapd这个名字会下意识联想到“swap”——毕竟名字里带个swap又常和swapon、swapoff、/proc/sys/vm/swappiness放在一起讲。但这种联想恰恰是理解 kswapd 最大的误区。它不负责写入 swap 分区也不决定哪页该换出它只做一件事当系统内存开始紧张时主动、渐进、低优先级地回收那些“可回收”的物理页把它们腾出来为即将到来的内存分配请求铺平道路。换句话说kswapd 是 Linux 内核里一位永远在后台踱步的内存管家它不等火烧眉毛才行动而是在烟雾刚起时就悄悄开窗通风。这个角色之所以关键是因为 Linux 的内存分配机制默认采用“乐观分配”overcommit。当你调用malloc()或mmap()申请内存时内核往往先答应下来只在真正往那块内存里写数据发生 page fault时才去物理上分配页框。这就意味着系统实际可用的物理内存可能远小于所有进程“声称”需要的内存总和。一旦多个进程同时开始真实使用它们之前申请的内存物理内存就会瞬间告急。如果没有 kswapd 这样的异步回收机制所有后续的内存分配请求都得卡在alloc_pages()里等待同步回收完成——这会导致进程长时间阻塞系统响应迟滞甚至触发 OOM Killer 杀掉进程。而 kswapd 的存在就是把这部分“脏活累活”提前、分散、非阻塞地干掉让前台进程几乎感觉不到内存压力的存在。你可以在任意一台运行中的 Linux 服务器上用ps aux | grep kswapd看到它。它通常显示为kswapd0、kswapd1……数字代表它所管理的 NUMA 节点编号kswapd0管理 node 0。它是一个标准的内核线程kernel thread没有用户空间上下文不消耗RSS内存也不出现在top的常规进程列表里——它只在/proc/[pid]/stat和ps -eLf的输出中露面。它的调度策略是SCHED_OTHER但优先级nice值被设为-12比绝大多数用户进程都要高确保它能在后台持续获得 CPU 时间片却又不会抢占前台关键任务。这种“低调但高效”的设计正是它能成为内存子系统稳定基石的原因。理解 kswapd本质上就是理解 Linux 如何在资源有限的前提下用精巧的异步协作机制换取整体系统的流畅与健壮。2. kswapd 的核心驱动力watermark 水线系统不是阈值而是一套压力等级地图kswapd 并非凭空启动它的每一次唤醒、每一次扫描、每一次回收动作都严格遵循一套由内核定义的watermark水线系统。这套系统不是简单的“剩余内存低于 X MB 就启动”而是一张精细的、分层的压力等级地图它将内存状态划分为多个区间每个区间对应不同的回收强度和行为模式。这是理解 kswapd 行为逻辑的绝对核心也是所有相关调优工作的起点。Linux 内核为每个内存管理区zone如 DMA、DMA32、Normal、HighMem维护三组关键水线WMARK_MIN、WMARK_LOW和WMARK_HIGH。它们的数值单位是页page其具体值由内核根据该 zone 的总大小、当前系统负载以及vm.min_free_kbytes参数动态计算得出。你可以通过/proc/zoneinfo查看每个 zone 的实时水线值。例如在一个 64GB 内存的服务器上Normalzone 的WMARK_MIN可能是 128MBWMARK_LOW是 256MBWMARK_HIGH是 512MB。这些数字本身没有绝对意义它们的意义在于彼此之间的相对关系和触发的行为。WMARK_MIN最小水线这是系统的“警戒线”。当 zone 的空闲页数低于此值时任何内存分配请求包括GFP_ATOMIC这种最紧急的分配都必须进入同步直接回收direct reclaim流程。此时 kswapd 已经来不及了内核会立刻暂停当前进程亲自上阵扫描并回收页面这会造成明显的延迟。WMARK_MIN的存在就是为了保证系统永远有最低限度的“应急储备金”避免完全死锁。WMARK_LOW低水线这是 kswapd 的“开工线”。当空闲页数跌破WMARK_LOWkswapd 线程就会被唤醒并开始以温和的节奏扫描内存尝试回收一些可回收页如文件缓存页、匿名页的 LRU 链表尾部页目标是将空闲页数拉回到WMARK_HIGH以上。这个过程是异步的、非阻塞的对前台进程影响极小。WMARK_HIGH高水线这是 kswapd 的“收工线”。当 kswapd 的努力成功将空闲页数推高到WMARK_HIGH时它就会暂时休眠等待下一次WMARK_LOW被击穿。WMARK_HIGH和WMARK_LOW之间的差值就是 kswapd 的“工作缓冲区”。这个缓冲区越大kswapd 的工作就越从容系统抖动越小反之如果这个缓冲区过小kswapd 就会陷入“刚睡醒就开工刚开工就收工”的高频震荡状态CPU 开销增大却收效甚微。提示vm.min_free_kbytes是一个全局参数它直接决定了WMARK_MIN的基准值。增大它会相应抬高所有水线给系统留出更多“安全余量”但也意味着更少的内存可用于缓存可能降低 I/O 性能。这是一个典型的“内存 vs. 性能”权衡点不能盲目调大。这套水线系统的设计哲学非常精妙。它避免了“一刀切”的阈值判断而是构建了一个具有弹性的压力反馈环。当内存压力轻微上升从HIGH降到LOWkswapd 温和介入当压力急剧恶化从LOW降到MIN系统则启动更激进的同步回收。这种分级响应机制是 Linux 内存管理能够兼顾吞吐量与响应延迟的关键所在。很多线上故障的根源就在于管理员只盯着free -h的“available”值却忽略了/proc/zoneinfo里各 zone 的水线是否已被反复击穿这才是 kswapd 是否已不堪重负的真正信号。3. kswapd 的工作流程从唤醒到回收一次完整的“内存清道夫”行动理解了 kswapd 的触发条件watermark接下来就要看清它被唤醒后究竟做了哪些事。它的整个生命周期可以概括为唤醒 → 扫描 → 回收 → 休眠。这个循环看似简单但每一步都充满了内核级别的精巧设计和工程取舍。3.1 唤醒机制谁按下了 kswapd 的启动按钮kswapd 不是定时器驱动的它没有固定的“每秒检查一次”。它的唤醒完全依赖于内核内存分配路径上的“通知”。当一个进程在alloc_pages()中发现当前 zone 的空闲页数已经低于WMARK_LOW时它不会立刻自己动手回收而是调用wake_all_kswapds()函数向所有管理该 zone 的 kswapd 线程发送一个唤醒信号。这个信号通过 Linux 的wait_event()机制传递kswapd 在kswapd()主循环里会一直sleep_on()在一个等待队列上直到收到这个信号。这里有一个重要的细节kswapd 的唤醒是“批量”的。一个 zone 可能被多个 CPU 同时访问多个分配请求可能在同一毫秒内触发唤醒。内核对此做了优化它会合并这些唤醒请求避免 kswapd 被反复、高频地打断。你可以把它想象成一个“叫号系统”只要有一个客户内存分配请求喊了“服务员”所有等待的“服务员”kswapd都会被叫到而不是每个客户都单独喊一次。3.2 扫描阶段LRU 链表上的“地毯式搜索”一旦被唤醒kswapd 就会进入它的核心工作区——扫描。它的扫描对象不是整个物理内存而是内核为每个 zone 维护的LRULeast Recently Used链表。LRU 链表将内存页按其最近使用时间排序分为active_anon、inactive_anon、active_file、inactive_file四个主要链表。其中anon匿名页主要来自进程的堆和栈file文件页主要来自文件缓存page cache。kswapd 的扫描策略是“从尾部开始逐页检查”。它首先会扫描inactive_file链表的尾部。为什么是“尾部”因为 LRU 的设计原则就是最久未使用的页最有可能是“冷数据”也最应该被回收。对于inactive_file页如果它对应的磁盘文件块没有被修改即page-mapping存在且page-dirty为 false那么它就可以被直接丢弃无需写入磁盘这是最廉价的回收方式。如果它是脏页dirtykswapd 会将其标记为PG_writeback然后交给pdflush或现代内核中的writeback线程去处理自己则继续扫描。当inactive_file链表被扫得差不多了kswapd 就会转向inactive_anon链表。这里的页就复杂得多。匿名页没有对应的磁盘文件要回收它们唯一的办法就是写入 swap 分区。因此kswapd 会检查这些页是否可以被换出page_is_file_cache()返回 false且PageSwapBacked()为 true然后调用try_to_unmap()尝试解除页表项PTE对该页的映射再调用swap_writepage()将其内容写入 swap。这个过程涉及 I/O速度远慢于文件页的丢弃所以 kswapd 会非常谨慎优先回收那些“老化”程度更高的页。3.3 回收决策不是“能回收就回收”而是“该回收才回收”kswapd 的扫描并非无脑地回收所有符合条件的页。它有一套严格的“回收配额”reclaim priority和“扫描深度”scanning depth控制机制。内核会为每次 kswapd 唤醒设定一个order分配阶数和一个gfp_mask分配标志。order决定了这次回收需要满足多大的连续内存块例如order0是 4KBorder3是 32KB。kswapd 会根据这个order估算出需要回收多少页才能满足需求并以此作为本次扫描的“目标配额”。更重要的是kswapd 会动态调整自己的扫描“激进程度”。这个激进程度由classzone_idx和priority参数控制。priority是一个从 0 到 12 的整数0 表示最激进不惜一切代价回收12 表示最保守只回收最容易的页。初始priority通常设为 12但如果 kswapd 发现自己扫描了一轮回收的页数远低于目标配额它就会在下一轮扫描时自动将priority降低比如降到 10这意味着它会扫描更长的 LRU 链表检查更多页甚至考虑回收一些“稍热”的页。这个自适应机制保证了 kswapd 能在内存压力变化时自动调整自己的工作强度既不过度消耗 CPU也不至于“出工不出力”。4. kswapd 与 swap 的关系它只是 swap 的“搬运工”而非“决策者”这是关于 kswapd 最普遍、也最有害的一个误解认为 kswapd 的存在就是为了启用 swap。事实恰恰相反。kswapd 的核心使命是尽可能地避免 swap 的发生。它优先回收的是inactive_file页也就是文件缓存。只有当文件缓存被大量回收仍无法满足内存需求时kswapd 才会不得已地转向inactive_anon页也就是进程的匿名内存这时才会触发真正的 swap 操作。我们可以用一个生活化的类比来理解把一台服务器的内存比作一个大型仓库。active_file和active_anon是仓库里正在被频繁使用的货物热数据inactive_file是那些刚入库、还没人来取但随时可能被取走的货物冷文件缓存inactive_anon则是那些长期堆在角落、积满灰尘、连货主都快忘了的旧货冷进程内存。kswapd 就是仓库的清洁主管。他的第一要务是清理那些“积灰的旧货”inactive_anon和“待取的货物”inactive_file。他绝不会一上来就去动那些正在被工人CPU使用的货物active链表因为那样会严重影响工作效率。只有当仓库已经爆满连“待取的货物”都快没地方放了他才会忍痛把一部分“积灰的旧货”打包运到郊区的临时仓库swap 分区去暂存。而这个“打包运货”的指令也不是 kswapd 自己下达的而是由内核的shrink_page_list()函数在评估了所有选项后最终做出的无奈选择。因此当你看到kswapd0进程的 CPU 使用率飙升并且siswap in和soswap out在vmstat 1的输出中持续为正这通常不是一个好信号。它意味着你的系统已经耗尽了所有“容易回收”的内存文件缓存正在被迫对进程的“工作内存”进行物理交换。这会带来巨大的 I/O 延迟因为磁盘速度比内存慢了几个数量级。此时问题的根源往往不是 kswapd 太“懒”而是你的应用内存泄漏、vm.swappiness设置过高、或者物理内存本身就严重不足。注意vm.swappiness参数默认值 60并不直接控制 kswapd 的行为它影响的是内核在shrink_page_list()阶段对anon页和file页的回收倾向性权重。一个较高的swappiness值会让内核在同等条件下更倾向于回收anon页从而触发 swap而不是file页。所以如果你的应用极度依赖文件缓存如数据库、Web 服务器将swappiness降低到 10 或 1可以显著减少不必要的 swap让 kswapd 更专注于清理“冷文件”这是比单纯“关闭 swap”更优雅、更有效的调优手段。5. 实操如何监控、诊断与调优 kswapd 的行为理论终归要落地。在生产环境中我们如何知道 kswapd 是否在正常工作它是不是已经成了系统瓶颈又该如何进行有针对性的调优这需要一套组合拳式的监控与诊断方法。5.1 核心监控指标超越free和topfree -h和top是入门级工具但对于 kswapd 的深度分析它们提供的信息远远不够。你需要关注以下更底层、更精确的指标/proc/zoneinfo这是 kswapd 的“体检报告单”。它会为你列出每个内存管理区zone的pages free、pages min、pages low、pages high等关键水线值以及pgpgin/pgpgout页入/页出计数、pgmajfault主要缺页中断等统计信息。重点关注pages free是否长期徘徊在pages low附近或者pages min是否被频繁击穿。如果pages min被击穿pgmajfault数值会急剧上升这是系统即将 OOM 的明确预警。/proc/vmstat这是内核内存子系统的“黑匣子”。其中pgpgin和pgpgout记录了总的页入/页出次数pgmajfault记录了主要缺页中断需要从磁盘加载pgpgin和pgpgout的差值大致反映了 swap 的活跃程度而pgscan_kswapd_*系列如pgscan_kswapd_normal则直接记录了 kswapd 在各个 zone 上扫描的页数。持续观察这些值的变化趋势比看瞬时值更有价值。vmstat 1这是一个轻量级的实时监控利器。重点关注siswap in和soswap out列。如果这两列在1秒间隔下持续大于0说明系统正在频繁进行 swap 操作kswapd 已经进入了“救火模式”。同时r运行队列长度如果长期大于 CPU 核心数b不可中断睡眠进程数如果持续不为0也往往是内存压力导致 I/O 等待的间接证据。5.2 诊断案例一次真实的 kswapd 高 CPU 故障排查我曾处理过一个典型故障一台用于视频转码的服务器kswapd0的 CPU 使用率常年维持在 25%-30%si/so持续为100但free显示仍有 2GB “available” 内存。直觉告诉我问题不在总量而在分布。第一步我执行cat /proc/zoneinfo | grep -A 10 Node 0, zone | head -n 20发现Normalzone 的pages free是123456而pages low是123450两者仅差 6 页这意味着 kswapd 正在一条“刀锋”上跳舞它刚把空闲页数拉回pages high下一秒就被某个进程的内存分配请求打回pages low以下立刻又被唤醒。这就是典型的“水线设置过紧”。第二步我检查vm.min_free_kbytes发现它被设置为了6553664MB。对于一台 128GB 内存的机器这个值偏低。我将其调整为524288512MB命令是echo 524288 /proc/sys/vm/min_free_kbytes。这个操作会立即重新计算所有 zone 的WMARK_MIN、WMARK_LOW和WMARK_HIGH将它们整体抬高。第三步观察效果。kswapd0的 CPU 使用率在 5 分钟内从 25% 下降到 2%si/so归零vmstat的r值也从平均8降到了1。系统响应速度明显提升。这个案例清晰地表明kswapd 的“高负荷”很多时候不是它自身有问题而是它所服务的“水线系统”被设置得过于苛刻让它不得不疲于奔命。5.3 关键调优参数vm.min_free_kbytes与vm.swappiness基于上述诊断我们可以总结出两个最核心、最安全的调优参数参数默认值推荐范围作用原理调优建议vm.min_free_kbytes动态计算通常为几十MB物理内存的 0.5% ~ 2%直接设定WMARK_MIN的基准值从而抬高所有水线对于内存充足的服务器64GB建议设为524288(512MB) 或更高为 kswapd 提供更宽松的工作缓冲区。vm.swappiness601 ~ 10I/O 密集型60通用100内存充足希望最大化利用 swap控制内核在shrink_page_list()中对anon页和file页的回收权重比例对于数据库、Web 服务器等严重依赖文件缓存的应用强烈建议降至10。这能强制 kswapd 优先清理文件缓存而非轻易 swap 进程内存。实操心得所有sysctl参数的修改都应该通过编辑/etc/sysctl.conf文件并执行sysctl -p来持久化。切勿只用echo写入/proc/sys/因为重启后会失效。另外vm.swappiness0并不意味着“完全禁用 swap”它只是让内核在有足够file页可回收时绝不考虑anon页。只有当file页也被耗尽时OOM Killer 依然会被触发。所以swappiness1是一个更务实、更安全的选择。6. 常见问题与避坑指南那些年我们踩过的 kswapd 坑在与 kswapd 打交道的十多年里我见过太多因误解或误操作而导致的线上事故。下面分享几个最具代表性、也最容易被忽视的“坑”希望能帮你绕开它们。6.1 陷阱一“安装 PVE 关闭 swap” —— 一个危险的伪命题网络上流传着一种说法“安装 Proxmox VE (PVE) 时必须关闭 swap否则会影响虚拟机性能。” 这是一个彻头彻尾的误导。PVE 本身就是一个基于 Debian 的 Linux 发行版它的内核行为与标准 Linux 完全一致。关闭 swap 并不能“提升性能”反而会极大地增加 OOM Killer 触发的风险。当 swap 被关闭swapoff -ainactive_anon页就失去了唯一的“逃生通道”。一旦内存压力上升kswapd 在耗尽所有file页后就只能眼睁睁看着pages free跌破WMARK_MIN然后由内核直接触发 OOM Killer随机杀死一个占用内存最多的进程通常是你的关键业务进程。而如果 swap 是开启的kswapd 至少还能把一部分“冷”进程内存换出为“热”进程争取宝贵的喘息时间。因此正确的做法是保留 swap但通过调优swappiness和min_free_kbytes让 swap 成为一个“安全气囊”而不是一个“日常通勤工具”。6.2 陷阱二“vim 产生的 swap 文件删除” —— 与内核 kswapd 完全无关另一个高频混淆点是 vim 的.swp文件。当你用 vim 编辑一个文件时它会在同目录下生成一个隐藏的.filename.swp文件用于崩溃恢复。这个文件是 vim 应用层创建的普通文件与内核的 swap 机制、kswapd 线程、/swapfile或swap分区没有任何技术关联。删除.swp文件只会让 vim 失去崩溃恢复能力对系统内存管理毫无影响。把这两个概念混为一谈是初学者最容易犯的认知错误。6.3 陷阱三“universal watermark disabler” —— 一个危险的 kernel patch在某些极端性能调优场景下有人会寻找所谓的 “universal watermark disabler” 补丁试图完全禁用 watermark 系统让 kswapd 永远不启动。这无异于拆掉汽车的刹车系统来追求“更快”。watermark 是内核内存管理的基石强行移除它会导致内核在内存分配时失去所有缓冲和预警能力系统会变得极其脆弱任何一次突发的内存峰值都可能导致瞬间宕机。对于绝大多数应用场景这不是调优而是自毁。真正的调优永远是在内核既定框架内通过合理配置参数来引导其行为而不是试图颠覆框架本身。6.4 陷阱四过度关注kswapd进程的 CPU 占用率最后一个常见的心理误区看到top里kswapd0的 CPU 占用率很高就立刻认定它是“罪魁祸首”想方设法要“杀死”或“限制”它。这是本末倒置。kswapd 的高 CPU从来都不是原因而是结果。它是在为系统内存压力“打工”。你限制它的 CPU只会让它干活更慢导致内存压力无法及时释放最终引发更严重的连锁反应如大量进程阻塞、OOM。正确的思路永远是顺着 kswapd 的高 CPU去找到那个真正制造内存压力的源头——是哪个进程在疯狂 malloc是哪个服务在泄露内存还是min_free_kbytes设置得太小解决了源头问题kswapd 自然就会“闲下来”。7. kswapd 的未来在 cgroup v2 和内存压缩时代它依然是那个沉默的守夜人随着 Linux 内核的演进特别是 cgroup v2 的普及和 zsmalloc、zram 等内存压缩技术的成熟kswapd 的角色也在悄然发生变化但它作为内存压力“第一响应者”的核心地位从未动摇。在 cgroup v2 时代内存控制器memory controller引入了memory.low和memory.high这样的新水线。它们的作用域不再是整个 zone而是针对单个 cgroup容器或进程组。这意味着kswapd 的回收工作现在可以被更精细地“定向”。当一个容器的内存使用逼近其memory.high时内核会优先在这个容器的inactive_anon和inactive_file链表上进行扫描和回收而不是像过去那样对整个系统进行“无差别清扫”。这极大地提升了多租户环境如 Kubernetes 集群的资源隔离性和公平性。kswapd 依然是那个执行回收动作的线程但它现在有了更精准的“任务清单”。另一方面zram 技术的兴起为 kswapd 提供了一个全新的“工作伙伴”。zram 是一个在 RAM 中创建的、经过压缩的块设备。当 kswapd 需要回收inactive_anon页时内核现在可以选择将它们压缩后存入 zram而不是写入物理的 swap 分区。由于 zram 的读写完全在内存中进行其速度比 SSD swap 快一个数量级以上。这使得 kswapd 在处理匿名页时拥有了一个“高速缓存”选项。它不再总是被迫走向缓慢的磁盘 I/O而是可以先尝试“内存内压缩”这在内存充足但 I/O 瓶颈明显的场景下带来了质的飞跃。然而无论技术如何变迁kswapd 的本质没有变。它依然是那个在后台默默运行、依据水线系统进行异步回收、力求在内存压力和系统响应之间取得最佳平衡的内核线程。它不喧哗不邀功但一旦它停止工作整个系统的稳定性就会在几分钟内土崩瓦解。理解 kswapd不是为了把它当成一个可以随意摆弄的开关而是为了读懂 Linux 内核这台精密机器上那根最基础、也最关键的“压力调节阀”的工作语言。当你下次再看到kswapd0在ps的输出中安静地存在着不妨对它报以一丝敬意——它正用自己无声的劳作守护着你所有应用的平稳运行。
返回列表