分段

分段

在这里插入图片描述

如图,地址空间(左)的堆和栈之间,有一块每一使用的内部碎片,但是,如果要将整个地址空间加载到内存,内部碎片也会被加载。在比较极端的情况下,有一个很大的地址空间,而地址空间的大部分是没有被使用的内部碎片,在加载到内存时,很可能因为没有足够大的连续空间而加载失败;即使加载成功,大量的内部碎片会造成内存极度的浪费。

在更多的情况下,地址空间的大小远远大于实际的物理内存,但是地址空间中使用到的空间却不多,这样的地址空间称为sparse address space(稀疏地址空间),因此,通过加载整个内存空间从而构建虚拟内存系统很不完善。

内部碎片不可避免,要解决内存浪费的问题,就是不加载内部碎片因此,不能将整个内存空间直接加载到内存中,而是需要分段

分段:泛化的基址/界限

分段,就是要将地址空间的逻辑段加载到内存,而不是将整个地址空间加载到内存。所以,内部碎片不会被加载。

在之前不分段时,要将整个地址空间加载到内存,一个CPU需要通过一个基址寄存器记录起始的物理地址,需要一个界限寄存器记录地址空间的长度。

一个典型的地址空间有三个逻辑段:代码段(code)、堆段(heap)、栈段(stack)。要将这三个逻辑段装入内存,每一个逻辑段都需要一个基址寄存器界限寄存器,对于有三个逻辑段的地址空间,MMU就需要3对基址/界限寄存器进行地址转换。分段的基址寄存器也被叫做段寄存器

举个例子:

在这里插入图片描述

当左边的地址空间分段的加载到内存后(如右图),段寄存器的值如下:

基址 大小(界限寄存器)
代码 32KB 2KB
34KB 2KB
28KB
对于栈来说,它的生长方向是负增长
这里的28KB是栈底,也就是高地址的一端
而代码和堆是正方向增长,32KB和34B都是低地址的一端
2KB

引用哪个段

问题来了:现在给出一个虚拟地址(假设地址长度为14bit),怎么知道这个虚拟地址表示的内存在哪个段?

隐式方式

根据硬件来判断:在访问内存时,基于程序计数器,则访问代码段;基于栈或基址指针,则访问栈段。

显式方式

使用虚拟地址开始的几位来标识不同的段。

举例:有14位的虚拟地址。如果有三个段(代码、堆、栈),则至少需要2位二进制标识不同的段。

因此,对虚拟地址有如下的划分

在这里插入图片描述

对虚拟地址划分的本质:将地址空间划分

在这里插入图片描述

所以通过掩码3000H,可以知道虚拟地址在哪个段。
通过掩码0fffH,可以知道虚拟地址相对于这个段的低地址端偏移量(一定要强调开始位置,因为对于栈来说,它的基址寄存器记录的是高地址端)。

再次使用上面的例子:
在这里插入图片描述

基址 大小(界限寄存器)
代码 32KB 2KB
34KB 2KB
28KB
对于栈来说,它的生长方向是负增长
所以栈开始的物理地址是(28KB)
而不是26KB
2KB

现在,要访问虚拟地址为5KB的内存单元;
5KB使用二进制表示:01 0100 0000 0000
通过01,硬件可以知道在哪个段(本例中,是在堆段),段内的偏移为 0100 0000 0000 = 1KB

偏移量1KB<堆段限制寄存器的值(2KB),所以没有越界
物理地址 = 基址寄存器 + 偏移量 = 34KB + 1KB = 35KB

讨论一下“生长方向”

访问虚拟地址为15KB的内存单元。
15KB使用二进制表示:11 1100 0000 0000。
偏移量 = 1100 0000 0000 = 3KB

物理地址 = 偏移量 + 基址寄存器 = 3 + 28 = 31KB 为什么会出错?

1100 0000 0000(3KB)是相对于11 0000 0000 0000(12KB)的偏移量,也就是说,是相对于“低地址端”的偏移量。
由于栈的生长方向是负的,基址寄存器记录的是“高地址端”的栈底。

从“低地址端”到“高地址端”的块,就是虚拟地址为11 0000 0000 0000,大小为4KB的一个划分。

相对于“低地址端”的偏移量为3KB,所以相对于“高地址端”的偏移量为3KB-4KB=-1KB。

在这里插入图片描述

因为偏移量的绝对值1KB<2KB,所以没有越界。物理地址 = 偏移量 + 基址寄存器 = -1 + 28KB = 27KB

地址转换总结

在计算物理地址时,一定要搞清楚是相对于哪一端(高地址或低地址),虚拟地址中的偏移量,是相对于低地址的偏移量。对于反向增长的段来说,基址寄存器记录的是高地址端,所以虚拟地址的偏移量不能直接使用,需要转换为相对于高地址端的偏移;对于正向增长的段,就不用这么麻烦。

操作系统的支持

  • 上下文切换时,保存/恢复各个段寄存器的值。
  • 管理空闲内存
  • 注册异常处理程序

总结

分段,解决了因加载整个地址空间而引发的问题:因内部碎片太大而浪费物理内存。

分段没有解决的问题是产生外部碎片:在物理内存的多个段之间,存在着一些没有使用的空闲内存,它们可以被分配使用,但是,外部碎片往往空间不是很大,因此很难再被分配给其他段使用。

解决外部碎片的问题:紧凑(效率低,一般不会使用)、空闲内存管理、分页(后面的博客讨论)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章