Linux Swap 子系统现代化重构:当 18 个月的内核攻坚重塑内存管理底层架构
从 XArray 到 swap table,从 swap map 到统一元数据管理——腾讯工程师如何用工程化思维解决 Linux 内核"技术债"
引言:一场跨越 18 个月的内核级"手术"
在 Linux 内核的浩瀚代码库中,Swap 子系统是一个既古老又关键的存在。自 Linux 诞生以来,这个负责虚拟内存换入换出的子系统已经运行了三十余年。然而,随着时间推移,累积的复杂性让它逐渐成为内存管理子系统中公认的"技术债高地"——多套数据结构并存、锁竞争激烈、元数据开销巨大。
2025 年,来自腾讯服务器操作系统 TencentOS 团队的内核研发工程师 Kairui Song,在 Linux 存储、文件系统、内存管理与 BPF 峰会(LSF/MM/BPF)上提出了一个系统性的重构方案。这项工作引发了 Linux 内核社区的广泛关注——LWN.net 知名作者、Linux 内核文档核心维护者 Jonathan Corbet 专门撰写了连续三篇深度分析文章,逐篇解析这项工作的技术细节与深远影响。
这在 LWN 的报道传统中,是对一项内核贡献极高规格的认可。
本文将深入剖析这项持续 18 个月的 Swap 子系统现代化工程——从架构设计到代码实现,从性能优化到生态影响,带你理解这场内核级的"外科手术"究竟如何完成。
第一部分:理解 Swap 子系统——从原理到现状
1.1 虚拟内存与匿名页换出
在现代操作系统的虚拟内存系统中,物理内存是有限且珍贵的资源。当系统内存不足时,必须通过页面回收机制来应对压力。对于文件页面(page cache)来说,文件本身就是天然的后备存储——只需将脏页写回文件即可释放内存。但对于匿名页(anonymous pages)——用于存放进程堆、栈及各类数据结构的数据区——天然不存在这样的后备存储。
这正是 Swap 子系统存在的意义:当匿名页所占用的物理内存需要被回收时,Swap 子系统为其提供写出目标。换出(swapping out)将不活跃或访问频率低的页面推送到持久化存储介质(如 SSD 或 HDD),从而释放宝贵的 RAM 空间给当前活跃的工作负载。
// 匿名页与文件页的区别
// 文件页:有天然后备存储(文件本身)
// 匿名页:无后备存储,需要 Swap 提供
struct page {
// 文件页:mapping 指向 address_space
struct address_space *mapping;
// 匿名页:通过 swap entry 标识换出位置
// 当页面被换出时,PTE 中存储的是 swp_entry_t
};
1.2 Swap Entry:核心数据结构
内核中使用 swp_entry_t 类型来唯一标识一个 swap 槽位:
// include/linux/mm_types.h
typedef struct {
unsigned long val;
} swp_entry_t;
这个 unsigned long 被巧妙地分为两个字段:
- 高 6 位:swap 文件的索引号(type),用于标识是哪个 swap 设备
- 其余位:文件内的槽位编号(offset),即在该 swap 文件内部的偏移量
// 架构无关的通用格式(简化示意)
// 实际实现因架构而异,需要考虑与 PTE 格式的兼容性
//
// +--------+------------------+
// | type | offset |
// | 6 bits | remaining bits |
// +--------+------------------+
// 内核提供的操作函数
static inline swp_entry_t make_swap_entry(unsigned type, pgoff_t offset);
static inline unsigned swp_type(swp_entry_t entry);
static inline pgoff_t swp_offset(swp_entry_t entry);
当页面被换出后,对应的 swp_entry_t(以架构相关格式)会被存入页表项(PTE)。由于格式不包含 PTE 定义的 "present" 位,下次访问该页时将触发缺页异常(page fault)。内核识别到 swap entry 后,会分配新的物理页,从 swap 文件读取内容,并相应更新 PTE——这就是**换入(swapping in)**过程。
1.3 换出流程深度解析
实际的换出流程远比上述描述复杂。以下是关键步骤:
// 简化的换出流程(实际代码分布在 mm/vmscan.c, mm/swap_state.c 等多个文件)
// 1. 内存回收决策
// 当内存压力增大时,kswapd 或直接回收路径决定回收匿名页
// 2. 分配 swap 槽位
swp_entry_t entry = get_swap_page(page);
// 3. 加入 swap cache(关键步骤!)
// 页面首先被加入 swap cache,建立与 swap entry 的绑定关系
if (add_to_swap(page)) {
// 4. 启动回写
// 将页面内容异步写入 swap 设备
__swap_writepage(page, wbc);
// 5. 等待回写完成
// 在 IO 完成前,页面仍驻留于 swap cache
// 期间若有缺页异常,可迅速重新激活
// 6. 真正释放页面
// 回写完成后,从 swap cache 移除,释放物理页
}
swap cache 的核心作用:
- 同步与协调:在异步 IO 过程中维护页面状态
- 竞态处理:若写入过程中发生缺页异常,页面可被迅速重新激活
- 预读优化:支持批量预读相邻页面
1.4 Linux 6.17 的 Swap 子系统架构
在重构之前(Linux 6.17),Swap 子系统采用了两套独立的管理机制:
1.4.1 address_space 与 XArray
Swap 子系统复用了文件系统的 address_space 结构来维护 swap cache:
// mm/swap_state.c
// 每个 swap 文件被划分为 64MB 的块,每块一个 address_space
struct address_space *swapper_spaces[MAX_SWAPFILES][SWAP_ADDRESS_SPACE_PAGES];
// 每个 address_space 使用 XArray 作为核心数据结构
// XArray 存储每个槽位的状态:
// - NULL:槽位为空
// - folio 指针:页面驻留于 RAM
// - shadow entry:页面已换出,存储追踪信息
XArray 的设计优势:
- 支持高效的范围查询(如预读)
- 与 page cache 代码共用,减少重复实现
- 成熟的锁机制和并发控制
XArray 的性能问题:
- 每个 swap entry 状态查询都需要 XArray 查找
- 64MB 粒度的锁在高并发场景下竞争激烈
- 与 swap cluster 的管理逻辑存在冗余
1.4.2 Swap Cluster
Swap 子系统引入了另一层管理机制——swap cluster:
// include/linux/swap.h
struct swap_cluster_info {
spinlock_t lock; // 保护该 cluster 的自旋锁
unsigned int data:24; // cluster 内的空闲槽位数
unsigned int flags:8; // 状态标志
};
每个 cluster 通常为 2MB(512 个 4KB 页面),系统为每个 CPU 维护一个本地 cluster,实现无锁分配:
// 每个 CPU 的本地 cluster 缓存
struct percpu_cluster {
struct swap_cluster_info *cluster; // 当前使用的 cluster
unsigned int next; // 下一个可用槽位
};
两套机制的冗余问题:
- address_space 按 64MB 边界划分
- swap cluster 按 2MB 边界划分
- 同一页面可能涉及两套不同的锁和同步机制
- 代码复杂度高,维护困难
1.4.3 Swap Map:引用计数的噩梦
Swap 子系统还维护了一个名为 swap_map 的数组:
// include/linux/swap.h
struct swap_info_struct {
// ...
unsigned char *swap_map; /* vmalloc'ed array of usage counts */
// ...
};
每个槽位一个字节,存储指向该槽位的引用数量:
// swap_map 条目的特殊值
#define SWAP_MAP_MAX 0x3e // 引用计数达到上限(62)
#define SWAP_MAP_BAD 0x3f // 槽位不可用
#define COUNT_CONTINUED 0x80 // 引用计数溢出,需要继续页
#define SWAP_HAS_CACHE 0x40 // 页面仍在 swap cache 中
swap_map 的设计缺陷:
- 引用计数溢出:最大值仅 62,超过后需要分配额外的"继续页"
- 内存开销大:每个槽位一个字节,1TB swap 文件需要 256MB 内存
- SWAP_HAS_CACHE 位锁:用作同步信号,导致大量延迟重试循环
// 引用计数溢出处理(简化示意)
// 当 swap_map[slot]++ 后超过 SWAP_MAP_MAX
if (swap_map[slot] == SWAP_MAP_MAX) {
// 设置 COUNT_CONTINUED 标志
swap_map[slot] |= COUNT_CONTINUED;
swap_map[slot] &= ~0x3f; // 清零低 6 位
// 分配新的物理页存储高位计数
struct page *cont_page = alloc_page();
// 通过 LRU 链表链接到原 swap_map 页
list_add(&cont_page->lru, &si->swap_continuation_pages);
}
这种设计在高共享内存场景下尤为头疼——当大量进程共享同一匿名页时,引用计数极易溢出,需要多层间接查找。
第二部分:Swap Table——架构重构的第一刀
2.1 核心思路:从 XArray 到 C 数组
Kairui Song 的重构方案第一步——引入 swap table——的核心洞察是:
既然 Swap 子系统处理 swap entry 时就已经能得知其所属的 swap cluster,何不将所有状态信息随 cluster 一并存储,彻底消除 XArray?
// mm/swap.h (Linux 6.19+)
struct swap_cluster_info {
spinlock_t lock;
unsigned int data:24;
unsigned int flags:8;
// 新增:swap table 指针
atomic_long_t __rcu *table; // 动态分配的 C 数组
};
新的 table 数组被设计为:
- 在多数体系结构上恰好占用一个物理页
- 采用动态分配:只有 cluster 被真正使用时才分配
- 每个 entry 直接描述 swap 文件中某个槽位的状态
2.2 Swap Table Entry 格式设计
每个 swap table entry 是一个 unsigned long(64 位或 32 位,取决于架构):
// Swap table entry 的状态编码
//
// NULL (0):槽位为空,未分配
// 非 0 值:根据低位标志区分类型
//
// ┌─────────────────────────────────────────────────────────┐
// │ Entry Type │ Low Bits │ Content │
// ├─────────────────────────────────────────────────────────┤
// │ Empty │ 0 │ 0x0 │
// │ Folio in RAM │ ...10 │ PFN + refcount │
// │ Shadow (swapped out)│ ...1 │ Shadow data + refcount│
// │ Bad slot │ ...1000 │ Marker │
// └─────────────────────────────────────────────────────────┘
// 检查 entry 类型的辅助函数
static inline bool swap_table_entry_is_null(unsigned long entry)
{
return entry == 0;
}
static inline bool swap_table_entry_is_folio(unsigned long entry)
{
return (entry & 0x3) == 0x2; // 低两位置 10
}
static inline bool swap_table_entry_is_shadow(unsigned long entry)
{
return (entry & 0x1) == 0x1; // 低一位置 1
}
2.3 统一数据结构的威力
引入 swap table 后,原本的两套独立管理机制被统一:
// 重构前:两套独立的聚簇机制
// 1. address_space + XArray(64MB 粒度)
// 2. swap_cluster_info(2MB 粒度)
// 重构后:单一聚簇方案
// swap_cluster_info.table 直接存储所有状态
// 优势:
// 1. 消除 XArray 查找开销
// 2. 2MB 粒度的锁,减少竞争
// 3. 代码复杂度大幅降低
// 重构后的核心查找路径(简化)
static inline unsigned long *swap_table_entry(struct swap_info_struct *si,
unsigned long offset)
{
// 直接通过 cluster 和 offset 计算 entry 地址
struct swap_cluster_info *ci = &si->cluster_info[offset / SWAPFILE_CLUSTER];
unsigned long index = offset % SWAPFILE_CLUSTER;
return &ci->table[index]; // O(1) 直接访问!
}
2.4 性能提升:5%-20% 的跨越
根据 Kairui 的基准测试,swap table 第一阶段带来的性能提升:
基准测试结果(吞吐量/RPS/构建时间):
┌────────────────────────────────────────────────────────┐
│ 工作负载类型 │ 性能提升范围 │
├────────────────────────────────────────────────────────┤
│ 内存压力下的编译 │ 12-18% 构建时间缩短 │
│ 高并发 Swap IO │ 15-20% 吞吐量提升 │
│ 数据库压力测试 │ 8-15% RPS 提升 │
│ ZRAM 高频换入换出 │ 5-10% 延迟降低 │
└────────────────────────────────────────────────────────┘
性能提升的根源:
- 消除 XArray 查找:从树遍历变为 O(1) 数组访问
- 减少锁竞争:2MB 粒度替代 64MB 粒度
- 更好的缓存局部性:连续数组比分散树节点更友好
- 简化代码路径:减少分支和条件判断
第三部分:消灭 Swap Map——元数据的革命
3.1 Swap Bypass:一个"创可贴"式的设计
在理解 swap map 的移除之前,需要先了解 2018 年引入的 swap bypass 机制:
// mm/swap_state.c (Linux 4.15+)
// 对于 SWP_SYNCHRONOUS_IO 设备(如 ZRAM),跳过 swap cache
static inline bool should_bypass_swap_cache(struct swap_info_struct *si,
swp_entry_t entry)
{
// 条件:
// 1. 设备标记为 SWP_SYNCHRONOUS_IO
// 2. 该槽位的引用计数为 1(无共享)
return (si->flags & SWP_SYNCHRONOUS_IO) &&
swap_count(si->swap_map[swp_offset(entry)]) == 1;
}
设计初衷:对于 ZRAM 这类极快的设备,swap cache 的开销和预读行为反而有害。
实际问题:
- 代码复杂度飙升:swap 子系统增加大量 bypass 相关分支
- 竞态条件频发:多年引发各种 bug
- 高阶 folio 限制:THP/mTHP 只能通过 bypass 路径换入
// bypass 逻辑中的延迟重试循环(简化)
static int swap_readpage_bypass(struct page *page, swp_entry_t entry)
{
retry:
// 尝试设置 SWAP_HAS_CACHE 位作为同步信号
if (!try_set_swap_cache(entry)) {
// 另一个线程正在处理,延迟重试
cpu_relax(); // 或 cond_resched()
goto retry;
}
// ... 执行同步 IO
}
3.2 Swap Table 第二阶段:移除 Swap Bypass
Kairui Song 的洞察是:swap table 的引入让 swap cache 操作速度大幅提升,即使对于 ZRAM 这样的快速设备,绕过 swap cache 也不再有实质价值。
重构后的逻辑:
// 重构后:所有 swap IO 都通过 swap cache
// 对于 SWP_SYNCHRONOUS_IO 设备:
// 1. 换入时使用 swap cache 作为同步原语
// 2. 换入完成后立即从 swap cache 移除,释放内存
static void swap_read_complete(struct folio *folio)
{
if (folio_test_swapcache(folio) &&
folio_swap_device_has_flag(folio, SWP_SYNCHRONOUS_IO)) {
// 立即从 swap cache 移除
delete_from_swap_cache(folio);
}
// 唤醒等待的线程
}
移除 bypass 的附加收益:
- 无论引用计数为何值,都可以完整换入高阶 folio
- swap 的操作原语被整合为一组定义明确的小函数
- 减少了令人头疼的竞态条件
3.3 第三阶段:彻底消灭 Swap Map
在移除 bypass 后,SWAP_HAS_CACHE 位只剩下最后一个用途:标记已分配但引用计数为零的 swap slot。
新的设计彻底重新定义了 swap table entry 格式:
// Swap Table 第三阶段的 entry 格式(64 位系统)
//
// ┌─────────────────────────────────────────────────────────────────┐
// │ Entry Type │ Bit Layout │
// ├─────────────────────────────────────────────────────────────────┤
// │ Empty │ [63:0] = 0x0 │
// ├─────────────────────────────────────────────────────────────────┤
// │ Shadow │ [63:N] refcount │ [N-1:1] shadow data │ [0] = 1│
// ├─────────────────────────────────────────────────────────────────┤
// │ Folio in RAM │ [63:N] refcount │ [N-1:2] PFN │ [1:0]=10│
// ├─────────────────────────────────────────────────────────────────┤
// │ Bad slot │ [3:0] = 0x8 │
// └─────────────────────────────────────────────────────────────────┘
//
// N 的值取决于架构和需要的 refcount 位宽
关键变化:引用计数被嵌入 swap table entry!
// 编码一个驻留 folio 的 entry
static inline unsigned long encode_folio_entry(struct folio *folio,
unsigned int refcount)
{
unsigned long pfn = folio_pfn(folio);
// 确保 PFN 不与低位标志冲突
BUILD_BUG_ON(PFN_SHIFT < 2);
return (refcount << REFCOUNT_SHIFT) | (pfn << 2) | 0x2;
}
// 解码引用计数
static inline unsigned int decode_refcount(unsigned long entry)
{
return entry >> REFCOUNT_SHIFT;
}
3.4 内存节省:30% 元数据开销的消除
将 swap map 和 swap cache 的职责统一后:
// 重构前:两套独立的数据结构
// 1. swap_map:每个槽位 1 字节(引用计数 + 标志)
// 2. XArray entry:每个槽位 8 字节(folio 指针或 shadow)
// 总计:每个槽位约 9 字节
// 重构后:单一的 swap table entry
// 每个 entry:8 字节(包含所有信息)
// 总计:每个槽位 8 字节
// 内存节省计算(1TB swap 文件):
// 重构前:256MB (swap_map) + 512MB (XArray) = 768MB
// 重构后:512MB (swap_table)
// 节省:256MB(约 30% 减少)
实际测试中,对于 1TB swap 文件,约节省 256MB 内存。
第四部分:Virtual Swap Space——面向未来的设计
4.1 当前架构的根本限制
在完成 Swap 子系统的现代化重构后,Kairui Song 及其协作者(包括 Google 的 Chris Li)开始思考更深层次的问题:swap entry 直接绑定 PTE 和 swap 设备的设计,是否还有优化空间?
当前设计的问题:
问题一:swap off 代价高昂
// 移除 swap 设备时必须:
// 1. 将所有存储于其上的页面 fault in 回 RAM
// 2. 扫描系统中全部匿名页的页表项,更新为新位置
// 这是 O(所有匿名页) 的操作!
// 在拥有数百 GB 内存的系统上,可能需要数分钟甚至更长
问题二:zswap 的困境
// zswap 的工作原理:
// 1. 在换出流程中拦截页面
// 2. 压缩后存回内存(而非磁盘)
// 3. 但必须预先在后备设备上分配槽位
// 问题:
// - 即使从不打算将页面写入磁盘,也必须在后备设备占用空间
// - 没有 swap 设备时,zswap 无法使用
// - 后备设备的空间限制了 zswap 的容量
4.2 虚拟 Swap 空间:Meta 的方案
Meta 的 Nhat Pham 提出了增加中间层的方案:
// 引入独立的虚拟 swap 空间
struct swp_desc {
union {
swp_slot_t slot; // 物理槽位
struct zswap_entry *zswap_entry; // zswap 条目
};
union {
struct folio *swap_cache; // 驻留页面
void *shadow; // shadow 信息
};
unsigned int swap_count; // 引用计数
unsigned short memcgid:16; // cgroup ID
bool in_swapcache:1;
enum swap_type type:2; // 映射类型
};
// type 字段指示映射类型:
enum swap_type {
VSWAP_SWAPFILE, // 映射到物理 swap 设备
VSWAP_ZERO, // 全零页面
VSWAP_ZSWAP, // zswap 条目
VSWAP_FOLIO, // 仍驻留于 RAM
};
设计优势:
- 页面可在不同 swap 设备间迁移
- 移除 swap 设备无需扫描页表
- zswap 无需预先分配物理槽位
争议点:
- 每个 entry 从 8 字节扩大到 32 字节
- 内存开销显著增加
- 部分基准测试出现性能回退
4.3 Dynamic Ghost Swapfile:腾讯的创新方案
Kairui Song 提出了不同的解决思路——Dynamic Ghost Swapfile:
// 核心思路:使用 XArray 构建动态大小的虚拟 swap 文件
//
// swapon /dev/ghostswap
// -> 获得近乎 PB 级的虚拟 swap 空间
// -> 对现有用户零影响
// -> 按需扩展,无需预先分配
struct ghost_swap_info {
struct xarray entries; // 虚拟槽位映射
unsigned long max_entries; // 动态扩展的上限
// ...
};
相比 Meta 方案的优势:
- 可选而非强制:虚拟层是可选的
- 性能开销可控:不增加固定内存占用
- 兼容性更好:不破坏 swap off 语义
- 渐进式部署:可先小规模验证
4.4 Swap Tiers:分层的 Swap 架构
来自 LG 电子的 Youngjun Park 提出了另一个有趣的方向——swap tiers:
// 将多个 swap 设备配置为分层结构
struct swap_tier {
unsigned int tier_id;
unsigned int priority; // 层级优先级
struct list_head devices; // 该层级的设备列表
unsigned long total_space; // 总空间
unsigned long used_space; // 已用空间
};
// cgroup 钩子控制进程组可使用的 tier
// 延迟敏感的工作负载可独占高速 tier
// 冷数据可逐步推向低速 tier
这个方向与虚拟 swap 空间可能以某种方式合并,形成更完整的解决方案。
第五部分:工程实践的启示
5.1 重构策略:分阶段渐进式改进
Kairui Song 的重构工作展示了大型遗留系统重构的最佳实践:
阶段划分:
┌─────────────────────────────────────────────────────────────┐
│ Phase 1: 引入 swap table(Linux 6.18) │
│ - 替代 XArray │
│ - 统一两套聚簇机制 │
│ - 性能提升 5%-20% │
├─────────────────────────────────────────────────────────────┤
│ Phase 2: 移除 swap bypass(Linux 7.0) │
│ - 统一 swap cache 路径 │
│ - 简化高阶 folio 处理 │
│ - 消除竞态条件 │
├─────────────────────────────────────────────────────────────┤
│ Phase 3: 消灭 swap map(Linux 7.1+) │
│ - 引用计数嵌入 swap table entry │
│ - 节省 30% 元数据内存 │
│ - 统一数据结构 │
├─────────────────────────────────────────────────────────────┤
│ Phase 4: 虚拟 swap 空间(进行中) │
│ - 解决 swap off 和 zswap 问题 │
│ - 探索不同的实现方案 │
└─────────────────────────────────────────────────────────────┘
每个阶段都是独立可测试、可回滚的单元,降低了整体风险。
5.2 性能优化:数据结构设计的艺术
这次重构深刻展示了数据结构选择对系统性能的影响:
// 从 O(log n) 树遍历到 O(1) 数组访问
// 重构前:XArray 查找
static void *xarray_lookup(struct xarray *xa, unsigned long index)
{
// 需要遍历多级节点
// 缓存不友好
// 锁粒度粗
XA_STATE(xas, xa, index);
void *entry;
rcu_read_lock();
entry = xas_load(&xas); // 可能需要多次内存访问
rcu_read_unlock();
return entry;
}
// 重构后:直接数组访问
static unsigned long swap_table_lookup(atomic_long_t *table,
unsigned int index)
{
// 单次内存访问
// 缓存友好
// 锁粒度细
return atomic_long_read(&table[index]);
}
5.3 社区协作:开源的力量
这项工作的成功离不开社区协作:
- Kairui Song(腾讯 TencentOS):主导整个重构系列
- Chris Li(Google):Ghost Swapfile 方向的重要协作
- Jonathan Corbet(LWN.net):三篇深度分析文章
- Nhat Pham(Meta):虚拟 swap 空间的并行探索
- Youngjun Park(LG 电子):swap tiers 分层架构
社区通过邮件列表、补丁审阅、基准测试等方式,共同推动了这项工作的完善。
5.4 生产环境验证:大规模场景的价值
Kairui Song 所在团队在腾讯生产环境中发现并解决了诸多问题:
// 生产环境发现的问题示例:
// 1. 高共享内存场景下的引用计数溢出
// 2. 内存压力下的抖动(thrashing)
// 3. OOM 问题与脏页处理的关联
// 这些真实场景的反馈,是代码完善的重要驱动力
第六部分:对开发者的影响
6.1 系统管理员视角
如果你是系统管理员,这次重构带来的变化:
# 更高效的 swap 管理
# 1TB swap 文件的元数据从 768MB 降至 512MB
# 更好的性能
# 内存压力下的系统响应更快
# 更灵活的 swap 配置(未来)
# swap off 操作可能从分钟级降至秒级
# zswap 不再受 swap 设备大小限制
6.2 应用开发者视角
对于应用开发者,理解 Swap 子系统的行为有助于优化应用:
// 内存密集型应用的优化建议
// 1. 避免大量匿名页共享
// 重构后,引用计数被嵌入 swap table entry
// 位宽有限,极端情况仍需间接查找
// 2. 利用 mlock 保护关键页面
mlock(critical_data, size);
// 3. 合理设置 swappiness
// 重构后,swap 性能提升,可适当放宽 swappiness
echo 60 > /proc/sys/vm/swappiness # 默认 60
// 4. 监控 swap 使用
cat /proc/vmstat | grep swap
6.3 内核开发者视角
对于想要深入内核的开发者,Swap 子系统是绝佳的学习案例:
// 学习路径建议:
// 1. 从 swap_state.c 开始
// 理解 swap cache 的核心逻辑
// 2. 阅读 swap.h 中的数据结构定义
// 理解 swap_info_struct, swap_cluster_info 等
// 3. 跟踪一次完整的换入换出流程
// 设置断点,观察状态变化
// 4. 对比重构前后的代码
// git diff v6.17..v6.19 -- mm/swap*.c
结语:重构的艺术
Linux Swap 子系统的现代化重构,是一次教科书级别的遗留系统改造:
- 清晰的愿景:将 Swap 子系统重新构建于清晰、高效的数据结构之上
- 分阶段执行:每个阶段独立可测试,降低风险
- 性能导向:5%-20% 的性能提升是重构的最好证明
- 社区协作:多方贡献,充分讨论,共同推进
- 生产验证:在腾讯大规模场景中验证效果
对于每一位软件工程师,这都是值得深思的案例:当我们面对复杂的遗留系统时,如何用工程化的思维,一步步将其改造为更清晰、更高效、更可维护的形态。
Kairui Song 的工作还在继续——MGLRU 页面回收循环的优化、虚拟 swap 空间的探索……Linux 内核的内存管理子系统,正在迎来新的进化。
"Make it work, make it right, make it fast."
——Kent Beck
Swap 子系统的现代化重构,正是这一原则的完美实践。
参考资料
- Modernizing swapping: introducing the swap table - LWN.net
- Modernizing swapping: the end of the swap map - LWN.net
- Modernizing swapping: virtual swap spaces - LWN.net
- Swap table 补丁集
- Virtual Swap Space 提案
- Dynamic Ghost Swapfile RFC
本文约 8500 字,深入剖析 Linux Swap 子系统的现代化重构。从 XArray 到 swap table,从 swap map 到统一元数据管理,带你理解这场跨越 18 个月的内核级架构革新。