前段时间我在微博上看到了阮一峰的一篇日志《计算机是如何启动的?》才想起来自己之前尝试探索X86架构计算机的启动流程来着,趁着还没遗忘就先记录下一部分结论吧。不过相对于阮一峰的这篇博文,我的侧重点在于BIOS查找“启动顺序”(Boot Sequence)之前,也就是从按下电源到BIOS移交权限之间的这一段。关于MBR分区体系磁盘的分区表,阮一峰描述的很详细,我就不重复造轮子了。

顺便罗嗦一下,有关“扩展分区”(Extended partition)描述,阮一峰这里是很正确的,而网上有不少说法是错误的。倘若读者真有疑问,不妨用WinHex之类的工具直接以二进制打开磁盘,一看便知。Linux也可以用dd命令拷贝出磁盘的内容到文件,再查看文件内容。比如dd if=/dev/sda of=/tmp/xxx bs=512 count=1什么的。

言归正传,开始我们的探索之旅吧。不过,我们不会深入到开机电路之类的硬件问题上去,毕竟,我们还是以一个程序员的角度来看待计算机,再者,我也没有能力从硬件角度去探究。同时,我们假定读者能完全读懂并理解上面我提到的那篇博文并且能理解寻址空间,实模式、保护模式、端口独立编址和端口统一编址等相关术语名词。

我们从按下电源开始。

首先,是CPU Reset。主板加电之后在电压尚未稳定之前,其北桥控制芯片会向CPU发出重置信号(Reset),此时CPU进行初始化。当电压稳定后,控制芯片会撤销Reset信号,CPU开始工作。我们要探讨的第一个问题就是CPU执行的第一条指令的位置。

现在网上流传的资料基本上是8086 CPU的资料,给出的说法一般是这样:CS寄存器初始化为0xF000,IP寄存器初始化为0xFFF0,所以按照CPU实模式地址计算法则,CPU执行的第一条指令地址是CS*10h+IP,即0xFFFF0处。也许8086CPU确实如此,但我们的问题是,80386及其以上的CPU怎么处理呢?其计算地址法则还是如此吗?当然不是,否则我说这些废话做什么。如果读者之前对实模式和保护模式寻址以及地址计算的理念根深蒂固的话,那么请先暂时忘却以前的认知,因为我下文说的也许有些惊世骇俗(我指的是相对于国产的教材来说)。

第一点,80386及其以上的现代CPU(以下简称CPU)加电Reset之后并不是直接进入实模式;

第二点,CPU在合成地址的时候不区分实模式和保护模式。

我们知道,CPU进入保护模式的方法是CR0寄存器的PE Bit置为1。而在CPU刚加电的时候,CR0寄存器的PE位确实没有置1,那么,此时是实模式吗?暂时还不是,Intel并没有给给出表示此时CPU状态的术语名词,我们姑且称之为混沌模式吧。自从80386以来,因为增加了保护模式的缘故,CS等段寄存器不再是简简单单的段寄存器了,而是一个包含了段选择器(segment selector)、段基址(segment base),以及段限制(segment limit)的一组复杂寄存器。显然段基址决定着内存段的基地址。不过需要说明的是作为程序员只能操作CS寄存器中的“段选择器”这16位的大小,其它的区域作为隐藏区域对程序员不可见,我们无法访问。

当CPU处于段寻址模式的时候,假设段选择器(我们能访问的那16位)装入了0xF000,那么CPU会先将F000 * 10h也就是F0000h装入段基址里。之后需要合成地址的时候不考虑别的,而是直接从之前合成好的段基址里读出基地址F0000h加上IP寄存器里的偏移生成地址。如果CS寄存器的值不发生改变,段基址部分就不会发生改变。所以我们说,CPU在合成地址的时候不区分实模式和保护模式,CPU只是机械的从隐藏区域读出来段基址和IP寄存器的数值相加。

Intel这样做的目的何在?当然是为了效率,也许实模式的地址计算很快,但是保护模式计算一个地址还要有去内存中寻找段描述符等工作,这会大大影响CPU的效率,而我们知道,程序具有访问局部区域里的数据和代码的趋势(局部性原理)。所以在CS寄存器没有发生变化的时候,直接从之前隐藏区域获取段基址岂不是更快?当CS寄存器被修改呢?那CPU就再进行一次查找段描述符的操作,然后更新隐藏区域。

顺便说一句,利用CPU的这个特性,我们可以先进入CPU的保护模式配置好某个起始地址为0,段限长为4G的段描述符并加载到除过CS的其他任意寄存器(CS寄存器改变太过频繁),然后退出保护模式进入实模式去执行指令,利用80386之后长达32位的ESI或EDI寄存器和那个段寄存器配合寻址就可以在实模式下访问全部地址空间了。(中途不可以修改那个载入过段描述符的寄存器,否则其隐藏区域会被更新)。这种方式是一种称之为Big Real Mode的模式,其区别于实模式和保护模式。

说了这么多,我们找张图片大概说明一下CS寄存器。下图来自Intel的“CPU使用说明书”,著名的那三卷开发文档的第三卷《Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 3A: System Programming Guide》P91中的插图,也算是对我上文的证明,证明下我不是在信口开河。至于隐藏区域有多大,不好意思,Intel没有说,不过我估计不会短于一个段描述符的大小,也就是至少有8字节(64位)吧。(纯属猜测,这句话不负责任)

有了上述的证明,我们可以继续说下去了。CPU在Reset之后,IP寄存器被置为0x0000FFF0,CS寄存器的段选择器默认值是0xF000,而隐藏区域中的段基址却没有按照实模式的标准去装入,而是被置为0xFFFF0000。那么CPU生成的第一条访问地址是什么呢?显而易见,80383之后的CPU合成的是0xFFFFFFF0这个地址,这也符合Intel文档的说法。也有资料显示从80286之后的CPU就是这个地址了,暂时我没有考证。即便是有其它文档有不同见解,我还是觉得Intel的说法更靠谱,毕竟还是人家造的处理器不是。

再来一张图,说明下80386以上CPU在保护模式下的寻址图,同样来自那个PDF。

所以我们说CPU执行第一条指令的时候不是实模式也不是保护模式,而是一种怪异的中间模式。

问题又来了,这一条指令在哪?我们知道计算机开机后首先读取哪里呢?BIOS!对就是它,这一条指令会被指向BIOS。

我们虽然已经假定过读者知道线性地址空间的大致意思,但我觉得还是有必要简单说一下,IBM PC一部分的端口采用独立编址,而另一部分采用端口统一编址,传统PC机使用0x000~0x3FF共1024个端口地址。现代PC则有多达64KB的 I/O 端口提供编址。不过显存等一些硬件的地址还有所有BIOS的编址却在线性地址空间里。这也是我们所谓的32位操作系统没办法完全利用4G内存的原因,尽管寻址能力有4G(共2的32次方个地址),但是并不是所有的地址都能分配给内存使用。

对于传统的CPU+北桥+南桥类型的主板来说,CPU的地址请求通过FSB(Front Side BUS前端总线)到达北桥,北桥将这个请求送到南桥。而对于最新的主板芯片组来说,北桥和CPU封装在一颗芯片里面,所以会看到这个请求通过DMI/QPI(Quick Path Interconnect,即快速通道互联,是Intel用来取代FSB的新一代高速总线,CPU与CPU之间或者CPU与北桥芯片之间都可以使用QPI相连。在民用级的i7+X58平台,i7处理器与X58北桥芯片之间就通过QPI总线相连)被送到南桥。请求到达南桥后,南桥根据目前的地址映射表的设置决定是否将请求转发到SPI(Serial Peripheral Interface)或者LPC(Low Pin Count)。

这里貌似说的过于底层了,简单说就是南桥芯片拥有一张地址映射表,当有地址解析的请求到来时,南桥查看这张表决定将地址解析到何处去。这张表里有两个特殊区域,一个是从地址空间4G向下,大小从4MB到16MB不等的一个区域,我们以4MB为例,地址空间从FFFC00000h~FFFFFFFFh。称之为Range 4G。第二个区域一般是是从1MB向下128KB的范围,即E Segment和F Segment,从E0000~FFFFF,称之为Legacy Range,也就是说,FFFC00000h~FFFFFFFFh之间和E0000~FFFFF之间的寻址请求都会被导向到SPI/LPC,最终指向了BIOS。

呼~说了这么多,这个地址总算是指向了BIOS了。解决了第一条指令,接下呢?厂商们有分歧了,Intel设计的EFI(Extensible Firmware Interface)的做法和传统的Legacy BIOS就不一样了。

就Legacy BIOS来说,放在0xFFFFFFF0的第一条指令一般是一个远跳转指令(far jump),也就是说CPU在执行Legacy BIOS时,会直接从0xFFFFFFF0跳回F Segment,回到1MB以下这个Legacy BIOS的老巢里去。而EFI BIOS的第一条指令是wbinvd(清洗CPU高速缓存),之后做一些设定之后,会直接进入保护模式。所以EFI BIOS是从南桥Region 4G通过,并不需要Legacy Region。

必须说明,这里提到的一些说法参考自一些国外论文及其译文,我只能考证其说法而没有办法考证原作者。虽然在博文内容上我要求自己按照论文来写,但是引文上没有办法考证的我就只考证说法的正确性而不注明出处了,大家见谅。

然后按照传统的BIOS所做的事情,接下来是Power-On Self-Test(开机自检)、查找启动设备等工作,这里有篇文章说的不错。我考证了下基本靠谱:

http://www.360doc.com/content/06/0810/13/0_177979.shtml

别人写过的我就不写了,大家就自己去看吧。有什么问题的话我们再探讨。这篇文章标题貌似有点问题了,我们毕竟只是讨论了CPU Reset的一个小问题,和计算机启动相去甚远呢。反而阮一峰那篇博文更贴切呢。

赏杯咖啡鼓励下~