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. 内存回收优化: 在交换空间满时更积极地释放页面
相关推荐
知星小度S10 分钟前
系统核心解析:深入文件系统底层机制——Ext系列探秘:从磁盘结构到挂载链接的全链路解析
linux
2401_8904430215 分钟前
Linux 基础IO
linux·c语言
智慧地球(AI·Earth)1 小时前
在Linux上使用Claude Code 并使用本地VS Code SSH远程访问的完整指南
linux·ssh·ai编程
老王熬夜敲代码2 小时前
解决IP不够用的问题
linux·网络·笔记
zly35003 小时前
linux查看正在运行的nginx的当前工作目录(webroot)
linux·运维·nginx
QT 小鲜肉3 小时前
【Linux命令大全】001.文件管理之file命令(实操篇)
linux·运维·前端·网络·chrome·笔记
问道飞鱼3 小时前
【Linux知识】Linux 虚拟机磁盘扩缩容操作指南(按文件系统分类)
linux·运维·服务器·磁盘扩缩容
egoist20234 小时前
【Linux仓库】超越命令行用户:手写C语言Shell解释器,解密Bash背后的进程创建(附源码)
linux·c语言·bash·xshell·环境变量·命令行参数·内建命令
Lenyiin4 小时前
《 Linux 修炼全景指南: 八 》别再碎片化学习!掌控 Linux 开发工具链:gcc、g++、GDB、Bash、Python 与工程化实践
linux·python·bash·gdb·gcc·g++·lenyiin
莲华君4 小时前
Bash Shell:从入门到精通
linux