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. 统计监控:全面的性能数据收集
相关推荐
Doro再努力1 小时前
Vim 快速上手实操手册:从入门到生产环境实战
linux·编辑器·vim
wypywyp1 小时前
8. ubuntu 虚拟机 linux 服务器 TCP/IP 概念辨析
linux·服务器·ubuntu
Doro再努力2 小时前
【Linux操作系统10】Makefile深度解析:从依赖推导到有效编译
android·linux·运维·服务器·编辑器·vim
senijusene2 小时前
Linux软件编程:IO编程,标准IO(1)
linux·运维·服务器
忧郁的橙子.2 小时前
02-本地部署Ollama、Python
linux·运维·服务器
醇氧2 小时前
【linux】查看发行版信息
linux·运维·服务器
No8g攻城狮3 小时前
【Linux】Windows11 安装 WSL2 并运行 Ubuntu 22.04 详细操作步骤
linux·运维·ubuntu
XiaoFan0123 小时前
免密批量抓取日志并集中输出
java·linux·服务器
souyuanzhanvip3 小时前
ServerBox v1.0.1316 跨平台 Linux 服务器管理工具
linux·运维·服务器
HalvmånEver4 小时前
Linux:线程互斥
java·linux·运维