如何维护映射为非透明大页的Linux folio的rmap及相关实现疑问
概述
Linux 6.0版本中,struct folio的文档指出:
它可以被映射到用户空间的任意页偏移地址……
但实际使用中发现,似乎只有当folio用作透明大页(THP)时,才能正确维护其页面的rmap——这要求用户空间地址也对齐到THP大小。那么对于非THP用途的folio,是否存在正确维护rmap的方法?
详细说明
folio是一组物理、虚拟和逻辑上连续的字节,大小为2的幂(来自内核文档)。struct folio是复合页的头页。folio的设计初衷是区分针对复合页头页的API和针对普通页的API,大页是其典型应用场景之一。
举个例子:一个order为2的folio,包含物理页pfn、pfn+1、pfn+2、pfn+3,其物理地址和内核虚拟地址都会对齐到4页边界(pfn & 0x3 == 0)。
根据文档描述,内核允许将folio映射到未对齐的用户空间虚拟地址(比如vpn、vpn+1、vpn+2、vpn+3,其中vfn & 0x3 != 0),这说明folio并非仅用于THP。我的使用场景中,该folio不是THP,也不符合2MB或1GB大页的order要求,但映射时需要正确维护rmap。
然而当前的rmap代码似乎仅支持THP用途的folio,例如page_add_new_anon_rmap函数中的代码片段:
void page_add_new_anon_rmap(struct page *page, struct vm_area_struct *vma, unsigned long address) { const bool compound = PageCompound(page); int nr = compound ? thp_nr_pages(page) : 1; if (compound) { VM_BUG_ON_PAGE(!PageTransHuge(page), page); .... } }
问题
- 当前Linux的
folio和rmap实现是否仅针对THP? - 是否存在一种“正确”的方式来映射非THP用途的folio同时维护
rmap?
回答
问题1:folio和rmap实现是否仅针对THP?
不是。struct folio的设计目标是统一处理所有复合页场景,THP只是其中一个典型应用。早期基于struct page的复合页API存在接口模糊的问题,folio的引入就是为了明确区分“复合页头页”与“普通页”的操作逻辑,覆盖的场景包括但不限于:
- 透明大页(THP)
- 块设备的bio聚合页
- 文件系统的多页缓存(如XFS的大页缓存)
- 自定义复合页使用场景
但rmap子系统的部分API确实与THP强绑定,比如你提到的page_add_new_anon_rmap——这个函数本身就是为匿名THP设计的,因此会强制检查PageTransHuge标记。普通复合页(非THP)需要使用通用复合页rmap接口,而非THP专属函数。
问题2:如何正确映射非THP的folio并维护rmap?
针对非THP的folio,需避开THP专属rmap API,改用通用复合页或folio专属接口,具体操作如下:
区分匿名页与文件页场景
- 匿名folio:不要使用
page_add_new_anon_rmap,可直接调用folio_add_anon_rmap(Linux 6.0+提供的folio专属API),该函数会自动遍历folio的所有子页并添加rmap条目;也可以手动遍历每个子页,分别调用page_add_new_anon_rmap。 - 文件页folio:使用
folio_add_file_rmap或针对子页调用page_add_file_rmap,根据映射需求选择。
- 匿名folio:不要使用
确保folio标记正确
- 非THP的folio不要设置
PageTransHuge标记,避免触发THP相关的rmap校验逻辑。 - 确保
folio_order等元数据正确配置,让rmap子系统能识别folio的大小。
- 非THP的folio不要设置
地址映射的处理
即使用户空间地址未对齐,只要为folio的每个子页正确关联对应的用户虚拟地址,rmap就能正常工作。比如order=2的folio映射到vpn起始地址时,需为vpn、vpn+1、vpn+2、vpn+3四个虚拟地址分别对应folio的四个子页完成rmap关联(使用folio_add_anon_rmap可自动完成此过程)。优先使用folio专属rmap API
Linux 6.0及以后版本提供了一批专为folio设计的rmap函数,无需手动处理子页,同时规避THP限制:folio_add_anon_rmap:为匿名folio添加rmapfolio_add_file_rmap:为文件页folio添加rmapfolio_remove_rmap:移除folio的rmap
示例代码(匿名folio场景):
struct folio *folio = folio_alloc_order(GFP_KERNEL, 2); // 假设已将folio映射到用户空间vpn起始地址 folio_add_anon_rmap(folio, vma, vpn);
需要注意的是,非THP的folio在rmap中会被当作多个独立页处理,每个子页的rmap条目单独维护——这与THP将整个folio作为单个大页维护rmap的逻辑不同,但这是符合非THP场景的预期行为。
内容的提问来源于stack exchange,提问作者TSG

