目录
[初识 GUP 机制
GUP 机制导论
FAST/FAST-ONLY/LOOGTERM GUP
REMOTE GUP
PIN/UNPIN GUP
MM-POPULATE
FAULT-IN
GUP 机制使用场景
LOOGTERM GUP 使用场景
FAST GUP 使用场景
FAST-ONLY 使用场景
REMOTE GUP 使用场景
PIN/UNPIN GUP 使用场景
PreALLOC with MM-POPULATE 使用场景
FAULT-IN 不安全缺页使用场景
FAULT-IN 安全缺页使用场景
GUP EXPORT/INTERNAL API
get_user_pages
get_user_pages_remote
get_user_pages_unlocked
get_user_pages_fast_only
get_user_pages_fast
pin_user_pages_fast
pin_user_pages_fast_only
pin_user_pages_remote
pin_user_pages
pin_user_pages_unlocked
unpin_user_page
unpin_user_pages_dirty_lock
unpin_user_page_range_dirty_lock
unpin_user_pages
fixup_user_fault
fault_in_writeable
fault_in_subpage_writeable
fault_in_safe_writeable
fault_in_readable
__mm_populate
faultin_vma_page_range
populate_vma_page_range
follow_page
try_grab_page
在 Linux 里,用户进程访问一段未映射物理内存的虚拟内存时,会触发缺页异常(PageFault), 缺页异常处理函数会为进程分配物理内存,并建立页表将虚拟内存映射到物理内存上,这样进程可以正常访问这段内存。相比用户空间的缺页机制,Get User Pages(GUP) 机制则提供了另外一种思路,其允许内核线程访问未映射物理内存的用户空间虚拟内存,然后通过提前缺页(PreFault)的方式分配物理内存,并建立页表将用户虚拟内存映射到物理内存上,那么用户进程可以直接访问虚拟内存而不会触发缺页, 除此之外, GUP 机制还支持以下功能:
- PreFault: 内核线程可以让未映射物理内存的用户空间虚拟内存提前发生缺页(PreFault),然后内核线程获得对应的物理内存,并采用临时映射向物理页写入指定数据,那么用户进程可以从虚拟内存里读到预设的数据.
- PreAlloc: 用户进程采用 SYS_MMAP 系统调用分配虚拟内存时,可以借助 GUP 机制在分配虚拟内存的同时也分配物理内存,并建立好页表映射,这样用户进程可以直接使用分配好的虚拟内存.
- 锁定页面: 在进行某些操作时(如进行直接内存访问 DMA 操作),需要页面保持不动,不被交换出去。GUP 提供的页面锁定确保在操作进行期间,页面保留在物理内存中
- 检测进程虚拟内存: 内核线程可以通过 GUP 机制访问指定用户进程的虚拟内存对应的物理页,那么内核线程可以使用物理页内容,以此影响或检测应用程序的行为
GUP 机制可以提供了内核线程使用的接口,也提供了用户进程使用的接口,其目的都是实现预缺页(PreFault) 的目的,例如上图 get_user_pages 函数的目的是内核线程可以通过 start 参数指明一段用户空间虚拟内存,然后让长度为 nr_pages 的虚拟内存区域触发缺页,此时触发缺页是由内核线程触发的,缺页完毕之后会将 pages 参数指向新分配的物理页. 经过这样的处理之后,这段用户空间虚拟内存已经映射了物理内存,那么用户进程访问这段虚拟内存不会引起缺页.
GUP 机制实现原理比较简单,将核心步骤整理出来如上图,其首先在 CONSULT-PT 处调用 follow_page_mask 函数查询虚拟内存对应的页表,试图通过查询页表获得对应的物理页,如果一切顺利的化,那么在 FOUND-PAGE 处可以从最后一级页表里获得物理页; 反之查询页表之后发现虚拟内存还没有建立页表映射,那么进入 PF-ROUTE 处调用 faultin_page 函数触发虚拟内存的缺页,此时和用户空间触发缺页的逻辑是一致的,handle_mm_fault 函数负责物理内存的分配和页表建立,函数处理完毕之后可以获得对应的物理页。以上便是 GUP 机制的实现逻辑,基于这个逻辑 GUP 机制还提供了适用于更多场景的接口函数.
GUP 机制在对外提供多种接口的同时,定义了一套标志用于控制 GUP 机制处理用户进程虚拟内存的过程,这些标志的适用可以让 GUP 机制更加灵活也能适应各种需求场景,具体含义如下:
- FOLL_WRITE: 检查页面表条目是否可写, 通常用于确保页面的写操作不会因写保护而失败
- FOLL_TOUCH: 标记页面为已访问, 这可以更新页面的访问时间,用于页面老化和替换算法
- FOLL_DUMP: 通常用于进程的核心转储和错误报告,处理内存区域中的空洞
- FOLL_TRIED: 表示操作重试,意味着之前的尝试已经开始了I/O
- FOLL_REMOTE: 表示在非当前任务/mm上操作,通常用于远程任务内存操作
- FOLL_ANON: 在操作期间更倾向于使用匿名内存(即,不由文件支持的内存)
- FOLL_HWPOISON: 检查页面是否已被硬件标记为坏(由于错误)
- FOLL_MIGRATION: 等待页面替换其迁移条目。这在内核的内存迁移上下文中使用
- FOLL_FORCE: 允许无视当前权限读写页面。这是一个强大的选项,应谨慎使用,因为它绕过了标准内存保护机制
- FOLL_NOWAIT: 如果需要(例如,对于换入页面的交换),启动所需的磁盘I/O,但不等待I/O完成。这可以在某些场景中提高响应性
- FOLL_NOFAULT: 在访问页面时不引起页面错误。这在某些页面错误代价高昂或不希望发生错误的场景中有用
- FOLL_NUMA: 强制NUMA(非统一内存访问)提示,可以影响多节点系统上的页面放置决策,优化内存访问模式
- FOLL_GET: 通过 get_page 增加页面的引用计数, 这对确保页面在使用时保留在内存中至关重要
- FOLL_LONGTERM: 表明映射的生命周期是无限的,这可能会影响内核如何处理这些页面,比如关于交换或回收
- FOLL_SPLIT_PMD: 在返回之前拆分巨大的页面表映射(PMDs), 这与处理巨大页面或透明巨大页面有关
- FOLL_PIN: 表明页面必须通过 unpin_user_page() 来释放,通常与页面固定函数一起使用,确保它们在显式取消固定之前保持在内存中
- FOLL_FAST_ONLY: 与快速用户页面查找功能一起使用,防止在快速路径失败时回退到更慢的方法