写在前面:
平时大家有没有思考过一个问题:操作系统中这么多进程,CPU怎么知道我下一个数据放在哪个位置? 请带着这个疑惑来阅读本文。
虚拟地址
如果说我们操作系统的内存中只有一个程序在运行,那么我们就可以使用CPU的实地址模式,因为程序的地址在链接🔗的时候就可以确定了。例如,如果有个数据的内存地址是0x10086
,那么每次都把它装入0x10086
就可以了。
但是如果又加载一个程序B,就会出现如下问题:
- 程序A和程序B如果有内存地址冲突怎么办?
- 程序A和程序B不会互相读写各自的内存空间么?
- 内存的容量不会够么?
为了解决这个问题,科学家提出了以下这个方案:让所有程序都各自享有一个从0开始到最大地址空间,这个空间是独立的,是该程序私有的,其他程序既看不到,也不能访问该地址空间,这个地址空间和其他程序无关,和具体的计算机也无关。这就是虚拟地址。
正如其名,这个地址是虚拟的,计算机把握不住,自然也和具体软件和硬件环境进行了解耦。
链接器在代码编译的时候,将多个代码模块组装在一起,并解决模块之间的引用,形成程序运行的静态内存空间视图,具体可以参考这篇博客 —— 进行虚拟地址空间分布。
物理地址
虽然虚拟地址解决了很多问题,但是虚拟地址只是逻辑上存在,无法作用于硬件电路。程序装进内存中想要执行,就需要和内存打交道,从内存中取得指令和数据。但是内存只认一种地址,那就是硬件地址。
硬件地址,即物理地址。物理地址在逻辑上也是一个数据,只不过这个数据会被地址译码器等电子器件变成电子信号,放在地址总线上,地址总线电子信号的各种组合就可以选择到内存的存储单元。如果不做特别说明,我们说的物理地址就是指选择内存单元的地址。
虚拟地址到物理地址的转换
明白了虚拟地址和物理地址之后,我们发现虚拟地址必须转换成物理地址,这样程序才能正常执行。相当于执行了一个函数 物理地址 = func(虚拟地址)
。
那么要怎么实现这个函数呢?
计算机科学家最终选择了软件硬件相结合的方式实现,他就是MMU(内存管理单元)。MMU可以接受软件给出的地址对应关系数据,进行地址转换。
以下是彭东老师整理的MMU工作原理框架图:
下面我们不妨想一想地址关系转换表的实现。如果在地址关系转换表中,这样来存放:一个虚拟地址对应一个物理地址。
那么在32位地址空间下,4GB虚拟地址的地址关系转换表就会把整个32物理地址空间用完,这显然不行。
系统设计者采用了这样一个方案:把虚拟地址空间和物理地址空间都分成同等大小的块,也称页,按照虚拟页和物理页进行转换。根据软件配置不同,这个页的大小可以设置位4KB、2MB、4MB、1GB,这样就进入了现代内存管理模式 —— 分页模型。
结合图片可以看出,一个虚拟页可以对应到一个物理页,由于页大小一经配置就是固定的,所以在地址关系转换表中,只要存放虚拟页地址对应的物理页地址。
页表
MMU即内存管理单元,是利用硬件电路逻辑实现的地址转换器件,它负责接受虚拟地址和地址关系转换表,以及输出物理地址。相当于它就是哪个func
函数。
根据实现方式的不同,MMU可以是独立的芯片,也可以是集成在其它芯片内部的,比如集成在CPU内部,X86、ARM系列的CPU就是将MMU集成在CPU核心中的。
一级页表
为了说清楚MMU这个函数的工作原理呢?我们就需要从一开始的分段模型时的地址转换开始。因为分页机制也是建立在分段基址的基础上,也就是说分页其实只能是在分段之后进行的。
上图中,程序代码中的虚拟地址,经过CPU的分段机制产生了线性地址,平坦模式和长模式下线性地址和虚拟地址是相等的。
而在分段的过程中又是怎么去进行这个过程转换的呢?
在不打开分页进制的前提下,段基址和段内偏移地址时经过段部件处理后所输出的线性地址,CPU就认为它是一个物理地址,可以直接定位到内存啦:
如果打开了分页机制,段部件输出的线性地址就不再等同于物理地址了,我们称之为虚拟地址。其需要经过页表转换才能被送上内存:
而这个页表也就是分页机制的核心了。其本质呢是线性虚拟地址到物理地址的映射关系,默认大小呢就是上面提到过的4KB啦。
注:为什么页表大小是4KB呢?
- 首先如果页表很小,假如说是每个字节一一对应,那么32位的情况下页表内存消耗就是4GB*4B = 16GB。页表的消耗比内存还大,我觉得不行。
- 如果页表过大,假如说一个页表对应4MB,那么32位的情况下页表内存就是1024*4B = 4096B。看起来内存很省了呢,但是无疑带来了内存碎片过多的问题。不是每个分页都能用满4MB的
- 至于为什么是4KB这个数呢,则是早期内存空间过小时的最佳方案,属于历史遗留问题啦。现在看来32KB、64KB也许更适合呢
而我们刚刚提到了一个32位地址的问题,这里要解释一下。它其实是地址,32位中分配如下:
这样的32位地址便能表示4GB的内存大小空间了,其中12 ~ 31共20位可以表示是哪个页,0 ~ 12位则表示页内偏移。而每个内存块的尺寸为4KB,那么我们计算一下:4GB/4KB = 1M。也就是说我们其实是有1048576
个页表项的。而页表项则可以看成一个有1048576
个元素的一维数组,里面的元素则是此页表项对应的物理地址了:
总结一下一级页表的工作原理:
二级页表
我们刚刚也看到了一级页表有多达1048576
个页表项,这实在是有点多了。除此之外呢,一级页表中所有页表项都必须提前建好,这也是一级页表的缺陷了。
所以我们就要用到二级页表了,其页标准尺寸也是4KB大小。不同的是,二级页表将这1M(1048576)个页表项放到1K个页表中。这样的每个页表的物理地址在页目录表中都以页目录项的形式存储,而存储这个也被称为页目录表。其模型如下所示:
这样看来,32位的地址在二级页表也就有了不同的表示了:
首先来看看二级页表是怎么表示4GB大小空间的:每个页表可以容纳1024个物理页,故每个页表可表示的内存容量1024*4KB=4MB,页目录中共有1024个页表,故所有页表可以表示1024*4MB=4GB
的空间。
那么我们定位到某个物理页,必然要先找到其所属的页表。页目录最大是1024个页表,也就是210,所以10位就够了。找到具体页表之后呢,就需要确定具体是哪一页了,这样需要10位。而剩下的12位则是具体物理页内偏移了。
总结一下二级页表:
参考文献
[1] 彭东.操作系统实战45讲.极客时间
[2] 操作系统真相还原