目录

图片无法显示,请右键点击新窗口打开图片


初识 PageMap 机制

在 Linux 里,存放页表的物理页称为页表页,内核将内核空间虚拟地址映射到页表页上,另外为了安全考虑,禁止将用户空间虚拟内存映射到页表页上,那么内核可以通过软件查页表获得所需页表的内容. 在某些场景下,应用程序想知道某段虚拟内存是否被访问过或者是否被写入过,那么其需要获得虚拟内存对应的页表才能知道这些信息,可惜的是应用权限没有能力读取页表,那么需要通过一些特殊途径获取,在 PTDUMP 机制章节开发者了解过其可以获得页表内容,但只能获得页表里特定标志为的信息,但如 PFNSoft-Dirty 标志位以及Present 标志位 都无法获得, 为此 Linux 提供了 PROC PageMap 机制, 该机制可以获得指定进程用户空间虚拟内存对应的页表信息。介绍这么多,开发者可以通过一个实践案例更加感官了解该机制,实践案例在 BiscuitOS 上的部署逻辑如下:

cd BiscuitOS
make menuconfig

  [*] Package  --->
      [*] Paging Mechanism  --->
          [*] PROC PageMap --->

# 源码目录
cd BiscuitOS/output/linux-6.0-x86_64/package/BiscuitOS-PAGING-PROC-PageMap-default/
# 部署源码
make download
# 在 BiscuitOS 中实践
make build

BiscuitOS-PAGING-PROC-PageMap-default Source Code on Gitee

图片无法显示,请右键点击新窗口打开图片

实践案例由一个应用程序构成,其首先在 67 行调用 malloc() 函数分配一段虚拟内存,然后在 72 行进行写操作,确保虚拟内存已经映射物理内存,接着在 75 行调用 detect_physcial_address() 函数,该函数首先在 25 行计算出虚拟地址对应的页号(Page Number), 然后将页号乘以 8 字节,这里的 8 字节正好对应页表项的长度,接着在 27 行获得页内存偏移(Page Offset). 准备好数据之后,函数在 30 行打开 “/proc/self/pagemap” 文件,也就是打开当前进程的 pagemap 文件,然后在 36 行将文件读写指针移动到距文件开始 voffset 处,接着在 42 行从该位置读出长度为 8 字节的内容,此时这 8 字节正好是虚拟内存对应的页表内容,那么函数在 48 行先确认页表的 Present 标志位是否置位,如果没有那说明页表不存在或者物理页被 SWAP OUT 到 SWAP SPACE; 反之 Present 标志位存在,那么函数在 55-56 行从页表项中获得物理页帧,然后加上页内存偏移就获得最终的物理地址,处理完毕之后函数关闭文件. 以上便是整个过程,那么接下来在 BiscuitOS 上进行实践:

图片无法显示,请右键点击新窗口打开图片

当 BiscuitOS 启动之后,直接运行 RunBiscuitOS.sh 脚本来运行实践案例,可以看到实践案例首先输出虚拟地址,接着输出物理地址,因此可以看到用户空间进程也可以获得用户空间虚拟内存对应的页表内容. 以上便是 PROC PageMap 机制 的一种表现,那么在本文会对机制的原理和应用做更多的讲解.

图片无法显示,请右键点击新窗口打开图片


PageMap 通识知识

图片无法显示,请右键点击新窗口打开图片

PROC PageMap 机制是 Linux 为每个进程在 /proc/pid 目录下提供了伪文件 pagemap, 其中 pid 为进程的 PID,该机制将进程的用户空间按 PAGE_SIZE 划分成不同的区域,每个区域在 pagemap 文件里对应一个 pagemap_entry_t, 也就是 pagemap 伪文件记录了整个用户空间的 Pagemap Entry,并且按顺序存储所有的 Pagemap Entry. 在第一章节开发者已经见识了 PROC Pagemap 机制使用流程,那么其工作原理如下:

图片无法显示,请右键点击新窗口打开图片

PROC PageMap 机制 pagemap 文件提供了 open/lseek/read 三个系统调用,其工作原理如下:

  • A: 当需要获得一个虚拟地址对应的物理内存请求时,首先打开 “/proc/self/pagemap” 文件,以此找到当前进程对应的 pagemap 文件,PROC PageMap 机制 接着将虚拟地址向右移 PAGE_SHIFT, 以此获得对应的页号(Page Number)
  • B: 接着将页号乘以 sizeof(pagemap_entry_t) 以此找到对应的 PTE Entry 位于文件的位置,最后调用 lseek 将文件读写指针定位到该位置.
  • C: 接着调用者通过 read 函数将当前文件读写指针的位置转换成页号(Page Number), 并获得页号对应的虚拟地址。接下来该机制找到虚拟地址对应的 2MiB 对齐的区域,并获虚拟地址到 2MiB 区域结束地址的区域,这个区域暂时称为目标区域.
  • D: 接着调用 PageWalk 机制遍历目标区域里所有的 PTE Entry,并将 PTE Entry 内容都记录在临时缓存里,待遍历完成后按 read 的需求将缓存的数据拷贝到用户空间
  • E: 用户空间读取内容之后,按 Pagemap Entry 的格式对页表内容进行解析,以此获得所需的内容.

图片无法显示,请右键点击新窗口打开图片

pagemap 伪文件里面存储了进程虚拟空间所有的 Pagemap Entry,Pagemap Entry 的位图如上,每个 bit 的含义如下:

  • PM_PRESENT(Bit63): 该标志位置位时表示物理页存在主内存里; 反之表示物理内存不存在主内存里.
  • PMD_SWAP(Bit62): 该标志位置位表示物理页存在 SWAP Space; 反之表示物理页不在 SWAP Space. 当 PM_SWAP 置位时 PM_PRESENT 必须清零; 当 PM_PRESENT 置位时 PM_SWAP 必须清零; 当 PM_PRESENT 和 PM_SWAP 同时为 0 时表示物理页不存在.
  • PM_FILE(61): 该标志置位时表示虚拟内存采用文件映射到物理页上; 反之表示虚拟内存采用匿名映射到物理页上
  • PM_UFFD_WP(Bit57): 该标志置位表示用户空间处理一次写保护错误
  • PM_MMAP_EXCLUSIVE(Bit56): 该标志置位表示只有一个用户空间虚拟内存映射到该物理页上; 反之表示有多个用户空间虚拟内存映射到该物理页上.
  • PM_SOFT_DIRTY(Bit55): 该标志位置位表示 Soft Dirty Bit 置位,软脏位是一种内存管理机制,用于追踪物理页是否被修改过.
  • Page Frame Filed(0-54): 当 PM_PRESENT 标志位置位,该字段表示物理页帧号.
  • SWAP Offset(5-54): 当 PM_SWAP 标志置位,该字段表示物理页在 SWAP Space 里的偏移.
  • SWAP Type(0-4): 当 PM_SWAP 标志置位,该字段表示物理页的 SWAP 类型.

Pagemap Entry 一共存在三种模式,首先是 PM_PRESENT 标志和 PM_SWAP 标志都为零,那么表示虚拟内存没有映射物理页,因此 Pagemap Entry 是空的; 第二种模式是 PM_PRESENT 标志置位且 PM_SWAP 标志清零,那么表示虚拟内存映射的物理页位于系统主内存里; 最后一种是 PM_PRESENT 标志清零且 PM_SWAP 标志置位,那么表示虚拟内存映射的物理页被 SWAP OUT 到 SWAP Space.


标准 4KiB 小页映射 Pagemap Entry

图片无法显示,请右键点击新窗口打开图片

在 Linux 里存在不同粒度的物理页,其中最常见的为 4KiB 粒度的物理页,Linux 需要使用五级页表才能将虚拟内存映射到 4KiB 物理页上,因此 PTE Entry 是 4KiB 粒度物理页最后一级页表. 用户空间普通分配的虚拟内存基本都是映射到 4KiB 物理页上,因此进程的 PROC Pagemap 伪文件里面记录的 Pagemap Entry,其内容大部分来自 PTE Entry.

图片无法显示,请右键点击新窗口打开图片

PROC Pagemap 机制将虚拟内存划分成 PAGE_SIZE(4KiB) 的区域,然后 Pagemap 伪文件为进程用户空间所有虚拟内存都分配了一个 Pagemap Entry, 每个 Pagemap 正好对应一个 4KiB 虚拟内存区域,因此可以使用 “Page Number * sizeof(pagemap_entry_t)” 找到 Page Number 对应的 Pagemap Entry. 另外 Pagemap Entry 的 Page Frame 字段(0-54) 指向一块 4KiB 物理区域,及该物理区域是按 4KiB 对齐的.

图片无法显示,请右键点击新窗口打开图片

PROC Pagemap 机制在获得起始虚拟地址之后,在虚拟内存中找到包含起始地址的 2MiB 区域,然后获得该 2MiB 区域的结束地址,那么使用 PageWalk 机制遍历起始虚拟地址到 2MiB 结束虚拟地址里所有的 PTE Entry,这些 PTE Entry 的内容将作为 Pagemap Entry 的数据来源.


标准 2MiB 大页映射 Pagemap Entry

图片无法显示,请右键点击新窗口打开图片

在 Linux 里也存在大于 4KiB 粒度的大页,比较常见的为 2MiB 粒度的大页,Linux 需要使用 4 级页表才能将 2MiB 的虚拟内存映射到 2MiB 物理页上,因此 PMD Entry 是 2MiB 粒度物理页最后一级页表。 用户空间可以使用 Hugetlbfs 或者 THP 映射到 2MiB 物理页上,因此对应的 Pagemap Entry 内存来自 PMD Entry.

图片无法显示,请右键点击新窗口打开图片

Pagemap 文件并没有按 2MiB 区域来映射一个 Pagemap Entry,相反 Pagemap Entry 还是按 4KiB 大小来映射,那么一个 2MiB 虚拟内存区域在 Pagemap 伪文件中对应 512 个 Pagemap Entry,这 512 个 Pagemap 除了 Page Frame 字段不同,其余字段均相同,且 Page Frame 字段就是 2MiB 拆成 4KiB 之后的 Page Frame, 并依次增加.

图片无法显示,请右键点击新窗口打开图片

PROC Pagemap 机制在遍历 2MiB 的虚拟地址时,其只需获得 2MiB 虚拟地址的结束地址即可,然后使用 PageWalk 机制遍历 2MiB 区域的 PMD Entry 即可,因此只需遍历一个 PMD Entry,并从中获得 Pagemap Entry 的内容,但值得注意的是 Pagemap 文件需要填充 512 个 Pagemap Entry,并且这些 Pagemap Entry 除了 Page Frame 字段不同,其他字段都相同,并且 Page Frame 依次增加.


标准 1Gig 大页映射 Pagemap Entry

图片无法显示,请右键点击新窗口打开图片

在 Linux 里也存在大于 2MiB 粒度的大页,比较常见的为 1Gig 粒度的大页,Linux 需要使用 3 级页表才能将 1Gig 的虚拟内存映射到 1Gig 物理页上,因此 PUD Entry 是 1Gig 粒度物理页最后一级页表。 用户空间可以使用 Hugetlbfs 映射到 1Gig 物理页上,因此对应的 Pagemap Entry 内存来自 PUD Entry.

图片无法显示,请右键点击新窗口打开图片

Pagemap 文件并没有按 1Gig 区域来映射一个 Pagemap Entry,相反 Pagemap Entry 还是按 4KiB 大小来映射,但也不是都将 1Gig 区域的都映射到 Pagemap 文件里,而是只映射指定虚拟内存对应的 2MiB 虚拟内存区域,其在 Pagemap 伪文件中对应 512 个 Pagemap Entry,这 512 个 Pagemap 除了 Page Frame 字段不同,其余字段均相同,且 Page Frame 字段就是 2MiB 拆成 4KiB 之后的 Page Frame, 并依次增加.

图片无法显示,请右键点击新窗口打开图片

PROC Pagemap 机制在遍历 2MiB 的虚拟地址时,其只需获得 2MiB 虚拟地址的结束地址即可,然后使用 PageWalk 机制遍历 2MiB 区域的 PMD Entry 即可,因此只需遍历一个 PMD Entry,并从中获得 Pagemap Entry 的内容,但值得注意的是 Pagemap 文件需要填充 512 个 Pagemap Entry,并且这些 Pagemap Entry 除了 Page Frame 字段不同,其他字段都相同,并且 Page Frame 依次增加.


Pagemap 数据结构

图片无法显示,请右键点击新窗口打开图片

Pagemap 伪文件描述进程用户空间虚拟内存映射关系,其由无数个 Pagemap Entry 构成,每个 Pagemap Entry 由 pagemap_entry_t 数据结构进行维护,可以看到其实际为长度为 64bit 的数据单元构成,每个 Pagemap Entry 记录了一段虚拟内存的页表信息,并且每个 Bit 位表示虚拟内存不特点,有下面的宏进行定义:

图片无法显示,请右键点击新窗口打开图片

Pagemap 机制定义了多个宏,用于描述 Pagemap Entry 每个 bit 位的含义,每个宏的含义如下:

  • PM_ENTRY_BYTES: 表示 Pagemap Entry 的长度
  • PM_PFRAME_BITS: 表示 Pagemap Entry 里描述 PFN 字段的长度
  • PM_PFRAME_MASK: Pagemap Entry 里 PFN 字段的掩码
  • PM_SOFT_DIRTY: Pagemap Entry 里 Soft Dirty 标志位
  • PM_MMAP_EXCLUSIVE: 用于描述对应的物理页是否只被一个用户空间虚拟内存映射
  • PM_UFFD_WP: 用于表示对应的虚拟内存是否在用户空间程序正在处理写保护异常
  • PM_FILE: 用于描述对应的虚拟内存是否为文件映射
  • PM_SWAP: 用于描述对应虚拟内存映射的物理页是否被 SWAP OUT
  • PM_PRESENT: 用于描述对应虚拟内存映射的物理页是否位于主内存
  • PM_END_OF_BUFFER: 表示记录页表信息的缓存区溢出

图片无法显示,请右键点击新窗口打开图片

struct pagemapread 数据结构用于存储读取 Pagemap 伪文件过程中的数据,其 buffer 成员维护了一个 pagemap_entry_t 缓存,缓存的长度为 len 成员,Pagemap 机制在遍历页表过程中,将信息合成 Pagemap Entry 格式之后存储在 buffer 缓存里,每添加一个 Pagemap Entry,那么 pos 成员加一,最后 show_pfn 成员用于控制是否将 PFN 信息也加入到 Pagemap Entry 里.

图片无法显示,请右键点击新窗口打开图片

proc_pagemap_operations 变量是内核为每个进程提供 Pagemap 伪文件的文件接口,只要对 “/proc/pid/pagemap” 进行文件操作时,open 函数会调用到 pagemap_open() 函数,llseek/lseek 函数会调用到 mem_lseek() 函数,read 函数会调用到 pagemap_read() 函数,最后 close 函数会调用到 pagemap_release() 函数.


Pagemap 源码解析

图片无法显示,请右键点击新窗口打开图片

PageMap 机制源码分作 4 部分,首先是通过 open 函数找到对应的进程地址空间(struct mm_struct), 然后通过 lseek 将虚拟内存区域的起始地址传递到内核空间,接着调用 read 函数从 Pagemap 文件里读取 Pagemap Entry 的内容,最后就是关闭文件收尾操作.

OPEN

图片无法显示,请右键点击新窗口打开图片

当用户进程通过 open 函数打开 “/proc/self/pagemap” 或者 “/proc/pid/pagemap”,那么函数会调用到 pagemap_open() 函数,该函数首先在 20 行通过 proc_mem_open() 函数根据 inode 参数获得对应进程的 struct mm_struct, 并且对 struct mm_struct 引用计数加 1,以防止其他进程将该地址空间释放. 当获得一个可用的进程地址空间之后,将其存储在文件描述符的 private_data 成员里,至此 open 操作完成.

LSEEK

图片无法显示,请右键点击新窗口打开图片

当用户进程成功打开 Pagemap 伪文件之后,用户进程在用户空间计数处虚拟内存对应的 Pagemap Entry 在文件内的偏移之后,然后调用 lseek 函数将文件读写指针移动到 Pagemap Entry 在文件偏移出, 那么此时会调用到 mem_lseek() 函数。如果在用户空间调用 lseek 函数时是通过 SEEK_SET 调整位置,那么函数会进入 908 分支; 反之如果使用 SEEK_CUR 调整位置,那么函数进入 911 分支,但无论虚拟地址对应的 Pagemap Entry 在文件的偏移存储到了文件描述符的 f_pos 成员里.

READ

图片无法显示,请右键点击新窗口打开图片

LSEEK 函数调用完毕之后,应用程序直接调用 Read 函数就可以获得虚拟内存对应 Pagemap Entry,其最终会调用 pagemap_read() 函数,该函数比较长,但其逻辑很简单,首先检查是否有权限获得 PFN 信息,然后分配一个 Buffer 存储遍历过程中记录的 Pagemap Entry, 接着就是调用 PageWalk 机制遍历指定范围的页表,此时提供了 pagemap_ops 遍历回调函数集合,可以看到其提供了 pmd_entry、hugetlb_entry 和 pte_hole 回调函数,也就是当遍历到 PMD Entry 是会调用 pagemap_pmd_range() 函数进行处理. 最后页表遍历完毕获得所需的 Pagemap Entry 之后调用 copy_to_user() 函数将数据拷贝到用户空间,然后将临时申请的内存全部释放掉. 以上便是 Read 的操作,那么接下来对代码片段进行分析:

图片无法显示,请右键点击新窗口打开图片

pagemap_read 函数用于获得虚拟内存对应的 Pagemap Entry,其 file 函数指向 Pagemap 文件描述符,buf 参数用于将数据拷贝到用户空间,count 表示拷贝到用户空间数据的长度,ppos 表示读写指针在文件的偏移. 函数首先在 630 行从文件描述符的 private_data 获得进程的地址空间(struct mm_struct), 那么开发者有没有想过为什么不直接用 current->mm 来获得地址空间呢? 函数接着在 628 行检查 mm 的有效性,这里主要确保 mm 对应一个有效的用户进程的地址空间。函数接着在 643 行检查 ppos 参数是否按 PM_ENTRY_BYTES 对齐,因为 ppos 的值起始就是文件描述符 f_pos 的值,因此此时该值表示虚拟内存对应的 Pagemap Entry 在 Pagemap 文件的内部偏移, 因此这里 ppos 的值一定是按 PM_ENTRY_BYTES 对齐的,否则就是一个非法的偏移值. 函数最后在 647 行检查 count 有效性,如果 count 为 0 即用户空间不需要读任何值,那么就没必要继续读操作.

图片无法显示,请右键点击新窗口打开图片

函数接着在 651 行调用 file_ns_capable() 函数检查本次文件操作者是否为超级用户,因为逻辑规定只有超级用户才能获得 PFN 信息,普通用户没有权限获得 PFN 信息。Pagemap 机制是按 PAGEMAP_WALK_SIZE 长的虚拟内存区域进行页表遍历,那么函数在 653 行计算拆分成 4KiB 区域需要多少个 Pagemap Entry 存储遍历的信息,那么函数在 654 动态分配存储能存储下这些 Pagemap Entry 的缓存,此时使用 pm 变量存储 Read 过程的数据,那么 pm.len 指明遍历多少个 Pagemap Entry,然后 pm.buffer 指向存储 Pagemap Entry 的缓存. 函数接着在 659 行从 ppos 中获得虚拟内存对应 Pagemap Entry 在文件中的偏移,然后除以 PM_ENTRY_BYTES 就可以获得虚拟内存的页号(Page Number), 那么这里就知道为什么用户空间程序需要将虚拟地址先转换成页号,然后将页表乘以 PM_ENTRY_BYTES 的值通过 LSEEK 存储到文件偏移了吧, 这就是一个逆向操作。函数接着在 661 行将进程对应的用户空间最大虚拟地址存储在 end_vaddr 变量里,并在接下来的 664-670 行获得一个有效的起始虚拟地址和结束地址.

图片无法显示,请右键点击新窗口打开图片

函数在获得虚拟内存之后进入 while 循环,在循环里函数首先将虚拟内存按 PAGEMAP_WALK_SIZE 的粒度进行遍历,那么在 688 行处遍历的虚拟内存范围就是 [start_vaddr, end), 接着函数从 688-692 行调用 PageWalk 机制遍历这段虚拟内存的页表,其提供了 pagemap_ops 回调函数合集,当遍历完页表获得 Pagemap Entry 之后,那么函数在 696 行调用 copy_to_user() 函数将数据拷贝到用户空间,如果还没有满足拷贝数据的长度,那么继续 while 循环,否则结束 while 循环.

图片无法显示,请右键点击新窗口打开图片

pagemap_ops 提供了 pmd_entry, hugetlb_entry 和 pte_hole 回调接口,那么当遍历到 PMD 页表时则调用 pagemap_pmd_range() 函数,而遍历 Hugetlbfs 大页页表,那么则调用 pagemap_hugetlb_range() 函数; 如果页表不存在,则调用 pagemap_pte_hole() 函数.

图片无法显示,请右键点击新窗口打开图片

由于 PageWalk 机制将 hugetlbfs 大页和其他页区别进行遍历,因此 Pagemap 遍历 Hugetlbfs 大页时页需要提供专门的回调函数,pagemap_hugetlb_range() 函数就是用来处理 Hugetlbfs 大页对应的 Pagemap Entry。函数首先在 546 行从 walk->private 中获得 pagemapread 变量,该变量用于存储 pagemap Read 过程中的数据,然后从 walk 中获得虚拟区域对应的 VMA,那么函数在 522 行从 VMA 中获得 PM_SOFT_DIRTY 信息,然后在 555 行调用 huge_ptep_get() 函数读取 Hugetlbfs 大页对应的页表,如果大页页表存在,那么进入到 557 行分支. 函数在 557 行获得页表映射物理页的 struct page,然后获得 PM_FILE、PM_MMAP_EXCLUSIVE、PM_UFFD_WP 和 PM_PRESENT 信息,如果是超级用户,那么还可以获得 PFN 信息。函数接着在 576 行将 2M 大页拆分成 4K 粒度,然后以此调用 add_to_pagemap() 函数将 4K 粒度的 Pagemap Entry 内容填充到 pm 的 buffer 里,因此这里要构造 512 个 Pagemap Entry.

图片无法显示,请右键点击新窗口打开图片

当虚拟内存映射是普通 4KiB 页或者 THP 大页,那么遍历页表 PMD Entry 时 pagemap_pmd_range() 函数会被调用,455-511 行逻辑用于处理 THP 大页,其与 Hugetlbfs 大页一样,获得对应的页表之后,需要构造 512 个 Pagemap Entry,并将其填充到 pm 的 buffer 里, Pagemap Entry 每个 bit 获得的逻辑与 hugetlbfs 类似.

图片无法显示,请右键点击新窗口打开图片

当虚拟内存映射为 4KiB 页时,通过之间的分析这段虚拟内存包括该地址到 2MiB 页结束的虚拟地址,因此最多需要构造 512 个 Pagemap Entry,最后只需构造一个 Pagemap Entry,函数在 528 行调用 pte_to_pagemap_entry() 函数构造 Pagemap Entry,529 行调用 add_to_pagemap() 将 Pagemap Entry 存储到 pm buffer 里.

图片无法显示,请右键点击新窗口打开图片

当虚拟地址空间没有映射物理内存时,遍历页表时会调用 pagemap_pte_hole() 函数,函数的处理逻辑和其他回调函数一致,只是构造的 Pagemap Entry 是空的,当构造完毕之后将 Pagemap Entry 也存储到 pm buffer 里.

图片无法显示,请右键点击新窗口打开图片

make_pme 函数用于构造 Pagemap Entry,add_to_pagemap 函数用于将 Pagemap Entry 写入 pm 的 buffer 里,写入一个就更新一下 pos,当 pos 大于 len 那么说明 Buffer 溢出了,直接返回 PM_END_OF_BUFFER; 没有溢出则返回 0.

CLOSE

图片无法显示,请右键点击新窗口打开图片

当应用程序处理完所需的数据之后,调用 close 关闭 pagemap 文件是,pagemap_release() 函数就会被调用,函数只做一个事情,open 函数调用是增加了对进程地址空间的引用,那么该函数就会减少对地址空间的引用,以此干净使用进程地址空间.


Pagemap 查询映射普通页的页表信息

本节用于讲解如何使用 Pagemap 机制查看用户空间映射普通物理页的页表信息,普通物理页指的就是 4KiB 物理页,那么接下来通过一个实践案例进行讲解,实践案例在 BiscuitOS 上的部署逻辑如下:

cd BiscuitOS
make menuconfig

  [*] Package  --->
      [*] Paging Mechanism  --->
          [*] PROC PageMap --->

# 源码目录
cd BiscuitOS/output/linux-6.0-x86_64/package/BiscuitOS-PAGING-PROC-PageMap-default/
# 部署源码
make download
# 在 BiscuitOS 中实践
make build

BiscuitOS-PAGING-PROC-PageMap-default Source Code on Gitee

图片无法显示,请右键点击新窗口打开图片

实践案例由一个应用程序构成,其首先在 67 行调用 malloc() 函数分配一段虚拟内存,然后在 72 行进行写操作,确保虚拟内存已经映射物理内存,接着在 75 行调用 detect_physcial_address() 函数,该函数首先在 25 行计算出虚拟地址对应的页号(Page Number), 然后将页号乘以 8 字节,这里的 8 字节正好对应页表项的长度,接着在 27 行获得页内存偏移(Page Offset). 准备好数据之后,函数在 30 行打开 “/proc/self/pagemap” 文件,也就是打开当前进程的 pagemap 文件,然后在 36 行将文件读写指针移动到距文件开始 voffset 处,接着在 42 行从该位置读出长度为 8 字节的内容,此时这 8 字节正好是虚拟内存对应的页表内容,那么函数在 48 行先确认页表的 Present 标志位是否置位,如果没有那说明页表不存在或者物理页被 SWAP OUT 到 SWAP SPACE; 反之 Present 标志位存在,那么函数在 55-56 行从页表项中获得物理页帧,然后加上页内存偏移就获得最终的物理地址,处理完毕之后函数关闭文件. 以上便是整个过程,那么接下来在 BiscuitOS 上进行实践:

图片无法显示,请右键点击新窗口打开图片

当 BiscuitOS 启动之后,直接运行 RunBiscuitOS.sh 脚本来运行实践案例,可以看到实践案例首先输出虚拟地址,接着输出物理地址,因此可以看到用户空间进程也可以获得用户空间虚拟内存对应的页表内容.

图片无法显示,请右键点击新窗口打开图片


Pagemap 查询没有映射物理页的页表信息

本节用于讲解如何使用 Pagemap 机制查看用户空间没有映射普通物理页的页表信息,普通物理页指的就是 4KiB 物理页,那么接下来通过一个实践案例进行讲解,实践案例在 BiscuitOS 上的部署逻辑如下:

cd BiscuitOS
make menuconfig

  [*] Package  --->
      [*] Paging Mechanism  --->
          [*] PROC PageMap with HOLE --->

# 源码目录
cd BiscuitOS/output/linux-6.0-x86_64/package/BiscuitOS-PAGING-PROC-PageMap-HOLE-default/
# 部署源码
make download
# 在 BiscuitOS 中实践
make build

BiscuitOS-PAGING-PROC-PageMap-HOLE-default Source Code on Gitee

图片无法显示,请右键点击新窗口打开图片

实践案例由一个应用程序构成,其首先在 69 行调用 mmap() 函数分配一段虚拟内存,但没有进行写操作,确保虚拟内存没有映射物理内存,接着在 80 行调用 detect_physcial_address() 函数,该函数首先在 25 行计算出虚拟地址对应的页号(Page Number), 然后将页号乘以 8 字节,这里的 8 字节正好对应页表项的长度,接着在 27 行获得页内存偏移(Page Offset). 准备好数据之后,函数在 30 行打开 “/proc/self/pagemap” 文件,也就是打开当前进程的 pagemap 文件,然后在 36 行将文件读写指针移动到距文件开始 voffset 处,接着在 42 行从该位置读出长度为 8 字节的内容,此时这 8 字节正好是虚拟内存对应的页表内容,那么函数在 48 行先确认页表的 Present 标志位是否置位,如果没有那说明页表不存在或者物理页被 SWAP OUT 到 SWAP SPACE; 反之 Present 标志位存在,那么函数在 55-56 行从页表项中获得物理页帧,然后加上页内存偏移就获得最终的物理地址,处理完毕之后函数关闭文件. 以上便是整个过程,那么接下来在 BiscuitOS 上进行实践:

图片无法显示,请右键点击新窗口打开图片

当 BiscuitOS 启动之后,直接运行 RunBiscuitOS.sh 脚本来运行实践案例,可以看到实践案例并没有找到对应的物理内存,因为页表是空的,因此 Pagemap Entry 也是空的.

图片无法显示,请右键点击新窗口打开图片


Pagemap 查询映射 2MiB Hugetlb 大页页表信息

本节用于讲解如何使用 Pagemap 机制查看用户空间映射 2MiB Hugetlbfs 物理页的页表信息,那么接下来通过一个实践案例进行讲解,实践案例在 BiscuitOS 上的部署逻辑如下:

cd BiscuitOS
make menuconfig

  [*] Package  --->
      [*] Paging Mechanism  --->
          [*] PROC PageMap with Hugetlbfs Hugepages --->

# 源码目录
cd BiscuitOS/output/linux-6.0-x86_64/package/BiscuitOS-PAGING-PROC-PageMap-Hugetlbfs-default/
# 部署源码
make download
# 在 BiscuitOS 中实践
make build

BiscuitOS-PAGING-PROC-PageMap-Hugetlbfs-default Source Code on Gitee

图片无法显示,请右键点击新窗口打开图片

实践案例由一个应用程序构成,其首先在 69 行调用 mmap() 函数分配 2MiB 的 hugetlbfs 大页内存,并在 77 行对该内存进行写操作,确保虚拟内存映射物理内存,接着在 80 行调用 detect_physcial_address() 函数,该函数首先在 25 行计算出虚拟地址对应的页号(Page Number), 然后将页号乘以 8 字节,这里的 8 字节正好对应页表项的长度,接着在 27 行获得页内存偏移(Page Offset). 准备好数据之后,函数在 30 行打开 “/proc/self/pagemap” 文件,也就是打开当前进程的 pagemap 文件,然后在 36 行将文件读写指针移动到距文件开始 voffset 处,接着在 42 行从该位置读出长度为 8 字节的内容,此时这 8 字节正好是虚拟内存对应的页表内容,那么函数在 48 行先确认页表的 Present 标志位是否置位,如果没有那说明页表不存在或者物理页被 SWAP OUT 到 SWAP SPACE; 反之 Present 标志位存在,那么函数在 55-56 行从页表项中获得物理页帧,然后加上页内存偏移就获得最终的物理地址,处理完毕之后函数关闭文件. 以上便是整个过程,那么接下来在 BiscuitOS 上进行实践:

图片无法显示,请右键点击新窗口打开图片

当 BiscuitOS 启动之后,直接运行 RunBiscuitOS.sh 脚本来运行实践案例,可以看到 2M hugetlbfs 内存分配成功,并获得了 2MiB 大页对应的物理内存,另外可以看到虚拟内存和物理内存都是 2MiB 对齐.

图片无法显示,请右键点击新窗口打开图片


Pagemap 查询映射 THP 大页页表信息

本节用于讲解如何使用 Pagemap 机制查看用户空间映射 2MiB THP 物理页的页表信息,那么接下来通过一个实践案例进行讲解,实践案例在 BiscuitOS 上的部署逻辑如下:

cd BiscuitOS
make menuconfig

  [*] Package  --->
      [*] Paging Mechanism  --->
          [*] PROC PageMap with THP --->

# 源码目录
cd BiscuitOS/output/linux-6.0-x86_64/package/BiscuitOS-PAGING-PROC-PageMap-THP-default/
# 部署源码
make download
# 在 BiscuitOS 中实践
make build

BiscuitOS-PAGING-PROC-PageMap-THP-default Source Code on Gitee

图片无法显示,请右键点击新窗口打开图片

实践案例由一个应用程序构成,其首先在 70 行调用 mmap() 函数分配 2MiB 的 THP 大页内存,并在 79 行对该内存进行写操作,确保虚拟内存映射物理内存,接着在 82 行调用 detect_physcial_address() 函数,该函数首先在 25 行计算出虚拟地址对应的页号(Page Number), 然后将页号乘以 8 字节,这里的 8 字节正好对应页表项的长度,接着在 27 行获得页内存偏移(Page Offset). 准备好数据之后,函数在 30 行打开 “/proc/self/pagemap” 文件,也就是打开当前进程的 pagemap 文件,然后在 36 行将文件读写指针移动到距文件开始 voffset 处,接着在 42 行从该位置读出长度为 8 字节的内容,此时这 8 字节正好是虚拟内存对应的页表内容,那么函数在 48 行先确认页表的 Present 标志位是否置位,如果没有那说明页表不存在或者物理页被 SWAP OUT 到 SWAP SPACE; 反之 Present 标志位存在,那么函数在 55-56 行从页表项中获得物理页帧,然后加上页内存偏移就获得最终的物理地址,处理完毕之后函数关闭文件. 以上便是整个过程,那么接下来在 BiscuitOS 上进行实践:

图片无法显示,请右键点击新窗口打开图片

当 BiscuitOS 启动之后,直接运行 RunBiscuitOS.sh 脚本来运行实践案例,通过 “/proc/meminfo” 可以看到 2M THP 内存分配成功,并获得了 2MiB 大页对应的物理内存,另外可以看到虚拟内存和物理内存都是 2MiB 对齐.

图片无法显示,请右键点击新窗口打开图片


Pagemap 查询映射 1Gig 大页页表信息

本节用于讲解如何使用 Pagemap 机制查看用户空间映射 1Gig 物理页的页表信息,那么接下来通过一个实践案例进行讲解,实践案例在 BiscuitOS 上的部署逻辑如下:

cd BiscuitOS
make menuconfig

  [*] Package  --->
      [*] Paging Mechanism  --->
          [*] PROC PageMap with 1G --->

# 源码目录
cd BiscuitOS/output/linux-6.0-x86_64/package/BiscuitOS-PAGING-PROC-PageMap-1G-default/
# 部署源码
make download
# 在 BiscuitOS 中实践
make build

BiscuitOS-PAGING-PROC-PageMap-1G-default Source Code on Gitee

图片无法显示,请右键点击新窗口打开图片

实践案例由一个应用程序构成,其首先在 73 行调用 mmap() 函数分配 1Gig 的 Hugetlbfs 大页内存,并在 82 行对该内存进行写操作,确保虚拟内存映射物理内存,接着在 85 行调用 detect_physcial_address() 函数,该函数首先在 25 行计算出虚拟地址对应的页号(Page Number), 然后将页号乘以 8 字节,这里的 8 字节正好对应页表项的长度,接着在 27 行获得页内存偏移(Page Offset). 准备好数据之后,函数在 30 行打开 “/proc/self/pagemap” 文件,也就是打开当前进程的 pagemap 文件,然后在 36 行将文件读写指针移动到距文件开始 voffset 处,接着在 42 行从该位置读出长度为 8 字节的内容,此时这 8 字节正好是虚拟内存对应的页表内容,那么函数在 48 行先确认页表的 Present 标志位是否置位,如果没有那说明页表不存在或者物理页被 SWAP OUT 到 SWAP SPACE; 反之 Present 标志位存在,那么函数在 55-56 行从页表项中获得物理页帧,然后加上页内存偏移就获得最终的物理地址,处理完毕之后函数关闭文件. 以上便是整个过程,那么接下来在 BiscuitOS 上进行实践:

图片无法显示,请右键点击新窗口打开图片

当 BiscuitOS 启动之后,直接运行 RunBiscuitOS.sh 脚本来运行实践案例,通过 “/proc/meminfo” 可以看到 1Gig Hugetlbfs 内存分配成功,并获得了 1Gig 大页对应的物理内存,另外可以看到虚拟内存和物理内存都是 1Gig 对齐.

图片无法显示,请右键点击新窗口打开图片


Pagemap 查询映射被 SWAP OUT 物理页页表信息

本节用于讲解如何使用 Pagemap 机制查看用户空间映射物理页被 SWAP OUT 之后的页表信息,那么接下来通过一个实践案例进行讲解,实践案例在 BiscuitOS 上的部署逻辑如下:

cd BiscuitOS
make menuconfig

  [*] Package  --->
      [*] Paging Mechanism  --->
          [*] PROC PageMap with SWAP --->

# 源码目录
cd BiscuitOS/output/linux-6.0-x86_64/package/BiscuitOS-PAGING-PROC-PageMap-SWAP-default/
# 部署源码
make download
# 在 BiscuitOS 中实践
make build

BiscuitOS-PAGING-PROC-PageMap-SWAP-default Source Code on Gitee

图片无法显示,请右键点击新窗口打开图片

实践案例由一个应用程序构成,其首先在 67 行调用 mmap() 函数分配一段虚拟内存,并在 77 行对该内存进行写操作,确保虚拟内存映射物理内存,然后在 80 行调用 madvise() 函数对虚拟内存发送 MADV_PAGEOUT 请求,以便让物理页被 SWAP OUT 到 SWAP Space。接着在 84 行调用 detect_physcial_address() 函数,该函数首先在 25 行计算出虚拟地址对应的页号(Page Number), 然后将页号乘以 8 字节,这里的 8 字节正好对应页表项的长度,接着在 27 行获得页内存偏移(Page Offset). 准备好数据之后,函数在 30 行打开 “/proc/self/pagemap” 文件,也就是打开当前进程的 pagemap 文件,然后在 36 行将文件读写指针移动到距文件开始 voffset 处,接着在 45 行从该位置读出长度为 8 字节的内容,此时这 8 字节正好是虚拟内存对应的页表内容,如果 Present 标志没有那说明页表不存在或者物理页被 SWAP OUT 到 SWAP SPACE; 反之 Present 标志位存在,那么函数在 55-56 行从页表项中获得物理页帧,然后加上页内存偏移就获得最终的物理地址,处理完毕之后函数关闭文件. 以上便是整个过程,那么接下来在 BiscuitOS 上进行实践:

图片无法显示,请右键点击新窗口打开图片

当 BiscuitOS 启动之后,直接运行 RunBiscuitOS.sh 脚本来运行实践案例,通过 “free” 命令可以看到有一个物理页被 SWAP OUT 了,那么读取虚拟内存对应的物理内存,但物理内存为 0,这里值得继续讨论.

图片无法显示,请右键点击新窗口打开图片


Pagemap 查询映射 MMIO 的页表信息

本节用于讲解如何使用 Pagemap 机制查看用户空间映射 MMIO 的页表信息,那么接下来通过一个实践案例进行讲解,实践案例在 BiscuitOS 上的部署逻辑如下:

cd BiscuitOS
make menuconfig

  [*] Package  --->
      [*] Paging Mechanism  --->
          [*] PROC PageMap with MMIO --->

# 源码目录
cd BiscuitOS/output/linux-6.0-x86_64/package/BiscuitOS-PAGING-PROC-PageMap-MMIO-default/
# 部署源码
make download
# 在 BiscuitOS 中实践
make build

BiscuitOS-PAGING-PROC-PageMap-MMIO-default Source Code on Gitee

图片无法显示,请右键点击新窗口打开图片

实践案例由两部分组成,其中一部分是一个内存模块,其主要目的就是将 MMIO 物理区域注册到系统地址空间里,因此模块在 32 行调用 request_resource() 函数将 21-26 行描述的 Broiler_mmio_res 物理区域注册到系统物理地址空间.

图片无法显示,请右键点击新窗口打开图片

实践案例的另外一部分由一个应用程序构成,其首先在 73 行通过 open() 函数打开 “/dev/mem” 伪文件,然后在 80 行调用 mmap() 函数分配一段虚拟内存映射到 MMIO_BASE 对应的 MMIO 上,并在 94 行对该内存进行写操作,确保虚拟内存映射 MMIO。接着在 98 行调用 detect_physcial_address() 函数,该函数首先在 25 行计算出虚拟地址对应的页号(Page Number), 然后将页号乘以 8 字节,这里的 8 字节正好对应页表项的长度,接着在 27 行获得页内存偏移(Page Offset). 准备好数据之后,函数在 30 行打开 “/proc/self/pagemap” 文件,也就是打开当前进程的 pagemap 文件,然后在 36 行将文件读写指针移动到距文件开始 voffset 处,接着在 45 行从该位置读出长度为 8 字节的内容,此时这 8 字节正好是虚拟内存对应的页表内容,如果 Present 标志没有那说明页表不存在或者物理页被 SWAP OUT 到 SWAP SPACE; 反之 Present 标志位存在,那么函数在 55-56 行从页表项中获得物理页帧,然后加上页内存偏移就获得最终的物理地址,处理完毕之后函数关闭文件. 以上便是整个过程,那么接下来在 Broiler 上进行实践(make broiler):

图片无法显示,请右键点击新窗口打开图片

当 BiscuitOS 启动之后,直接运行 RunBiscuitOS.sh 脚本来运行实践案例,此时看到可以读出之前向 MMIO 写入的数据,可是无法获得虚拟内存对应的 MMIO 物理地址,并且 Pagemap Entry 的值也是 0,那么需要跟踪代码查看为什么变为 0.

图片无法显示,请右键点击新窗口打开图片


Pagemap 查询匿名映射页表信息

本节用于讲解如何使用 Pagemap 机制查看用户空间匿名映射普通物理页的页表信息,普通物理页指的就是 4KiB 物理页,那么接下来通过一个实践案例进行讲解,实践案例在 BiscuitOS 上的部署逻辑如下:

cd BiscuitOS
make menuconfig

  [*] Package  --->
      [*] Paging Mechanism  --->
          [*] PROC PageMap with Anonymous --->

# 源码目录
cd BiscuitOS/output/linux-6.0-x86_64/package/BiscuitOS-PAGING-PROC-PageMap-ANON-default/
# 部署源码
make download
# 在 BiscuitOS 中实践
make build

BiscuitOS-PAGING-PROC-PageMap-ANON-default Source Code on Gitee

图片无法显示,请右键点击新窗口打开图片

实践案例由一个应用程序构成,其首先在 67 行调用 malloc() 函数分配一段虚拟内存,然后在 72 行进行写操作,确保虚拟内存已经映射物理内存,接着在 75 行调用 detect_physcial_address() 函数,该函数首先在 25 行计算出虚拟地址对应的页号(Page Number), 然后将页号乘以 8 字节,这里的 8 字节正好对应页表项的长度,接着在 27 行获得页内存偏移(Page Offset). 准备好数据之后,函数在 30 行打开 “/proc/self/pagemap” 文件,也就是打开当前进程的 pagemap 文件,然后在 36 行将文件读写指针移动到距文件开始 voffset 处,接着在 42 行从该位置读出长度为 8 字节的内容,此时这 8 字节正好是虚拟内存对应的页表内容,那么函数在 48 行先确认页表的 Present 标志位是否置位,如果没有那说明页表不存在或者物理页被 SWAP OUT 到 SWAP SPACE; 反之 Present 标志位存在,那么函数在 55-56 行从页表项中获得物理页帧,然后加上页内存偏移就获得最终的物理地址,处理完毕之后函数关闭文件. 以上便是整个过程,那么接下来在 BiscuitOS 上进行实践:

图片无法显示,请右键点击新窗口打开图片

当 BiscuitOS 启动之后,直接运行 RunBiscuitOS.sh 脚本来运行实践案例,可以看到实践案例首先输出虚拟地址,接着输出物理地址,因此可以看到用户空间进程也可以获得用户空间虚拟内存匿名映射对应的页表内容.

图片无法显示,请右键点击新窗口打开图片


Pagemap 查询文件映射页表信息

本节用于讲解如何使用 Pagemap 机制查看用户空间文件映射普通物理页的页表信息,普通物理页指的就是 4KiB 物理页(Page CACHE),那么接下来通过一个实践案例进行讲解,实践案例在 BiscuitOS 上的部署逻辑如下:

cd BiscuitOS
make menuconfig

  [*] Package  --->
      [*] Paging Mechanism  --->
          [*] PROC PageMap with File --->

# 源码目录
cd BiscuitOS/output/linux-6.0-x86_64/package/BiscuitOS-PAGING-PROC-PageMap-FILE-default/
# 部署源码
make download
# 在 BiscuitOS 中实践
make build

BiscuitOS-PAGING-PROC-PageMap-FILE-default Source Code on Gitee

图片无法显示,请右键点击新窗口打开图片

实践案例由一个应用程序构成,其首先在 71 行调用 open 函数打开 “/tmp/BiscuitOS.txt” 文件,然后在 77 行调用 mmap 函数将文件映射到进程的虚拟地址空间,其地址存储在变量 base 里,然后在 89 行进行从文件里读数据,确保虚拟内存已经映射到文件,接着在 92 行调用 detect_physcial_address() 函数,该函数首先在 25 行计算出虚拟地址对应的页号(Page Number), 然后将页号乘以 8 字节,这里的 8 字节正好对应页表项的长度,接着在 27 行获得页内存偏移(Page Offset). 准备好数据之后,函数在 30 行打开 “/proc/self/pagemap” 文件,也就是打开当前进程的 pagemap 文件,然后在 36 行将文件读写指针移动到距文件开始 voffset 处,接着在 42 行从该位置读出长度为 8 字节的内容,此时这 8 字节正好是虚拟内存对应的页表内容,那么函数在 48 行先确认页表的 Present 标志位是否置位,如果没有那说明页表不存在或者物理页被 SWAP OUT 到 SWAP SPACE; 反之 Present 标志位存在,那么函数在 55-56 行从页表项中获得物理页帧,然后加上页内存偏移就获得最终的物理地址,处理完毕之后函数关闭文件. 以上便是整个过程,那么接下来在 BiscuitOS 上进行实践:

图片无法显示,请右键点击新窗口打开图片

当 BiscuitOS 启动之后,直接运行 RunBiscuitOS.sh 脚本来运行实践案例,可以看到实践案例首先输出虚拟地址,接着输出物理地址,因此可以看到用户空间进程也可以获得用户空间虚拟内存文件映射对应的页表内容.

图片无法显示,请右键点击新窗口打开图片


Pagemap 机制实践

BiscuitOS 目前支持对 PageMap 机制的实践,开发者可以参考本节在 BiscuitOS 上实践案例. 在实践之前,开发者需要准备一个 Linux 6.0 X86 架构实践环境,可以参考:

BiscuitOS Linux 6.X x86_64 Usermanual

BiscuitOS 使用手册

部署完毕之后,针对 PageMap 机制 的实践,需要 BiscuitOS 使用 make menuconfig 选择如下配置:

cd BiscuitOS
make menuconfig
  [*] Package  --->
      [*] Paging Mechanism  --->
          [*] PROC PageMap  --->

make
# 源码目录
# Module
cd BiscuitOS/output/linux-6.0-x86_64/package/BiscuitOS-PAGING-PROC-PageMap-default/
# 部署源码
make download
# 在 BiscuitOS 中实践
make build

BiscuitOS-PAGING-PROC-PageMap-default Source Code on Gitee

通过上面的命令,开发者可以获得指定的源码目录,使用 “make download” 命令可以下载实践用的源码, 然后使用 tree 命令可以看到实践源码 main.c 和编译脚本 Makefile. 接下来在当前目录继续使用 “make build” 进行源码编译、打包并在 BiscuitOS 上实践:

图片无法显示,请右键点击新窗口打开图片

BiscuitOS 运行之后,可以直接运行 RunBiscuitOS.sh 脚本直接运行实践所需的所有步骤,开发者只需在意最后的运行结果,可以提升实践效率。以上便是最简单的实践,具体实践案例存在差异,以实践文档介绍为 准.

图片无法显示,请右键点击新窗口打开图片