深入浅出内存管理-虚拟地址和物理地址转换

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/rikeyone/article/details/84672000

谈起内存管理,首先我们就要搞清楚虚拟地址和物理地址的关系。本文就是简单介绍下这两个基础概念。

物理地址

物理地址指实际存在的物理内存地址,比我有一个2G的内存芯片,那么系统的物理内存就是2G,我要访问该内存中的一个地址,那就需要对应的物理内存。

虚拟地址

虚拟地址,就是就是一种逻辑意义上的地址,而当我们想要访问这个虚拟地址时,是需要转换到物理地址才能够真实的访问到,比如我一个2G的内存,那么虚拟地址可能会超过2G的范围,那么直接去物理内存中寻找该地址是根本不存在的,因此我们需要一个虚实的转换,这个动作是由MMU内存管理单元来做的。

虚拟地址和物理地址映射

ARM32 Linux系统中每个进程都享有4G大小的虚拟地址空间,而物理地址大小要看设备配置了多大的物理内存,这个是实际存在的物理内存,比如2G。那么这就存在一个冲突,每个进程4G虚拟地址,怎么映射到仅仅只有2G的物理内存上去呢?这就是人类聪明才智体现的地方。MMU的设计就是为了解决这种虚实映射关系的,当然MMU并非单纯靠硬件即可完成关系的映射,它需要软件的参与和配合。MMU通过页表来对我们的地址进行转换,那么我们需要在MMU使能之前,把需要访问的地址转换页表配置完成并告知MMU,这样MMU就可以按照配置的页表来自动进行地址转换了这一点是最终靠硬件完成。

32 bit的Linux内核软件最大支持3级映射,而ARM32硬件上最大只支持2级映射关系,Linux内核是完全可以支持的,还是以它为例,:

 +-----------+------------+----------+
 |31       20|19        12|11       0|
 +-----------+------------+----------+
          |         |         |       
          |         |         |        
          |         |         |        
          |         |         |         
          |         |         +-----------> [0:11] in-page offset
          |         +---------------------> [19:12] 二级 index
          +-------------------------------> [31:20] 一级 index
  
TTBRx寄存器存放页表基地址

首先我们需要创建一级页表到一块内存区域内,每个页表项4Byte,12个bit寻址,那么这块内存区域大小不能超过16K。那么一个虚拟地址的[31:20]这12个bit就可以直接找到对应的一级页表项(PGD)

TTBRx
存放PGD页表基地址,4096个一级PGD页表项,虚拟地址的[31:20] 12 bit一共能寻址4096个PGD页表项

+------+
|4 byte| PGD entry
+------+
|4 byte| PGD entry
+------+
|4 byte| PGD entry
+------+

PGD页表项
存放二级PTE页表基地址,一个二级页表中存放有256个二级PTE页表项,可以通过虚拟地址的[19:12]来进行寻址二级页表项。

 +-----------+------------+----------+
 |31                    10|9        0|           PGD entry
 +-----------+------------+----------+
            |                  |       
            |                  |        
            |                  |        
            |                  |         
            |                  +-----------> [9:0] 各种标志位
            |         
            +-------------------------------> [31:10] 二级页表基地址(二级页表页存放的物理地址范围是12个bit)
                                                                      +------+
                                                                      |4 byte| PTE entry
                                     虚拟地址的[19:12] offset--------> +------+
                                                                      |4 byte| PTE entry
                                                                      +------+
                                                                      |4 byte| PTE entry
                                                                      +------+

PTE页表项
存放4KB物理页的基地址,这里是最终要访问的物理页。

 +-----------+------------+----------+
 |31                    12|11        0|           PTE entry
 +-----------+------------+----------+
            |                  |       
            |                  |        
            |                  |        
            |                  |         
            |                  +-----------> [11:0] 各种标志位
            |         
            +-------------------------------> [31:12] 物理内存页基地址

由于我们配置的是4KB大小的物理页面,那么一个4K大小的内存页,寻址特定的地址就需要12个bit,恰好我们的虚拟内存,剩余[11:0]是用来寻址最后物理内存页中的offset的。

通过这种转换后,可以看到最后访问的物理内存地址也是一个32 bit大小的地址,所以在这种平台,最大支持4G的物理访问地址空间。但是这种转换带来的好处时,我们的虚拟地址和实际的物理地址,并不是相等的,我们可以通过这种页表的映射来把不同的物理地址映射到虚拟地址上,这样通过内核的内存管理机制,我们可以给每个进程分配4G的虚拟内存,并且只在对应虚拟地址需要访问时才进行实际的映射,这样就能保证各个进程感觉自己真有4G的内存一样。

如何把4G虚拟地址映射到2G的内存上来

这个问题我想肯定困扰过很多刚刚接触内存管理的技术人员,笔者当初也是百思不得其解,经过后来的逐渐深入才见的真章。
缺页异常
一个是4G,一个是2G,怎么算都不会相等,其实内核的内存管理机制并不会立刻对这些地址进行映射,有基础的人应该都听说过直接映射和动态映射的概念,内核中是会划分一部分物理内存作为直接映射区,这部分是会在系统启动时直接映射到虚拟内存区域中,比如我们的kernel代码运行区域,除此之外,其余的物理内存都会保留下来供内核统一管理,这些同一个管理的内存只会在需要的时候才会进行映射,比如当我们的进程访问一个虚拟地址,而此时该虚拟地址恰好没有映射,那么就会产生缺页异常,此异常会被内核接收到,然后内存管理机制就开始进行处理,把需要访问的虚拟地址进行映射,映射到实际的物理内存上去。
swap机制
除了这个之外还有一个问题,系统中运行了那么多的进程,如果不停的访问不存在的虚拟内存,触发内核的缺页异常进行动态映射,那么总有一刻,2G的内存都被映射完了,那么此时还要继续访问更多虚拟地址,那么改怎么办呢?此时就涉及到内核的另一个内存管理机制,那就是swap机制,我们知道当安装一个Linux系统时,我们都要制定一个swap分区,这个分区就是在此时时候的,内核会把一些不常使用的内存页置换出来,数据保存到我们的硬盘上,这样就会有空余出来的内存继续被系统映射了,那么当其他进程要访问被置换的数据时,系统同样再从swap分区把对应数据恢复到内存中来。就是利用这种方式实现了2G物理内存当4G使用的目的。

不同进程4G的虚拟地址空间如何切换

前面我们介绍,MMU进行虚拟地址到物理地址转换时需要页表的,那么不同进程都有各自的4G虚拟地址空间,那么这些不同的区间如何划分和切换呢?实际上针对不同的进程,内核会维护各自进程的内存页表,我们内核代码是通过配置不同进程的内存页表来完成不同进程虚拟地址切换的。当我们的CPU在进行进程调度的时候,有一个节点就是要重新设置对应进程的页表。这样切换到不同的进程运行不用的程序时,就能保证各自空间的独立性。

猜你喜欢

转载自blog.csdn.net/rikeyone/article/details/84672000