从 zpool 解压页面到 DRAM 的详细过程
1. 触发页面解压
当系统访问一个被 zswap
缓存并压缩存储在 zpool
中的页面时,可能会触发以下情况之一:
- 进程访问已被交换到
zswap
的页面,触发 缺页异常 (Page Fault)。 swapin
机制被触发,从zswap
读取该页面的压缩数据。- 内存管理子系统决定将
zswap
内存中的数据重新加载到 DRAM。
2. 在 zswap
中查找压缩页面
zswap
维护一个 红黑树 (rb_tree) 来存储交换缓存的映射关系。
当需要访问某个 swap entry
时,zswap
通过 zswap_rb_search()
在 rb_tree
中查找该页面的压缩数据。
struct zswap_entry *zswap_rb_search(swap_entry_t entry);
- 如果找到该条目,说明该页面在
zpool
里有压缩存储。 zswap_entry
结构体包含handle
,用于索引zpool
里的数据。
3. 读取 zpool
里的压缩数据
zpool
负责管理 zswap
使用的压缩数据。
zswap_entry->handle
指向zpool
里的数据。zpool
后端可以是zbud
或zsmalloc
。- zbud: 一个物理页存储 2 个压缩页。
- zsmalloc: 允许多个小页面共享一个物理页。
struct zswap_entry {
struct rb_node rbnode;
unsigned long handle; // 指向 zpool 里的压缩数据
};
通过 zpool_malloc()
访问压缩数据:
void *compressed_data = zpool_malloc(zswap_pool, handle);
4. 分配 DRAM 物理页
在解压之前,系统需要为该页分配 DRAM 物理页。
alloc_page(GFP_KERNEL)
申请一个 4KB 页框。- 在 NUMA 体系结构中,可能需要从
node0
(DRAM) 分配。
struct page *page = alloc_page(GFP_KERNEL);
5. 解压数据
zswap
读取 zpool
里的压缩数据,并调用相应的解压缩函数,例如 LZ4_decompress_safe()
或 zstd_decompress()
。
LZ4_decompress_safe(compressed_data, output_page, compressed_size, PAGE_SIZE);
代码路径:
zswap_frontswap_load()
处理前端交换加载。zpool_malloc()
读取zpool
数据。zswap_entry_get()
获取handle
关联的压缩数据。zswap_decompress()
进行解压。
6. 更新页表
解压后的数据被写入 DRAM 物理页后,需要更新 页表 (page table),将该物理页映射回用户进程的地址空间。
- 通过
set_pte_at()
或pte_mkpresent()
让该页重新可用。
set_pte_at(mm, address, ptep, pte);
7. 释放 zpool
里的压缩数据
如果该页面不再需要压缩存储,则可以从 zswap
中删除:
zswap_rb_erase(entry);
zpool_free(entry->handle);
8. 关键数据结构
zswap_entry 结构体
struct zswap_entry {
struct rb_node rbnode; // 红黑树节点
unsigned long handle; // zpool 里的数据索引
...
};
zpool API 相关函数
void *zpool_malloc(struct zpool *pool, size_t size);
void zpool_free(struct zpool *pool, void *ptr);
完整流程总结
- 进程访问
zswap
里的压缩页面,触发 Page Fault。 zswap_rb_search()
在rb_tree
里查找zswap_entry
。zpool_malloc()
读取zpool
里的压缩数据。alloc_page(GFP_KERNEL)
分配 DRAM 物理页。zswap_decompress()
通过 LZ4/ZSTD 解压数据。set_pte_at()
更新页表,映射到用户进程地址空间。zpool_free()
释放zpool
里的压缩数据(可选)。