x86系列的CPU是指最早由Intel公司生产的8086系列以及以后的后继产品,也包括了各CPU生产厂商仿制的产品。但不包括后来的各种形式的64位的CPU。
段是一个存储概念。
下面描述的主要是我的想象,你可以把它当成演义来看:),不必当真。
从8086
开始,Intel的CPU就已经是16位的了。所谓16位,是指它的数据总线,地址总线(注意,地址总线并不是如此的,后面会有详细的说明,事实上,它的
地址总线是20位的),各个寄存器都是16位宽的。那真是一个简单的年代。16位地址总线意味着可以寻址64K的地址空间。甚至在那个年代,64K也有点
小了。所以Intel决定(配合IBM的要求)采用20位地址总线。但这引发了一个问题,那就是:指令集如何设置?要知道,20位比16位多了4个位,这
四个位放在何处?寄存器都是16位的!聪明的Intel的工程师开始想到了一个办法(我估计是受DEC的启发,它的CPU就是分段式的内存,不过,
Intel的分段跟他还是有很大区别的,DEC的是不重叠的分段,而Intel的分段可以重叠),就是把地址空间分成两部分,一部分叫作段基址,一部分叫
作段内地址。段内地址就是原始的16位地址,所以,段的大小最大是64K,那么段基址怎么办?我们知道,段基址要在20位的地址空间中(也就是1M的地址
空间)表示一个地址,最合理的选择就是20位。但是,Intel的工程师当时认为,在一个16位的CPU中放置一个20位的寄存器不合情理,所以采用了一
个聪明的偷懒办法。用一个16位的寄存器表达一个低4位为零的20位地址。说得明确一点,我用16进制表示,一个16位的寄存器,其值为NNNN,那么我
们就把它看成NNNN0这样一个地址。这就是段基址寄存器。简单的叫做段寄存器。在我们想访问1M地址空间中的任意一个地址区域的时候,我们可以把段寄存
器的值调整的靠近(所谓的靠近,也就是比这个地址空间小,但是不小于64K的某个地方)这个地址空间,然后通过段基址和段内地址之和,就可以访问1M内任
意的地址空间了。修整段寄存器地址的这个动作叫作段切换。段内地址仍然是16位长,这也限制了段的长度是64K。看到这儿,你或许会想起些什么,或许会对
所谓的近指针、远指针的概念有了一些回忆。哈哈。机制描述完了,我们开始看看这个设计引发的一些有趣的限制和问题。首先,段基址被限定为低4位为0,也就
是NNNN0这种形式,那么,段基址一定是16的整数倍。呵呵,这个16被称作para(paragraph,段!注意,我们上面所描述的段被Intel
的工程师称为segment)。其次,由于FFFF0 +
FFFF寻址的地址已经大于1M了(事实上,整整比1M大了一个segment的长度,64K),而实际上20位地址总线确实只能寻址1M的地址空间,这
就说明了我们Intel工程师的这个设计其实没有达标!呵呵,确实没有想到会有这么一个问题。后来,应该是某个Intel的心理稍微有点变态的家伙说:我
们让他回环吧。一开始,估计大家都觉得很失败,后来,大家都不禁被这个想法逗乐了,回环就这样被确定下来了。我说说回环是什么。很有意思。由于段基址+段
内地址可以表达超过1M的地址空间,而地址总线只能寻址1M的地址空间,所以,所有超过1M地址空间的地址都减去1M。也就是说,所有大于1M的地址空间
都重新从0开始计算,就好像又绕回来了一样。这就是回环。
8086CPU内部有5?个段寄存器。也就是说,同一时间我们可以维持5个段的存在。如果我们需要多于5个段的地址空间,段切换就行了。
这是段的缘起。
由于80186是失败的,而且可以到手的资料少得可怜,我就掠过去不说,80286问世了。她第一次引入了保护。我们想一下,由于我们可以把内存
分段使用,所以似乎我们也可以简单的规定对某个段我们有什么可能的权利。比如:该段是TEXT段,只可以执行,或者该段是RDATA段,只能被读取什么
的。所以,给最初的段机制加上保护似乎是顺理成章的事情。但是,286在加入保护这个问题上考虑的太少了,以至于其实是不完善的。
我们仔细的分析一下。由于段是在para的边界指定的。同时,只要在para的边界上,我们就可以指定一个段,再次强调,para是16。这说
明:两个不同基址的段相差最少可以是
16。而段的最大长度却可以使64K。这回导致什么?这会导致段重叠。也就是说,一个真实的地址空间的地址,可以同时隶属于多个段。那么我们对它的保护是
什么样子的?所有段的可访问性的总和?最小可访问性?没有什么定论。所以说,通过段进行存储空间的保护,在Intel的CPU上,学理不太通顺(我并不是
要否定段保护,相反,完善的段保护机制非常有用,可惜,在x86的机器上行不通)。在x86的机器上,我们缺少了一些东西,一般认为,我们要加上段界限,
同时禁止段重叠,才能真正的实施段保护。当然,286也确实实施了一定的保护,所以286的段切换要比8086代价高。原因自然是段切换需要切换的场景信
息更多的缘故。
我上面的描述其实并不是事实,或者至少有一部分不是事实。那就是:286中的段寄存器存放的不再是简单的段基址了。它存放的是一个叫做段选择子的
东西,这个东西指向存储空间中的段描述符表中的某一项,或者简单的说,指向段描述符。段描述符里面存放的才是真正的段基址什么的。用比较直白的术语,我们
说它引入了一重间接性。
386出场了,看到别的CPU的寻址空间和OS都在飞速扩张,Intel终于搞出来一个32位的CPU出来。32位的地址空间逻辑上可以寻址4G
的存储系统。简单的说,我们可以认为段内地址扩张成32位的了,但是段这个概念以及其实现方式跟286没有太大差别。386引入了一个页的概念,不过这与
我们的主题无关。
我们说说OS方面的发展。由于地址空间扩大,而物理内存并没有同比例的扩大。(注:16
位扩展成32位,地址空间增加为原来的65536倍,就算从20位增加为32位,地址空间也增加了4096倍)。物理空间增加了大约500倍左右。这中间
有一个巨大的差值。聪明的人们于是搞出来一个叫做虚拟内存的东西。希望能够尽量的使用地址空间。基本的策略就是用硬盘(或者别的外部存储设备)的一部分假
装是内存。这样做有什么好处呢?这得从程序的特性说起,一个程序,不可能同时访问地址空间内所有的数据,而是相反,一段时间内集中在一个区域。这个特性叫
做局部性。根据局部性,对于需要立即访问的放在物理内存中,不需要立即访问的放在虚拟内存中,等到需要访问时,再装入内存中以供访问。这简化了应用程序的
逻辑,应用程序只需要按照自己的意图是用全部的地址空间就行了。这种物理内存和虚拟内存之间的倒腾由OS完成。实现虚拟内存需要CPU的支持(其实如果没
有也行,不过性能根本就没办法保证),现在常用的甚至是唯一的方式就是内存分页,这个倒腾的过程也因而叫做换页。逻辑上,我们可以用分段来实现虚拟内存的
(想想前面提到的段切换),但是由于段的不规整性(页的大小是一致的,规整的,而段不是,参见前面的描述),实现相对复杂,需要CPU的支持更多,性能更
难以保证。所以,现代OS全都不约而同地采用分页的方式实现虚存,而没有采用分段的方式。
那么,对于x86这种既支持分段,有支持分页的CPU来说,OS究竟怎么办?其实,很简单,简单的忽略分段就行了。那位说了,这不是你想忽略就忽
略的吧。x86CPU访问存储空间地址的时候就是采用段基址+段内地址的呀,这是硬件的实现,岂是我们可以忽略的?呵呵,如果我们把段基址规定成0怎么
样?这样我们就可以不关心段了。是的,现代OS都是这样做的。
比如:Windows系统,其几个段寄存器(存放的是段选择子了)都是指向28(好像是)号段描述符,而28号段描述符都是0基址的。
持平的说,x86系列CPU的段机制设计的还算是比较好的,颇有巴洛克的味道,跟Intel第一次64位CPU的尝试安腾一样。呵呵。
上面所说的一切都是记忆,不提供任何正确性担保,聪明人都不会看它的,当然,看了的人更聪明。
阅读(750) | 评论(0) | 转发(0) |