系列:[PATCH v2 00/69] mm: Generalize HVO for HugeTLB and device DAX作者: Muchun Song版本: v2(47 个 patch)
HVO(Hugepage Vmemmap Optimization)是内核中一项关键的 struct page 内存节省技术。当使用大页(如 HugeTLB 的 1GB 页或 device DAX 的 2MB/1GB 映射)时,每个物理页都需要一个 struct page(通常 64 字节)来管理,但对于大页的大部分 tail page,其中的大量字段从未被使用。HVO 通过复用(remap)一个共享的 vmemmap tail page 来消除这些冗余的 struct page 映射,从而节省大量物理内存。
历史上,HVO 是作为 HugeTLB 子系统的一个专属优化实现的。当 device DAX 需要类似的 vmemmap 优化时,它开发了一套并行但独立的处理路径。作者在封面信中明确指出问题的本质:"The existing code grew around the original HugeTLB-specific HVO path, while device DAX developed similar but separate vmemmap optimization handling. As a result, the current implementation carries duplicated logic, boot-time special cases, and subsystem-specific interfaces around what is fundamentally the same sparse-vmemmap optimization."
这导致了几层问题:首先,HVO 的初始化时机严重受限于启动顺序——原来的 HVO 必须在 zone 信息可用后才能进行,但 bootmem 大页在此之前就已经分配;其次,HugeTLB 和 DAX 各自维护了不同的 tail page 查找、映射和管理逻辑;第三,powerpc 架构有专门的一整套 vmemmap compound page 填充代码,与通用路径完全不同。
SPARSEMEM_VMEMMAP_PREINIT 和 pre-HVO 路径在 zone 尚未初始化时就尝试设置优化映射,导致启动 panic(Patch 1 修复的 CONFIG_DEBUG_VM 启动 panic)vmemmap_can_optimize() 分布在 HugeTLB、powerpc、通用代码三处,且语义不一致该系列是一个大型重构系列,从 69 个初始 patch 对 vmemmap 优化的整个生命周期进行了系统性的重新设计。工作分为以下几个层次:
第一层:基础修复(Patches 1-6)
在重构之前修复了 3 个关键 bug:
CONFIG_DEBUG_VM 下的 HVO 启动 panic:commit 622026e87c40 后 HVO 使用 per-zone 共享 tail page(zone->vmemmap_tails[]),但 bootmem HugeTLB folio 在 gather_bootmem_prealloc() 中准备时,这些共享 tail page 尚未初始化(原在 hugetlb_vmemmap_init() 中初始化)。修复的方式是将共享 tail page 的初始化提前到 gather_bootmem_prealloc() 中,确保在 bootmem hugepage 被访问前可用__hugetlb_vmemmap_optimize_folios() 中的逻辑错误addr_pfn 追踪错误第二层:启动顺序重排(Patches 7-11)
这是使统一框架成为可能的关键基础工作。原有的启动顺序中,sparse_init() 在 zone 初始化之前运行,导致 vmemmap 优化无法在此时获取 zone 信息。该系列将:
sparse_init() 延迟到 zone 初始化之后("Defer sparse_init() until after zone initialization")sparse_init() 中移除 set_pageblock_order() 调用sparse_vmemmap_init_nid_late() 合并到 sparse_init_nid() 中这确保了当 vmemmap 优化基础设施运行时,zone 和 pageblock 状态已完全可用。
第三层:通用 section-based vmemmap 优化基础设施(Patches 12-27)
这是整个系列的核心创新。引入了基于 mem_section 的通用 vmemmap 优化框架:
在 struct mem_section 中追踪 compound page order:新增 section_order 字段,由 vmemmap 优化用户(HugeTLB、DAX)在创建大页时设置。这使得 sparse-vmemmap 代码无需了解是大页子系统还是 DAX 在使用优化——统一通过 section 元数据来判断。
vmemmap_page_optimizable() 函数:通用的判断 struct page 是否落在共享 tail vmemmap 范围内的辅助函数:
staticinlineboolvmemmap_page_optimizable(const struct page *page)
{
unsignedlong pfn = page_to_pfn(page);
unsignedlong nr_pages = 1UL << pfn_to_section_order(pfn);
if (!is_power_of_2(sizeof(struct page)))
returnfalse;
return (pfn & (nr_pages - 1)) >= OPTIMIZED_FOLIO_VMEMMAP_NR_STRUCT_PAGES;
}
该函数利用 pfn_to_section_order() 获取该 section 对应的 compound order,判断当前 pfn 是否落在头部的 OPTIMIZED_FOLIO_VMEMMAP_NR_STRUCT_PAGES 范围内(这部分需要实际映射的 struct page),还是落在尾部共享范围内。
memmap_init_range() 跳过共享 tail page:在启动时的 memmap_init_range() 中,如果检测到 vmemmap_page_optimizable() 返回 true,则跳过该范围的 struct page 初始化,仅设置 pageblock migratetype。这既避免了冗余初始化工作,也防止覆盖已共享的 vmemmap tail page 内容。
vmemmap_shared_tail_page() 通用辅助函数:提供一个通用的共享 tail page 获取接口,替代原来 HugeTLB 和 powerpc DAX 各自实现的 tail page 查找逻辑。该函数根据 order 从 per-zone 的预分配数组中找到对应的共享 tail page。
**引入 CONFIG_SPARSEMEM_VMEMMAP_OPTIMIZATION**:新的通用 Kconfig 选项,替代原来的 CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP。当 HugeTLB 或 DAX 中任一需要 vmemmap 优化时自动选中。该选项控制通用基础设施的编译,而非绑定到特定子系统。
第四层:HugeTLB 切换至通用框架(Patches 28-36)
将 HugeTLB 从原有的专用 HVO 路径切换到新的 section-based 通用优化路径:
vmemmap_populate_hvo() 等 HugeTLB 专用的 pre-HVO 函数section_set_order_range() 设置 section order,通用 sparse-vmemmap 填充路径会自动完成共享 tail page 的分配和映射HUGETLB_VMEMMAP_RESERVE_SIZE 等)替换为通用定义(OPTIMIZED_FOLIO_VMEMMAP_SIZE 等)HUGE_BOOTMEM_HVO 和 HUGE_BOOTMEM_CMA 特殊标记,简化启动路径第五层:device DAX 切换至通用框架(Patches 37-44)
将 device DAX 的 vmemmap 优化路径统一到相同的基础设施:
vmemmap_shared_tail_page() 获取共享 tail page,不再自己走页表查找memremap.c 中通过设置 section order 来标记 DAX 大页的 vmemmap 优化vmemmap_populate_compound_pages() 变为 static,不再有外部调用者vmemmap_can_optimize(),统一使用通用检查第六层:健壮性增强(Patches 45-47)
vmemmap_wrprotect_hvo() 将共享的 tail page 映射设为 read-only,防止意外写入破坏多个大页共享的 struct page 数据vmemmap_can_optimize() 和 section order 元数据vmemmap_wrprotect_hvo() 的 HugeTLB 专用版本文档重写:
将 Documentation/mm/vmemmap_dedup.rst 从 HugeTLB-only 视角重写为统一的 HVO 设计文档,同时删除 powerpc 专用的 Documentation/arch/powerpc/vmemmap_dedup.rst。
作者明确表示:"I have only built and tested this series on x86. I do not currently have a powerpc test environment, so any testing or feedback on powerpc would be much appreciated."
作者未提供性能 benchmark 数据。从代码逻辑推断的预期收益:
SPARSEMEM_VMEMMAP_PREINIT 整个概念和相关的 pre-HVO 路径,启动时的 vmemmap 处理逻辑更清晰CONFIG_DEFERRED_STRUCT_PAGE_INIT 禁用时,优化的 struct page 可以跳过 memmap_init() 初始化,减少启动开销系列:[PATCH 0/3] kasan: hw_tags: some micro-optimizations作者: Dev Jain版本: v1(3 个 patch)
硬件标签化 KASAN(KASAN_HW_TAGS,基于 Arm MTE 即 Memory Tagging Extension)使用硬件内存标签来检测内存越界访问和 use-after-free 错误。与软件 KASAN(Generic/Generic SW_TAGS)不同,硬件 KASAN 通过在分配时给内存区域设置特定标签(tag),在访问时由硬件自动检查标签匹配。
在硬件 KASAN 的工作流中,内存的 poison(投毒,设置为无效标签)和 unpoison(解毒,设置为有效标签)是关键操作。但当前的实现中存在若干不必要的重复操作——在某些路径中,内存被 unpoison 后又立即被 repunson,或在 buddy allocator 中 unpoison 的页面在 slab 分配器中立即再次 poison。这些冗余操作增加了不必要的指令开销。
作者在 cover letter 中简洁地描述了优化的目标:"Patch 1 uses GFP_SKIP_KASAN to skip unpoisoning of a slab page in the page allocator, since slab allocator itself poisons the slab page immediately. Patch 2 and 3 remove wasted work while poisoning the tail end of the vmalloc/slab allocation."
kasan_poison_slab() 再次 poison 整个页面。两次操作结果抵消,unpoison 是浪费的object + allocation_size 开始 poison redzone 区域。对于硬件标签 KASAN,free object 和 redzone 使用相同的无效标签(KASAN_TAG_INVALID),因此 unpoison 整个 object 后再 poison redzone 是多余的——只需 unpoison 到分配大小即可Patch 1:跳过 slab 页面的 buddy unpoison
利用现有的 __GFP_SKIP_KASAN 标志(此前已用于 vmalloc 路径),在 slab 分配器请求新页面时设置该标志:
// mm/slub.c: alloc_slab_page()
flags |= __GFP_SKIP_KASAN;
这使得 alloc_frozen_pages_nolock() 从 buddy 获取页面后跳过 kasan_unpoison_pages() 调用。关键前提是 slab 分配器在获取页面后会立即调用 kasan_poison_slab() 对整个 slab 页面进行 poison —— 因此 buddy 的 unpoison 在语义上是完全冗余的。
该 patch 还需要放宽 alloc_frozen_pages_nolock_noprof() 中对 gfp flag 的限制,原本只允许 __GFP_ACCOUNT,现在也需要允许 __GFP_SKIP_KASAN。
Patch 2:优化 tag-based KASAN 的 kmalloc redzone 处理
这是三个 patch 中最复杂的优化,涉及 kasan 和 slab 接口的多个修改点:
kasan_has_tag_based_kmalloc_redzones() 辅助函数:staticinlineboolkasan_has_tag_based_kmalloc_redzones(void)
{
return kasan_enabled() &&
(IS_ENABLED(CONFIG_KASAN_SW_TAGS) || kasan_hw_tags_enabled());
}
该谓词的意义在于:SW_TAGS 和 HW_TAGS 都使用单一的无效标签值(而非 Generic KASAN 使用的区分 shadow byte 值)来表示 free 内存和 redzone。作者在注释中解释:"For tag-based modes, kmalloc redzones all use the same invalid tag." 因此无需在 unpoison 后再单独 poison redzone。
修改 kasan_slab_alloc() 接口:增加 size_t size 参数,传递实际分配大小(而不仅是 cache 的 object_size)。
unpoison_slab_object() 新增 slab_unpoison_size() 逻辑:
staticinlinesize_tslab_unpoison_size(struct kmem_cache *cache, size_t size)
{
if (kasan_has_tag_based_kmalloc_redzones() && is_kmalloc_cache(cache))
returnmin_t(size_t, size, cache->object_size);
return cache->object_size;
}
对于 tag-based KASAN 的 kmalloc cache:只 unpoison 到实际分配的 size 字节,后面的 redzone 区域保持 poison 状态。对于 Generic KASAN(使用不同的 shadow byte 值区分 free 和 redzone)以及非 kmalloc cache:保持原有行为,unpoison 整个 object。
__kasan_kmalloc() 优化:对于 tag-based KASAN,不再调用 poison_kmalloc_redzone() 重新 poison 尾部,因为尾部从未被 unpoison。只需保存 alloc info。
slab_post_alloc_hook() 修改:对于 tag-based KASAN 的 kmalloc cache,将 zero_size 设为 orig_size(实际分配大小)而非整个 object size,确保 memset(0) 不会触及 redzone 区域。
Patch 3:优化 vmalloc redzoning
修改 __kasan_unpoison_vmalloc() 的执行顺序。原实现:
1. kasan_unpoison(start, size) // unpoison 整个分配区域+部分redzone
2. kasan_poison(redzone_start, ...) // 再 poison redzone
优化后:
// 1. 计算 redzone 的起始和大小
redzone_start = round_up((unsignedlong)start + size, KASAN_GRANULE_SIZE);
redzone_size = round_up(redzone_start, PAGE_SIZE) - redzone_start;
// 2. 仅 unpoison 分配区域(到 redzone_start 为止)
kasan_unpoison(start, redzone_start - (unsignedlong)start, ...);
// 3. poison redzone
if (redzone_size)
kasan_poison((void *)redzone_start, redzone_size, KASAN_TAG_INVALID, ...);
原始代码先 unpoison 了 start 到 start + size(可能跨越 tag granule 边界覆盖部分 redzone),然后在第二步才纠正。优化后直接精确到 redzone 边界,避免了中间的 unpoison + re-poison 往返。
作者未提供性能数据。从代码逻辑推断的预期收益:
kasan_unpoison_pages() 调用能减少指令数和 cache misspoison_kmalloc_redzone() 中的标签设置开销及相关的 kasan_save_alloc_info() 重复调用。在 kmalloc 密集型工作负载中,每次分配都可节省若干指令系列:[PATCH v2 0/3] mm/hmm: Add mmap lock-drop support for userfaultfd-backed mappings作者: Stanislav Kinsburskii版本: v2(3 个 patch)
HMM(Heterogeneous Memory Management)是 Linux 内核中用于异构内存管理的基础框架,允许设备驱动(如 GPU 驱动、FPGA 驱动)通过 hmm_range_fault() 将进程虚拟地址空间中的页面填充(fault in)并映射到设备页表中。这个框架在 GPU 计算(如 NVIDIA 的 nouveau 驱动、AMD GPU 驱动)和 CXL 设备等场景中广泛使用。
然而,hmm_range_fault() 的一个根本性限制是它在整个调用期间持有 mmap_read_lock,这与其调用 handle_mm_fault() 处理缺页的方式直接冲突。作者在封面信中解释了核心矛盾:"Some page fault handlers -- most notably userfaultfd -- require the mmap lock to be released so that userspace can resolve the fault. The current HMM interface never sets FAULT_FLAG_ALLOW_RETRY, making it impossible to fault in pages from userfaultfd-registered regions."
userfaultfd 是 Linux 的 user-space page fault 处理机制,允许用户态程序接管缺页处理(例如虚拟机迁移中的 post-copy 实时迁移、数据库的内存管理、容器检查点/恢复)。当 handle_mm_fault() 遇到 userfaultfd 注册的 VMA 时,会返回 VM_FAULT_RETRY 或 VM_FAULT_COMPLETED 并释放 mmap_lock,等待用户态程序解决缺页。但 HMM 框架从未设置 FAULT_FLAG_ALLOW_RETRY,意味着它无法从 userfaultfd 映射中获取页面。
hmm_range_fault() 永远不在 fault flags 中设置 FAULT_FLAG_ALLOW_RETRY,而 userfaultfd 的缺页处理要求该 flaghandle_mm_fault() 导致锁定层级问题:原有的 hmm_vma_fault() 在 page table walk 的中间层(可能持有 pte spinlock 或 hugetlb_vma_lock)调用 handle_mm_fault(),如果 mmap lock 在此被释放,walk 框架的后续 unlock 操作会因为 VMA 可能已被释放而导致 use-after-free该系列通过 3 个 patch 实现了 HMM 的 mmap lock-drop 支持,遵循了 mm/gup.c 中 get_user_pages_remote() 已经验证的 int *locked 模式。
Patch 1:将 page fault 处理移出 walk callback(重构)
这是使 Patch 2 成为可能的基础重构。核心思路是将 hmm_range_fault() 的职责清晰地分为两层:
hmm_vma_walk_pmd()、hmm_vma_walk_pud()、hmm_vma_walk_hugetlb_entry() 等 walk callback 不再直接调用 handle_mm_fault(),而是通过新的 hmm_record_fault() 在 struct hmm_vma_walk 中记录需要 fault 的范围和类型,然后返回私有的 HMM_FAULT_PENDING(-EAGAIN)哨兵值。staticinthmm_record_fault(unsignedlong addr, unsignedlong end,
unsignedint required_fault,
struct mm_walk *walk)
{
structhmm_vma_walk *hmm_vma_walk = walk->private;
hmm_vma_walk->last = addr;
hmm_vma_walk->end = end;
hmm_vma_walk->required_fault = required_fault;
return HMM_FAULT_PENDING;
}
hmm_range_fault() 的外层循环在收到 HMM_FAULT_PENDING 后,调用新的 hmm_do_fault() 函数。此时 walk_page_range() 已经返回,pte spinlock 和 hugetlb_vma_lock 均已释放,仅持有 mmap_read_lock。hmm_do_fault() 在干净的锁定上下文中调用 handle_mm_fault()。这一拆分带来的直接好处包括:
Patch 2:添加 hmm_range_fault_unlockable()(核心功能)
这是功能新增的核心 patch。新增的 hmm_range_fault_unlockable() 接受一个 int *locked 参数,遵循 GUP 的语义模式:
*locked = 1 并持有 mmap_read_lock*locked == 1:锁仍被持有,返回值语义与 hmm_range_fault() 相同*locked == 0:mmap lock 在 fault 过程中被释放了(handle_mm_fault() 返回了 VM_FAULT_RETRY 或 VM_FAULT_COMPLETED),调用者需要重新获取锁并使用新的 notifier 序列号重新开始整个 walk实现细节:
hmm_do_fault() 扩展:当 locked 非 NULL 时,设置 FAULT_FLAG_ALLOW_RETRY | FAULT_FLAG_KILLABLE。当 handle_mm_fault() 返回 VM_FAULT_RETRY 或 VM_FAULT_COMPLETED 时,设置 *locked = 0 并返回私有哨兵值 HMM_FAULT_UNLOCKED(-ENOLCK)。
**outer loop 处理 HMM_FAULT_UNLOCKED**:
if (ret == HMM_FAULT_PENDING) {
ret = hmm_do_fault(mm, &hmm_vma_walk);
if (ret == HMM_FAULT_UNLOCKED) {
if (fatal_signal_pending(current))
return -EINTR;
return0; /* caller must restart */
}
}
当收到 HMM_FAULT_UNLOCKED 时,检查是否有 fatal signal pending。有则返回 -EINTR;否则返回 0 表示调用者需要重新获取 lock 并重试。
hmm_range_fault() 重构为薄 wrapper:inthmm_range_fault(struct hmm_range *range)
{
return hmm_range_fault_unlockable(range, NULL);
}
传入 NULL 的 locked 参数确保行为与原有实现完全一致,所有现有调用者均不受影响。
Documentation/mm/hmm.rst 新增了完整的调用模式示例代码,展示了驱动如何使用 hmm_range_fault_unlockable() —— 包括 notifier 序列号管理、锁的重新获取、以及 goto again 重试模式。作者在 cover letter 中总结了 hugetlb 的兼容性:"Hugetlb regions therefore participate in the unlockable path uniformly with PTE- and PMD-level mappings; no special case is required."
Patch 3:userfaultfd + HMM unlockable 路径的 selftest
添加了一套完整的自测,验证 hmm_range_fault_unlockable() 在 userfaultfd 映射上的正确性:
lib/test_hmm.c 中新增 dmirror_range_fault_unlockable()、dmirror_fault_unlockable()、dmirror_read_unlockable() 以及新的 HMM_DMIRROR_READ_UNLOCKABLE ioctl(0x09)tools/testing/selftests/mm/hmm-tests.c 中新增测试用例:创建匿名 mmap 区域 → 通过 UFFDIO_REGISTER_MODE_MISSING 注册到 userfaultfd → 启动 handler 线程用已知模式(0xAB)通过 UFFDIO_COPY 解决 page fault → 通过 unlockable ioctl 触发 HMM fault → 验证数据一致性作者未提供性能数据。从代码逻辑推断的预期收益:
int *locked 模式已在 get_user_pages_remote() 中广泛使用和验证,HMM 采用相同模式降低了驱动开发者的学习曲线hmm_range_fault() 的语义和行为完全不变,所有现有调用者无需任何修改系列:[PATCH v7 0/6] mm/memory-failure: add panic option for unrecoverable pages作者: Breno Leitao版本: v7(6 个 patch)
硬件内存错误(如多比特 ECC 错误)命中内核页时,memory failure 处理器的默认行为是设置 PG_hwpoison 标志、记录事件日志,然后内核继续运行。但坏页仍可被内核访问,导致两种严重后果:"silent data corruption"——损坏数据被静默使用并可能传播到持久化存储;"delayed unattributable crash"——内核在数秒到数分钟后在完全无关的代码路径崩溃。在大规模集群中,这种延迟崩溃的根因分析需要大量工程投入:kdump 配置下,原始错误上下文(故障 PFN、MCE/GHES 记录、页面状态)在延迟崩溃发生时早已丢失。
作者明确指出:"In a large fleet that delayed, unattributable crash turns into significant engineering effort to root-cause."
PG_hwpoison 设置后内核继续运行,损坏数据仍可访问,风险为静默数据损坏或延迟崩溃本系列方案分为三个层次:
第一层:清理死代码(Patch 1)
删除 error_states[] 表中第一项 { reserved, reserved, MF_MSG_KERNEL, me_kernel } 及其对应的 me_kernel() 函数。作者论证了该条目不可达:memory_failure() 到达 identify_page_state() 之前必须先通过 get_hwpoison_page() 返回 1,而 get_any_page() 中 HWPoisonHandlable() 拒绝 PG_reserved 页面,因此 PG_reserved 页在 identify_page_state() 之前就以 -EBUSY/-EIO 失败退出。保留 MF_MSG_KERNEL 枚举值用于后续复用。无功能改变。
第二层:区分不可恢复内核页(Patch 2-4)
Patch 2 是关键的重构:get_any_page() 原来将三种不同失败模式统一折叠为 -EIO 返回值:
!count_increased 路径)HWPoisonHandlable() 拒绝(经 shake_page() 重试耗尽后)HWPoisonHandlable() 拒绝(count_increased=true 路径)现在将它们区分:前两种保持返回 -EIO(无法证明页面永久属内核),第三种返回新的错误码 -ENOTRECOVERABLE——"the caller's reference is structural evidence that the page is owned by the kernel"。
Patch 3 在 memory_failure() 的 get_hwpoison_page() 返回码 switch 分支中,将 -ENOTRECOVERABLE 映射为 MF_MSG_KERNEL,使得 PG_reserved 页和 slab/vmalloc/page table/kernel stack 等内核页能被统一标记。
Patch 4 加入 PG_reserved 短路检查:在调用 get_hwpoison_page() 之前先判断 PageReserved(),避免对确定不可恢复的 reserved 页进行徒劳的 shake_page() 重试。
第三层:panic 机制(Patch 5-6)
新增 sysctl vm.panic_on_unrecoverable_memory_failure,默认值为 0(保持现有行为)。当值为 1 时,action_result() 中调用 panic_on_unrecoverable_mf() 判断是否应触发 panic。
Panic 判定条件采用保守策略,只覆盖 MF_MSG_KERNEL + MF_IGNORED 组合:
MF_MSG_GET_HWPOISON:可能由瞬时 refcount 竞争触发,panic 会误杀可恢复的 userspace 页面MF_MSG_KERNEL_HIGH_ORDER:同理可被瞬时分配竞争触发MF_MSG_UNKNOWN:页面状态无法分类不能作为 panic 决策基础文档(Patch 6)详细描述了四个使用场景:大规模集群延迟崩溃根因分析、kdump 环境保留原始故障上下文、高可用集群快速确定节点故障、内核/平台开发者使用 mce-inject 调试时立即可见问题。
作者未提供量化的 benchmark 数据。从代码逻辑推断的预期收益:
MF_MSG_KERNEL + MF_IGNORED)将误panic 风险降至最低系列:[PATCH RFC V2 00/14] arm64/mm: Enable 128 bit page table entries作者: Anshuman Khandual, Linu Cherian版本: RFC V2(14 个 patch)
FEAT_D128 是 ARMv9.3 引入的可选架构特性,新增了 VMSAv9-128 地址翻译系统。在此之前,arm64 平台始终使用 VMSAv8-64 翻译系统,页表描述符(page table descriptor)固定为 64 位宽。64 位页表项限制了物理地址空间和虚拟地址空间的进一步扩展能力,也限制了可用于硬件和软件管理的 MMU feature bit 数量。FEAT_D128 将页表项扩展至 128 位,使得平台可以支持更大的地址空间,同时为更多 MMU 管理功能位提供物理承载空间。
开启 FEAT_D128 后,arm64 平台可以同时拥有两套翻译系统——VMSAv8-64 和 VMSAv9-128——由内核通过 CONFIG_ARM64_D128 配置项选择启用哪一种。
READ_ONCE() 直接读取页表项,这在 64 位系统上是原子安全的,但 128 位页表项无法用单个 64-bit load 指令原子读取,需要平台特定的访问器来保证单次拷贝原子性(single copy atomicity)vma->vm_page_prot 当前通过 READ_ONCE()/WRITE_ONCE() 进行无锁读写,D128 下 pgprot_t 升级为 128 位后这些宏不再安全__print_bad_page_map_pgtable() 等调试路径硬编码了 %08llx 格式,无法适配 128 位 PTE 值的打印本系列的分层策略。
通用 mm 基础设施(Patch 1-2):
抽象 pxd_val() 打印:引入 pxdval_t 类型——当编译器支持 __SIZEOF_INT128__ 时定义为 u128,否则保持为 unsigned long long。同时引入 __PRIpxx 格式宏和 __PRIpxx_args() 参数宏,使得打印 PTE 值的代码可以通过平台重定义这些宏来适配不同位宽。__print_bad_page_map_pgtable() 和 print_bad_page_map() 中的变量类型及 pr_alert() 格式串全部切换为新的抽象。
抽象 vm_page_prot 访问:新增 pgprot_read() 和 pgprot_write() 内联函数,默认实现为 READ_ONCE() 和 WRITE_ONCE(),但通过 #ifndef 守卫允许平台按需覆盖。mm/memory.c、mm/huge_memory.c、mm/migrate.c、mm/mmap.c 中所有对 vma->vm_page_prot 的直接 READ_ONCE()/WRITE_ONCE() 访问全部替换为这两个新访问器。
arm64 平台侧重构(Patch 3-13):
arm64 将各级页表读取统一路由到 pxxval_get() 宏——在未开启 D128 时等价于 READ_ONCE()。定义了 pmdp_get()、pudp_get()、p4dp_get()、pgdp_get() 四个级别特定的访问器,并替换了 arm64 页表代码中所有对 READ_ONCE() 的直接调用。对称地定义了 pxxval_set() 用于写入,pxxval_cmpxchg_relaxed() 和 pxxval_xchg_relaxed() 用于原子操作,统一路由所有页表原子操作。新增 TLB 操作抽象层 tlbi_op 为后续引入 TLBIP 指令做准备。修复 5 级 fixmap 支持以适配 D128 下可能增加的页表层数。
FEAT_D128 核心实现(Patch 14):
这是系列的核心 patch(374 行新增),在 CONFIG_ARM64_D128 下:
PTE 位布局完全重定义:在 pgtable-hwdef.h 中新增整套 D128 格式的硬件定义。所有 PTE 属性位映射到 128 位空间中的新位置:PTE_VALID 仍在 bit[0],PTE_AF 在 bit[10],PTE_DBM 移至 bit[116],PTE_USER/PTE_PXN/PTE_UXN 移至 bit[115]/[117]/[118](通过 Permission Indirection Extension 的 PIIndex 机制),SW 定义位 PTE_DIRTY/PTE_SPECIAL 分别重定位到 bit[91]/[92]。PTE 地址掩码 PTE_ADDR_LOW 覆盖 bit[55:12]。
Permission Indirection 为强制要求:D128 格式独占地使用 permission indirection 风格管理页表项权限,因此 CONFIG_ARM64_D128 要求 FEAT_S1PIE 同时存在,启动时若硬件不满足则内核 panic。
128 位原子读写:pxxval_get() 在 D128 下展开为内联汇编 ldp(load pair),一次性加载 128 位值的高 64 位和低 64 位,保证单次拷贝原子性。pxxval_set() 对称地使用 stp(store pair)。pxxval_cmpxchg_relaxed() 调用 cmpxchg128_relaxed(),pxxval_xchg_relaxed() 使用 LSE128 扩展的 swpp 指令。
页表几何调整:由于每个 128 位的 PTE 占用空间翻倍,每级页表能容纳的表项数减半(PTRS_PER_PXX 相应调整)。各级页表的 leaf entry 覆盖范围变化显著——以 4K 页为例,PMD 映射从 2M 缩小到 1M,PUD 映射从 1G 缩小到 256M。Contiguous PTE/PMD 的覆盖范围也相应变化。
TLB 维护使用 TLBIP 指令:引入 __tlbip() 宏,针对 D128 使用 tlbip(TLB Invalidate with PA)指令替代传统 tlbi。TLB 刷新参数 tlbi_args_t 在 D128 下变为 union __u128_halves 以承载 128 位地址参数。
寄存器访问适配:TTBR0/1_EL1 和 PAR_EL1 在 D128 下变为 128 位宽,启动时通过 msrr 指令清零全部 128 位。由于当前 PA_BITS 仍限制在 52 位,对低 64 位的访问通过 mrs/msr 即可满足。
Kconfig 依赖链:CONFIG_ARM64_D128 依赖编译器支持 __int128(ARCH_SUPPORTS_INT128)、汇编器支持 LSE128 和 ARMv9.3-a、同时排除 KVM、KASAN 和 UNMAP_KERNEL_AT_EL0(这些功能暂不支持 D128)。
作者未提供性能数据。从代码逻辑推断的预期收益:
pxdval_t/__PRIpxx/pgprot_read|write 抽象层同时惠及其他架构未来可能的大位宽页表需求CONFIG_ARM64_D128 均无问题),并在 x86、powerpc、riscv 等平台通过了编译构建测试系列:[PATCH v2] cgroup/dmem: introduce a peak file作者: Thadeu Lima de Souza Cascardo版本: v2(1 个 patch)
cgroup dmem(device memory)控制器用于管理和统计设备内存(如 GPU VRAM)的使用情况,模仿 memory cgroup 的接口设计。已有的 memory.peak 文件记录了 cgroup 自创建以来的内存使用峰值,对于容量规划和异常检测非常有用。但 dmem 控制器缺少对应的峰值统计接口,管理员无法了解设备内存使用的历史峰值。
memory.peak 接口不对称,降低了 dmem 控制器的可用性和一致性本 patch 完全依托已有的 page_counter 基础设施。page_counter 内部已维护 watermark 字段,在每次 page_counter_try_charge() 成功后更新为 max(watermark, new_usage),无需额外维护开销。
实现要点:
get_resource_peak() 函数,通过 READ_ONCE(pool->cnt.watermark) 读取对应 dmem region 的 page_counter watermarkdmem_cgroup_region_peak_show() 回调,复用已有的 dmemcg_limit_show() 模板函数,将 get_resource_peak 作为函数指针传入files[] 数组中注册新的 cgroup 控制文件项,文件名为 "peak",标志位 CFTYPE_NOT_ON_ROOT(仅在非根 cgroup 上显示),回调设置为 dmem_cgroup_region_peak_showDocumentation/admin-guide/cgroup-v2.rst 中 dmem 接口文档关键代码路径:dmem.peak 读取时进入 dmem_cgroup_region_peak_show(),该函数遍历 cgroup 的每个 dmem region,对每个 region 调用 dmemcg_limit_show(),后者按嵌套键(nested-keyed)格式输出 region ID 和对应的 get_resource_peak() 返回值。
作者未提供性能数据。从代码逻辑推断的预期收益:
page_counter 原有计费路径维护,仅新增读路径memory.peak 接口一致,降低运维人员的学习成本和脚本适配成本| HVO 泛化 | SPARSEMEM_VMEMMAP_PREINIT pre-HVO 路径减少启动开销;device DAX 额外节省一个尾页;共享 tail page 只读映射提升健壮性 | |
| KASAN HW_TAGS 微优化 | ||
| HMM mmap lock-drop | hmm_range_fault_unlockable() 采用 GUP int *locked 模式,首次支持 userfaultfd 映射 |
| panic_on_unrecoverable_mf | vm.panic_on_unrecoverable_memory_failure sysctl,遇到不可恢复内核页 hwpoison 时可立即 panic 保留故障现场;保守判定策略仅覆盖 MF_MSG_KERNEL + MF_IGNORED;默认关闭 | |
| arm64 D128 页表 | pxdval_t/__PRIpxx/`pgprot_read | |
| dmem.peak | page_counter 已有 watermark,零额外运行时开销 |
BUG_ON(!anon_vma) 内核 panic | ||
hstart >= hend 时无符号下溢导致返回错误超大值 | ||
resv_map->rw_sema 与 mmap_lock 循环锁依赖死锁(syzbot) | ||
memblock_free() 在 memblock_discard() 后被调用导致的 KASAN UAF | ||
kfree(wb_ctl) 的 UAF,改用 kfree_rcu() | ||
__add_memory_block() 失败时 device_unregister() 触发 WARN_ON | ||
kvcalloc() 分配被 kfree() 释放的问题 | ||
ctx->mmap_changing 未递减 | ||
hugetlb_vma_assert_locked() 的假阳性断言 | ||
!ptr 代替 MAP_FAILED 检查 | ||
| mm/memory_failure bool 转化 | try_memory_failure_hugetlb() 中 int 参数改为 bool,无功能变化 | |
| mm/shrinker guard() 重构 | guard(mutex) 替代显式 mutex_lock/mutex_unlock 和 goto 错误路径 | |
| mm misc cleanups | for_each_free_list() | |
| landlock audit 计数 | GFP_KERNEL_ACCOUNT,与 mm 子系统无关 |