一、概述

交换空间(Swap Space)是 Linux 虚拟内存系统的核心组件,当物理内存不足时将不活跃的匿名页换出到磁盘,在缺页中断时再换入。本文完整分析从 swapon 激活交换空间到 swapoff 关闭交换空间的全生命周期,涵盖系统调用入口、数据结构初始化、I/O 路径、页缓存管理和资源清理。

核心文件索引

文件

职责

mm/swapfile.c

交换空间管理核心:swapon/swapoff、swap slot 分配/释放

mm/swap_slots.c

Per-CPU swap slot 缓存、get_swap_page()

mm/swap_state.c

Swap Cache(换出页缓存)管理

mm/page_io.c

Swap I/O 读写实现

mm/frontswap.c

Frontswap 压缩换出层

mm/memory.c

do_swap_page() 缺页处理

include/linux/swap.h

核心数据结构与接口声明


二、数据结构基础

2.1 struct swap_info_struct (sis)

每个激活的交换空间由 struct swap_info_struct 表示:

struct swap_info_struct {
    unsigned char *swap_map;          // 逐 slot 引用计数地图
    unsigned int max;                 // 最大 slot 数
    unsigned int pages;               // 可用页数(不含 header)
    unsigned int inuse_pages;         // 当前已使用页数
    unsigned int prio;                // 优先级(负值=自动分配)
    struct file *swap_file;           // 交换文件/设备
    struct block_device *bdev;        // 块设备(如果是块设备)
    struct swap_cluster_info *cluster_info;  // SSD 集群管理
    unsigned long *frontswap_map;     // frontswap 位图
    struct percpu_cluster __percpu *percpu_cluster; // per-CPU 集群指针
    struct rw_semaphore semaphore;    // 互斥锁
    spinlock_t lock;                  // 自旋锁
    struct rb_root swap_extent_root;  // swap extent 红黑树
    struct list_head list;            // swap_active_head 链表
    struct plist_node avail_lists[MAX_NUMNODES]; // NUMA 可用列表
    unsigned long flags;              // SWP_WRITEOK, SWP_SOLIDSTATE 等
    int type;                         // 交换空间类型编号
};

2.2 全局管理结构

struct swap_info_struct *swap_info[MAX_SWAPFILES];  // 所有交换空间数组
struct plist_head swap_active_head;                 // 所有激活的交换空间(swapoff 用)
struct plist_head *swap_avail_heads;                // 可用的交换空间(get_swap_page 用)
atomic_t nr_swap_pages;                             // 总可用 swap 页数
spinlock_t swap_lock;                               // 保护 swap 相关全局状态

2.3 Swap Map 计数语义

swap_map[slot] 的值含义:

含义

0

slot 空闲

1~124

被 N 个进程映射

125 (0x7D)

SHMEM 临时使用

126 (0x7E)

SWAP_MAP_BAD(坏 slot)

127 (0x7F)

SWAP_HAS_CACHE(swap cache 存在)


三、swapon:激活交换空间

2026/09/halo_brak7vk.webp

3.1 系统调用入口

用户空间: swapon("/path/to/swapfile", 0)
    ↓
SYSCALL_DEFINE2(swapon, specialfile, swap_flags)    // mm/swapfile.c:3127
    ↓
// 1. 权限和前置检查
capable(CAP_SYS_ADMIN)              // 需要 root 权限
!swap_avail_heads                   // 确保 swap 子系统已初始化
    ↓
// 2. 分配 swap_info_struct
p = alloc_swap_info()               // 从 swap_info[] 找空闲槽位
INIT_WORK(&p->discard_work, swap_discard_work)  // 初始化延迟 discard 工作
    ↓
// 3. 打开交换文件
swap_file = file_open_name(name, O_RDWR|O_LARGEFILE, 0)
mapping = swap_file->f_mapping
inode = mapping->host
    ↓
// 4. 声明交换空间所有权
claim_swapfile(p, inode)            // 设置 i_swapfile 标志
    ↓
// 5. inode 锁和合法性检查
inode_lock(inode)
d_unlinked(dentry) → -ENOENT        // 文件未被删除
cant_mount(dentry) → -ENOENT        // 不可挂载
IS_SWAPFILE(inode) → -EBUSY         // 已被其他进程使用

3.2 读取交换头部

// 6. 读取 swap header(第一个页)
page = read_mapping_page(mapping, 0, swap_file)
swap_header = kmap(page)
    ↓
// 7. 解析并验证 header
maxpages = read_swap_header(p, swap_header, inode)
  → 验证 magic 和 version
  → 获取页面总数、bad block 信息
  → 返回 maxpages

3.3 分配映射地图和集群信息

// 8. 分配 swap_map(逐 slot 引用计数)
swap_map = vzalloc(maxpages)
    ↓
// 9. 检测写入稳定特性
p->flags |= SWP_STABLE_WRrites       // blk_queue_stable_writes()
p->flags |= SWP_SYNCHRONOUS_IO       // bdev->fops->rw_page 存在
    ↓
// 10. SSD/HDD 差异化优化
if (blk_queue_nonrot(bdev)) {
    // SSD: 随机分布以均衡磨损
    p->flags |= SWP_SOLIDSTATE
    p->cluster_next_cpu = alloc_percpu(unsigned int)  // per-CPU 随机起点
    p->percpu_cluster = alloc_percpu(struct percpu_cluster)
    cluster_info = kvcalloc(nr_cluster, sizeof(*cluster_info))
} else {
    // HDD: 顺序访问减少寻道
    atomic_inc(&nr_rotate_swap)
}

3.4 Swap Cgroup 和 Extents 设置

// 11. 初始化 Swap Cgroup
swap_cgroup_swapon(p->type, maxpages)
  → 分配 swap_cgroup 映射表
  → 支持 cgroup 级别的 swap 统计和限制
    ↓
// 12. 建立 swap extent 映射(offset → disk sector)
nr_extents = setup_swap_map_and_extents(p, swap_header, swap_map,
                                        cluster_info, maxpages, &span)
  → setup_swap_extents(sis, span)
    → 块设备: 单一 extent (0 ~ max)
    → 常规文件: generic_swapfile_allocate() 或 a_ops->swap_activate()
    → 构建红黑树 swap_extent_root,将文件块映射为连续 extent

3.5 Frontswap 初始化

// 13. 分配 frontswap 位图(如果 CONFIG_FRONTSWAP)
frontswap_map = kvcalloc(BITS_TO_LONGS(maxpages), sizeof(long))
    ↓
// 14. Discard 策略设置(SSD 优化)
if (p->bdev && (swap_flags & SWAP_FLAG_DISCARD) && swap_discardable(p)) {
    p->flags |= SWP_DISCARDABLE | SWP_AREA_DISCARD
    discard_swap(p)                   // swapon 时整体 discard
}

3.6 初始化 Swap Address Space

// 15. 初始化 Swap Cache 的 address_space
error = init_swap_address_space(p->type, maxpages)
  → 分配 swapper_spaces[type](多个 address_space 数组)
  → 每个 address_space:
    • i_pages = XArray(页 radix 树)
    • a_ops = &swap_aops
    • 清除 writeback tags(swap cache 不需要)

3.7 最终启用

// 16. 标记 inode 并刷写
inode->i_flags |= S_SWAPFILE         // 防止截断和写入
inode_drain_writes(inode)            // 刷写所有 dirty mappings
    ↓
// 17. 启用交换空间
mutex_lock(&swapon_mutex)
enable_swap_info(p, prio, swap_map, cluster_info, frontswap_map)
  → frontswap_init(p->type, frontswap_map)
    → frontswap_map_set(sis, map)
    → for_each_frontswap_ops(ops) ops->init(type)
  → setup_swap_info(p, prio, ...)     // 设置 prio、swap_map、cluster_info
  → percpu_ref_resurrect(&p->users)   // 使 sis 可被并发引用
  → _enable_swap_info(p)
    → p->flags |= SWP_WRITEOK         // 允许写入
    → atomic_long_add(p->pages, &nr_swap_pages)
    → plist_add(&p->list, &swap_active_head)  // 加入全局链表
    → add_to_avail_list(p)            // 加入可用列表

日志输出:

Adding %uk swap on %s. Priority:%d extents:%d across:%lluk %s%s%s%s%s

四、Swap 分配:get_swap_page

4.1 获取 Swap Slot

当匿名页需要换出时,内核需要分配一个 swap slot:

get_swap_page(page)                    // mm/swap_slots.c:303
    ↓
// THP 特殊处理
if (PageTransHuge(page)) {
    get_swap_pages(1, &entry, HPAGE_PMD_NR)
    goto out
}
    ↓
// Per-CPU 缓存快速路径
cache = raw_cpu_ptr(&swp_slots)
if (check_cache_active() && cache->slots) {
    mutex_lock(&cache->alloc_lock)
    entry = cache->slots[cache->cur++]  // 从缓存取出
    cache->nr--
    mutex_unlock(&cache->alloc_lock)
} else {
    get_swap_pages(1, &entry, 1)        // 全局分配
}
    ↓
// Memcg 记账
if (mem_cgroup_try_charge_swap(page, entry))
    put_swap_page(page, entry)
    ↓
return entry

4.2 全局分配:get_swap_pages

get_swap_pages(n_goal, swp_entries, entry_size)  // mm/swapfile.c:1039
    ↓
avail_pgs = atomic_long_read(&nr_swap_pages) / size
plist_for_each_entry(si, &swap_avail_heads[node], avail_lists[node])
    ↓
// 优先同 NUMA node
if (si->flags & SWP_SOLIDSTATE) {
    swap_alloc_cluster(si, swp_entries)   // SSD: 集群分配
} else {
    scan_swap_map_slots(si, ...)          // HDD: 顺序扫描
}

4.3 SSD 集群分配策略

scan_swap_map_slots(si, usage, nr, slots)  // mm/swapfile.c:777
    ↓
if (si->flags & SWP_SOLIDSTATE)
    scan_base = this_cpu_read(*si->cluster_next_cpu)  // per-CPU 随机起点
else
    scan_base = si->cluster_next                        // 全局顺序扫描
    ↓
// SSD 算法: scan_swap_map_try_ssd_cluster()
→ 优先找空闲集群 (SWAPFILE_CLUSTER = 64 页)
→ 避免分散交换页,减少磁盘寻道
    ↓
if (si->cluster_info)  // SSD with cluster_info
    scan_swap_map_try_ssd_cluster()
      → 从 per-CPU 起点查找空闲集群
      → 找到后清空 swap_map,标记集群为已分配
else                    // 传统 HDD 模式
    → 先尝试找空集群
    → 找不到则线性扫描(cluster_nr 递减)

五、Swap I/O 路径

5.1 换出:swap_writepage

swap_writepage(page, wbc)              // mm/page_io.c:228
    ↓
// 尝试释放(页已被收回但不需要写磁盘)
if (try_to_free_swap(page)) { unlock_page(page); return; }
    ↓
// arch 特有处理(如保存 memory tags)
arch_prepare_to_swap(page)
    ↓
// Frontswap 快速路径(压缩换出到内存)
if (frontswap_store(page) == 0) {
    set_page_writeback(page)
    end_page_writeback(page)          // 直接完成,无需磁盘 IO
    return
}
    ↓
// 持久化换出:__swap_writepage()
__swap_writepage(page, wbc, end_swap_bio_write)
    ↓
if (sis->flags & SWP_FS_OPS) {         // 文件系统 swap(如 swap-over-NFS)
    kiocb.ki_pos = page_file_offset(page)
    mapping->a_ops->direct_IO(&kiocb, &from)
} else {                               // 块设备 swap
    bio = bio_alloc(GFP_KERNEL, 1)
    bio->bi_opf = REQ_OP_WRITE
    bio->bi_iter.bi_sector = swap_page_sector(page)
    bio->bi_end_io = end_swap_bio_write
    submit_bio(bio)
}

5.2 换入:swap_readpage

swap_readpage(page, synchronous)       // mm/page_io.c:356
    ↓
// Frontswap 快速路径(从内存压缩缓存加载)
if (frontswap_load(page) == 0) {
    SetPageUptodate(page); unlock_page(page); return
}
    ↓
if (sis->flags & SWP_FS_OPS) {         // 文件系统
    mapping->a_ops->readpage(swap_file, page)
} else if (sis->flags & SWP_SYNCHRONOUS_IO) {  // 同步设备(如 NVMe)
    bdev_read_page(sis->bdev, swap_page_sector(page), page)
} else {                               // 标准块设备异步 IO
    bio = bio_alloc(GFP_KERNEL, 1)
    bio->bi_opf = REQ_OP_READ
    bio->bi_end_io = end_swap_bio_read
    bio->bi_iter.bi_sector = swap_page_sector(page)
    if (synchronous) {
        bio->bi_opf |= REQ_HIPRI
        get_task_struct(current)       // 防止 OOM  killer 终止当前进程
    }
    submit_bio(bio)
    // 同步等待...
}

六、Swap Cache 管理

6.1 Swap Cache 数据结构

Swap Cache 是内核页缓存 (Page Cache) 在交换空间上的镜像:

swapper_spaces[type][page_offset]
    → struct address_space (i_pages = XArray)
        → 每个条目指向 struct page(swap cache page)

6.2 添加 Swap Cache

add_to_swap_cache(page, entry)         // mm/swap_state.c
    ↓
radix_tree_insert(&space->i_pages, offset, page)
set_page_swap_cache(page)             // 标记页为 swap cache
set_page_private(page, entry.val)     // 存储 swap entry

6.3 删除 Swap Cache

delete_from_swap_cache(page)           // mm/swap_state.c
    ↓
radix_tree_delete(&space->i_pages, offset)
clear_page_swap_cache(page)
set_page_private(page, 0)

2026/09/halo_cshjit5.webp

七、缺页处理:do_swap_page

7.1 完整流程

do_swap_page(vmf)                      // mm/memory.c:3506
    ↓
entry = pte_to_swp_entry(vmf->orig_pte)  // 从 PTE 提取 swap entry
    ↓
// 处理非标准 swap entry(迁移页、设备页、硬件损坏)
if (non_swap_entry(entry)) {
    migration_entry_wait()
    remove_device_exclusive_entry()
    ...
}
    ↓
// 防止 swapoff 影响
si = get_swap_device(entry)            // 引用计数 +1
    ↓
// 在 swap cache 查找(可能已被其他进程换入)
page = lookup_swap_cache(entry, vma, address)
    ↓
if (!page) {
    // 需要真正从磁盘换入
    if (si->flags & SWP_SYNCHRONOUS_IO && __swap_count(entry) == 1) {
        // 同步设备 + 唯一映射: 快速路径
        page = alloc_page_vma(...)
        lru_cache_add(page)
        swap_readpage(page, true)       // 同步读
    } else {
        // 标准路径: 换入预读
        page = swapin_readahead(entry, gfp, vmf)
    }
}
    ↓
// 分配 PTE 并映射
pte = pte_offset_map_lock(...)
set_pte_at(mm, address, pte, mk_pte(page, vma->vm_page_prot))
page_add_anon_rmap(page, vma, address, false)
dec_mm_counter(mm, MM_SWAPENTS)
inc_mm_counter(mm, MM_ANONPAGES)
    ↓
// 释放 swap slot
swap_free(entry)                       // swap_map[slot]--
    ↓
// 释放 swap device 引用
put_swap_device(si)

7.2 Swapin Readahead

swapin_readahead(entry, gfp, vmf)      // mm/swap_state.c
    ↓
// 基于 VMA 和缺页地址,预读后续页面
// 策略:
// 1. 线性预读:address + 4KB, 8KB, 16KB, ...
// 2. VMA 感知:考虑 VMA 边界和对齐
// 3. 异步换入:不阻塞缺页进程
for (i = 0; i < nr_to_read; i++) {
    entry = swp_entry(swp_type(entry), swp_offset(entry) + offset[i])
    __read_swap_cache_async(entry, gfp, vmf)
}

八、Swap Slot 释放

8.1 正常释放路径

swap_free(entry)                       // mm/swapfile.c:1303
    ↓
__swap_entry_free(p, entry)            // swap_map[slot]--
    ↓
if (count == 0) {
    free_swap_slot(entry)              // mm/swap_slots.c:271
      → Per-CPU cache 快速路径
      → cache->slots_ret[n_ret++] = entry
      → 缓存满时 swapcache_free_entries()
}

8.2 free_swap_and_cache

free_swap_and_cache(entry)             // mm/swapfile.c:1755
    ↓
__swap_entry_free(p, entry)
    ↓
if (count == SWAP_HAS_CACHE) {
    __try_to_reclaim_swap(si, offset, TTRS_UNMAPPED | TTRS_FULL)
      → find_get_page(swap_address_space(entry), offset)
      → try_to_free_swap(page)
        → delete_from_swap_cache(page)
        → SetPageDirty(page)           // 页重新标记为 dirty
}

8.3 try_to_free_swap

try_to_free_swap(page)                 // mm/swapfile.c:1716
    ↓
// 前置检查
!PageSwapCache(page) → 跳过
PageWriteback(page) → 跳过
page_mapped(page) → 跳过
pm_suspended_storage() → 跳过(休眠中不释放)
    ↓
// 核心操作
delete_from_swap_cache(page)           // 从 swap cache 移除
SetPageDirty(page)                     // 重新标记为 dirty,等待回写

九、swapoff:关闭交换空间

2026/09/halo_neg8w9s.webp

9.1 系统调用入口

SYSCALL_DEFINE1(swapoff, specialfile)  // mm/swapfile.c:2518
    ↓
// 1. 权限和文件打开
capable(CAP_SYS_ADMIN)
pathname = getname(specialfile)
victim = file_open_name(pathname, O_RDWR, O_LARGEFILE)
    ↓
// 2. 查找匹配的 swap_info_struct
spin_lock(&swap_lock)
plist_for_each_entry(p, &swap_active_head, list)
    if (p->flags & SWP_WRITEOK)
        if (p->swap_file->f_mapping == mapping)
            found = 1, break
    ↓
// 3. 内存账户回退
security_vm_enough_memory_mm(current->mm, p->pages)
vm_unacct_memory(p->pages)            // 回退 swapon 时分配的内存账户
    ↓
// 4. 从全局链表中移除
del_from_avail_list(p)
if (p->prio < 0) {                     // 自动分配的优先级需要重新分配
    plist_for_each_entry_continue(si, &swap_active_head, list)
        si->prio++
    least_priority++
}
plist_del(&p->list, &swap_active_head)
atomic_long_sub(p->pages, &nr_swap_pages)
total_swap_pages -= p->pages
p->flags &= ~SWP_WRITEOK              // 标记为只读,阻止新分配

9.2 try_to_unuse:核心迁移逻辑

这是 swapoff 最复杂和最关键的部分——将所有进程中的 swap 条目迁移回去。

try_to_unuse(type, frontswap=false, pages_to_unuse=0)  // mm/swapfile.c:2157
    ↓
// Phase 1: 迁移 shmem/tmpfs 中的 swap 条目
retval = shmem_unuse(type, frontswap, &pages_to_unuse)
    → find_get_page(swap_address_space(entry), offset)
    → unuse_pte_range()
      → swapin_readahead()  // 需要时换入页面
      → unuse_pte()         // PTE 替换
        → set_pte_at(..., mk_pte(page, prot))  // swap PTE → 物理页 PTE
        → page_add_anon_rmap()
        → swap_free(entry)
        → try_to_free_swap(page)
    ↓
// Phase 2: 遍历所有进程的 mm
prev_mm = &init_mm
mmget(prev_mm)
spin_lock(&mmlist_lock)
p = &init_mm.mmlist
while (si->inuse_pages && !signal_pending(current)) {
    mm = list_entry(p->next, struct mm_struct, mmlist)
    if (mmget_not_zero(mm)) {
        spin_unlock(&mmlist_lock)
        mmput(prev_mm)
        prev_mm = mm
        retval = unuse_mm(mm, type, frontswap, &pages_to_unuse)
          → mmap_read_lock(mm)
          → for (vma = mm->mmap; vma; vma = vma->vm_next)
              if (vma->anon_vma)
                  unuse_vma(vma, type, ...)
                    → pgd → p4d → pud → pmd → pte 逐级遍历
                      → unuse_pte_range()  // 逐 PTE 替换
          → mmap_read_unlock(mm)
        cond_resched()              // 让出 CPU
        spin_lock(&mmlist_lock)
    }
}
    ↓
// Phase 3: 扫描 swap cache 中剩余的条目
i = 0
while (si->inuse_pages) {
    entry = swp_entry(type, i = find_next_to_unuse(si, i, frontswap))
    page = find_get_page(swap_address_space(entry), i)
    if (page) {
        lock_page(page)
        wait_on_page_writeback(page)
        try_to_free_swap(page)       // 从 swap cache 移除,标记 dirty
        unlock_page(page)
    }
}
    ↓
// 重试机制:全局内存压力下可能有新 swap 条目被插入
if (si->inuse_pages) {
    if (!signal_pending(current))
        goto retry                   // 从头再来
    retval = -EINTR
}

9.3 清理阶段

c// swapoff.c 继续...
reenable_swap_slots_cache_unlock()   // 重新启用 swap slot 缓存

// 等待所有 swap 操作完成
percpu_ref_kill(&p->users)           // 停止引用计数
synchronize_rcu()                    // 等待 RCU 宽限期
wait_for_completion(&p->comp)        // 等待 percpu_ref 完全释放
    ↓
flush_work(&p->discard_work)         // 刷写延迟 discard 工作
    ↓
// 销毁 swap extent
destroy_swap_extents(p)
if (p->flags & SWP_CONTINUED)
    free_swap_count_continuations(p)
    ↓
// 减少旋转磁盘计数
if (!p->bdev || !blk_queue_nonrot(...))
    atomic_dec(&nr_rotate_swap)
    ↓
// 释放 per-CPU 资源
free_percpu(p->percpu_cluster)
free_percpu(p->cluster_next_cpu)
    ↓
// 释放 swap_map 和 cluster_info
vfree(swap_map)
kvfree(cluster_info)
kvfree(frontswap_map)
    ↓
// Swap Cgroup 清理
swap_cgroup_swapoff(p->type)
    ↓
// Swap Address Space 清理
exit_swap_address_space(p->type)
  → for (i = 0; i < nr_swapper_spaces[type]; i++)
      VM_WARN_ON_ONCE(!mapping_empty(&spaces[i]))
  → kvfree(spaces)
    ↓
// 清理 inode 标志
inode->i_flags &= ~S_SWAPFILE
    ↓
// 关闭文件
filp_close(swap_file, NULL)
    ↓
// 清除 SWP_USED 标志,允许 swapon 复用
spin_lock(&swap_lock)
p->flags = 0
spin_unlock(&swap_lock)
    ↓
// 唤醒 /proc/swaps 轮询者
atomic_inc(&proc_poll_event)
wake_up_interruptible(&proc_poll_wait)

十、Frontswap 层

Frontswap 是一个"二级缓存"层,在磁盘 I/O 之前先尝试将交换页压缩存储到内存中。

10.1 swapon 时的初始化

frontswap_init(type, frontswap_map)
  → frontswap_map_set(sis, map)
  → for_each_frontswap_ops(ops)
      ops->init(type)                 // 后端初始化(如 zsmalloc)

10.2 swap_writepage 时的存储

if (frontswap_store(page) == 0) {
    // 成功压缩存储到内存,跳过磁盘 I/O
    end_page_writeback(page)
    return
}
// 失败则回退到磁盘 I/O
__swap_writepage(page, ...)

10.3 swap_readpage 时的加载

if (frontswap_load(page) == 0) {
    // 从内存压缩缓存解压
    SetPageUptodate(page)
    unlock_page(page)
    return
}
// 缓存未命中则从磁盘读
swap_readpage(page, ...)

10.4 swapoff 时的无效化

frontswap_invalidate_area(type)       // swapoff 时调用
  → for_each_frontswap_ops(ops)
      ops->invalidate_area(type)      // 释放压缩缓存中的所有页

十一、关键同步机制

11.1 锁层次

swapon_mutex                          // 最外层:swapon/swapoff 互斥
    ↓
swap_lock (spinlock)                  // 保护全局 swap 状态
    ↓
sis->lock (spinlock)                  // 保护单个 swap_info_struct
    ↓
    • swap_map
    • cluster_info
    • inuse_pages
    • highest_bit
    • cluster_next

11.2 Per-CPU 引用计数

percpu_ref_init(&p->users)            // swapon 时初始化
percpu_ref_resurrect(&p->users)       // 启用后"复活"
percpu_ref_kill(&p->users)            // swapoff 时停止
synchronize_rcu()                     // 等待所有引用者退出
wait_for_completion(&p->comp)         // 等待完全释放

11.3 Swap Scan 保护

while (p->flags >= SWP_SCANNING) {
    // swapoff 等待所有 swap slot 扫描器完成
    schedule_timeout_uninterruptible(1)
}

十二、边界情况和错误处理

12.1 swapon 错误路径

bad_swap_unlock_inode:
    kunmap(page)
    put_page(page)
    p->swap_file = NULL
    unlock_inode(inode)
    claim_swapfile 反向操作

bad_swap:
    free_swap_info(p)
    return error

12.2 swapoff 失败回滚

if (try_to_unuse() != 0) {
    reinsert_swap_info(p)             // 重新加入链表
    reenable_swap_slots_cache_unlock()
    goto out_dput                     // 返回错误
}

12.3 OOM 交互

// swapoff 期间不允许被 OOM killer 终止
set_current_oom_origin()
try_to_unuse(p->type, false, 0)
clear_current_oom_origin()

// do_swap_page 期间防止被 OOM killer 终止
if (synchronous) get_task_struct(current)

十三、性能优化要点

13.1 Swap Slot Cache (Per-CPU)

// 分配: cache->slots[SWAP_SLOTS_CACHE_SIZE] 预分配
// 释放: cache->slots_ret 延迟释放
// 效果: 避免高频 get/put swap page 时的锁竞争

13.2 SSD 集群分配

// 64 页为一组(cluster),优先分配完整空集群
// per-CPU 随机起点,避免多核冲突
// 均衡 SSD 磨损

13.3 Swapin Readahead

// 基于局部性原理,缺页时预读后续页面
// 异步换入,不阻塞缺页进程
// 可根据缺页频率动态调整预读量

13.4 Frontswap

// 内存压缩换出:零磁盘 I/O
// swapoff 时自动 invalidate,无需额外处理
// 特别适合内存充足但偶发缺压的系统

十四、总结

2026/09/halo_d6nloet.webp

交换空间的完整生命周期可以概括为以下五个阶段:

阶段

核心操作

关键函数

激活

解析 header、分配 map/extents、初始化 cache、注册到全局链表

sys_swapon, enable_swap_info

分配

Per-CPU 缓存命中或全局扫描、集群分配、memcg 记账

get_swap_page, get_swap_pages

换出

释放 slot、写入 frontswap 或磁盘

swap_writepage, __swap_writepage

换入

Swap Cache 查找、缺页换入、PTE 映射

do_swap_page, swap_readpage

关闭

迁移所有 swap 条目、清理 cache/extents、释放资源

sys_swapoff, try_to_unuse

整个系统通过精妙的锁层次、Per-CPU 缓存和 Frontswap 机制,在保证正确性的同时实现了高性能的交换空间管理。