struct ptdesc的定义在 Linux 内核的include/linux/mm_types.h文件中(早期版本曾放在include/linux/pgtable.h)。它的设计目标是将页表元数据从struct page中拆分出来,目前通过完全覆盖(overlay)struct page的内存布局来实现。
定义代码
根据内核补丁(以 v6 版本为例),其完整定义如下:
struct ptdesc { unsigned long __page_flags; union { struct rcu_head pt_rcu_head; struct list_head pt_list; struct { unsigned long _pt_pad_1; pgtable_t pmd_huge_pte; }; }; unsigned long _pt_s390_gaddr; union { struct mm_struct *pt_mm; atomic_t pt_frag_refcount; unsigned long _pt_pad_2; }; #if ALLOC_SPLIT_PTLOCKS spinlock_t *ptl; #else spinlock_t ptl; #endif unsigned int __page_type; atomic_t _refcount; #ifdef CONFIG_MEMCG unsigned long pt_memcg_data; #endif };核心字段说明
| 字段 | 说明 |
|---|---|
__page_flags | 与page->flags相同,页表场景下通常未使用 |
pt_rcu_head | 用于延迟释放页表页的 RCU 回调(与pt_list共用内存) |
pt_list | 串联已使用的页表,用于 s390 和 x86 |
pmd_huge_pte | 受ptl保护,用于透明大页(THP)场景 |
_pt_s390_gaddr | 仅 s390 架构使用,用于 gmap 地址追踪 |
pt_mm | 仅 x86 架构使用,用于 PGD(页全局目录) |
pt_frag_refcount | 仅 PowerPC 和 s390 使用,用于碎片化页表追踪 |
ptl | 页表锁,保护页表操作(ALLOC_SPLIT_PTLOCKS控制是指针还是内嵌) |
_refcount | 引用计数,与page->_refcount相同,s390 页表使用 |
pt_memcg_data | 内存 cgroup 数据,追踪页表的内存使用 |
关键设计约束
定义末尾有一条静态断言,这是整个设计的核心约束:
static_assert(sizeof(struct ptdesc) <= sizeof(struct page));
这意味着ptdesc的大小不能超过struct page,因为当前它完全复用struct page的内存空间。同时,代码通过TABLE_MATCH宏断言关键字段的偏移量与struct page中对应字段完全一致,确保两种类型可以安全地相互转换。
在NOMMU配置下,struct ptdesc的定义会被移到mm_types.h,因为spinlock_t和struct page的定义顺序会影响编译。
核心定位
struct ptdesc目前完全覆盖(overlay)在struct page之上,也就是说它暂时复用struct page的内存空间,但以独立的类型存在。它的长期目标是让页表可以独立于struct page进行动态分配,从而进一步精简struct page结构。
与pgtable_t的关系
在大多数架构上,pgtable_t原本就是struct page *的 typedef。struct ptdesc的引入是为了逐步用struct ptdesc *替代pgtable_t,使页表描述符的类型更加明确。
不过,这一转换面临架构差异的挑战:在 s390、PowerPC、sparc 和 m68k(除 SUN3)上,pgtable_t被定义为pte_t *,因为每个页表可能小于一个PAGE_SIZE,无法直接映射到单个struct page。因此,struct ptdesc的推广需要各架构的配合。
在内存管理中的角色
struct ptdesc是页表操作(分配、释放、遍历)的核心载体。在___pmd_free_tlb中,virt_to_ptdesc(pmd)就是将页表页的虚拟地址转换为struct ptdesc指针,然后通过tlb_remove_ptdesc加入延迟释放队列。这种设计使得页表的生命周期管理可以与struct page解耦,为未来更灵活的页表内存管理铺平道路。