操作系统存储管理之分段机制和段页机制

连续分配内存需要改进

连续内存分配在实际的运用中有一些问题和缺点,操作系统需要改进这个方式,于是出现了分段、分页和段页结合方式来管理内存,这些方式使我们的进程不用在乎内存的实际物理大小,甚至操作系统可以加载比物理内存还大的进程。我们把这些方式统称为非连续内存分配,非连续内存分配可以使内存的使用率更高,接下来介绍分段机制

内存分段机制

直接分配和使用物理地址会出现重定位的问题,为了解决这个问题,我们可以采用内存分段机制,我们的Intel 8086处理器访问内存时就是用了内存分段机制,我们来看看Intel 8086处理器是如何使用这种机制的。内存分段的原理就是,将内存分成一个一个段,段开始的地址就是我们的段地址,当我们程序执行时,只需要将程序的代码段或者数据段分配到这些段中,访问里面的内容时,我们只需要采用“段地址:偏移地址”方式即可。不过不难发现,还是会有重定位问题,因为我们的代码段有可能不知道数据段的段地址,所以这里还需要借助两个寄存器,我们称它们为段寄存器,分别是代码段寄存器(CS:Code Segment)和数据段寄存器(DS:Data Segment),改变CS里面的内容,处理器将从新的代码段开始执行,同样的,在开始访问数据段时,需要将CS设置好,使它指向执行正确的段地址,而在我们代码段的指令中,存放的并不是物理地址了,而是偏移地址,用DS中的段地址和偏移地址则可以访问到正确的物理地址。对于偏移地址在程序编译以后,就可以确定:

这是一段编译后的汇编代码,第二列我们称为汇编地址,他其实和我们的偏移地址是相等的,因此在编译后,偏移地址就确定了,这个偏移地址也称为逻辑地址,通过逻辑地址和段地址我们就可以得到正确的物理地址。高级语言逻辑地址生成过程:

内存如何分段

刚才我们已经知道在Intel 8086CPU中有两个段寄存器CS和DS,还有两个段寄存器ES(Extra Segment附加段寄存器)和SS(Stack Segment栈段寄存器)。另外在8086CPU中还有一个指令指针寄存器(Instruction Pointer),它只和CS一起使用,只有CPU才能修改它的内容,当一段代码执行时,CS指向段地址,IP指向段内偏移。段寄存器和IP都是16位的,16位加16位还是16位,也就是我们的地址可以是0000H到FFFFH,也就最多访问64K,但是我们8086CPU的地址总线是20位的(00000H到FFFFFH),可以访问1Mb的内存区域,逻辑地址到物理地址只能访问64K,但是总线能让我们访问1Mb的地址,我们不能把剩下的浪费了啊,为了解决这个问题,8086CPU在形成物理地址时,会先将段寄存器(代码段寄存器)的内容左移4位,形成二十位的逻辑地址,然后再和16位的偏移地址相加,得到20位的物理地址。比如:F000H:052DH,形成物理地址时,先将F000H左移4位,变为F0000H,再加上052DH,形成了物理地址F052DH,这个地址就可以占满总线,于是16位的寄存器也可以访问1Mb的物理地址了。现在我们开始分段,由于偏移地址是16位的,偏移地址能方位的大小最多64K,也就是我们的每个段最多64K,所以1Mb的物理内存,最多只能划分成16个段,每个段64K。分段开始位置如何确定呢,为了考虑溢出的情况,我们的起始地址必须为16的倍数,这称为16字节对齐。于是段的划分可以是任意大小,长度只要不超过64K,并且开始位置内存地址是16倍数即可。

程序也要分段

我们的内存区域可以分段了,而且分段的大小是比较灵活的,如果们的进程需要n个字节空间,我们只需要在内存中给它分配一个大小差不多的段,就可以满足它使用了,但是如果们的程序很大,内存分段的大小又不是可以任意大小的(偏移地址有限制),我们分段的意思就似乎没有了,所以我们为了尽量在限制范围内使用我们的内存段大小,我们的程序也需要按照需求进行分段,连续内存分配中我们把程序分成了代码和数据,我么可以继续将我们的程序分得更细:

  • 主代码段
  • 子模块代码段
  • 公用库代码段
  • 堆栈段
  • 堆数据
  • 初始化数据段
  • 符号表段

在我们的Java虚拟机中,就是采用的类似这种分法,将我们的Java程序分为多个部分,然后映射到我们的内存分段中,这样分是有一定依据的,因为这些段彼此的访问跨越比较少,如果我们把它们分得更细,可能会导致大量的跳跃访问,这就给操作系统带来更多问题,所以分段的粒度是需要综合考虑的

物理空间分段和逻辑地址空间的分段

上面我们已经将我们的内存空间和程序里面各自分段了,现在我们要做的就是将它们相互之间联系起来,将程序的分段映射到物理分段(段之间可以是不连续的)上,将内存分段的段号分配给各自的段,再利用程序中的偏移地址就可以访问内存了

数据结构

在分段机制中,一个程序加载时,我们的操作系统为它的各块分配了不同的块地址,操作需要将这些段地址记录在一个段表中,对应不同的进程,在进程切换时,可以通过进程加逻辑地址在段表中查到相应的物理地址

逻辑和物理都分段还有进步空间

分段的方式也有缺点,虽然我们已经将内存分段,但是对于程序段我们还是必须在连续的内存单元中,这样也会在一定程度上造成一定的外碎片。刚才提到了分段的粒度是一个一个的段,我们是否可以将我们的内存分得更细,使内存的利用率更高呢,这就是就进一步的段页式机制了

段页式存储管理

页式存储管理我们需要将虚拟的内存和实际的内存都进行分页,但是如果我们把程序的虚拟内存分得太细不利于我们的管理,而程序分成段的粒度是我们可以很容易接受的,所以我们需要采用段页来满足我们的需求。段页式就是在为段分配页表,CPU通过段表找页表,然后从页表中找到帧号计算出物理地址

坚持原创分享,您的支持将鼓励我不断前行!