一、为什么 Linux 需要 Swap?

在正式阅读 Swap 源码之前,我们首先需要回答一个最基础的问题:

Linux 为什么需要 Swap?

现代 Linux 使用虚拟内存机制。对于一个用户进程来说,它看到的是一片连续的虚拟地址空间,而真正承载这些数据的是有限的物理内存。

例如:

进程 A:8 GB 虚拟地址空间
进程 B:12 GB 虚拟地址空间
进程 C:4 GB 虚拟地址空间

              ↓

        Linux 虚拟内存系统

              ↓

           8 GB RAM

虚拟地址空间可以远远大于机器实际安装的物理内存,因为并不是所有虚拟地址都需要同时驻留在 RAM 中。

真正的问题出现在:

当物理内存越来越紧张时,Linux 应该怎样腾出一些物理页?

这正是内存回收(memory reclaim)系统需要解决的问题。

从最粗略的角度看,用户态页面可以分成两大类:

                    用户进程使用的页面
                           │
             ┌─────────────┴─────────────┐
             │                           │
       file-backed page             anonymous page
          文件页                       匿名页
             │                           │
             ▼                           ▼
      原始内容来自文件              没有天然后备文件

普通文件页的处理相对简单。

假设某个页面来自:

/home/test/data.bin

如果这个文件页是干净的,那么 Linux 在回收内存时甚至可以直接把物理页释放掉。

以后进程再次访问:

虚拟地址
   ↓
page fault
   ↓
重新从 data.bin 读取

因为它的数据本来就在磁盘文件中。

但匿名页不一样。

典型的匿名页包括:

  • malloc()

  • calloc()

  • new

  • 匿名 mmap

  • 进程的 heap

  • 进程的 stack

这些页面中的数据可能是:

  • 订单缓存

  • Java 对象

  • Python 对象

  • 数据库临时状态

程序运行过程中计算出的数据

它们并不存在于某个可以重新读取的普通文件中。

如果 Linux 在内存紧张时直接丢弃一个匿名页,那么其中的数据也就永久消失了。

因此对于匿名页而言:

如果想释放它占用的物理内存,就必须先为它寻找一个新的后备存储位置。

这个位置就是:Swap

于是 Swap 最核心的意义并不是简单的:“拿硬盘冒充内存。”

而是:为某些原本没有持久后备存储的内存页建立一个临时的后备存储,使物理页可以从 RAM 中被回收。

这才是理解 Linux Swap 子系统最重要的出发点。


二、哪些页面能够进入 Swap?

理解 Swap 时,很容易产生一个误解:

“内存不足时,Linux 会把所有内存页都写到 Swap。”

实际上并不是这样。

从内存回收角度,可以先建立下面这个模型:

                    reclaim
                       │
          ┌────────────┴────────────┐
          │                         │
     file-backed                 anonymous
       page                        page
          │                         │
          ▼                         ▼
  clean:直接丢弃             写入 swap
  dirty:先 writeback              │
          │                         ▼
          ▼                    swap slot
     backing file

2.1. 普通文件页

普通 file-backed page 已经有自己的后备文件。

因此:

file page
    ↓
释放物理页
    ↓
以后缺页
    ↓
重新从原文件读取

通常没有必要再给它复制一份到 Swap。

如果页面被修改过,则可能需要先执行文件系统 writeback,把脏数据写回原文件。


2.2. 匿名页

匿名页没有普通文件作为后备存储。

所以它被回收时需要:

anonymous page
      ↓
申请 swap slot
      ↓
写入 swap
      ↓
页表记录 swap entry
      ↓
释放 physical page

这就是最经典的 swap-out。


2.3. tmpfs / shmem

tmpfs 和共享匿名内存稍微特殊一些。

它虽然表现得像文件系统对象,但数据本质上仍主要存在于内存中,因此也可以使用 Swap 作为后备存储。

Linux 5.15 的 swap_map 中甚至存在专门的:

#define SWAP_MAP_SHMEM 0xbf

用于表示由 shmem/tmpfs 持有的 swap entry。


因此可以先记住:

普通文件页
    │
    └── 后备存储通常是原文件

匿名页 / shmem
    │
    └── 可以使用 Swap 作为后备存储

三、Swap Area、Swap Slot 与 Swap Entry

进入源码之前,需要先区分三个最重要的概念:

  • Swap Area

  • Swap Slot

  • Swap Entry

3.1 Swap Area

Linux 可以同时启用多个 Swap 区域,例如:

/dev/sda2
/swapfile
/dev/nvme0n1p3

它们可以是:

  • swap 分区

  • swap 文件

每一个已经启用的交换区域,都可以理解为一个 Swap Area

Linux 使用:

struct swap_info_struct *swap_info[MAX_SWAPFILES];

管理所有 Swap Area。

每个 Swap Area 都拥有一个 type,而这个 type 实际上就是 swap_info[] 的索引:

type = 0  →  swap_info[0]
type = 1  →  swap_info[1]
type = 2  →  swap_info[2]

3.2 Swap Slot

一个 Swap Area 会被逻辑上划分成许多以页面为单位的槽位:

Swap Area
│
├── slot 0
├── slot 1
├── slot 2
├── slot 3
├── ...
└── slot N

一个普通 Swap Slot 可以理解为:

用于保存一个页面内容的逻辑存储单元。

注意,Swap Slot 是逻辑编号,并不一定直接等于最终的磁盘扇区位置。


3.3 Swap Entry

匿名页被换出之后,页表必须记录它被放到了哪里。

Linux 使用 swp_entry_t 表示 Swap Entry。

从逻辑上可以把它理解为:

swp_entry_t
    │
    ├── type
    │    └── 哪一个 Swap Area
    │
    └── offset
         └── Swap Area 中哪一个 Slot

例如:

type   = 2
offset = 1024

表示:

swap_info[2]
    │
    ▼
第 1024 个 Swap Slot

因此,理解 Swap 最重要的一条关系就是:

Swap Entry = type + offset

四、页面被换出以后,到底去了哪里?

这是进入 Swap 源码之前最重要的一幅图

假设进程中的某个虚拟地址:0x7f12_4000

当前映射到一个物理页:

Virtual Address
      │
      ▼
     PTE
      │
      ▼
Physical Page
      │
      ▼
     RAM

此时 PTE 是 present 状态,因此 MMU 可以通过页表找到物理页。

但当该匿名页被换出后,物理页最终可以被释放。

问题来了:

以后进程再次访问这个地址时,Linux 怎么知道原来的数据被放在 Swap 的什么位置?

答案是:Swap Entry

页面换出后,PTE 不再描述一个普通的 present 物理页,而是编码一个 swap entry:

Virtual Address
      │
      ▼
     PTE
      │
      │ present
      ├──────────────► Physical Page
      │
      │ !present + swap entry
      ▼
   swp_entry_t
      │
      ├── type
      │
      └── offset

也就是说:

Swap Entry 是连接“页表中的虚拟内存”和“Swap 空间中的数据”之间最核心的逻辑地址。

swap type 和对应 swap type 内部的 offset 会被编码进 PTE,同时也会用于 swap cache。


五、Swap 的整体寻址模型

有了 typeoffset,Swap 中最重要的地址转换链就可以建立起来:

这里实际上存在两层定位:

第一层:

type
  ↓
swap_info[type]

确定页面属于哪个 Swap Area。

第二层:

offset
  ↓
Swap Slot
  ↓
Swap Extent
  ↓
磁盘块

确定数据最终位于 Swap Area 的哪个物理位置。

这条链是整个 Swap 子系统最核心的骨架。

六、struct swap_info_struct —— 一个 Swap Area 的核心描述符

Linux 5.15 中,每一个活动的 Swap Area 都对应一个:

struct swap_info_struct

它可以理解为:

内核中一个 Swap Area 的总控制块。

其中比较重要的字段包括:

struct swap_info_struct {
    struct percpu_ref users;
    unsigned long flags;
    signed short prio;

    signed char type;
    unsigned int max;
    unsigned char *swap_map;

    struct swap_cluster_info *cluster_info;
    struct swap_cluster_list free_clusters;

    unsigned int lowest_bit;
    unsigned int highest_bit;
    unsigned int pages;
    unsigned int inuse_pages;

    struct rb_root swap_extent_root;

    struct block_device *bdev;
    struct file *swap_file;

    spinlock_t lock;

    struct plist_node avail_lists[];
};

这些字段大体可以分成几类。

Swap Area 身份

signed char type;
unsigned long flags;
signed short prio;

其中:

  • type:Swap Area 编号,也是 swap_info[] 的索引

  • flags:Swap Area 当前状态

  • prio:多个 Swap Area 之间的优先级


Swap Slot 管理

unsigned char *swap_map;

unsigned int lowest_bit;
unsigned int highest_bit;
unsigned int pages;
unsigned int inuse_pages;

其中最重要的是:

swap_map

它并不是“Swap Slot 到物理页的映射表”,而是用于记录每个 Swap Slot 当前的:

  • 使用计数

  • 状态信息

可以简单理解为:

swap_map
│
├── [0]  → slot 0 状态
├── [1]  → slot 1 状态
├── [2]  → slot 2 状态
└── ...

因此:

swap_info[type]->swap_map[offset]

就是某个 Swap Entry 所对应 Slot 的状态。

七、Swap Cluster —— 对 Swap Slot 进行分组管理

如果每次申请 Swap Slot 都从整个 swap_map 中随机寻找一个空闲位置,不仅效率低,而且容易带来锁竞争和较差的 I/O 局部性。

因此 Linux 会进一步把 Swap Slot 按 Cluster 组织。

可以简单理解为:

Swap Area

cluster 0
├── slot 0
├── slot 1
├── ...
└── slot 255

cluster 1
├── slot 256
├── ...
└── slot 511

swap_info_struct 中与此相关的字段包括:

struct swap_cluster_info *cluster_info;
struct swap_cluster_list free_clusters;

其中:

  • cluster_info:记录 Cluster 状态

  • free_clusters:维护空闲 Cluster

对于 SSD,Linux 还会结合 per-CPU Cluster,使不同 CPU 尽可能从各自的 Cluster 中分配 Swap Slot,以减少竞争。

具体 Cluster 分配算法将在后续 Swap 空间管理章节中继续展开。


八、Swap Slot Cache —— 减少分配路径上的锁竞争

Swap-Out 时需要不断申请 Swap Slot。

如果每换出一个匿名页,都直接进入全局 Swap Slot 分配器,会导致多个 CPU 频繁竞争全局锁。

因此 Linux 5.15 在 mm/swap_slots.c 中维护了:

static DEFINE_PER_CPU(struct swap_slots_cache, swp_slots);

也就是:

per-CPU Swap Slot Cache

每个 CPU 可以提前批量缓存一部分 Swap Entry:

              Global Swap Pool
                     │
          ┌──────────┼──────────┐
          │          │          │
          ▼          ▼          ▼
        CPU0       CPU1       CPU2
       cache      cache      cache

普通分配时优先从本 CPU 缓存中取:

get_swap_page()
      │
      ▼
per-CPU slot cache
      │
      ├── 有缓存 → 直接返回
      │
      └── 无缓存 → 批量 refill

它的核心目标不是改变 Swap 的逻辑模型,而是:

降低高并发 Swap Slot 分配时的锁竞争。

具体的 refill、释放和 cache 启停机制将在后续章节展开。


九、Swap Cache —— Swap Entry 与物理页之间的缓存

Swap Cache 是另一个非常重要的概念。

假设某个 Swap Entry 对应的数据正在被换入。

如果两个线程同时访问同一个已经换出的页面:

Thread A ──┐
           ├── same swap entry
Thread B ──┘

显然不应该让两个线程都重新从磁盘读取一次。

因此 Linux 维护 Swap Cache:

swap entry
    │
    ▼
Swap Cache
    │
    ▼
physical page

换入时首先检查:

这个 Swap Entry 是否已经有对应的 Page?

如果命中:

直接复用现有 Page

如果没有:

分配 Page
   ↓
加入 Swap Cache
   ↓
从 Swap 读取数据

Linux 5.15 中 Swap Cache 建立在:

address_space->i_pages

之上,而 i_pages 的底层已经使用 XArray。

后续第 3 章会专门分析:

__read_swap_cache_async()

以及 Swap Entry 与 Swap Cache Page 的映射关系。


十、Swap Extent —— 从逻辑 Slot 找到磁盘块

Swap Entry 中保存的是:

type + offset

offset 只是逻辑 Swap Page 编号。

对于 Swap File 来说,文件逻辑块在磁盘上并不一定连续,所以 Linux 还需要维护:

struct swap_extent

Linux 5.15 中:

struct swap_extent {
    struct rb_node rb_node;
    pgoff_t start_page;
    pgoff_t nr_pages;
    sector_t start_block;
};

它描述的是:

一段连续的 Swap Page
        ↓
映射到
        ↓
一段连续的磁盘 Block

所有 Swap Extent 通过:

struct rb_root swap_extent_root;

组织成红黑树。

因此最终可以完成:

swap offset
    │
    ▼
swap_extent_root
    │
    ▼
swap_extent
    │
    ▼
disk block

对于 Swap File,这一层尤其重要,因为文件逻辑地址和磁盘物理块之间可能并不是简单的线性关系。

十一、Swap-Out 与 Swap-In 的整体流程

到这里,我们已经可以先从架构层面理解一次完整的 Swap-Out。

十二、核心数据结构关系

到这里,可以把整个 Swap 子系统压缩成下面这张图:


十二、总结

本章的目标不是深入某一个具体函数,而是先建立整个 Swap 子系统的坐标系。

首先,Swap 的存在主要是为了给匿名页等没有普通后备文件的页面提供后备存储。

一个已经换出的页面通过:

swp_entry_t(type, offset)

描述它在 Swap 中的位置。

其中:

type
  → 定位 swap_info[type]

offset
  → 定位该 Swap Area 中的 Swap Slot

Linux 使用:

struct swap_info_struct

管理一个 Swap Area。

其中:

swap_map
    → 管理 Swap Slot 的引用计数和状态

cluster_info
    → 对 Swap Slot 进行分组管理

swap_slots_cache
    → per-CPU 缓存,减少 Slot 分配锁竞争

Swap Cache
    → 建立 Swap Entry 与内存 Page 的缓存关系

swap_extent
    → 将逻辑 Swap Page 映射到实际磁盘 Block

因此,整个 Swap 子系统最重要的链路可以总结为:

2026/08/halo_yiynodj.webp

理解这条链以后,后面再阅读 swapon()swapoff()、Swap Slot 分配、Swap Cache、Swap-In 和 Swap-Out,就不再是一堆彼此孤立的函数。