《操作系统真象还原》——2.2 软件接力第一棒,BIOS-阿里云开发者社区

开发者社区> 开发与运维> 正文

《操作系统真象还原》——2.2 软件接力第一棒,BIOS

简介: Intel 8086有20条地址线,故其可以访问1MB的内存空间,即2的20次方=1048576=1MB,地址范围若按十六进制来表示,是0x00000到0xFFFFF。不知道硬件工程师当时设计的初衷是什么,总之人家有自己的理由,这1MB的内存空间被分成多个部分。

本节书摘来自异步社区《操作系统真象还原》一书中的第2章,第2.2节,作者:郑钢著,更多章节内容可以访问云栖社区“异步社区”公众号查看

2.2 软件接力第一棒,BIOS

BIOS全称叫Base Input & Output System,即基本输入输出系统。

人们给任何事物起名字,肯定都不是乱起的,必然是根据该事物的特点,通过总结,精练出一些文字来标识此事物,这个便是对一般事物取名的方法。通过名字,就能够反应出该事物的特性。最符合特性的名字就是昵称和外号了,比如抽油机是用来开采石油的一种机器,因为其工作时,就像“磕头”一样,所以大家给其起了更形象的名字—“磕头机”。

回到BIOS上,输入输出我理解,命名中加上系统二字也明白,可为什么还要用“基本”来修饰呢?不知道您是不是和我一样喜欢咬文嚼字,我们必须得把它搞清楚。

2.2.1 实模式下的1MB内存布局
先来点背景知识,很久很久以前:

Intel 8086有20条地址线,故其可以访问1MB的内存空间,即2的20次方=1048576=1MB,地址范围若按十六进制来表示,是0x00000到0xFFFFF。不知道硬件工程师当时设计的初衷是什么,总之人家有自己的理由,这1MB的内存空间被分成多个部分。
为了让大家先有个印象,免得太抽象不容易理解,先把实模式下1MB内存给大家梳理一下,很辛苦的,各位看官要仔细看哈,所以感兴趣或有强迫症的同学一定要背下来(玩笑),见表2-1。
screenshot
screenshot
先从低地址看,地址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称为基本输入输出系统的原因。

现在开始解释另一个问题,在CPU眼里,为什么我们插在主板上的物理内存不是它眼里“全部的内存”。

地址总线宽度决定了可以访问的内存空间大小,如16位机的地址总线为20位,其地址范围是1MB,32位地址总线宽度是32位,其地址范围是4GB。但以上的地址范围是指地址总线可以触及到的边界,是指计算机在寻址上可以到达的疆域。可是人家并没有说要寻哪里,就拿16位机来说,并没有说这1MB的寻址范围必须得是物理内存(内存条),难道人家20位的地址总线就认得这一亩三分地?完全不是。

归根结底的原因是这样的:在计算机中,并不是只有咱们插在主板上的内存条需要通过地址总线访问,还有一些外设同样是需要通过地址总线来访问的,这类设备还很多呢。若把全部的地址总线都指向物理内存,那其他设备该如何访问呢?由于这个原因,只好在地址总线上提前预留出来一些地址空间给这些外设用,这片连续的地址给显存,这片连续的地址给硬盘控制器等。留够了以后,地址总线上其余的可用地址再指向DRAM,也就是指插在主板上的内存条、我们眼中的物理内存。示意如图2-1所示。
screenshot
物理内存多大都没用,主要是看地线总线的宽度。还要看地址总线的设计,是不是全部用于访问DRAM。所以说,地址总线是决定我们访问哪里、访问什么,以及访问范围的关键。我们平时用的机器一般是32位,上面的内存条并不是全部都用到了,按理说内存条大小超过4GB就没意义了,超过了地址总线的势力就是浪费。不过通过前面的介绍,即使内存条大小没有超过地址总线的范围,也不会全都能被访问到,毕竟要预留一些地址用来访问其他外设,所以最终还得看地址总线把地址指向哪块内存了。这就是安装了4GB内存,电脑中只显示3.8GB左右的原因。

总之,表示地址的那串数字是地址总线的输入,相当于其参数,和内存条没关系。CPU能够访问一个地址,这是地址总线给做的映射,相当于给该地址分配了一个存储单元,而该存储单元要么落在某个rom中,要么落到了某个外设的内存中,要么落到了物理内存条上。可以想像成,CPU给地址总线提交一个数字,在地址总线看来,这串数字就是地址。地址分配电路根据此地址的范围,决定在哪个存储介质中分配一个存储单元,最后将此地址与此存储单元对应起来。当然事实上未必是这样,我刚才说了,可以想像成这样。我们学习新的知识,很多时候都是建立在原有的知识上,用原有的知识帮助学习新的知识,就像第一次听说电动车的时候,我们潜意识里是用车和蓄电池的概念在联想电动车的形象。如果要学的是一种全新的知识,并且无从用旧的知识来辅助学习时,试图靠想像力是非常有效的。对于知识的掌握,这并没有什么标准,每个人对知识的理解都是不同的,即使两个人都考了满分,其思考过程也是不同的。所以,对于一个新知识的掌握,本质上是给了一个能够说服自己的理由,能够自圆其说,这就够了。

2.2.2 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中抓的图,下面给大家分析一下这图中的信息都代表什么。

screenshot

首先得承认,这张图有点超前了,这是在有了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并填写中断例程。

好了,终于到了接力的时刻,这是这场接力赛的第一棒,它将交给谁呢?咱们下回再说。

2.2.3 为什么是0x7c00
计算机执行到这份上,BIOS也即将完成自己的历史使命了,完成之后,它又将睡去。想到这里,心中不免一丝忧伤,甚至有些许挽留它的想法。可是,这就是它的命,它生来被设计成这样,在它短暂的一生中已经为后人创造了足够的精彩。何况,在下一次开机时,BIOS还会重复这段轮回,它并没有消失。好了,让伤感停止,让梦想前行。

先说重点,BIOS最后一项工作校验启动盘中位于0盘0道1扇区的内容。

在此插播一段小告示:在计算机中是习惯以0作为起始索引的,因为人们已经习惯了偏移量的概念,无论是机器眼里和程序员眼里,用“相对”的概念,即偏移量来表示位置显得很直观,所以很多指令中的操作数都是用偏移量表示的。0盘0道1扇区本质上就相当于0盘0道0扇区。为什么称为1呢,因为硬盘扇区的表示法有两种,我们描述0盘0道1扇区用的便是其中的一种:CHS方法,即柱面Cylinder 磁头Header 扇区Sector(另外一种是LBA方式,暂不关心),“0盘”说的是0磁头,因为一张盘是有上下两个盘面的,一个盘面上对应一个磁头,所以用磁头Header来表示盘面。“0道”是指0柱面,柱面Cylinder指的是所有盘面上、编号相同的磁道的集合,形象一点描述就是把很多环叠摞在一起的样子,组合在一起之后是一个立体的管状。“1扇区”才是我们要解释的部分,将磁道等距划分成一段段的小区间,由于磁道是圆形的,确切地说是圆环,这些被划分出来的小区间便是扇形,所以称为扇区。好了,背景交待完了,重点来了,在CHS方式中扇区的编号是从1开始的,不是0,不是0,原谅我说了两次,良苦用心你懂的,所以0盘0道1扇区其实就相当于0盘0道0扇区,它就是磁盘上最开始的那个扇区。而LBA方式中,扇区编号是从0开始的。关于硬盘的知识我会在以后章节专门来讲,这里我若没表达清楚,大家先不要着急,只要知道MBR所在的位置是磁盘上最开始的那个扇区就行了。

继续说,如果此扇区末尾的两个字节分别是魔数0x55和0xaa,BIOS便认为此扇区中确实存在可执行的程序(在此先剧透一下,此程序便是久闻大名的主引导记录MBR),便加载到物理地址0x7c00,随后跳转到此地址,继续执行。

这里有个小细节,BIOS跳转到0x7c00是用jmp 0:0x7c00实现的,这是jmp指令的直接绝对远转移用法,段寄存器cs会被替换,这里的段基址是0,即cs由之前的0xf000变成了0。

如果此扇区的最后2个不是0x55和0xaa,即使里面有可执行代码也无济于事了,BIOS不认,它也许还认为此扇区是没格干净呢。

不过,这就又抛出两个问题。

(1)为什么是0盘0道1扇区的内容?

(2)为什么是物理地址0x7c00,而不是个好记或好看的其他地址?

先回答第1个,我想这个问题不用官方解释了,因为官方确实没什么好说的,不过他们出于尊重客户,还是会像我一样说出类似下面的话。

我就个人观点给大家一个理由,未经核实,仅是自己一面之词,请大家提高警惕,小心谨慎^—^。

在计算机中处处充满了协议、约定,所以,将0盘0道1扇区作为mbr的栖身之地,我完全可以理解为规定。我们反证一下,如果不存在这个“规定”,会发生什么。当然,此扇区最初是给BIOS使用的,咱们设想一下BIOS的工作将变成怎样。

主引导记mbr是段程序,无论位于软盘、硬盘或者其他介质,总该有个地方保存它。Ok,现在不告诉BIOS它存储在哪个位置了。BIOS只好将所有检测到的存储设备上的每一个存储单位都翻一遍,挨个对比,如果发现该存储单位最后的两个字节是0x55和0xaa,就认为它是mbr。这就好比查字典一样,不用偏旁部首和拼音检索的方法,只能一页一页翻了。

几经花开花落,找到mbr的那一刻,BIOS满脸疲惫地说:“你是我找了好久好久的那个人”。mbr抬起经不起岁月等待的脸:“难得你还认得我,我等你等到花儿都谢了”。其实BIOS的心声是:“看我手忙脚乱的样子,你们这是要闹哪样啊。就那么512字节的内容,害我找遍全世界,我们是在跑接力赛啊,下一棒的选手我都不知道在哪里……以后让它站在固定的位置等我!”

由于0盘0道1扇区是磁盘的第一个扇区,mbr选择了离BIOS最近的位置站好了,从此以后再也不担心被BIOS骂了。

计算机中处处有固定写死的东西,还用举个例子吗?不用了吧?因为任何一个魔数都是啊,有请下一个魔数0x7c00登场。

至于0x7c00,很久之前,比我好奇心大的人查遍了Intel开发手册都没找到相关的说明。要想知道事情的来龙去脉,还是要从个人计算机的初始说起,同样是很久很久以前……

1981年8月,IBM公司生产了世界上第一台个人计算机PC 5150,所以它就是现代x86个人计算机兼容机的祖先。说到有关历史的东西,不给来点真相就感觉气场不足,图2-3所示便是IBM PC 5150,有没有感受到计算机文化底蕴呢?

screenshot

既然Intel开发手册中没有相关说明,那咱们就朝其他方向找答案,换句话说,既然不是CPU的硬性规定,那很可能就是代码中写死的。为了搞清楚0x7c00是哪里来的,咱们先探索下“IBM PC 5150”的BIOS的秘密。请先深深呼吸一大口气,“0x7C00”最早出现在IBM 公司出产的个人电脑PC5150的ROM BIOS的 INT19H中断处理程序中,说了这么多定语,感觉气都喘不上来了。

通电开机之后,BIOS处理程序开始自检,随后,调用BIOS中断0x19h,即 call int 19h。在此中断处理函数中,BIOS要检测这台计算机有多少硬盘或软盘,如果检测到了任何可用的磁盘,BIOS就把它的第一个扇区加载到0x7c00。

现在应该搞清楚了为什么在x86手册里找不到它的说明了,它是属于BIOS中的规范。似乎这下好办了,既然是BIOS中的规范,那肯定是IBM PC 5150 BIOS 开发团队规定的这个数。

个人计算机肯定要运行操作系统,在这台计算机上,运行的操作系统是DOS 1.0,不清楚此系统要求的最小内存是16KB,还是32KB,反正PC 5150 BIOS研发工程师就假定其是32KB的,所以此版本BIOS是按最小内存32KB研发的。

MBR不是随便放在哪里都行的,首先不能覆盖已有的数据,其次,不能过早地被其他数据覆盖。不覆盖已有数据,这个好理解。说一下后面这个“其次”。通常,MBR的任务是加载某个程序(这个程序一般是内核加载器,很少有直接加载内核的)到指定位置,并将控制权交给它。所谓的交控制权就是jmp过去而已。之后MBR就没用了,被覆盖也没关系。我说的过早被覆盖,是指不能让mbr破坏自己,比如被加载的程序,如内核加载器,其放置的内存位置若是MBR自己所在的范围,这不就是破坏自己了吗,这就是我所说的“过早”了,怎么也得等MBR执行完才行。

重现一下当时的内存使用情况。

8086CPU要求物理地址0x0~0x3FF存放中断向量表,所以此处不能动了,再选新的地方看看。

按DOS 1.0要求的最小内存32KB来说,MBR希望给人家尽可能多的预留空间,这样也是保全自己的作法,免得过早被覆盖。所以MBR只能放在32KB的末尾。

MBR本身也是程序,是程序就要用到栈,栈也是在内存中的,MBR虽然本身只有512字节,但还要为其所用的栈分配点空间,所以其实际所用的内存空间要大于512字节,估计1KB内存够用了。

结合以上三点,选择32KB中的最后1KB最为合适,那此地址是多少呢?32KB换算为十六进制为0x8000,减去1KB(0x400)的话,等于0x7c00。这就是倍受质疑的0x7c00的由来,这下清楚了。

可见,加载MBR的位置取决于操作系统本身所占内存大小和内存布局。

我想大家现在都心痒痒了吧,说了这么久,CPU中运行的都是BIOS的代码,连自己一句代码都没跑起来呢。事不宜迟,马上写一个MBR,先让它跑起来再说。

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享:
开发与运维
使用钉钉扫一扫加入圈子
+ 订阅

集结各类场景实战经验,助你开发运维畅行无忧

其他文章