虚拟地址到物理地址的映射(二)

虚拟内存到物理内存的推导

本文只介绍最普遍的64位地址,四级页表,每个页表4k的这种最基本最常见的情况。

虚拟地址到物理地址的映射(二)_第1张图片

linux内核将一个进程的内存映射表建立好之后,在该进程被调度运行的时候,会将PGD的物理地址放置到MMU的页表基地址寄存器中,在X86_64架构下,该寄存器为CR3,ARM64架构下,该寄存器为ttbr0_el1和ttbr1_el1,接下来的寻址过程中,就不需要linux来干预了,MMU会通过PGD-PUD-PMD-PTE-PAGE-OFFSET这个过程,根据虚拟地址,找到其对应的物理地址。

那么这个过程是怎样的呢?

1) 我们首先来拆分一下虚拟地址,以“filemap-addr:0x7fc3d3e4c000为例”,0x7fc3d3e4c000被分为5个部分,其中

  • 0-11bit为页内偏移地址,根据页基地址+偏移量找到对应的物理内存;
  • 12-20bit为PTE的索引,该索引可以找到物理内存页面的基地址;
  • 21-29bit为PMD的索引,该索引可以找到PTE的页基地址;
  • 30-38bit为PUG的索引,该索引可以找到PMD的页基地址;
  • 39-47bit为PGD的索引,该索引可以找到PUD的页基地址。

首先我们使用命令bc来得到0x7fc3d3e4c000的二进制:

我们将该地址按上述规则拆分一下:

PGD索引:11111111,需要左移12bit,得到11111111000,即0x7f8

PUD索引:100001111,需要左移12bit,得到100001111000,即0x878

PMD索引:010011111,需要左移12bit,得到010011111000,0x4f8

PTE索引:001001100,需要左移12bit,得到001001100000,即0x260

这样看起来更直观:

注意,PGD,PUD.PMD,PTE的索引都要左移12bit,可以看出来PGD的索引7f8,pud的索引878,PMD的索引4f8,PTE的索引260,都能和vtop给的信息对应上。

虚拟地址到物理地址的映射(二)_第2张图片

我们再使用rd命令直接从内存中读取信息看一下:

虚拟地址到物理地址的映射(二)_第3张图片

虚拟地址到物理地址的映射(二)_第4张图片

虚拟地址到物理地址的映射(二)_第5张图片

从这个过程中,我们可以看到MMU有两个数据信息,即PGD的基地址47de000和虚拟地址0x7fc3d3e4c000,MMU通过PGD+0x7f8找到PUD的地址5f7c000,通过PUD+878得到PMD的地址0x5f66000,通过PMD+4f8得到PTE的地址0x5f64000,通过PTE+260得到物理页面的地址28ba000,本例没有页内偏移量,索引0x7fc3d3e4c000对应的物理地址就是28ba000。

2)g-addr:0x4c82f0

转换成二进制

获取索引

可以看出PGD和PUD的索引为0,PMD索引为0x10,PTE的索引为0x640,都能和vtop给的信息对应上。

虚拟地址到物理地址的映射(二)_第6张图片

在得到页面的基地址0x1f4ed000后,再加上该变量在页内的偏移量之后0x2f0,得到该变量的物理地址0x1f4ed2f0。

使用rd命令直接从内存中读取信息看一下:

虚拟地址到物理地址的映射(二)_第7张图片

虚拟地址到物理地址的映射(二)_第8张图片

最后再看一下该变量的值:

与代码中赋值相同。

3)stack-addr:0x7ffe03a8ef1c

转换成二进制

获取索引

可以看出来PGD的索引7f8,pud的索引fc0,PMD的索引0e8,PTE的索引470,都能和vtop给的信息对应上。

虚拟地址到物理地址的映射(二)_第9张图片

在得到页面的基地址0x2940000后,再加上该变量在页内的偏移量之后0xf1c,得到该变量的物理地址2940f1c。

使用rd命令直接从内存中读取信息看一下:

虚拟地址到物理地址的映射(二)_第10张图片

虚拟地址到物理地址的映射(二)_第11张图片

虚拟地址到物理地址的映射(二)_第12张图片

4)heap-addr:0x11226f0

最后,我们在看一下堆内变量0x11226f0,先转换成二进制:

获取索引:

可以看出PGD和PUD的索引为0,PMD索引为0x40,PTE的索引为0x910,都能和vtop给的信息对应上。

虚拟地址到物理地址的映射(二)_第13张图片

在得到页面的基地址28b9000后,再加上该变量在页内的偏移量之后0x6f0,得到该变量的物理地址28b96f0。

使用rd命令直接从内存中读取信息看一下:

虚拟地址到物理地址的映射(二)_第14张图片

虚拟地址到物理地址的映射(二)_第15张图片

虚拟地址到物理地址的映射(二)_第16张图片

最后再看一下物理内存中的值:

与代码中赋值相符。

内存映射

我们在查阅内存映射关系的资料的时候,通常会找到一个这样一个图:

虚拟地址到物理地址的映射(二)_第17张图片

这个图很清楚的表示了PGD,PUD,PMD和PTE的关系,下面我们把本例中涉及到的地址数据填充进去,效果看起来会更直观和清晰。

虚拟地址到物理地址的映射(二)_第18张图片

总结

总结起来,一个变量的寻址过程就是,在编译或运行时被分配虚拟地址和物理内存,内核为该虚拟地址和物理内存的地址以该进程的PGD表为基础,建立映射关系,并将PGD的物理地址交给MMU,MMU根据映射关系通过虚拟地址找到物理地址,并按照程序的要求读写其中的内容。

1)编译和链接

在本例中有四种类型的变量:

  • filemap-addr:0x7fc3d3e4c000 内存映射文件虚拟地址
  • g-addr:0x4c82f0  全局变量虚拟地址
  • stack-addr:0x7ffe03a8ef1c   栈内变量虚拟地址
  • heap-addr:0x11226f0    堆内变量虚拟地址

其中内存映射文件的虚拟地址是内核执行mmap的时候分配的,栈和堆都是在进程创建的时候分配的物理内存并指定了虚拟内存地址,栈内变量和堆内变量的虚拟地址就是堆栈的虚拟地址加上偏移量获得。

全局变量的地址是在编译链接的过程中指定的,本例的全局变量没有初始化,所以放在a.out的bss区,bss区的起始虚拟地址为00000000004c82a0:

虚拟地址到物理地址的映射(二)_第19张图片

全局变量g的虚拟地址为:

2) 内存页表的建立

在进程创建的时候,内核会为a.out的每个section分配物理内存,堆栈也要分配物理内存,同时根据为进程创建物理内存和虚拟内存的映射关系。最后,把PGD的物理地址放到MMU的页表基地址寄存器中,剩下的事儿交给MMU。

可以理解为从虚拟内存到物理内存的转换的逆过程。

3)MMU进行地址转换

从虚拟内存到物理内存的转换是mmu通过页表映射来实现的,无需操作系统干预,本文所讲的寻址过程是最基础的虚拟地址到物理地址的转换过程,但MMU还会利用tlb来优化地址转换的效率,这个不在本文讨论的范围内。

本文只介绍了最简单的4级页表,4k页面的映射关系和寻址过程,其实,内核的内存管理还有更复杂的映射,比如使用5级页表,或者每个映射单位为2M或1G的内存块,这些映射方法的索引级数和各级索引所占的bit位都有所不同。

我们大多从一开始学习linux就听说了虚拟内存的概念,从32位系统就知道了三级页表,到64位的四级页表,但我并没有真真切切的在内存中看到这些页表的存在和它里面的内容,直到做了jeff老师的这个实验,通过dump_guest_memory获取内存,通过crash分析内存,根据拆分虚拟地址,获取每一级页表的索引,最终找到对应的物理内存。这个实践的过程让我对linux页表有了更好的理解,希望也能帮助到有同样困惑的朋友。

以上测试用例,分析方法,均来自jeff老师的内存管理课程,具体情况见以下链接:jeff老师的内存课程

你可能感兴趣的:(linux,运维,服务器)