一、概述
交换空间(Swap Space)是 Linux 虚拟内存系统的核心组件,当物理内存不足时将不活跃的匿名页换出到磁盘,在缺页中断时再换入。本文完整分析从 swapon 激活交换空间到 swapoff 关闭交换空间的全生命周期,涵盖系统调用入口、数据结构初始化、I/O 路径、页缓存管理和资源清理。
核心文件索引
二、数据结构基础
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] 的值含义:
三、swapon:激活交换空间

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)

七、缺页处理: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:关闭交换空间

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,无需额外处理
// 特别适合内存充足但偶发缺压的系统
十四、总结

交换空间的完整生命周期可以概括为以下五个阶段:
整个系统通过精妙的锁层次、Per-CPU 缓存和 Frontswap 机制,在保证正确性的同时实现了高性能的交换空间管理。