前言
想象一下Linux的内存管理系统就像一座现代化大城市的环卫系统:
- 内存回收机制如同城市的垃圾回收车,定期清理不再使用的"废弃物"(内存页面)
- TLB管理如同城市的交通信号系统,确保地址转换的"交通流畅"
- 批量释放优化如同高效的垃圾处理中心,批量处理提高整体效率
当系统内存紧张时,Linux需要像精密的环卫系统一样,高效地识别、收集和处理不再使用的内存资源,同时确保系统的稳定运行。这个过程涉及复杂的锁管理、竞态条件防护和性能优化。
本文将深入解析Linux内存回收的核心机制,揭示系统如何在保证性能的前提下,安全高效地管理宝贵的内存资源
三级回收架构:从微观到宏观
第一级:TLB刷新层
c
// TLB刷新工作流程
tlb_finish_mmu() // 收尾工作
↓
tlb_flush_mmu() // 批量刷新
↓
flush_tlb_mm() // 实际刷新
TLB刷新的必要性:
- 当页表修改后,CPU缓存中的旧映射必须失效
- 防止访问已释放或已移动的物理页面
- 确保多核处理器间的内存一致性
智能刷新策略:
c
if (mm == current->active_mm) // 只刷新当前使用的映射
__flush_tlb();
第二级:批量释放层 - 高效的"垃圾处理中心"
c
// 批量释放的优化策略
free_pages_and_swap_cache() // 顶层批量接口
↓
release_pages() // 智能批量释放
↓
__pagevec_free() // 底层批量操作
批量优化的核心思想:
- 减少锁竞争:相同zone的页面批量处理
- 降低函数开销:批量调用减少上下文切换
- 提高缓存命中:连续访问相同内存区域
分块处理策略:
c
int chunk = 16; // 每次处理16个页面
while (nr) {
int todo = min(chunk, nr); // 计算本次处理量
// 批量处理当前块
}
第三级:内存回收层 - 主动的"资源回收站"
c
// 内存回收的核心循环
shrink_cache()
↓
批量获取页面 → 锁外处理 → 实际回收 → 未释放页面回退
回收策略的平衡艺术:
- 扫描限制 :
max_scan防止过度回收影响性能 - 优先级区分 :
kswapd与直接回收的不同统计 - 失败回退:未能释放的页面安全返回LRU
协同工作流程
场景1:进程退出时的资源清理
进程退出 → 清理页表 → tlb_finish_mmu()更新统计 →
free_pages_and_swap_cache()批量释放 → 页面回归系统
场景2:内存压力下的主动回收
内存紧张 → kswapd唤醒 → shrink_cache()扫描LRU →
批量获取页面 → shrink_list()尝试回收 → 更新统计信息
场景3:大范围内存操作优化
大范围unmap → tlb_gather_mmu收集页面 →
批量TLB刷新 → 批量页面释放 → 最小化性能影响
性能优化深度解析
锁竞争缓解策略
问题 :zone->lru_lock是高度竞争的全局锁
解决方案:
c
// 批量获取,锁外处理
spin_lock_irq(&zone->lru_lock);
// 批量获取SWAP_CLUSTER_MAX个页面
spin_unlock_irq(&zone->lru_lock);
// 在锁外处理这些页面
// 处理完成后重新获取锁
效果:将锁持有时间从"每个页面"减少到"每批页面"
竞态条件安全防护
双重检查模式:
c
if (get_page_testone(page)) {
// 页面正在被其他地方释放
// 安全回退,避免重复释放
}
原子操作保障:
c
if (TestClearPageLRU(page)) // 原子性状态变更
if (TestSetPageLRU(page)) // 原子性状态恢复
TLB刷新和内存管理tlb_flush_mmu
c
static inline void
tlb_flush_mmu(struct mmu_gather *tlb, unsigned long start, unsigned long end)
{
if (!tlb->need_flush)
return;
tlb->need_flush = 0;
tlb_flush(tlb);
if (!tlb_fast_mode(tlb)) {
free_pages_and_swap_cache(tlb->pages, tlb->nr);
tlb->nr = 0;
}
}
/*
* .. because we flush the whole mm when it
* fills up.
*/
#define tlb_flush(tlb) flush_tlb_mm((tlb)->mm)
static inline void flush_tlb_mm(struct mm_struct *mm)
{
if (mm == current->active_mm)
__flush_tlb();
}
代码功能概述
这段代码实现了TLB的刷新和关联页面的释放,是内存管理中的关键操作
代码逐段解析
tlb_flush_mmu 函数
c
static inline void
tlb_flush_mmu(struct mmu_gather *tlb, unsigned long start, unsigned long end)
{
- 函数声明 :
static inline内联函数,避免函数调用开销 - 参数 :
tlb:mmu_gather结构,收集需要处理的页面信息start,end:地址范围
c
if (!tlb->need_flush)
return;
- 快速检查:如果不需要刷新,立即返回
- 性能优化:避免不必要的TLB刷新操作
need_flush标志:在之前的操作中设置,表示有需要刷新的TLB项
c
tlb->need_flush = 0;
- 清除刷新标志:标记TLB刷新操作即将开始
- 防止重复刷新:确保不会重复执行刷新
c
tlb_flush(tlb);
- 执行TLB刷新:调用实际的TLB刷新函数
c
if (!tlb_fast_mode(tlb)) {
free_pages_and_swap_cache(tlb->pages, tlb->nr);
tlb->nr = 0;
}
}
- 慢速模式处理:如果不是快速模式,释放收集的页面
- 批量释放页面 :
free_pages_and_swap_cache释放所有收集的页面 - 重置计数器 :
tlb->nr = 0清空页面计数,准备下一次收集
tlb_flush 宏
c
#define tlb_flush(tlb) flush_tlb_mm((tlb)->mm)
- 宏定义 :将
tlb_flush映射到flush_tlb_mm函数 - 参数传递 :传递
tlb->mm(内存管理结构)
flush_tlb_mm 函数
c
static inline void flush_tlb_mm(struct mm_struct *mm)
{
- 函数功能:刷新指定内存管理结构的TLB
- 参数 :
mm- 内存管理结构指针
c
if (mm == current->active_mm)
__flush_tlb();
}
- 当前进程检查:如果刷新的是当前进程的内存映射
- 执行TLB刷新 :
__flush_tlb()执行实际的硬件TLB刷新操作
详细技术说明
TLB刷新的必要性
TLB(Translation Lookaside Buffer)的作用:
- 缓存虚拟地址到物理地址的映射
- 加速地址转换过程
需要刷新的场景:
- 页表修改:当页表项被修改或删除时
- 页面释放:当物理页面被释放时
- 进程切换:当切换到不同进程的地址空间时
current->active_mm 检查的重要性
为什么需要检查:
c
if (mm == current->active_mm)
__flush_tlb();
原因:
- 只刷新当前正在使用的内存映射
- 避免刷新其他进程的TLB项(没有必要)
总结
函数功能总结:
- TLB刷新管理:根据需要刷新转址旁路缓存
- 竞态防护:确保TLB与页表状态的一致性
TLB操作收尾函数tlb_finish_mmu
c
static inline void
tlb_finish_mmu(struct mmu_gather *tlb, unsigned long start, unsigned long end)
{
int freed = tlb->freed;
struct mm_struct *mm = tlb->mm;
int rss = mm->rss;
if (rss < freed)
freed = rss;
mm->rss = rss - freed;
tlb_flush_mmu(tlb, start, end);
/* keep the page table cache within bounds */
check_pgt_cache();
}
代码功能概述
这个函数在TLB刷新操作结束时调用,负责更新内存统计、执行最终的TLB刷新,并检查页表缓存状态
代码逐段解析
函数声明
c
static inline void
tlb_finish_mmu(struct mmu_gather *tlb, unsigned long start, unsigned long end)
- 参数 :
tlb:mmu_gather结构,包含操作状态信息start,end:操作的地址范围
变量初始化
c
int freed = tlb->freed;
struct mm_struct *mm = tlb->mm;
int rss = mm->rss;
- 获取释放页面数 :
freed = tlb->freed从tlb结构中获取已释放的页面数量 - 获取内存管理结构 :
mm = tlb->mm获取关联的内存管理结构 - 获取当前RSS :
rss = mm->rss获取进程的常驻内存集大小
RSS统计更新
c
if (rss < freed)
freed = rss;
- 边界检查:如果RSS小于声称释放的页面数
- 防止下溢:将释放数量调整为不超过当前RSS值
- 安全措施:防止RSS统计变为负数
c
mm->rss = rss - freed;
- 更新RSS统计:从当前RSS中减去释放的页面数
- 反映内存使用:准确跟踪进程的实际内存使用情况
TLB刷新
c
tlb_flush_mmu(tlb, start, end);
- 执行TLB刷新 :调用前面分析的
tlb_flush_mmu函数 - 刷新TLB并释放页面:确保TLB与内存状态一致
- 批量操作:处理所有收集的页面映射
详细技术说明
RSS(Resident Set Size)管理
RSS的含义:
- 进程当前在物理内存中的页面数量
- 反映进程的实际内存占用
- 用于内存统计和限制
总结
函数功能总结:
- 统计更新:准确更新进程的RSS内存统计
- TLB刷新:执行最终的TLB刷新和页面释放
- 资源清理:完成所有内存管理操作的收尾工作
批量释放页面和交换缓存free_pages_and_swap_cache
c
/*
* Passed an array of pages, drop them all from swapcache and then release
* them. They are removed from the LRU and freed if this is their last use.
*/
void free_pages_and_swap_cache(struct page **pages, int nr)
{
int chunk = 16;
struct page **pagep = pages;
lru_add_drain();
while (nr) {
int todo = min(chunk, nr);
int i;
for (i = 0; i < todo; i++)
free_swap_cache(pagep[i]);
release_pages(pagep, todo, 0);
pagep += todo;
nr -= todo;
}
}
代码功能概述
这个函数批量释放页面数组,首先从交换缓存中移除它们,然后释放页面引用
代码逐段解析
函数声明
c
void free_pages_and_swap_cache(struct page **pages, int nr)
-
传入页面数组,从交换缓存中移除所有页面
-
然后释放它们
-
页面从LRU移除,如果是最后使用则释放
-
参数:
pages:页面指针数组nr:数组中页面的数量
变量初始化
c
int chunk = 16;
struct page **pagep = pages;
chunk = 16:定义处理块大小,每次处理16个页面pagep = pages:创建指针指向页面数组开头,用于遍历
LRU排空操作
c
lru_add_drain();
- 函数作用:将per-CPU LRU缓存中的页面排空到全局LRU列表
- 为什么需要:确保所有页面都在全局LRU列表中,便于后续统一处理
- 添加页面时会优先放到per_CPU 缓存,缓存满时再放到全局LRU列表,避免频繁竞争全局zone锁
主循环结构
c
while (nr) {
int todo = min(chunk, nr);
int i;
- 循环条件 :
while (nr)当还有页面需要处理时继续 - 计算本次处理数量 :
todo = min(chunk, nr)取块大小和剩余数量的最小值 - 循环变量 :
i用于内层循环
交换缓存处理循环
c
for (i = 0; i < todo; i++)
free_swap_cache(pagep[i]);
- 遍历当前块 :对块内的每个页面调用
free_swap_cache free_swap_cache(pagep[i]):如果页面在交换缓存中,移除它- 作用:确保页面不再与交换空间关联
页面释放操作
c
release_pages(pagep, todo, 0);
- 批量释放:一次性释放当前块的所有页面
- 参数 :
pagep:当前块的起始位置todo:要释放的页面数量0:释放选项(通常为0)
- 内部操作:减少页面引用计数,如果归零则真正释放
指针和计数更新
c
pagep += todo;
nr -= todo;
}
}
- 移动指针 :
pagep += todo将指针移动到下一个块的起始位置 - 减少剩余计数 :
nr -= todo更新剩余待处理页面数量 - 循环继续:回到while条件检查,直到所有页面处理完成
函数功能总结
核心功能:高效批量释放页面数组,正确处理交换缓存和页面引用
主要作用:
- 交换缓存清理:从交换缓存中移除相关页面
- 页面引用释放:减少页面引用计数,必要时释放页面
- LRU管理:确保页面从LRU列表中正确移除
- 批量优化:通过分块处理提高性能
批量释放页面release_pages
c
void release_pages(struct page **pages, int nr, int cold)
{
int i;
struct pagevec pages_to_free;
struct zone *zone = NULL;
pagevec_init(&pages_to_free, cold);
for (i = 0; i < nr; i++) {
struct page *page = pages[i];
struct zone *pagezone;
if (PageReserved(page) || !put_page_testzero(page))
continue;
pagezone = page_zone(page);
if (pagezone != zone) {
if (zone)
spin_unlock_irq(&zone->lru_lock);
zone = pagezone;
spin_lock_irq(&zone->lru_lock);
}
if (TestClearPageLRU(page))
del_page_from_lru(zone, page);
if (page_count(page) == 0) {
if (!pagevec_add(&pages_to_free, page)) {
spin_unlock_irq(&zone->lru_lock);
__pagevec_free(&pages_to_free);
pagevec_reinit(&pages_to_free);
zone = NULL; /* No lock is held */
}
}
}
if (zone)
spin_unlock_irq(&zone->lru_lock);
pagevec_free(&pages_to_free);
}
static inline void pagevec_free(struct pagevec *pvec)
{
if (pagevec_count(pvec))
__pagevec_free(pvec);
}
void __pagevec_free(struct pagevec *pvec)
{
int i = pagevec_count(pvec);
while (--i >= 0)
free_hot_cold_page(pvec->pages[i], pvec->cold);
}
代码功能概述
这个函数批量释放页面引用计数,如果计数归零则从LRU移除并释放页面,优化锁操作和批量处理
代码逐段解析
函数声明
c
void release_pages(struct page **pages, int nr, int cold)
-
批量版本的
page_cache_release -
减少所有页面的引用计数,如果归零则从LRU移除并释放
-
尽量避免获取zone锁,但如果获取了就保持到操作结束
-
与
shrink_cache竞争:在锁内重新检查页面计数 -
参数:
pages:页面指针数组nr:页面数量cold:是否释放为冷页面
变量初始化
c
int i;
struct pagevec pages_to_free;
struct zone *zone = NULL;
pagevec_init(&pages_to_free, cold);
- 循环变量 :
i用于遍历页面数组 - 释放页面缓存 :
pages_to_free用于批量释放页面 - zone跟踪 :
zone = NULL表示当前没有持有任何zone的锁 - 初始化pagevec :
pagevec_init(&pages_to_free, cold)初始化释放缓存
主循环开始
c
for (i = 0; i < nr; i++) {
struct page *page = pages[i];
struct zone *pagezone;
- 遍历所有页面:从0到nr-1
- 获取当前页面 :
page = pages[i] - zone变量 :
pagezone用于存储页面所属的zone
快速检查跳过
c
if (PageReserved(page) || !put_page_testzero(page))
continue;
- 保留页面检查 :
PageReserved(page)检查是否是内核保留页面 - 引用计数测试 :
!put_page_testzero(page)原子性地减少计数并测试是否归零 - 跳过条件:如果是保留页面或引用计数未归零,跳过后续处理
Zone锁管理
c
pagezone = page_zone(page);
if (pagezone != zone) {
if (zone)
spin_unlock_irq(&zone->lru_lock);
zone = pagezone;
spin_lock_irq(&zone->lru_lock);
}
- 获取页面zone :
pagezone = page_zone(page) - zone切换检查 :如果切换到新的zone
- 释放旧zone的锁(如果存在)
- 更新当前zone
- 获取新zone的LRU锁
- 优化:相同zone的页面批量处理,减少锁操作
LRU列表操作
c
if (TestClearPageLRU(page))
del_page_from_lru(zone, page);
- 原子性LRU标志清除 :
TestClearPageLRU(page)测试并清除LRU标志 - 从LRU移除:如果页面在LRU列表中,将其移除
- 防止竞态 :这里预期页面是有可能不在LRU列表中的,因为
shrink_cache回收函数也会遍历LRU列表并摘取页面
最终引用检查
c
if (page_count(page) == 0) {
if (!pagevec_add(&pages_to_free, page)) {
- 最终引用检查 :
page_count(page) == 0再次检查引用计数- 如果该页面被
shrink_cache函数摘取的话,则引用计数不会为0 - 这里放弃释放,交由
shrink_cache函数回收
- 如果该页面被
- 添加到释放缓存 :
pagevec_add尝试添加到释放缓存 - 缓存满处理:如果缓存满了(返回0),需要立即释放
批量释放处理
c
spin_unlock_irq(&zone->lru_lock);
__pagevec_free(&pages_to_free);
pagevec_reinit(&pages_to_free);
zone = NULL; /* No lock is held */
}
}
}
- 释放zone锁:批量释放期间不需要持有锁
- 执行批量释放 :
__pagevec_free释放所有缓存的页面 - 重新初始化缓存 :
pagevec_reinit清空缓存 - 重置zone状态 :
zone = NULL表示当前没有持有锁
最终清理
c
if (zone)
spin_unlock_irq(&zone->lru_lock);
pagevec_free(&pages_to_free);
}
- 释放最后的zone锁:如果还持有锁,释放它
- 释放剩余的页面 :
pagevec_free处理缓存中剩余的页面
辅助函数
c
static inline void pagevec_free(struct pagevec *pvec)
{
if (pagevec_count(pvec))
__pagevec_free(pvec);
}
void __pagevec_free(struct pagevec *pvec)
{
int i = pagevec_count(pvec);
while (--i >= 0)
free_hot_cold_page(pvec->pages[i], pvec->cold);
}
- 条件释放:只有缓存中有页面时才调用实际释放函数
- 遍历释放 :逐个调用
free_hot_cold_page释放页面
关键技术和设计原理
竞态条件防护
c
if (page_count(page) == 0) {
// 在持有锁的情况下重新检查引用计数
// 防止shrink_cache函数在本函数获取zone锁之前已经获取且摘取该页面
}
- 进程B(shrink_cache):在LRU扫描中获取到这个页面
- 进程A:重新检查
page_count(page),发现不为0 - 结果:放弃释放,让shrink_cache处理
锁优化策略
c
if (pagezone != zone) {
if (zone) spin_unlock_irq(&zone->lru_lock); // 释放旧锁
zone = pagezone;
spin_lock_irq(&zone->lru_lock); // 获取新锁
}
- 相同zone的页面批量处理,减少锁操作
- 只有zone变化时才切换锁
- 保持锁直到批量操作完成
批量释放优化
- 页面收集 :使用
pagevec缓存待释放页面 - 批量释放:缓存满时一次性释放所有页面
c
// 低效方式:每次释放都调用free_hot_cold_page
for (每个页面) {
free_hot_cold_page(page, cold); // 多次函数调用开销,频繁获取锁
}
// 高效方式:批量释放
__pagevec_free(&pages_to_free); // 一次处理多个页面
性能优化总结
- 减少锁操作:相同zone页面批量处理
- 批量释放 :通过
pagevec减少函数调用开销 - 快速路径:保留页面和计数未归零页面快速跳过
- 内存局部性:连续处理相同zone的页面
- 竞态防护:在锁内重新检查关键状态
内存回收核心函数shrink_cache
c
static void shrink_cache(struct zone *zone, struct scan_control *sc)
{
LIST_HEAD(page_list);
struct pagevec pvec;
int max_scan = sc->nr_to_scan;
pagevec_init(&pvec, 1);
lru_add_drain();
spin_lock_irq(&zone->lru_lock);
while (max_scan > 0) {
struct page *page;
int nr_taken = 0;
int nr_scan = 0;
int nr_freed;
while (nr_scan++ < SWAP_CLUSTER_MAX &&
!list_empty(&zone->inactive_list)) {
page = lru_to_page(&zone->inactive_list);
prefetchw_prev_lru_page(page,
&zone->inactive_list, flags);
if (!TestClearPageLRU(page))
BUG();
list_del(&page->lru);
if (get_page_testone(page)) {
/*
* It is being freed elsewhere
*/
__put_page(page);
SetPageLRU(page);
list_add(&page->lru, &zone->inactive_list);
continue;
}
list_add(&page->lru, &page_list);
nr_taken++;
}
zone->nr_inactive -= nr_taken;
spin_unlock_irq(&zone->lru_lock);
if (nr_taken == 0)
goto done;
max_scan -= nr_scan;
if (current_is_kswapd())
mod_page_state_zone(zone, pgscan_kswapd, nr_scan);
else
mod_page_state_zone(zone, pgscan_direct, nr_scan);
nr_freed = shrink_list(&page_list, sc);
if (current_is_kswapd())
mod_page_state(kswapd_steal, nr_freed);
mod_page_state_zone(zone, pgsteal, nr_freed);
sc->nr_to_reclaim -= nr_freed;
spin_lock_irq(&zone->lru_lock);
/*
* Put back any unfreeable pages.
*/
while (!list_empty(&page_list)) {
page = lru_to_page(&page_list);
if (TestSetPageLRU(page))
BUG();
list_del(&page->lru);
if (PageActive(page))
add_page_to_active_list(zone, page);
else
add_page_to_inactive_list(zone, page);
if (!pagevec_add(&pvec, page)) {
spin_unlock_irq(&zone->lru_lock);
__pagevec_release(&pvec);
spin_lock_irq(&zone->lru_lock);
}
}
}
spin_unlock_irq(&zone->lru_lock);
done:
pagevec_release(&pvec);
}
代码功能概述
这个函数是Linux内存回收的核心,从zone的非活动LRU列表中批量获取页面进行回收,通过减少锁竞争来提高性能
代码逐段解析
函数声明
c
static void shrink_cache(struct zone *zone, struct scan_control *sc)
zone->lru_lock竞争激烈,通过批量获取页面并在锁外处理来缓解- 未能释放的页面会重新加回LRU
- 将回收的页面数加到
sc->nr_reclaimed
变量初始化
c
LIST_HEAD(page_list);
struct pagevec pvec;
int max_scan = sc->nr_to_scan;
pagevec_init(&pvec, 1);
- 页面列表 :
LIST_HEAD(page_list)初始化临时页面链表 - 页面向量 :
pvec用于批量释放页面 - 最大扫描数 :
max_scan = sc->nr_to_scan设置最大扫描页面数 - 初始化
pagevec:pagevec_init(&pvec, 1)初始化冷页面向量
准备工作
c
lru_add_drain();
spin_lock_irq(&zone->lru_lock);
- LRU排水 :
lru_add_drain()确保所有per-CPU LRU缓存中的页面都进入全局LRU - 获取zone锁 :
spin_lock_irq(&zone->lru_lock)禁用中断并获取LRU锁
主回收循环
c
while (max_scan > 0) {
struct page *page;
int nr_taken = 0;
int nr_scan = 0;
int nr_freed;
- 循环条件 :
max_scan > 0还有页面需要扫描 - 页面变量 :
page当前处理的页面 - 计数变量 :
nr_taken:成功获取的页面数nr_scan:实际扫描的页面数nr_freed:成功释放的页面数
批量获取页面循环
c
while (nr_scan++ < SWAP_CLUSTER_MAX &&
!list_empty(&zone->inactive_list)) {
page = lru_to_page(&zone->inactive_list);
- 循环条件 :
- 扫描数小于
SWAP_CLUSTER_MAX(通常32) - 非活动列表不为空
- 扫描数小于
- 获取页面 :
lru_to_page从非活动列表尾部获取页面
c
prefetchw_prev_lru_page(page,
&zone->inactive_list, flags);
- 预取优化:预取下一个页面的数据,提高缓存命中率
c
if (!TestClearPageLRU(page))
BUG();
list_del(&page->lru);
- 原子性LRU清除 :
TestClearPageLRU测试并清除LRU标志 - 从LRU移除 :
list_del将页面从非活动列表中移除
c
if (get_page_testone(page)) {
/*
* It is being freed elsewhere
*/
__put_page(page);
SetPageLRU(page);
list_add(&page->lru, &zone->inactive_list);
continue;
}
- 引用检查 :
get_page_testone检查页面是否正在被其他地方释放- 将页面计数加1,如果为0返回true,否则返回false
- 竞态处理 :如果引用计数为1(正在被释放):
__put_page减少引用计数- 重新设置LRU标志
- 将页面加回非活动列表
- 跳过此页面继续循环
c
list_add(&page->lru, &page_list);
nr_taken++;
}
- 添加到临时列表 :将页面添加到临时链表
page_list - 增加计数 :
nr_taken++增加成功获取的页面数
更新统计和释放锁
c
zone->nr_inactive -= nr_taken;
spin_unlock_irq(&zone->lru_lock);
- 更新zone统计:减少非活动页面计数
- 释放zone锁:在锁外处理页面,减少锁持有时间
空检查
c
if (nr_taken == 0)
goto done;
- 空检查:如果没有获取到任何页面,跳转到结束
更新扫描计数和统计
c
max_scan -= nr_scan;
if (current_is_kswapd())
mod_page_state_zone(zone, pgscan_kswapd, nr_scan);
else
mod_page_state_zone(zone, pgscan_direct, nr_scan);
- 更新剩余扫描数 :
max_scan -= nr_scan - 更新统计:根据当前进程类型更新页面扫描统计
实际回收操作
c
nr_freed = shrink_list(&page_list, sc);
if (current_is_kswapd())
mod_page_state(kswapd_steal, nr_freed);
mod_page_state_zone(zone, pgsteal, nr_freed);
sc->nr_to_reclaim -= nr_freed;
- 执行回收 :
shrink_list实际尝试回收页面列表 - 更新统计:记录成功回收的页面数
- 更新回收目标:减少需要回收的页面数
重新获取锁并处理未释放页面
c
spin_lock_irq(&zone->lru_lock);
/*
* Put back any unfreeable pages.
*/
while (!list_empty(&page_list)) {
page = lru_to_page(&page_list);
if (TestSetPageLRU(page))
BUG();
list_del(&page->lru);
- 重新获取锁:处理未释放的页面需要锁保护
- 遍历剩余页面:处理未能释放的页面
- 设置LRU标志 :
TestSetPageLRU原子性设置LRU标志 - 从临时列表移除 :
list_del将页面从临时链表移除
c
if (PageActive(page))
add_page_to_active_list(zone, page);
else
add_page_to_inactive_list(zone, page);
- 重新分类添加:根据页面活动状态重新添加到相应的LRU列表
c
if (!pagevec_add(&pvec, page)) {
spin_unlock_irq(&zone->lru_lock);
__pagevec_release(&pvec);
spin_lock_irq(&zone->lru_lock);
}
}
}
- 批量释放:使用pagevec批量释放页面引用
- 锁管理:在批量释放期间释放和重新获取锁
最终清理
c
spin_unlock_irq(&zone->lru_lock);
done:
pagevec_release(&pvec);
}
- 释放最后的锁:确保锁被正确释放
- 释放剩余页面 :
pagevec_release处理pvec中剩余的页面
性能优化总结
- 锁竞争缓解:批量获取,锁外处理
- 批量操作:减少锁操作和函数调用开销
- 缓存友好:预取和局部性优化
- 竞态安全:原子操作和状态重检查
- 统计监控:全面的性能数据收集