Linux内存回收与TLB管理:高效释放与缓存刷新的精密协作

前言

想象一下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)的作用

  • 缓存虚拟地址到物理地址的映射
  • 加速地址转换过程

需要刷新的场景

  1. 页表修改:当页表项被修改或删除时
  2. 页面释放:当物理页面被释放时
  3. 进程切换:当切换到不同进程的地址空间时

current->active_mm 检查的重要性

为什么需要检查

c 复制代码
if (mm == current->active_mm)
    __flush_tlb();

原因

  • 只刷新当前正在使用的内存映射
  • 避免刷新其他进程的TLB项(没有必要)

总结

函数功能总结

  1. TLB刷新管理:根据需要刷新转址旁路缓存
  2. 竞态防护:确保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->freedtlb结构中获取已释放的页面数量
  • 获取内存管理结构mm = tlb->mm 获取关联的内存管理结构
  • 获取当前RSSrss = 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的含义

  • 进程当前在物理内存中的页面数量
  • 反映进程的实际内存占用
  • 用于内存统计和限制

总结

函数功能总结

  1. 统计更新:准确更新进程的RSS内存统计
  2. TLB刷新:执行最终的TLB刷新和页面释放
  3. 资源清理:完成所有内存管理操作的收尾工作

批量释放页面和交换缓存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条件检查,直到所有页面处理完成

函数功能总结

核心功能:高效批量释放页面数组,正确处理交换缓存和页面引用

主要作用

  1. 交换缓存清理:从交换缓存中移除相关页面
  2. 页面引用释放:减少页面引用计数,必要时释放页面
  3. LRU管理:确保页面从LRU列表中正确移除
  4. 批量优化:通过分块处理提高性能

批量释放页面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的锁
  • 初始化pagevecpagevec_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);
		}
  • 获取页面zonepagezone = 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锁之前已经获取且摘取该页面
}
  1. 进程B(shrink_cache):在LRU扫描中获取到这个页面
  2. 进程A:重新检查page_count(page),发现不为0
  3. 结果:放弃释放,让shrink_cache处理

锁优化策略

c 复制代码
if (pagezone != zone) {
    if (zone) spin_unlock_irq(&zone->lru_lock);  // 释放旧锁
    zone = pagezone;
    spin_lock_irq(&zone->lru_lock);              // 获取新锁
}
  • 相同zone的页面批量处理,减少锁操作
  • 只有zone变化时才切换锁
  • 保持锁直到批量操作完成

批量释放优化

  1. 页面收集 :使用pagevec缓存待释放页面
  2. 批量释放:缓存满时一次性释放所有页面
c 复制代码
// 低效方式:每次释放都调用free_hot_cold_page
for (每个页面) {
    free_hot_cold_page(page, cold);  // 多次函数调用开销,频繁获取锁
}

// 高效方式:批量释放
__pagevec_free(&pages_to_free);  // 一次处理多个页面

性能优化总结

  1. 减少锁操作:相同zone页面批量处理
  2. 批量释放 :通过pagevec减少函数调用开销
  3. 快速路径:保留页面和计数未归零页面快速跳过
  4. 内存局部性:连续处理相同zone的页面
  5. 竞态防护:在锁内重新检查关键状态

内存回收核心函数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设置最大扫描页面数
  • 初始化pagevecpagevec_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中剩余的页面

性能优化总结

  1. 锁竞争缓解:批量获取,锁外处理
  2. 批量操作:减少锁操作和函数调用开销
  3. 缓存友好:预取和局部性优化
  4. 竞态安全:原子操作和状态重检查
  5. 统计监控:全面的性能数据收集
相关推荐
硬核子牙2 小时前
硬盘第一关:MBR VS GPT
linux
LCG元2 小时前
Linux 日志分析全攻略:快速从海量日志中定位问题
linux
_Power_Y2 小时前
Linux&git入门&设计模式(常考点)
linux·git·设计模式
海蓝可知天湛2 小时前
Ubuntu24.10禁用该源...+vmware无法复制黏贴“天坑闭环”——从 DNS 诡异解析到 Ubuntu EOL 引发的 apt 404排除折腾记
linux·服务器·安全·ubuntu·aigc·bug
vvw&2 小时前
如何在 Ubuntu 24.04 上安装和使用 AdGuard
linux·运维·服务器·ubuntu·adguard
遇见火星3 小时前
Linux 网络配置实战:RHEL/CentOS 7+ 永久静态路由配置与优先级调整全攻略
linux·网络·centos·静态路由·centos 7
安审若无4 小时前
linux怎么检查磁盘是否有坏道
linux·运维·服务器
HalvmånEver4 小时前
Linux的第二章 : 基础的指令(二)
linux·运维·服务器·开发语言·学习
大梦南柯4 小时前
linux创建网站
linux·运维·服务器