PGD: Page Global Directory
PUD: Page Upper Directory
PMD: Page Middle Directory
PTE: Page Table Entry
本测试程序构建了一个场景,打印了一个内存映射文件的虚拟地址,一个全局变量虚拟地址,一个栈内变量的虚拟地址和一个在堆上动态分配变量的虚拟地址。接下来,我们使用qemu启动内核和rootfs,然后运行这个测试程序,看一下MMU是如何从这几个地址的虚拟地址找到其物理地址的。
1) 使用qemu启动系统
内核使用的是5.3,打开了CONFIG_PAGE_OWNER选项,上述test.c已经编译好,放到了rootfs的/test/mm目录下。
2) 运行测试程序
其中,内存映射文件变量的虚拟地址为0x7fc3d3e4c000,全局变量的虚拟地址为0x4c82f0,栈内变量的虚拟地址为0x7ffe03a8ef1c,堆内变量的虚拟地址为0x11226f0。
在64位操作系统中,无论是X86_64还是ARM64,都使用48bit的虚拟地址,同时都将虚拟内存地址空间分为内核地址空间和用户地址空间两个部分,内核地址空间的地址都已0xFFFF开头,用户地址空间以0x0000开头,所以上述几个虚拟地址都是用户空间的地址,属于a.out这个进程。
除了a.out打印的信息以外,我们还可以通过maps来看一下a.out进程内存映射的基本情况:
从这个图中,可以看出a.out代码段,堆栈,全局变量,动态链接库等映射的虚拟地址空间的范围、比如,栈的虚拟地址范围为“7ffe03a6f000-7ffe03a90000”,栈内变量的虚拟地址0x7ffe03a8ef1c刚好在这个区间内,还有堆的虚拟范围为“01121000-01143000”,堆内变量的虚拟地址0x11226f0也处于这个区间内。
3) 获取vmcore
接下来我们使用qemu monitor的dump-guest-memory功能将虚拟机中的内存dump出来,用于后续分析a.out虚拟地址和物理地址的映射关系.
我们在用qemu启动虚拟机的时候,使用了-monitor参数
-monitor unix:/tmp/mm-lesson-vm-mon,server,nowait \
这样的话,在qemu虚拟机里,可以使用ctrl a + c进入qemu的控制台。然后运行命令“dump-guest-memory -p dump-file-name”,这样虚拟机的物理内存就被dump到指定文件里了。
我们使用crash来对dump文件进行分析,首先我们使用crash打开dump文件,同时,也需要将内核的elf文件作为参数。
进入crash命令行之后,首先使用vm命令看一下a.out内存使用的大致情况:
可以看出,与/proc/80/maps信息基本相同。此外,我们还得到了更多的信息,比如a.out在内核中task_struct的地址是ffff888004ffbc80,pgd的地址是ffff8880047de000,我们使用crash命令来查看一下,看看信息是不是对的上。
接下来我们看下a.out里四个变量的信息:
1) 首先我们使用vtop看一下filemap-addr:0x7fc3d3e4c000的信息:
从上面信息可以看出0x7fc3d3e4c000所对应的物理地址是0x28ba000,我们之前看到过a.out的pgd的物理地址是47de000,那么47de7f8中的0x7f8就是虚拟地址0x7fc3d3e4c000所对应的PUD的地址在PGD中的偏移量,也就是说47de000 + 7f8这个地址里面,存放着PUD页表的物理地址,从上述信息来看,PUD的物理地址为5f7c000,后面的0x67为该页面的页表属性。以上就是对“PGD: 47de7f8 => 8000000005f7c067”这一行信息的解读。
接下来“PUD: 5f7c878 => 5f66067”,PUD的物理地址是5f7c000,878为PMD的索引,所以,对这一行的解读就是在5f7c000 + 878这个地址内存放着PMD的地址,即5f66000,067位页面属性。“PMD: 5f664f8 => 5f64067”也类似,PMD的物理地址是5f66000,4f8是PTE的索引,所以,对这一行的解读就是在5f66000 + 4f8这个地址内存放着PTE的地址,即5f64000,067位页面属性。“PTE: 5f64260 => 80000000028ba025”,这一行即将找到0x7fc3d3e4c000所处物理地址页面的基地址,也就是page table的基地址,即28ba000,后面的025是页面属性。
至此0x7fc3d3e4c000找到了它对应的物理地址28ba000,通常,最后12bit是虚拟地址在物理地址4k页面内的偏移量,但0x7fc3d3e4c000是内存映射文件的地址,内存映射通过mmap将内核的页面映射到用户进程的页表中,是4k对齐,所以最后12bit为0.
我们还可以通过内核提供的page owner功能来28ba000这个页面分配的过程。
首先计算28ba000的PFN,即右移12bit,得到28ba,十进制为10426:
然后回到qemu虚拟机中,进行如下操作:
可以看到PFN位10426这个页面分配的过程。
2)接下来我们再看全局变量“g-addr:0x4c82f0”,操作基本相同
从上述信息可以看出,PGD的地址是47de000,而且PUD的地址就存放在47de000的第0个索引内,也就是47de000 + 0,PUD的地址为5f7e000。PUD的第0个索引内放着PMD的地址,为5f7d000,PMD的5f7d000 + 010存放着PTE的地址5f7b000,PTE的5f7b000 + 640的地址存放着0x4c82f0所对应的物理地址的页面的基地址1f4ed000,再加上0x4c82f0的12bit页内偏移地址2f0,所以0x4c82f0对应的物理地址是1f4ed2f0。
通过page owner查看分配过程:
3)接下来我们再看“stack-addr:0x7ffe03a8ef1c”
从上述信息可以看出,PGD的地址是47de000,而且PUD的地址就存放在47de000的第7f8个索引内,也就是47de000 + 7f8,PUD的地址为5f7c000。PUD的第fc0个索引内放着PMD的地址,为5f7f000,PMD的5f7f000 + 0e8存放着PTE的地址5f7a000,PTE的5f7a000 + 470的地址存放着0x7ffe03a8ef1c所对应的物理地址的页面的基地址2940000,再加上0x7ffe03a8ef1c的12bit页内偏移地址f1c,所以0x7ffe03a8ef1c对应的物理地址是2940f1c。
通过page owner查看分配过程:
4)接下来我们看堆内的变量“heap-addr:0x11226f0”
从上述信息可以看出,PGD的地址是47de000,而且PUD的地址就存放在47de000的第0个索引内,也就是47de000 + 0,PUD的地址为5f7e000。PUD的第0个索引内放着PMD的地址,为5f7d000,PMD的5f7d000 + 040存放着PTE的地址5f65000,PTE的5f65000 + 910的地址存放着0x11226f0所对应的物理地址的页面的基地址28b9000,再加上0x11226f0的12bit页内偏移地址6f0,所以0x4c82f0对应的物理地址是28b96f0。
通过page owner查看分配过程:
总的来讲,解读vtop的打印信息需要了解,存储PGD,PUD,PMD,PTE的内存都是4k对齐,也就是最后12bit为0,vtop打印的信息通常就是页面基地址加上索引,4k对齐的页面在内存管理中,最后12bit会用于描述该页面的属性,如067,025等,对应这样的组合。
本文通过一个实验打印的信息以及使用crash对内存的分析,介绍了一个进程a.out的各级页表的内容和对应关系,下一张我们将介绍MMU是如何通过虚拟地址得到各级页表的索引,然后一级一级的找到最终对应的物理地址的过程。
以上测试用例,分析方法,均来自jeff老师的内存管理课程,具体情况见以下链接:jeff老师的内存课程