1. 背景与意义
1.1 虚拟内存与设备协作的挑战
在现代操作系统中,虚拟内存(VM)为进程提供了独立、连续的地址空间,并通过页表(Page Table)实现虚拟地址到物理地址的映射。随着虚拟化(如 KVM)、异构计算(如 GPU/FPGA 直连)、高性能网络(如 RDMA)等场景的普及,越来越多的设备需要直接访问用户进程的虚拟地址空间,实现高效的数据共享和“零拷贝”访问。
然而,进程的虚拟地址空间是动态变化的,可能随时发生如下事件:
-
内存区域被
munmap()、mremap()、mprotect()等系统调用修改或释放; -
页表项被回收、迁移、换出(swap out);
-
物理页被迁移到设备私有内存(如 GPU 显存)或回迁到系统内存;
-
进程退出,整个地址空间被销毁。
如果设备端(如 GPU 页表、DMA 映射等)不能及时感知这些变化,可能导致数据不一致、非法访问、内存泄漏甚至系统崩溃。
1.2 mmu_notifier 与 mmu_interval_notifier 的作用
为了解决上述问题,Linux 内核引入了 mmu_notifier 和 mmu_interval_notifier 机制,允许设备驱动注册回调函数,在进程虚拟地址空间发生变化时获得通知,从而实现设备页表、DMA 映射等的同步和一致性管理。
-
mmu_notifier:为整个进程的地址空间或大范围内存区间提供失效通知,适合 KVM、虚拟化等场景。
-
mmu_interval_notifier:为任意虚拟地址区间(interval)提供高效、细粒度、异步失效通知,适合 HMM、GPU SVM、RDMA 等场景。
2. mmu_notifier 的原理与用法
2.1 设计目标
- 为设备驱动提供进程虚拟地址空间变化的通知机制。
- 支持多种事件类型(如 unmap、clear、protection change、migrate 等)。
- 支持同步和异步通知,保证设备端与 CPU 页表的一致性。
2.2 关键数据结构
2.2.1 struct mmu_notifier_ops
- 每个回调函数对应一种内存事件,驱动可按需实现。
struct mmu_notifier_ops {
void (*release)(struct mmu_notifier *subscription, struct mm_struct *mm);
int (*clear_flush_young)(struct mmu_notifier *subscription, struct mm_struct *mm, unsigned long start, unsigned long end);
int (*clear_young)(struct mmu_notifier *subscription, struct mm_struct *mm, unsigned long start, unsigned long end);
int (*test_young)(struct mmu_notifier *subscription, struct mm_struct *mm, unsigned long address);
int (*invalidate_range_start)(struct mmu_notifier *subscription, const struct mmu_notifier_range *range);
void (*invalidate_range_end)(struct mmu_notifier *subscription, const struct mmu_notifier_range *range);
void (*arch_invalidate_secondary_tlbs)(struct mmu_notifier *subscription, struct mm_struct *mm, unsigned long start, unsigned long end);
struct mmu_notifier *(*alloc_notifier)(struct mm_struct *mm);
void (*free_notifier)(struct mmu_notifier *subscription);
};
2.2.2 struct mmu_notifier
- 维护回调函数、关联的进程 mm_struct、引用计数等。
struct mmu_notifier {
struct hlist_node hlist;
const struct mmu_notifier_ops *ops;
struct mm_struct *mm;
struct rcu_head rcu;
unsigned int users;
};
2.2.3 struct mmu_notifier_range
- 描述失效事件的区间、类型、标志等。
struct mmu_notifier_range {
struct mm_struct *mm;
unsigned long start;
unsigned long end;
unsigned flags;
enum mmu_notifier_event event;
void *owner;
};
2.2.4 enum mmu_notifier_event
enum mmu_notifier_event {
MMU_NOTIFY_UNMAP,
MMU_NOTIFY_CLEAR,
MMU_NOTIFY_PROTECTION_VMA,
MMU_NOTIFY_PROTECTION_PAGE,
MMU_NOTIFY_SOFT_DIRTY,
MMU_NOTIFY_RELEASE,
MMU_NOTIFY_MIGRATE,
MMU_NOTIFY_EXCLUSIVE,
};
- 事件类型,驱动可根据类型做不同处理。
2.3 主要 API
| API 名称 | 作用说明 |
|---|---|
| mmu_notifier_register | 注册 notifier 到指定 mm_struct |
| mmu_notifier_unregister | 注销 notifier |
| mmu_notifier_get / mmu_notifier_put | 获取/释放 notifier 引用 |
| mmu_notifier_invalidate_range_start / end | 通知区间失效开始/结束 |
| mmu_notifier_clear_flush_young / clear_young / test_young | 管理页表 accessed/young 位 |
| mmu_notifier_release | 通知 mm_struct 释放 |
2.4 用法流程
-
定义并实现 mmu_notifier_ops 回调
设备驱动实现关心的回调函数,如 invalidate_range_start/end、release 等。 -
注册 notifier
调用mmu_notifier_register()将 notifier 注册到进程的 mm_struct。 -
事件通知
当进程虚拟地址空间发生变化时,内核自动调用所有注册 notifier 的回调。 -
注销 notifier
设备不再需要时,调用mmu_notifier_unregister()注销。
2.5 典型应用场景
-
KVM 虚拟化:KVM 通过 mmu_notifier 感知 guest 物理内存的变化,动态同步 shadow page table。
-
GPU 驱动:同步 GPU 页表与 CPU 页表,处理 SVM 区间失效。
-
RDMA/DPDK:管理 DMA 映射的生命周期,防止非法访问。
3. mmu_interval_notifier 的原理与用法
3.1 设计目标
-
为设备驱动提供任意虚拟地址区间的高效、细粒度、异步失效通知。
-
支持高并发、大规模区间的高效管理。
-
支持异步、可重试的通知机制,适合 HMM、GPU SVM、RDMA 等场景。
3.2 关键数据结构
3.2.1 struct mmu_interval_notifier_ops
-
invalidate 回调,区间失效时调用。
struct mmu_interval_notifier_ops {
bool (*invalidate)(struct mmu_interval_notifier *interval_sub,
const struct mmu_notifier_range *range,
unsigned long cur_seq);
};
3.2.2 struct mmu_interval_notifier
-
维护区间信息、回调、序号等。
struct mmu_interval_notifier {
struct interval_tree_node interval_tree;
const struct mmu_interval_notifier_ops *ops;
struct mm_struct *mm;
struct hlist_node deferred_item;
unsigned long invalidate_seq;
};
3.3 主要 API
| API 名称 | 作用说明 |
|---|---|
| mmu_interval_notifier_insert | 注册 interval_notifier 到指定区间 |
| mmu_interval_notifier_remove | 注销 interval_notifier |
| mmu_interval_read_begin | 开始一次区间的“读保护”操作,获取当前序号 |
| mmu_interval_read_retry | 检查读操作期间是否有失效事件发生,需重试 |
| mmu_interval_set_seq | 在 invalidate 回调中设置新的失效序号 |
| mmu_interval_check_retry | 长操作中间检查是否有失效,便于提前重试 |
3.4 用法流程
-
定义并实现 mmu_interval_notifier_ops 回调
设备驱动实现 invalidate 回调,处理区间失效。 -
注册 interval_notifier
调用mmu_interval_notifier_insert()注册到指定区间。 -
设备访问前同步
-
调用
mmu_interval_read_begin()获取序号。 -
执行实际操作(如页表同步、DMA 映射、数据迁移等)。
-
调用
mmu_interval_read_retry()检查期间是否有失效,若有则重试。
-
-
失效事件发生
-
内核检测到区间变化,遍历 interval tree,调用所有重叠区间的 invalidate 回调。
-
回调中驱动暂停设备访问、unmap 页表、准备恢复等,并调用
mmu_interval_set_seq()更新序号。
-
-
注销 interval_notifier
操作结束或区间不再需要时,调用mmu_interval_notifier_remove()注销。
3.5 典型应用场景
-
HMM(异构内存管理):GPU/FPGA 等设备通过 mmu_interval_notifier 感知用户空间 SVM 区间的失效,动态同步页表和数据迁移。
-
KFD/ROCm SVM:AMD GPU 驱动通过 mmu_interval_notifier 管理 SVM 区间,实现 CPU/GPU 共享虚拟内存和 page fault 支持。
-
RDMA/DPDK:高性能网络设备通过 mmu_interval_notifier 管理 DMA 映射,感知用户空间 buffer 的失效和回收。
4. 内核实现机制
4.1 mmu_notifier 的实现
-
内核为每个 mm_struct 维护一个 notifier 链表,存储所有注册的 mmu_notifier。
-
当虚拟地址空间发生变化时,内核遍历 notifier 链表,调用所有回调。
-
事件类型丰富,支持多种内存管理场景。
4.2 mmu_interval_notifier 的实现
-
内核为每个 mm_struct 维护一个 interval tree,管理所有注册的 mmu_interval_notifier。
-
当虚拟地址空间发生变化时,内核遍历 interval tree,查找所有与变化区间重叠的 notifier,并调用其 invalidate 回调。
-
每个 notifier 维护一个 invalidate_seq 序号,支持高效的同步与重试机制。
4.3 失效同步与重试机制
-
设备访问前,调用 mmu_interval_read_begin 获取当前序号。
-
操作期间如果有失效事件,invalidate 回调会调用 mmu_interval_set_seq 更新序号。
-
操作结束后用 mmu_interval_read_retry 检查序号是否变化,若变化则重试。
5. 总结
-
mmu_notifier 和 mmu_interval_notifier 是 Linux 内核支持虚拟化、异构计算、高性能设备协作的关键基础设施,为设备驱动提供了高效、细粒度、异步的虚拟地址区间失效通知机制。
-
驱动开发者应充分利用其区间注册、失效回调、序号同步等机制,保证设备端与 CPU 页表的一致性和安全性。
-
在 SVM、HMM、RDMA、虚拟化等场景下,mmu_notifier 和 mmu_interval_notifier 能极大提升系统性能和健壮性。
-
合理设计区间粒度、失效处理和重试机制,可进一步优化性能和资源利用。
如有帮助,请三连:点赞、收藏、加关注。
870

被折叠的 条评论
为什么被折叠?



