接上文,本来按照顺序来讲这一部分应该在前的,但是这一篇又是由上一篇引出来了,一步一步的深入,一点一点的陷进去,就像平时查东西,查了A又要去查B,然后……
上篇讲了计算机操作系统的启动,这篇就讲讲计算机的启动,从理所当然的按下电源键那一刻开始,别问我为什么要按电源键……
其实作者讲的非常清楚了,原著中由此引出的好多问题,都给予了解答,今天针对开机流程这个问题,我就总结一下,具体的请看链接:http://www.epubit.com.cn/book/onlinechapter/37376
从按下主机上的power键后,第一个运行的软件是BIOS。于是产生了三个问题。
(1)它是由谁加载的。
(2)它被加载到哪里。
(3)它的cs:ip是谁来更改的。
BIOS全称叫Base Input & Output System,即基本输入输出系统。
人们给任何事物起名字,肯定都不是乱起的,必然是根据该事物的特点,通过总结,精练出一些文字来标识此事物,这个便是对一般事物取名的方法。通过名字,就能够反应出该事物的特性。最符合特性的名字就是昵称和外号了,比如抽油机是用来开采石油的一种机器,因为其工作时,就像“磕头”一样,所以大家给其起了更形象的名字—“磕头机”。
回到BIOS上,输入输出我理解,命名中加上系统二字也明白,可为什么还要用“基本”来修饰呢?
实模式下的1MB内存布局
先来点背景知识,很久很久以前:
Intel 8086有20条地址线,故其可以访问1MB的内存空间,即2的20次方=1048576=1MB,地址范围若按十六进制来表示,是0x00000到0xFFFFF。不知道硬件工程师当时设计的初衷是什么,总之人家有自己的理由,这1MB的内存空间被分成多个部分。
为了让大家先有个印象,免得太抽象不容易理解,先把实模式下1MB内存给大家梳理一下,很辛苦的,各位看官要仔细看哈,所以感兴趣或有强迫症的同学一定要背下来(玩笑),见表2-1。
表2-1 实模式下的内存布局
起始 | 结 束 | 大 小 | 用 途 |
---|---|---|---|
FFFF0 | FFFFF | 16B | BIOS入口地址,此地址也属于BIOS代码,同样属于顶部的640KB字节。只是为了强调其入口地址才单独贴出来。此处16字节的内容是跳转指令jmp f000:e05b |
F0000 | FFFEF | 64KB-16B | 系统BIOS范围是F0000~FFFFF共640KB,为说明入口地址,将最上面的16字节从此处去掉了,所以此处终止地址是0XFFFEF |
C8000 | EFFFF | 160KB | 映射硬件适配器的ROM或内存映射式I/O |
C0000 | C7FFF | 32KB | 显示适配器BIOS |
B8000 | BFFFF | 32KB | 用于文本模式显示适配器 |
B0000 | B7FFF | 32KB | 用于黑白显示适配器 |
A0000 | AFFFF | 64KB | 用于彩色显示适配器 |
9FC00 | 9FFFF | 1KB | EBDA(Extended BIOS Data Area)扩展BIOS数据区 |
7E00 | 9FBFF | 622080B约608KB | 可用区域 |
7C00 | 7DFF | 512B | MBR被BIOS加载到此处,共512字节 |
500 | 7BFF | 30464B约30KB | 可用区域 |
400 | 4FF | 256B | BIOS Data Area(BIOS数据区) |
000 | 3FF | 1KB | Interrupt Vector Table(中断向量表) |
先从低地址看,地址0~0x9FFFF处是DRAM(Dynamic Random Access Memory),即动态随机访问内存,我们所装的物理内存就是DRAM,如DDR、DDR2等。又要开始咬文嚼字了,动态是什么意思?动态指此种存储介质由于本身电气元件的性质,需要定期地刷新。内存中的每一位都是由电容和晶体管来组成的,您想,单条内存现在都到4GB,内存条的体积大小您也清楚,那么小的面积得集成多少电容才能够拼凑出4GB的内存容量,不包括相关电路元件,也得是4GB×8个电容了。如此小的电容,其缺点也是明显的,漏电很快,所以漏电了就要及时把电补充上去,这样数据才不至于丢失。这个补充电的过程就称为刷新。其实不仅是电容需要刷新,就连电信号也是一样的,不知道您注意了没有,我们平时使用的网线,也是需要在每隔一定长度距离时接个中继放大器,这个就是来放大电信号的,因为物理链路一长,信号衰减就特别严重,只好通过这种“打气”的方式来保持稳定了。终于把动态这一词搞定了,不过我们最终要搞定的词是BIOS中的“基本”,所以咱们还得接着看。
见表2-1,内存地址0~0x9FFFF的空间范围是640KB,这片地址对应到了DRAM,也就是插在主板上的内存条。有没有人开始小声嘀咕了:为什么是对应到了DRAM,难道不是直接访问到我的物理内存DRAM吗?难道我的内存条不是全部的内存?还可以访问到别处吗?如果您有这样的疑问,我除了回答是啊是啊之外,还是很欣慰的,终于有人和我之前想的一样了。
一会再解释这个,否则咱们离“基本”越来越远了。表2-1,看顶部的0xF0000~0xFFFFF,这64KB的内存是ROM。这里面存的就是BIOS的代码。BIOS的主要工作是检测、初始化硬件,怎么初始化的?硬件自己提供了一些初始化的功能调用,BIOS直接调用就好了。BIOS还做了一件伟大的事情,建立了中断向量表,这样就可以通过“int中断号”来实现相关的硬件调用,当然BIOS建立的这些功能就是对硬件的IO操作,也就是输入输出,但由于就64KB大小的空间,不可能把所有硬件的IO操作实现得面面俱到,而且也没必要实现那么多,毕竟是在实模式之下,对硬件支持得再丰富也白搭,精彩的世界是在进入保护模式以后才开始,所以挑一些重要的、保证计算机能运行的那些硬件的基本IO操作,就行了。这就是BIOS称为基本输入输出系统的原因。
BIOS是如何苏醒的
BIOS其实一直睡在某个地方,直到被唤醒……
前面热火朝天地说了BIOS的功能和内存布局,似乎还没说到正题上,BIOS是如何启动的呢?因为BIOS是计算机上第一个运行的软件,所以它不可能自己加载自己,由此可以知道,它是由硬件加载的。那这个硬件是谁呢?其实前面已经提到过了,相当于是只读存储器ROM,因为它一直就睡在那里不动。
大家知道,只读存储器中的内容是不可擦除的,也就是它不像动态随机访问存储器DRAM那样,掉电后,里面的数据就会丢失。这种存储介质是用来存储一成不变的数据的,当数据写进去后,便与日月同辉,庭前坐看花开花落,不朽于天地万物之间,哈哈,有点夸张了。
BIOS代码所做的工作也是一成不变的,而且在正常情况下,其本身是不需要修改的,平时听说的那些主板坏了要刷BIOS的情况属于例外。于是BIOS顺理成章地便被写进此ROM。ROM也是块内存,内存就需要被访问。此ROM被映射在低端1MB内存的顶部,即地址0xF0000~0xFFFFF处,可以参考表2-1顶部的BIOS部分。只要访问此处的地址便是访问了BIOS,这个映射是由硬件完成的。
BIOS本身是个程序,程序要执行,就要有个入口地址才行,此入口地址便是0xFFFF0。
最重要的一点来了,知道了BIOS在哪里后,CPU如何去执行它,即CPU中的cs:ip值是如何组合成0xFFFF0的。
如果大家不了解内存的分段访问机制,可以参考第0章,里面有讲解CPU为什么分段方式内存。说正事,CPU访问内存是用段地址+偏移地址来实现的,由于在实模式之下,段地址需要乘以16后才能与偏移地址相加,求出的和便是物理地址,CPU便拿此地址直接用了。这个“段基址:段内偏移地址”的组合是0xffff:0吗?或者是0xF000:0xFFF0?或者是更奇葩一点的组合:0xFEEE:0x1110?或者您想出的组合比我的还奇葩,好啦,不折磨大家了,还是说正事要紧。既然作为第一个运行的程序都没开始执行,自然就没办法用软件搞定这件事了,还是得靠硬件支持才行。
在开机的一瞬间,也就是接电的一瞬间,CPU的cs:ip寄存器被强制初始化为0xF000:0xFFF0。由于开机的时候处于实模式,再重复一遍加深印象,在实模式下的段基址要乘以16,也就是左移4位,于是0xF000:0xFFF0的等效地址将是0xFFFF0。上面说过了,此地址便是BIOS的入口地址。
当我给出这个地址后,不知道大家意识到什么没有。BIOS是在实模式下运行的,而实模式只能访问1MB空间(20位地址线,2的20次方是1MB)。而地址0xFFFF0距1MB只有16个字节了(见表2-1除标题外的第一行),这么小的空间够干吗?BIOS又要检测硬件,做各种初始化工作,还要建立中断向量表……16字节的机器指令肯定干不了这么多事。也许有的同学会问,超过寄存器宽度会怎么样呢?比如0xFFFF0+16,这样就溢出了,由于实模式下的寄存器宽度是16位,0xFFFF0+16已经超过了其最大值0xFFFFF。溢出的部分就会回卷到0,又会重新开始,即0xFFFF0+16等于0,0xFFFF0+17等于1。
既然此处只有16字节的空间了,这只能说明BIOS真正的代码不在这,那此处的代码只能是个跳转指令才能解释得通了。好,既然心里有了推断,那咱们就来证明这个推断正确与否。
图2-2是我在bochs中抓的图,下面给大家分析一下这图中的信息都代表什么。
▲图2-2 bochs开机界面
首先得承认,这张图有点超前了,这是在有了MBR后才能抓到的,否则会提示boot failed: not a bootable disk,而我们还没有MBR,还没有写主引导记录。先不管这张图是怎么来的啦,反正大家立即就能够在自己的虚拟机里看到这张图了。大家先注意框框中的内容。一共有3个,最上面左边第1个标有cs:ip的那个框,cs寄存器的值是0xf000,ip寄存器的值是0xfff0,也就是段基址0xf000,段内偏移地址0xfff0,这个组合出来的地址便是0xffff0,这是处理器下一条待执行指令的地址。这与上面所说的BIOS入口地址是吻合的。另外,因为cs和ip寄存器中存储的是下一条要执行的指令,目前还没有执行,也就是说,当前还没有执行BIOS,这是机器刚开机的那一刻。这一刻还是值得庆祝的,因为即使是计算机行业的同学都很少看到这一刻,何况我们让这一刻停了下来,成为永恒。
按理说,既然让CPU去执行0xFFFF0处的内容(目前还不知道其是指令,还是数据),此内容应该是指令才行,否则这地址处的内容若是数据,而不是指令,CPU硬是把它当成指令来译码的话,一定会弄巧成拙铸成大错。现在咱们又有了新的推断,物理地址0xFFFF0处应该是指令,继续探索。
继续看第二个框框,里面有条指令jmp far f000:e05b,这是条跳转指令,也就是证明了在内存物理地址0xFFFF0处的内容是一条跳转指令,我们的判断是正确的。那CPU的执行流是跳到哪里了呢?段基址0xf000左移4位+0xe05b,即跳向了0xfe05b处,这是BIOS代码真正开始的地方。
第三个框框cs:f000,其意义是cs寄存器的值是f000,与我们刚刚所说的加电时强制将cs置为f000是吻合的,正确。
接下来BIOS便马不停蹄地检测内存、显卡等外设信息,当检测通过,并初始化好硬件后,开始在内存中0x000~0x3FF处建立数据结构,中断向量表IVT并填写中断例程。
好了,终于到了接力的时刻,这是这场接力赛的第一棒,它将交给谁呢?咱们下回再说。