1.实际上的内存管理机制
前边讲了两种内存管理的机制,分别是分段和分页。从用户的角度,我们希望将程序分成若干段存入内存中,因为我们通常喜欢用“代码段的第50条指令”“数据段数组的第500个单元”等描述查找内容。但是从物理内存的角度来看,分页才是真正的好选择。因为分页可以避免分段产生的内存碎片问题,能提高物理内存的空间利用率。
在实际的内存管理中,分段机制和分页机制其实是同时存在的,段面向用户,页面向硬件。
那么如何做到这一点呢?
查看图中的那个双向箭头,我们可以设计一种中间结构。一个程序用分段机制分成多个段,并且从中间结构上分割出一些区域和每个段建立映射,完成分段机制。中间结构可以被分成一小块一小块的页,将这些页放到物理内存的页框中,并建立这个页和页框的映射,完成分页机制。这样段和页的机制不久结合到一起了吗。
由于这个中间结构能分割成区和页,而区和页实际上就是一段地址区域,所以这个中间结构其实就是一个地址空间。但是这个地址空间和物理内存地址是不同的,物理内存地址对应了真实的存储单元,而中间结构的地址没有对应存储单元,也就是说这些中间结构其实是虚拟的,只不过看起来和物理内存很像。所以我们给这个结构一个名字,叫做虚拟内存。它也可以认为是一个物理字节数组,不过这个字节数组是存储在磁盘上。
也就是说将分段后得到的内存地址其实是假的,虚拟内存地址,这个地址还需要被操作系统进行分页映射才会得到真正的物理地址。
因此有了虚拟内存的辅助,分段和分页两种机制就能完美的结合到了一起使用。用户只需要关心逻辑地址映射到虚拟地址的过程,而虚拟地址映射到真正物理地址的过程用户则不需要关心。
2.段页同时存在时程序放入内存的过程
段页结合下,一个程序放入内存的过程大致如下:
(1)分虚拟内存:在虚拟内存中分出一些区域,将程序的各个段放入,虚拟内存分割的算法可以参照之前说的可变分区算法实现。但是这不是真正的放入,而是建立映射关系,假装放入。
(2)建立段表:建立段表记录这个映射关系
(3)分配物理内存:将虚拟内存分割成页,建立页到实际物理内存的页框的映射关系。
(4)建立页表:建立页表来记录虚拟内存页和物理内存页框之间的映射关系。
3.段页同时存在时的重定位(地址翻译)过程
以call 40为例,这个指令的翻译成机器码后的逻辑地址为[CS:40]。假定代码段是第0段,那么我们查询段表取出第0段对应的基址1000。可以算出虚拟地址为100+40=1040。
再用这个1040查询页号,假定一个页的大小为100,那么1040/100=10…40。也就是说页号为10,页内偏移为40。查表得到页40对应的物理页框号为5,所以最终的物理地址为540。
在地址总线中放入540后进行取地址,就可以取出我们要的正确而指令”mov 1,[300]”了。
这整个过程由MMU硬件完成。只要将MMY的段表寄存器(LDTR)以及页表寄存器(CR3)的值设置为正确的段表初始地址和页表初始地址,执行每一条指令时MMU就会自动完成上述地址翻译的过程。这就是真正的实际上计算机内存中发生的故事。
这个过程的代码部分请参考《操作系统原理,实现与实践》P173~P179。
补充:这里有一篇很好的参考文章,梳理了老师上课讲的很多内存管理的知识点
https://blog.csdn.net/daocaokafei/article/details/116207148