编程 Linux Swap 子系统现代化重构:当 18 个月的内核攻坚重塑内存管理底层架构

2026-04-13 06:55:20 +0800 CST views 15

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 的核心作用

  1. 同步与协调:在异步 IO 过程中维护页面状态
  2. 竞态处理:若写入过程中发生缺页异常,页面可被迅速重新激活
  3. 预读优化:支持批量预读相邻页面

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 的设计缺陷

  1. 引用计数溢出:最大值仅 62,超过后需要分配额外的"继续页"
  2. 内存开销大:每个槽位一个字节,1TB swap 文件需要 256MB 内存
  3. 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% 延迟降低                  │
└────────────────────────────────────────────────────────┘

性能提升的根源

  1. 消除 XArray 查找:从树遍历变为 O(1) 数组访问
  2. 减少锁竞争:2MB 粒度替代 64MB 粒度
  3. 更好的缓存局部性:连续数组比分散树节点更友好
  4. 简化代码路径:减少分支和条件判断

第三部分:消灭 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 的开销和预读行为反而有害。

实际问题

  1. 代码复杂度飙升:swap 子系统增加大量 bypass 相关分支
  2. 竞态条件频发:多年引发各种 bug
  3. 高阶 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 子系统的现代化重构,正是这一原则的完美实践。


参考资料

  1. Modernizing swapping: introducing the swap table - LWN.net
  2. Modernizing swapping: the end of the swap map - LWN.net
  3. Modernizing swapping: virtual swap spaces - LWN.net
  4. Swap table 补丁集
  5. Virtual Swap Space 提案
  6. Dynamic Ghost Swapfile RFC

本文约 8500 字,深入剖析 Linux Swap 子系统的现代化重构。从 XArray 到 swap table,从 swap map 到统一元数据管理,带你理解这场跨越 18 个月的内核级架构革新。

复制全文 生成海报 Linux 内核 内存管理 Swap 性能优化

推荐文章

宝塔面板 Nginx 服务管理命令
2024-11-18 17:26:26 +0800 CST
MySQL用命令行复制表的方法
2024-11-17 05:03:46 +0800 CST
服务器购买推荐
2024-11-18 23:48:02 +0800 CST
三种高效获取图标资源的平台
2024-11-18 18:18:19 +0800 CST
纯CSS实现3D云动画效果
2024-11-18 18:48:05 +0800 CST
五个有趣且实用的Python实例
2024-11-19 07:32:35 +0800 CST
php指定版本安装php扩展
2024-11-19 04:10:55 +0800 CST
Vue3结合Driver.js实现新手指引功能
2024-11-19 08:46:50 +0800 CST
记录一次服务器的优化对比
2024-11-19 09:18:23 +0800 CST
内网穿透技术详解与工具对比
2025-04-01 22:12:02 +0800 CST
Elasticsearch 文档操作
2024-11-18 12:36:01 +0800 CST
程序员茄子在线接单