- 分段
问:为什么要有分段呢?
答: 到目前为止讨论的虚拟地址都是一维的,虚拟地址从0到最大地址,一个地址接着另一个地址。但是对于很多问题来说,有多个地址空间可能比只有一个要好得多。比如,一个编译器在编译过程中会建立许多表(比如源程序正文,符号表,整型量浮点常量表,语法分析树,编译器内部调用使用的堆栈),这些表随着编译的进行不断增长和缩小,但如果有时候一个表远比别的表大,那我们需要一种新的方法,使程序员不用管理表扩张和收缩。
问: 接着说!
答: 这样我们就想出了一个方法,就是在机器上提供多个互相独立的地址空间,称为段,每个段由一个从0到最大的线性地址序列构成。段的长度在运行期间可以动态改变,(比如,堆栈段的长度在数据被压入时增长,在数据被弹出时又减小)。因为每个段是相互独立的,因此我们可以独立的增长或减小而不会影响其他的段。 要在这种分段中指示一个地址,程序必须提供两个地址,一个段号,一个段内地址。 也就是说我们可以把段看成是一种二维的地址空间。
问:那段是虚拟的还是?
答:我们把段看成是一个逻辑实体,一个段可以包括一个过程,一个数组等,但一般它不会同时包含多种不同类型的内容。
问:那如果修改了段后被重新编译,那需要改相应的其他段么?
答: 不需要的,因为它们是相互独立的地址空间,并且我们没有修改它们的起始地址。然后在一维地址中,中间没有间隙,因此修改一个过程的大小会影响其他无关的过程的起始地址。
再问:分段还有别的优势么?
答:分段也有助于几个进程之间共享过程和数据。典型的就是:共享库。在分段系统中,我们可以把一个库放到一个单独的段中由各个进程共享,从而不需要在每个进程的地址空间中都保存一份。而在分页系统中就很麻烦,并且实际上也是模拟分段来实现的。
再问:那怎么保护段呢?
答:不同的段都有不同种类的保护,比如一个过程段被指明只允许执行,从而禁止对它的读出和写入,一个浮点数组可以被指明为允许读写但是不能执行。这样的保护有助于找到编程错误。 - 分页和分段的比较:
- 纯分段的实现:
问:分段和分页有什么不同呢?
答: 页面是定长的而段不是。当我们在分段中,当小段替换了大段,这样造成的空间碎片我们称为“外部碎片”,当然这样可以通过内存紧缩来解决。
这时我们在想,如何能把分段和分页结合起来(段页)呢? - 分段和分页的结合:MULTICS(麻省理工的操作系统):
如果一个段比较大时,把它保存在内存中很不方便,甚至不可能,因此我们在想是否可以把它分页处理。这样做就是为了和问也一样,只有真正需要的页面才会被调入到内存。
虚拟存储架构:
核心思想:就是给每个段都看作一个虚拟内存并对它进行分页,以结合分页的优点(统一页面的大小,以及使用段的一部分时不用把它全部调入内存)和分段的优点(易于编程,模块化,保护和共享)。
每个段都是一个虚拟地址空间,一个地址由两部分构成,段号+段内地址,段内地址又由页号和页内的字组成,也就是说:
地址=段号+页号+偏移量
执行流程(如图3-36):
1、根据段号找到段描述符,也就是包含存放页表地址的地方
2、根据页表是否在内存中,来做出相应判断,如果在则找到它的位置,如果不在,就类似于缺页中断,不过叫做“段错误”。
3、检查页表项,如果查找的页不在页表中就发生缺页中断,如果在就取出这个页面在内存的起始地址
4、偏移量+页面的起始地址==要访问的页在内存中的地址
5、最后进行读或写。
(在上面这个执行流程中,我们忽略了段描述符自己也要分页的事实,事实上我们的第一步不是通过段号找段描述符,而是通过一个寄存器(描述符基址寄存器)找到描述符段的页表,这个页表指向描述符段的页面。)来找到段描述符。
问:既然用到了分段和分页结合,那么用到TLB了么?
答: 当然用到了,我们在MULTICS硬件包含了16个高速TLB,对给定的关键字它能并行搜索所有的表项,也就是说当一个虚拟地址被送到计算机,寻址硬件首先检查虚拟机是否在TLB中,而不是先到段描述符或者页表中去查找。 - 分段和分页结合:x86:
和MULTICS相比,它有16K个独立的段(2^16),每个段容纳32位字。虽然段相对觉少,但是基本满足需要。因为一般程序很少需要1000个以上的段,但往往需要的是大段(具体就不写了,感觉记不过来了……)。 - 弦外话:
1、内存和磁盘上的空闲空间:位图,空闲区链表来保存。
2、几乎没有操作系统开发者会仔细考虑分段(因为它们更注重其他的内存模型)。