浅谈kswapd按照什么原则来换出页面的底层原理

kswapd的核心使命

kswapd的主要任务是在后台(当内存压力尚未达到临界点时)预防性地释放内存,将其添加到空闲列表(free list) 中,以满足未来的内存分配请求。它的目标是尽量避免让系统陷入需要由直接页面回收(alloc_pages路径中的__alloc_pages_slowpath触发的同步回收)这种严重影响性能的状况。

1.LRU(最近最少使用)架构与链表管理:

核心数据结构: 内核维护多组LRU(Least Recently Used)链表来组织物理内存页(struct page),主要的分类维度是:

页面类型:

匿名页(Anonymous Pages): 没有对应后备文件(比如堆、栈、共享内存、mmap(MAP_ANON)等进程专用数据)。这些页面只能换出到交换分区/Swap文件。

文件页(Page Cache / File-backed Pages): 有对应后备文件(比如已读入的可执行文件、数据文件、映射文件的读写等)。这些页面可以被丢弃(Drop/Discard) 或写回同步(Sync/Writeback) 到底层存储。丢弃不需要磁盘I/O(如果页面是干净的),是最快速的回收方式;写回脏页需要I/O。

活动状态:

活动链表(Active LRU Lists): 存放近期被访问过的页面。内核认为这些页面有"价值",不应优先被换出。进一步分为active_anon和active_file。

非活动链表(Inactive LRU Lists): 存放较长时间未被访问或已被内核判定为"不太有价值"的页面。换出主要针对这些链表。进一步分为inactive_anon和inactive_file。当kswapd需要回收内存时,主要扫描的就是非活动链表。

页面迁移: 一个页面被首次访问时加入对应类型的活动链表。内核通过mm/vmscan.c中的shrink_list()等函数定期扫描(比如kswapd扫描循环的一部分),使用第二次机会(Second Chance)/ 时钟算法(Clock Algorithm)的变种:

内核会检查页面的访问位(通常是PG_referenced或者硬件提供的Accessed bit)。

如果一个页面在被扫描时被发现其访问位被置位(表明它最近被访问过),内核会清除该位并将其移动到对应活动链表的尾部(给它第二次机会)。

如果一个页面在被扫描时其访问位未被置位(表明它在上次扫描后到这次扫描之间未被访问),它就被认为是老化(aged)的、不活跃的,会被移动到对应非活动链表的尾部。

PG_referenced的作用: 这个标志位是内核跟踪页面近期访问情况的关键。硬件MMU在访问该页时会自动设置硬件访问位(如PTE_YOUNG)。内核的后台线程或页面扫描逻辑(kswapd, pdflush后代)会将硬件访问位(如果已设置)复制到PG_referenced软件标志位,然后清除硬件访问位,这样扫描逻辑(如kswapd)只需检查PG_referenced即可判断页面在最近的时间窗口内是否被访问过。

2.优先级扫描(Scan Priority):

当kswapd启动回收时,它从MAX_PRIORITY(最低扫描强度)开始扫描。

如果在扫描了指定数量的页框(SWAP_CLUSTER_MAX)后仍未能回收足够的内存以满足水印要求,它会提高扫描优先级(降低priority数值)。

更高的扫描优先级(更低的priority数值)意味着:

扫描深度增加:扫描更多的页框。

扫描范围更激进:会扫描活动链表(不仅仅是非活动链表)来寻找可回收的页。

回收要求变严格:尝试回收"更不愿意放手"的页(例如强制回收更"年轻"的页、回收共享内存页,甚至当优先级很高且系统极端紧急时强制将脏页立即写入磁盘)。

priority初始值为DEF_PRIORITY(通常为12),递减至0(最高优先级,最激进)。

3.水印机制(Watermark Levels):

内核为每个内存管理区(struct zone,如ZONE_NORMAL, ZONE_DMA)定义了三个关键水印(watermark[WMARK_MIN], watermark[WMARK_LOW], watermark[WMARK_HIGH])。

这些水印值基于min_free_kbytes全局参数计算。

kswapd的目标:当空闲内存低于WMARK_LOW水印时,kswapd会被唤醒。

kswapd持续回收内存,直到该zone的空闲页框数至少达到WMARK_HIGH水印。这个差值提供了一个缓冲区(Buffer),以应对短时间内较大的内存分配请求,避免立即跌入WMARK_LOW以下。

4.交换倾向(Swappiness - /proc/sys/vm/swappiness):

swappiness是一个0到100之间的整数值(默认通常为60)。

它主要控制kswapd在回收内存时对待匿名页与文件页的相对偏好。

高swappiness值(接近100):内核更乐意换出匿名页。

低swappiness值(接近0):内核更偏向回收文件页(尤其是丢弃干净的Page Cache)并尽量避免换出匿名页。

值60:大致表示匿名页和文件页的回收比例与它们在内存中的占用比例相对应。

内部计算: kswapd使用这个值来计算一个回收成本分数(reclaim cost)。它通过函数get_scan_count()(在mm/vmscan.c中)根据当前扫描优先级(priority)和swappiness来决定扫描每类LRU链表(inactive_anon, active_anon, inactive_file, active_file)的比例和强度。

系统倾向: swappiness=0并不完全禁用Swap。当文件页(Page Cache)非常少或都脏页需要写回时,系统面临严重的直接内存回收压力时,即使swappiness=0,匿名页仍然可能被换出以保证系统运行。但kswapd在后台工作中会极力避免主动换出匿名页。

6.回收步骤(扫描非活动链表):

kswapd在扫描循环中调用shrink_node() -> shrink_node_memcgs() -> shrink_lruvec() -> shrink_list()(针对每个内存控制组memcg和LRU链表)。

对于非活动文件页(inactive_file):

检查页面状态:如果页面是干净的(Clean - 内容与磁盘一致),可以直接丢弃(Drop),立刻变成空闲。这是最高效的回收。

如果页面是脏的(Dirty - 内容被修改未写回),需要启动写回(Writeback) 到底层存储设备(通过mapping->a_ops->writepage)。这些页面不能立刻变成空闲,需要异步等待I/O完成。写回本身消耗I/O带宽和CPU。

对于非活动匿名页(inactive_anon):

因为没有后备文件,回收的唯一方式是换出(Swap Out) 到交换分区或交换文件。

内核查找swap_info_struct结构找到活动的交换区域。

内核需要分配一个交换槽(Slot) 来存放此页。

然后发起页面内容的写操作到交换分区。

在页表项(Page Table Entry - PTE) 中做标记:将原来的指向物理内存的映射改为一个特殊的交换条目(Swap Entry)(包含交换区域和槽位的索引)。PTE被设置为无效(Present Bit Clear) 并标记为交换条目(Special Bits Set)。

物理页框被释放回伙伴系统(Buddy System)的空闲列表中。

重要: 换出过程涉及昂贵的磁盘写操作。

案例说明

场景: 一个运行着大量应用程序(Web服务器+应用服务器+数据库)的生产服务器,物理内存128GB。其配置如下:

swappiness = 60 (默认)

min_free_kbytes = 65536 (~64MB, 自动计算出的或管理员设置)

交换区大小:16GB(交换分区)

事件: 随着业务高峰来临,Web服务器接收大量请求,应用服务器处理业务逻辑,数据库进行复杂查询。

kswapd行为分析

1.内存需求增加:

应用进程不断申请内存(如创建新连接、处理用户会话、加载查询结果等)。

内核(通过__alloc_pages)尝试从普通区(ZONE_NORMAL)的空闲列表中分配页框。

2.跌破WMARK_HIGH:

一段时间后,随着分配持续,ZONE_NORMAL的空闲内存持续下降。

当空闲页框数量低于watermark[WMARK_HIGH] (比如,原空闲20GB,现在降到19.9GB,而WMARK_HIGH设置在20.1GB)时,不会触发任何特殊动作。kswapd处于休眠状态或空闲状态。

3.跌破WMARK_LOW:

分配继续。空闲页框跌破watermark[WMARK_LOW] (比如19.8GB < WMARK_LOW=19.9GB)。

kswapd被唤醒: 负责ZONE_NORMAL的kswapd内核线程被内核调度器唤醒(通过wake_up_kswapd() -> wake_up_interruptible(&pgdat->kswapd_wait))。

4.kswapd回收循环(初始优先级=12 DEF_PRIORITY):

kswapd调用其主函数kswapd_try_to_sleep() -> balance_pgdat() -> kswapd_shrink_node() -> shrink_node() -> ...)。

开始扫描(通过shrink_lruvec())非活动链表(inactive_anon, inactive_file)。

get_scan_count()计算扫描比例:

假设当前内存中匿名页占80GB(75GB Active, 5GB Inactive),文件页占40GB(35GB Active, 5GB Inactive)。(注意这是非精确示例,比例会动态变化)。

根据swappiness=60和priority=12,该函数计算扫描权重:

inactive_file权重相对较高(因为swappiness为默认值,且文件页回收通常更快)。

inactive_anon也有一定权重。

扫描可能先尝试快速回收一部分易回收的文件页(清理干净的页面缓存)。

回收inactive_file (优先尝试):

扫描队列,找到干净的页面(如/usr/lib下某个库文件副本,长时间未被访问) - 直接丢弃 => 立即释放内存(例如50个4KB页)。

找到脏页面(如某个应用日志文件的缓冲区)- 标记为待写回,将其加入脏页写回队列,但仍留在非活动链表。本次扫描无法立即回收这些脏页占用的内存。写回由其他线程异步完成。

回收inactive_anon:

扫描队列,发现某匿名页面(如一个闲置服务的堆内存页)访问位PG_referenced未被设置。

尝试换出:

查找交换区:检查swap_info_struct链表,找到配置好的16GB交换分区。

分配交换槽:在交换区找到空闲槽位(如槽位号23456)。

写操作:发起磁盘I/O,将该页内容写入交换分区第23456槽位。这是一个阻塞操作,但由kswapd异步调度。

更新页表:找到映射该物理页的所有进程的页表项(使用反向映射 rmap)。将PTE设置为无效(清除Present bit),并标记为swp_entry_t(交换分区ID, 槽位号)。该物理页被释放。I/O完成后回调将其加入空闲列表。

一轮扫描结束: kswapd查看是否释放了足够页框使其达到WMARK_HIGH。如果没有(比如只回收了100MB,目标差1GB),它会将扫描优先级priority降低到11(变得稍激进)。

5.扫描优先级提升:

priority=11:kswapd扫描更多的页框(SWAP_CLUSTER_MAX * (20 - priority)),扫描力度变大。get_scan_count()可能会略微调整扫描比例。

如果回收仍然不足,优先级继续下降到10,9,...。

例如,当priority降到5(比较激进的级别):

扫描范围扩大: get_scan_count()将开始分配一定的扫描比例给活动链表(active_anon, active_file)。这意味着kswapd会尝试将一些"看起来有点活跃但实际可能没那么活跃"的页面从活动链表移到非活动链表,然后再尝试回收。

回收更"有价值"的页: 它可能会回收共享库的活跃匿名页(多个进程映射的libc.so部分),或者强制将重要应用程序很久没碰但还在活动链表的脏文件页标记为需要写回(更积极地清理文件页),或者尝试去换出那些"刚刚获得第二次机会"的匿名页(即刚刚被从活动链表移下来的匿名页)。

写入压力增大: 回收匿名页(换出)和写回脏文件页都会增加对存储系统(磁盘或SSD)的写入压力(I/O带宽和延迟)。

kswapd持续这个过程,不断提高优先级(降低priority值,加大扫描力度),直到:

成功将空闲页框提升到WMARK_HIGH或以上(目标达成)。

或者达到了最高优先级priority=0,依然回收困难,但这通常意味着系统内存压力极大(可能接近OOM)。

6.恢复正常: 如果业务高峰过去或应用释放了内存,系统空闲内存逐渐回升。

当空闲页框超过WMARK_HIGH 并稳定一段时间后,kswapd可能会降低其活动性,降低扫描优先级(提升priority值),最终进入浅度休眠状态(但仍等待在唤醒队列kswapd_wait上),直到再次检测到内存低于WMARK_LOW。

案例总结

在这个案例中,kswapd根据活动状态(LRU链表)、页面类型、swappiness参数、内存水印和扫描优先级等因素,动态地决定:

1.从哪里回收: 优先扫描非活动链表(尤其是非活动文件页以快速丢弃干净的缓存)。当内存压力增大时,会扩大到扫描活动链表并更积极回收匿名页。

2.如何回收: 对于文件页(Page Cache),优先丢弃干净页(最快回收),然后写回脏页(较慢)。对于匿名页,只能通过代价较高的换出操作。

3.回收力度: 根据回收效果(是否达到WMARK_HIGH)逐步提升回收强度(降低扫描优先级)。

这一切的目标是在后台主动地、预防性地管理内存,维护系统在水印指标之上的稳定运行状态,避免应用进程直接执行耗时的同步页面回收(直接回收路径)导致显著的性能抖动或延迟。监控kswapd的CPU使用率、si(Swap in), so(Swap out)量、wa(I/O等待)时间以及vmstat的输出(特别是nr_inactive_anon, nr_active_anon, nr_inactive_file, nr_active_file, nr_dirty, nr_writeback),是诊断系统内存压力的重要手段。kswapd过于频繁地高优先级运行,通常表明物理内存可能不足或swappiness设置不合理。

相关推荐
獭.獭.6 小时前
Linux -- 线程控制
linux·pthread·线程分离·线程取消·线程局部存储·lwp·线程栈
feng_blog66886 小时前
环形缓冲区实现共享内存
linux·c++
chen36736 小时前
嵌入式AI Arm_linux_第一个Demo_让IPU跑起来
linux·arm开发·人工智能
2501_938791226 小时前
服务器恶意进程排查:从 top 命令定位到病毒文件删除的实战步骤
java·linux·服务器
LCG元6 小时前
SSH密钥对认证配置详解:告别密码登录,实现Linux服务器安全免密远程连接
linux
Jack电子实验室7 小时前
Linux系统调用lseek详解:文件指针的灵活控制
linux·运维·服务器
TracelessLe7 小时前
/usr/bin/ld: cannot find -lcuda报错分析
linux·运维·服务器
程序员buddha7 小时前
curl开发常用方法总结
linux
huangyuchi.7 小时前
【Linux网络】Socket编程实战,基于UDP协议的Echo Server
linux·运维·服务器·udp·socket·客户端·网络通信