Linux交换缓存深度解析:从条目编码到缓存管理的完整机制

前言:交换系统的"物流管理中心"

想象一下Linux的交换系统就像一个高效的物流中心:当物理内存紧张时,它需要将不常用的"货物"(内存页面)暂时存放到"仓库"(交换空间)中,等需要时再快速取回。这个过程中涉及复杂的"库存管理"、"货物追踪"和"空间优化"

Linux通过一套精密的交换管理机制实现了这一切:

  • 交换条目就像仓库的货架编号系统
  • 交换结构相当于实时库存管理系统
  • 引用计数确保货物不会被错误处理
  • 锁机制防止并发操作导致数据混乱

本文将深入解析这套交换系统的核心组件,揭示Linux如何高效管理虚拟内存与物理存储之间的数据流动

核心架构:四级管理层次

第一层:交换条目编码

c 复制代码
// 交换条目编码原理
swp_entry_t entry = swp_entry(type, offset);  // 编码
unsigned type = swp_type(entry);              // 解码类型  
pgoff_t offset = swp_offset(entry);           // 解码偏移

设计精妙之处

  • 架构无关:32位/64位系统使用相同格式
  • 基数树优化:偏移量在低位,提高缓存局部性
  • 边界安全:自动确保值在有效范围内

第二层:交换信息管理

c 复制代码
// 交换设备验证流程
swap_info_get(entry)
    ↓
类型检查 → 设备状态检查 → 偏移量检查 → 引用计数检查 → 锁获取

完整性验证

  • 检查交换文件是否存在且启用
  • 验证偏移量在有效范围内
  • 确认交换槽位确实被使用
  • 更新全局交换设备优先级

第三层:引用计数管理

c 复制代码
// 引用计数语义
0: 槽位空闲
1: 单一使用者
2+: 多个共享者
SWAP_MAP_MAX: 槽位失效

释放时的智能处理

c 复制代码
swap_entry_free(p, offset)
    ↓
递减计数 → 如果归零 → 更新偏移边界位 → 更新统计信息

第四层:缓存管理 - "库存优化系统"

c 复制代码
// 独占页面检查条件
remove_exclusive_swap_page(page) 需要满足:
1. 页面在交换缓存中
2. 不在回写过程中  
3. 引用计数恰好为2
4. 交换映射计数为1

工作流程:从释放到回收

场景1:普通页面释放

复制代码
free_swap_and_cache(entry)
    ↓
swap_info_get(entry)        // 验证交换条目
    ↓
swap_entry_free()           // 释放交换槽位
    ↓
查找对应页面                // 在交换缓存中查找
    ↓
条件检查                    // 独占或空间紧张
    ↓
delete_from_swap_cache()    // 从缓存移除

场景2:独占页面优化释放

复制代码
remove_exclusive_swap_page(page)
    ↓
前置条件检查                // 锁定状态、缓存状态等
    ↓
双重验证                    // 锁保护下的重新检查
    ↓
__delete_from_swap_cache()  // 安全移除
    ↓
资源释放                    // 交换条目和页面

安全机制:并发访问防护

锁的层次结构

c 复制代码
// 锁获取顺序
swap_list_lock()        // 全局交换列表锁
swap_device_lock(p)     // 特定设备锁  
swapper_space.tree_lock // 交换缓存树锁
page_lock              // 页面锁

防止死锁的关键

  • 严格按照层次顺序获取锁
  • 逆序释放锁
  • 锁保护下的重新验证

竞态条件防护

双重检查模式

c 复制代码
// 第一次检查(无锁)
if (page_count(page) == 2) {
    // 第二次检查(有锁保护)
    spin_lock_irq(&lock);
    if (page_count(page) == 2) {
        // 执行操作
    }
    spin_unlock_irq(&lock);
}

智能优化策略

内存压力响应

c 复制代码
// 交换空间紧张时的积极释放
if (vm_swap_full()) {
    // 即使页面还有多个使用者,也尝试释放
    // 缓解交换空间压力
}

惰性释放策略

  • 首选:只有唯一使用者时才释放
  • 备选:交换空间满时强制释放
  • 避免:页面正在回写时不释放

根据交换条目获取对应的交换信息结构swap_info_get

c 复制代码
static struct swap_info_struct * swap_info_get(swp_entry_t entry)
{
	struct swap_info_struct * p;
	unsigned long offset, type;

	if (!entry.val)
		goto out;
	type = swp_type(entry);
	if (type >= nr_swapfiles)
		goto bad_nofile;
	p = & swap_info[type];
	if (!(p->flags & SWP_USED))
		goto bad_device;
	offset = swp_offset(entry);
	if (offset >= p->max)
		goto bad_offset;
	if (!p->swap_map[offset])
		goto bad_free;
	swap_list_lock();
	if (p->prio > swap_info[swap_list.next].prio)
		swap_list.next = type;
	swap_device_lock(p);
	return p;

bad_free:
	printk(KERN_ERR "swap_free: %s%08lx\n", Unused_offset, entry.val);
	goto out;
bad_offset:
	printk(KERN_ERR "swap_free: %s%08lx\n", Bad_offset, entry.val);
	goto out;
bad_device:
	printk(KERN_ERR "swap_free: %s%08lx\n", Unused_file, entry.val);
	goto out;
bad_nofile:
	printk(KERN_ERR "swap_free: %s%08lx\n", Bad_file, entry.val);
out:
	return NULL;
}	

代码功能概述

这个函数根据交换条目(swp_entry_t)获取对应的交换信息结构,并进行全面的有效性验证

代码逐段解析

函数声明和变量定义

c 复制代码
static struct swap_info_struct * swap_info_get(swp_entry_t entry)
{
	struct swap_info_struct * p;
	unsigned long offset, type;
  • 参数entry - 交换条目,包含类型和偏移量信息
  • 局部变量
    • p:指向交换信息结构的指针
    • offset:交换条目中的偏移量
    • type:交换条目中的类型

基本有效性检查

c 复制代码
	if (!entry.val)
		goto out;
  • 检查交换条目是否有效 :如果entry.val为0,表示无效的交换条目
  • 跳转到out标签返回NULL
c 复制代码
	type = swp_type(entry);
	if (type >= nr_swapfiles)
		goto bad_nofile;
  • 提取类型并检查
    • type = swp_type(entry):从交换条目中提取类型字段
    • type >= nr_swapfiles:检查类型是否超出有效范围
    • 如果超出范围,跳转到bad_nofile错误处理
c 复制代码
	p = & swap_info[type];
	if (!(p->flags & SWP_USED))
		goto bad_device;
  • 获取交换信息结构
    • p = &swap_info[type]:从全局交换信息数组中获取对应类型的结构
    • !(p->flags & SWP_USED):检查该交换设备是否正在使用
    • 如果未使用,跳转到bad_device错误处理

偏移量有效性检查

c 复制代码
	offset = swp_offset(entry);
	if (offset >= p->max)
		goto bad_offset;
  • 提取并检查偏移量
    • offset = swp_offset(entry):从交换条目中提取偏移量
    • offset >= p->max:检查偏移量是否超出交换设备的最大容量
    • 如果超出,跳转到bad_offset错误处理
c 复制代码
	if (!p->swap_map[offset])
		goto bad_free;
  • 检查交换槽是否被使用
    • !p->swap_map[offset]:检查对应偏移量的交换映射是否为0
    • 如果为0,表示该交换槽未被使用,跳转到bad_free错误处理

锁操作和返回

c 复制代码
	swap_list_lock();
	if (p->prio > swap_info[swap_list.next].prio)
		swap_list.next = type;
	swap_device_lock(p);
	return p;
  • 获取全局交换列表锁swap_list_lock()保护全局交换列表
  • 更新交换列表 :如果当前交换设备的优先级更高,更新swap_list.next
  • 获取设备锁swap_device_lock(p)锁定特定的交换设备
  • 返回有效指针:返回获取到的交换信息结构指针

错误处理部分

c 复制代码
bad_free:
	printk(KERN_ERR "swap_free: %s%08lx\n", Unused_offset, entry.val);
	goto out;
  • 未使用交换槽错误:打印错误信息,跳转到out
c 复制代码
bad_offset:
	printk(KERN_ERR "swap_free: %s%08lx\n", Bad_offset, entry.val);
	goto out;
  • 偏移量越界错误:打印错误信息,跳转到out
c 复制代码
bad_device:
	printk(KERN_ERR "swap_free: %s%08lx\n", Unused_file, entry.val);
	goto out;
  • 未使用设备错误:打印错误信息,跳转到out
c 复制代码
bad_nofile:
	printk(KERN_ERR "swap_free: %s%08lx\n", Bad_file, entry.val);
out:
	return NULL;
}
  • 错误文件错误:打印错误信息
  • 统一返回:所有错误路径都返回NULL

函数功能总结

核心功能:根据交换条目安全地获取对应的交换信息结构

主要作用

  1. 有效性验证:全面检查交换条目的所有组成部分
  2. 状态检查:验证交换设备和交换槽的使用状态
  3. 锁管理:获取必要的锁来保护并发访问
  4. 优先级更新:维护全局交换设备的选择顺序
  5. 错误诊断:提供详细的错误信息和分类

交换条目编码和解码swp_entry/swp_type/swp_offset

c 复制代码
#define SWP_TYPE_SHIFT(e)	(sizeof(e.val) * 8 - MAX_SWAPFILES_SHIFT)
#define SWP_OFFSET_MASK(e)	((1UL << SWP_TYPE_SHIFT(e)) - 1)

/*
 * Store a type+offset into a swp_entry_t in an arch-independent format
 */
static inline swp_entry_t swp_entry(unsigned long type, pgoff_t offset)
{
	swp_entry_t ret;

	ret.val = (type << SWP_TYPE_SHIFT(ret)) |
			(offset & SWP_OFFSET_MASK(ret));
	return ret;
}

/*
 * Extract the `type' field from a swp_entry_t.  The swp_entry_t is in
 * arch-independent format
 */
static inline unsigned swp_type(swp_entry_t entry)
{
	return (entry.val >> SWP_TYPE_SHIFT(entry));
}

/*
 * Extract the `offset' field from a swp_entry_t.  The swp_entry_t is in
 * arch-independent format
 */
static inline pgoff_t swp_offset(swp_entry_t entry)
{
	return entry.val & SWP_OFFSET_MASK(entry);
}

代码功能概述

这段代码定义了交换条目(swp_entry_t)的架构无关格式,实现了类型和偏移量的编码、解码操作

代码逐段解析

c 复制代码
#define SWP_TYPE_SHIFT(e)	(sizeof(e.val) * 8 - MAX_SWAPFILES_SHIFT)
  • 计算类型移位量
    • sizeof(e.val) * 8:获取e.val的位数(如32位或64位)
    • MAX_SWAPFILES_SHIFT:最大交换文件数的位宽(通常为5,支持32个交换文件)
    • 结果:类型字段应该左移的位数
c 复制代码
#define SWP_OFFSET_MASK(e)	((1UL << SWP_TYPE_SHIFT(e)) - 1)
  • 计算偏移量掩码
    • 1UL << SWP_TYPE_SHIFT(e):创建只有类型位设置的掩码
    • -1:得到所有偏移量位的掩码
    • 示例:32位系统中,SWP_TYPE_SHIFT = 27,掩码为0x07FFFFFF

swp_entry 函数 - 编码

c 复制代码
/*
 * Store a type+offset into a swp_entry_t in an arch-independent format
 */
static inline swp_entry_t swp_entry(unsigned long type, pgoff_t offset)
{
	swp_entry_t ret;
  • 函数功能:将类型和偏移量编码为架构无关的交换条目
  • 参数
    • type:交换文件类型(0-31)
    • offset:页面在交换文件中的偏移量
  • 返回值swp_entry_t结构
c 复制代码
	ret.val = (type << SWP_TYPE_SHIFT(ret)) |
			(offset & SWP_OFFSET_MASK(ret));
  • 编码操作
    • type << SWP_TYPE_SHIFT(ret):将类型移到高位
    • offset & SWP_OFFSET_MASK(ret):确保偏移量在有效范围内
    • |:将两部分合并
c 复制代码
	return ret;
}

swp_type 函数 - 解码类型

c 复制代码
/*
 * Extract the `type' field from a swp_entry_t.  The swp_entry_t is in
 * arch-independent format
 */
static inline unsigned swp_type(swp_entry_t entry)
{
	return (entry.val >> SWP_TYPE_SHIFT(entry));
}
  • 函数功能:从交换条目中提取类型字段
  • 操作 :将entry.val右移SWP_TYPE_SHIFT位,得到类型值

swp_offset 函数 - 解码偏移量

c 复制代码
/*
 * Extract the `offset' field from a swp_entry_t.  The swp_entry_t is in
 * arch-independent format
 */
static inline pgoff_t swp_offset(swp_entry_t entry)
{
	return entry.val & SWP_OFFSET_MASK(entry);
}
  • 函数功能:从交换条目中提取偏移量字段
  • 操作:用掩码提取偏移量部分

详细技术说明

为什么需要架构无关格式?

问题:不同架构可能有不同的字长和字节序

  • 32位系统:4字节
  • 64位系统:8字节
  • 大端序 vs 小端序

解决方案

c 复制代码
// 架构相关(不好)
struct swp_entry_t {
    unsigned short type;
    unsigned long offset;
};

// 架构无关(好)
typedef struct {
    unsigned long val;
} swp_entry_t;

通过位操作在统一的val字段中编码所有信息,确保在不同架构间的一致性

基数树优化原理

基数树(Radix Tree)特性

  • 基于键的位模式进行分层查找
  • 低阶位密集的键有更好的缓存局部性

交换条目的键就是entry.val

c 复制代码
// 在基数树中查找
page = radix_tree_lookup(&swapper_space.page_tree, entry.val);

通过将偏移量放在低位,确保:

  • 相同交换文件的页面在基数树中相邻存储
  • 提高空间局部性,减少缓存失效

函数功能总结

核心功能:提供交换条目的架构无关编码和解码机制

主要作用

  1. 统一编码:将类型和偏移量编码为单一的架构无关值
  2. 精确解码:从编码值中准确提取类型和偏移量
  3. 边界安全:确保编码值在有效范围内
  4. 性能优化:为基数树存储优化位布局

swap_info_put/swap_entry_free/swap_free

c 复制代码
static void swap_info_put(struct swap_info_struct * p)
{
	swap_device_unlock(p);
	swap_list_unlock();
}

static int swap_entry_free(struct swap_info_struct *p, unsigned long offset)
{
	int count = p->swap_map[offset];

	if (count < SWAP_MAP_MAX) {
		count--;
		p->swap_map[offset] = count;
		if (!count) {
			if (offset < p->lowest_bit)
				p->lowest_bit = offset;
			if (offset > p->highest_bit)
				p->highest_bit = offset;
			nr_swap_pages++;
			p->inuse_pages--;
		}
	}
	return count;
}

/*
 * Caller has made sure that the swapdevice corresponding to entry
 * is still around or has not been recycled.
 */
void swap_free(swp_entry_t entry)
{
	struct swap_info_struct * p;

	p = swap_info_get(entry);
	if (p) {
		swap_entry_free(p, swp_offset(entry));
		swap_info_put(p);
	}
}

代码功能概述

这段代码实现了交换条目的释放机制,管理交换空间的使用计数和空闲页面统计

代码逐段解析

swap_info_put 函数

c 复制代码
static void swap_info_put(struct swap_info_struct * p)
{
	swap_device_unlock(p);
	swap_list_unlock();
}
  • 函数功能:释放交换信息结构相关的锁
  • 操作顺序
    • swap_device_unlock(p):先释放设备锁
    • swap_list_unlock():后释放列表锁
  • 锁顺序原则:按获取的逆序释放,防止死锁

swap_entry_free 函数

c 复制代码
static int swap_entry_free(struct swap_info_struct *p, unsigned long offset)
{
	int count = p->swap_map[offset];
  • 函数功能:释放指定的交换槽位
  • 参数
    • p:交换信息结构
    • offset:交换槽位偏移量
  • 获取当前计数p->swap_map[offset]获取该槽位的引用计数
c 复制代码
	if (count < SWAP_MAP_MAX) {
		count--;
		p->swap_map[offset] = count;
  • 检查是否可递减count < SWAP_MAP_MAX确保不是特殊值
  • 递减引用计数count--减少使用计数
  • 更新映射表p->swap_map[offset] = count写回新的计数值
c 复制代码
		if (!count) {
			if (offset < p->lowest_bit)
				p->lowest_bit = offset;
			if (offset > p->highest_bit)
				p->highest_bit = offset;
  • 检查是否完全释放!count表示计数降为0(槽位空闲)
  • 更新最低位 :如果新释放的槽位比当前最低位还低,更新lowest_bit
  • 更新最高位 :如果新释放的槽位比当前最高位还高,更新highest_bit
c 复制代码
			nr_swap_pages++;
			p->inuse_pages--;
		}
	}
	return count;
}
  • 更新全局统计nr_swap_pages++增加全局空闲交换页面数
  • 更新设备统计p->inuse_pages--减少该设备的在用页面数
  • 返回新计数:返回释放后的引用计数值

swap_free 函数

c 复制代码
void swap_free(swp_entry_t entry)
{
	struct swap_info_struct * p;
  • 调用者已确保交换设备仍然存在且未被回收
  • 函数功能:释放交换条目
  • 参数entry - 要释放的交换条目
c 复制代码
	p = swap_info_get(entry);
	if (p) {
		swap_entry_free(p, swp_offset(entry));
		swap_info_put(p);
	}
}
  • 获取交换信息swap_info_get(entry)验证并获取对应的交换信息结构
  • 条件执行 :如果成功获取交换信息(p != NULL
    • 调用swap_entry_free(p, swp_offset(entry))释放槽位
    • 调用swap_info_put(p)释放锁
  • 空指针安全 :如果swap_info_get返回NULL,静默跳过(交换条目可能已无效)

关键数据结构说明

交换映射计数语义

c 复制代码
// p->swap_map[offset] 的含义:
0: 槽位空闲
1: 只有一个使用者
2+: 多个使用者共享
SWAP_MAP_MAX: 特殊值,表示槽位失效

交换信息结构关键字段

c 复制代码
struct swap_info_struct {
    unsigned char *swap_map;      // 交换映射数组
    unsigned long lowest_bit;     // 最低空闲位(搜索起点)
    unsigned long highest_bit;    // 最高空闲位(搜索终点)
    unsigned int inuse_pages;     // 在用页面数
    // ...
};

全局变量

c 复制代码
unsigned int nr_swap_pages;       // 全局空闲交换页面总数

函数功能总结

核心功能:安全地释放交换条目,管理交换空间的使用计数和空闲资源

主要作用

  1. 引用计数管理:递减交换槽位的使用计数
  2. 资源回收:当计数降为0时,标记槽位为空闲
  3. 统计维护:更新全局和设备级别的统计信息
  4. 优化信息:维护边界位以加速后续分配

交换缓存独占页面检查与释放remove_exclusive_swap_page

c 复制代码
int remove_exclusive_swap_page(struct page *page)
{
	int retval;
	struct swap_info_struct * p;
	swp_entry_t entry;

	BUG_ON(PagePrivate(page));
	BUG_ON(!PageLocked(page));

	if (!PageSwapCache(page))
		return 0;
	if (PageWriteback(page))
		return 0;
	if (page_count(page) != 2) /* 2: us + cache */
		return 0;

	entry.val = page->private;
	p = swap_info_get(entry);
	if (!p)
		return 0;

	/* Is the only swap cache user the cache itself? */
	retval = 0;
	if (p->swap_map[swp_offset(entry)] == 1) {
		/* Recheck the page count with the swapcache lock held.. */
		spin_lock_irq(&swapper_space.tree_lock);
		if ((page_count(page) == 2) && !PageWriteback(page)) {
			__delete_from_swap_cache(page);
			SetPageDirty(page);
			retval = 1;
		}
		spin_unlock_irq(&swapper_space.tree_lock);
	}
	swap_info_put(p);

	if (retval) {
		swap_free(entry);
		page_cache_release(page);
	}

	return retval;
}

代码功能概述

这个函数检查一个交换缓存页面是否只有当前引用,如果是则从交换缓存中移除并释放它

代码逐段解析

函数声明和变量定义

c 复制代码
int remove_exclusive_swap_page(struct page *page)
{
	int retval;
	struct swap_info_struct * p;
	swp_entry_t entry;
  • 参数page - 要检查的页面指针
  • 局部变量
    • retval:操作结果(1=成功移除,0=失败)
    • p:交换信息结构指针
    • entry:交换条目,标识页面在交换空间中的位置

前置条件检查

c 复制代码
	BUG_ON(PagePrivate(page));
	BUG_ON(!PageLocked(page));
  • 第一个BUG_ONPagePrivate(page)检查页面是否被设置为PG_private
    • page->private字段在交换缓存中用于存储交换条目,在文件页面中用于存储文件系统私有数据
    • 也就是说PG_privatePG_swapcache这两个标志是冲突的
  • 第二个BUG_ON!PageLocked(page)检查页面是否被被设置为PG_locked
    • 如果页面未锁定,触发内核BUG,因为下面的操作需要页面锁保证原子性
c 复制代码
	if (!PageSwapCache(page))
		return 0;
  • 检查是否在交换缓存中:如果页面不在交换缓存中,直接返回0(失败)
c 复制代码
	if (PageWriteback(page))
		return 0;
  • 检查是否正在回写:如果页面正在写入交换空间,不能移除,返回0
c 复制代码
	if (page_count(page) != 2) /* 2: us + cache */
		return 0;
  • 检查引用计数 :期望的引用计数是2
    • 1个引用:来自交换缓存本身
    • 1个引用:来自当前函数调用者
    • 如果计数不等于2,说明有其他使用者,返回0

获取交换信息

c 复制代码
	entry.val = page->private;
	p = swap_info_get(entry);
	if (!p)
		return 0;
  • 获取交换条目page->private存储了交换条目的值
  • 获取交换信息swap_info_get(entry)根据交换条目获取对应的交换信息结构
  • 错误检查:如果获取失败(交换条目无效),返回0

核心检查逻辑

c 复制代码
	/* Is the only swap cache user the cache itself? */
	retval = 0;
	if (p->swap_map[swp_offset(entry)] == 1) {
  • 初始化返回值retval = 0(假设失败)
  • 检查交换映射计数p->swap_map[swp_offset(entry)] == 1
    • swp_offset(entry):从交换条目中提取偏移量
    • p->swap_map[]:交换空间使用计数数组
    • == 1:表示只有当前页面在使用这个交换槽
c 复制代码
		/* Recheck the page count with the swapcache lock held.. */
		spin_lock_irq(&swapper_space.tree_lock);
  • 获取交换缓存锁:在持有锁的情况下重新检查条件
  • spin_lock_irq:禁用中断并获取自旋锁,防止并发访问
c 复制代码
		if ((page_count(page) == 2) && !PageWriteback(page)) {
  • 在锁保护下重新检查
    • 引用计数仍然为2
    • 页面不在回写状态
c 复制代码
			__delete_from_swap_cache(page);
			SetPageDirty(page);
			retval = 1;
  • 从交换缓存删除__delete_from_swap_cache(page)将页面从交换缓存中移除
  • 标记页面为脏SetPageDirty(page)因为内存中的页面内容可能已修改,需要确保正确写回
  • 设置成功标志retval = 1表示成功移除
c 复制代码
		}
		spin_unlock_irq(&swapper_space.tree_lock);
	}
  • 释放交换缓存锁spin_unlock_irq恢复中断状态并释放锁

资源清理

c 复制代码
	swap_info_put(p);
  • 释放swap_list_lockswap_device_lock
c 复制代码
	if (retval) {
		swap_free(entry);
		page_cache_release(page);
	}
  • 成功时的清理 :如果retval == 1(成功移除)
    • swap_free(entry):释放交换条目,减少交换空间使用计数
    • page_cache_release(page):释放页面引用,可能真正释放页面
c 复制代码
	return retval;
}
  • 返回操作结果:1表示成功移除,0表示失败

函数功能总结

核心功能:检查并释放只有当前引用的交换缓存页面

主要作用

  1. 条件验证:验证页面是否满足独占释放的条件
  2. 竞态防护:通过锁和双重检查防止并发问题
  3. 资源回收:安全地从交换缓存移除页面并释放资源
  4. 状态维护:正确维护页面和交换空间的状态

从交换缓存删除页面__delete_from_swap_cache

c 复制代码
/*
 * This must be called only on pages that have
 * been verified to be in the swap cache.
 */
void __delete_from_swap_cache(struct page *page)
{
	BUG_ON(!PageLocked(page));
	BUG_ON(!PageSwapCache(page));
	BUG_ON(PageWriteback(page));

	radix_tree_delete(&swapper_space.page_tree, page->private);
	page->private = 0;
	ClearPageSwapCache(page);
	total_swapcache_pages--;
	pagecache_acct(-1);
	INC_CACHE_INFO(del_total);
}

代码功能概述

这个函数负责从交换缓存中安全地移除页面,是页面换入和交换缓存清理的关键操作

代码逐段解析

函数声明和注释

c 复制代码
/*
 * This must be called only on pages that have
 * been verified to be in the swap cache.
 */
void __delete_from_swap_cache(struct page *page)
  • 这个函数只能对已经确认在交换缓存中的页面调用
  • 函数名__delete_from_swap_cache中的__通常表示内部函数,调用者需要确保前置条件
  • 参数page - 要从交换缓存中移除的页面

前置条件检查(BUG_ON)

c 复制代码
	BUG_ON(!PageLocked(page));
  • 检查页面锁:确保页面已被锁定
c 复制代码
	BUG_ON(!PageSwapCache(page));
  • 检查交换缓存标志:确保页面确实在交换缓存中
c 复制代码
	BUG_ON(PageWriteback(page));
  • 检查写回状态:确保页面不在写回过程中

从基数树删除

c 复制代码
	radix_tree_delete(&swapper_space.page_tree, page->private);
  • 核心删除操作:从交换空间的基数树中移除页面
  • 参数
    • &swapper_space.page_tree:全局交换空间的基数树
    • page->private:页面的交换条目,作为基数树的键
  • 作用:断开页面与交换缓存的数据结构链接

页面元数据清理

c 复制代码
	page->private = 0;
  • 清空私有字段 :将page->private设为0
  • 重要性:这个字段之前存储的是交换条目,现在页面不再属于交换缓存
  • 为重用做准备:清除后,这个字段可以用于其他用途
c 复制代码
	ClearPageSwapCache(page);
  • 清除交换缓存标志 :将页面的PG_swapcache标志位清零
  • 状态更新:页面正式不再属于交换缓存
  • 语义变化:页面现在可以视为普通的内存页面

统计信息更新

c 复制代码
	total_swapcache_pages--;
  • 减少全局计数:递减全局交换缓存页面计数器
  • 系统级统计:跟踪系统中交换缓存页面的总数
c 复制代码
	pagecache_acct(-1);
  • 页面缓存统计:更新页面缓存相关的内存记账信息
  • 资源管理:帮助内核跟踪内存使用情况
c 复制代码
	INC_CACHE_INFO(del_total);
  • 增加缓存操作统计:递增删除操作计数器
  • 性能监控:用于调试和性能分析

关键数据结构说明

1. 交换空间结构(swapper_space)

c 复制代码
struct address_space swapper_space = {
    .page_tree = RADIX_TREE_INIT(GFP_ATOMIC|__GFP_NOWARN),
    .i_mmap = RB_ROOT,
    .a_ops = &swap_aops,
    // ...
};

这是全局的交换缓存管理结构,所有交换缓存页面都存储在其基数树中。

2. 页面标志位

c 复制代码
#define PG_swapcache     12    /* Page is in swap cache */

PageSwapCache宏检查的就是这个标志位。

3. 页面private字段的语义变化

在交换缓存中时

c 复制代码
page->private = swp_entry_t.val  // 存储交换条目

从交换缓存删除后

c 复制代码
page->private = 0  // 可被重新用于其他目的

函数功能总结

核心功能:从交换缓存中安全地移除页面,恢复为普通内存页面

主要作用

  1. 数据结构清理:从交换缓存基数树中移除页面引用
  2. 状态重置:清除页面的交换缓存相关标志和字段
  3. 资源更新:准确维护系统统计信息和记账数据

交换条目和页面缓存释放free_swap_and_cache

c 复制代码
/*
 * Free the swap entry like above, but also try to
 * free the page cache entry if it is the last user.
 */
void free_swap_and_cache(swp_entry_t entry)
{
	struct swap_info_struct * p;
	struct page *page = NULL;

	p = swap_info_get(entry);
	if (p) {
		if (swap_entry_free(p, swp_offset(entry)) == 1) {
			spin_lock_irq(&swapper_space.tree_lock);
			page = radix_tree_lookup(&swapper_space.page_tree,
				entry.val);
			if (page && TestSetPageLocked(page))
				page = NULL;
			spin_unlock_irq(&swapper_space.tree_lock);
		}
		swap_info_put(p);
	}
	if (page) {
		int one_user;

		BUG_ON(PagePrivate(page));
		page_cache_get(page);
		one_user = (page_count(page) == 2);
		/* Only cache user (+us), or swap space full? Free it! */
		if (!PageWriteback(page) && (one_user || vm_swap_full())) {
			delete_from_swap_cache(page);
			SetPageDirty(page);
		}
		unlock_page(page);
		page_cache_release(page);
	}
}

函数功能

释放交换条目(swap entry),并且如果这是最后一个使用者,也尝试释放对应的页面缓存条目

代码分段详解

函数声明和变量定义

c 复制代码
void free_swap_and_cache(swp_entry_t entry)
{
    struct swap_info_struct * p;
    struct page *page = NULL;
  • entry: 要释放的交换条目,包含交换分区信息和偏移量
  • p: 指向交换信息结构的指针
  • page: 指向对应页面的指针,初始化为NULL

获取交换信息结构

c 复制代码
    p = swap_info_get(entry);
  • swap_info_get(entry): 根据交换条目获取对应的交换信息结构
  • 这会获取交换信息结构的锁

交换条目释放检查

c 复制代码
    if (p) {
        if (swap_entry_free(p, swp_offset(entry)) == 1) {
  • 如果成功获取交换信息结构
  • swap_entry_free(p, swp_offset(entry)): 释放交换条目,返回剩余引用计数
  • 返回值1的含义: 表示这是最后一个引用,交换条目现在可以完全释放了

查找对应的页面缓存

c 复制代码
            spin_lock_irq(&swapper_space.tree_lock);
            page = radix_tree_lookup(&swapper_space.page_tree, entry.val);
            if (page && TestSetPageLocked(page))
                page = NULL;
            spin_unlock_irq(&swapper_space.tree_lock);
  • spin_lock_irq(&swapper_space.tree_lock): 获取交换空间的树锁,禁用中断
  • radix_tree_lookup(&swapper_space.page_tree, entry.val): 在基数树中查找交换条目对应的页面
  • TestSetPageLocked(page): 尝试锁定页面,如果页面已被锁定则返回true
  • 如果页面被锁定,将page设为NULL(避免等待)
  • spin_unlock_irq(&swapper_space.tree_lock): 释放锁,恢复中断

释放交换信息结构引用

c 复制代码
        swap_info_put(p);
    }
  • swap_info_put(p): 释放交换信息结构的锁
  • 与之前的swap_info_get配对使用

页面处理准备

c 复制代码
    if (page) {
        int one_user;

        BUG_ON(PagePrivate(page));
        page_cache_get(page);
        one_user = (page_count(page) == 2);
  • 如果找到了对应的页面
  • BUG_ON(PagePrivate(page)): 确保页面没有私有标志(交换缓存页面不应该有)
    • 如果有标志PagePrivate,那么page->private存储的将不是交换条目,而是文件系统私有数据
  • page_cache_get(page): 增加页面缓存引用计数
  • page_count(page) == 2: 检查是否只有一个用户
    • 引用计数为2表示:页面缓存引用 + 当前函数临时引用

页面释放条件判断

c 复制代码
        /* Only cache user (+us), or swap space full? Free it! */
        if (!PageWriteback(page) && (one_user || vm_swap_full())) {
  • !PageWriteback(page): 页面没有在回写过程中
  • one_user: 只有一个用户(页面缓存)
  • vm_swap_full(): 交换空间已满
  • 条件: 页面不在回写中,并且(只有一个用户或交换空间满)

从交换缓存中删除页面

c 复制代码
            delete_from_swap_cache(page);
            SetPageDirty(page);

功能

  • delete_from_swap_cache(page): 从交换缓存中删除页面
  • SetPageDirty(page): 将页面标记为脏页,确保后续正确处理

清理工作

c 复制代码
        unlock_page(page);
        page_cache_release(page);
    }
}
  • unlock_page(page): 解锁页面(对应之前的TestSetPageLocked
  • page_cache_release(page): 释放页面缓存引用(对应之前的page_cache_get)

关键设计要点

  1. 引用计数管理: 精细控制交换条目和页面的引用计数
  2. 锁顺序: 正确获取和释放各种锁
  3. 条件释放: 只在特定条件下才从交换缓存中删除页面
  4. 内存回收优化: 在交换空间满时更积极地释放页面
相关推荐
---学无止境---2 小时前
Linux内存回收与TLB管理:高效释放与缓存刷新的精密协作
linux
硬核子牙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&3 小时前
如何在 Ubuntu 24.04 上安装和使用 AdGuard
linux·运维·服务器·ubuntu·adguard
遇见火星3 小时前
Linux 网络配置实战:RHEL/CentOS 7+ 永久静态路由配置与优先级调整全攻略
linux·网络·centos·静态路由·centos 7
安审若无4 小时前
linux怎么检查磁盘是否有坏道
linux·运维·服务器
HalvmånEver4 小时前
Linux的第二章 : 基础的指令(二)
linux·运维·服务器·开发语言·学习