计算机启动过程

关于这一个问题,网上有很多答案,但是说法都比效官方:
BIOS 按照“启动顺序”,把控制权转交给排在第一位的存储设备:硬盘。然后在硬盘里寻找主引导记录的分区,这个分区告诉电脑操作系统在哪里,并把操作系统被加载到内存中,然后你就能看到经典的启动界面了,这个开机过程也就完成了。
看完这一段话,你肯定会有很多疑问:为什么是 BIOS 主导这一切?怎么叫按照启动顺序?这个分区咋就被加载到内存了,有咋告诉电脑操作系统在哪里了?

对于以上的官方回答,我们来说说人话。但是在说人话之前,我们得先了解一些前置知识:
1、内存是存储数据的地方,给出一个地址信号,内存可以返回该地址所对应的数据。
2、CPU 的工作方式就是不断从内存中取出指令,并执行。
3、CPU 从内存的哪个地址取出指令,是由一个寄存器中的值决定的,这个值会不断进行 +1 操作,或者由某条跳转指令指定其值是多少。

好了,我们现在知道了CPU的工作方式,就是不断从内存中取指令并执行,那为什么会说是执行 BIOS 里的程序呢?这就不得不说说内存映射了。

二、内存映射
CPU 地址总线的宽度决定了可访问的内存空间的大小。比如:
16 位的 CPU 地址总线宽度为 20 位,地址范围是 1M。
32 位的 CPU 地址总线宽度为 32 位,地址范围是 4G。
但是,可以访问的内存空间,并不是全都给内存用,即:寻址的对象不只有内存,部分外设也要通过地址总线的方式去访问。
如何去访问这些外设呢?就是在地址范围中划出一片片的区域,分配给外设使用。
有一种不太正确的理解方式:内存中的这块位置就是显存,那块位置就是硬盘控制器。我们在相应的位置上读取或者写入,就相当于在显存等外设的相应位置上读取或者写入,外设的存储区域,被映射到了内存中的某一区域。

三、内存分布
内存中哪块区域,分给了哪块外设呢?参考实模式下的内存分布,如下图:
请添加图片描述
而计算机在刚开机的时候只有 1M 的内存可用。

我们看到,内存被各种外设瓜分了,即映射在了内存中。其中BIOS映射到了内存 0xC0000 - 0xFFFFF 的位置,甚至占用了开头的一些区域,比如把中断向量表写在了内存开始的位置。

四、怎么就从 BIOS 里的程序开始执行了
好了,现在我们知道 BIOS 里的信息被映射到了内存 0xC0000 - 0xFFFFF 位置,其中最为关键的系统BIOS 被映射到了 0xF0000 - 0xFFFFF 位置。

这里要用到另一个前置知识,就是 CPU 从内存的哪个位置取出执行并执行呢?是 PC 寄存器中的地址值。BIOS 程序的入口地址也就是开始地址是 0xFFFF0(人家就那么写的),也就是开机键一按下,一定有一个神奇的力量,将 pc 寄存器中的值变成 0xFFFF0,然后 CPU 就开始马不停蹄地跑了起来。没错,接下来这句话,可能就是你找了很久的答案,请做好准备:

在你开机的一瞬间,CPU 的 PC 寄存器被强制初始化为 0xFFFF0。如果再说具体些,CPU 将段基址寄存器 cs 初始化为 0xF000,将偏移地址寄存器 IP 初始化为 0xFFF0,根据实模式下的最终地址计算规则,将段基址左移 4 位,加上偏移地址,得到最终的物理地址也就是抽象出来的 PC 寄存器地址为 0xFFFF0。

至于如何强制初始化,就超纲了,况且各个厂商的硬件实现也不一定相同,有很多办法,也很简单。讨论起来意义就不大了。

五、BIOS 里到底写了什么程序
首先我们来猜测,入口地址是 0xFFFF0,说明程序是从这执行的。实模式下内存的下边界就是 0xFFFFF,也就是只剩下 16 个字节的空间可以写代码了,这够干啥的呢?如果你有心的话应该能猜出,入口地址处可能是个跳转指令,跳到一个更大范围的空间去执行自己的任务。没错就是这样,0xFFFF0 处存储的机器指令,翻译成汇编语言是:
jmp far f000:e05b
意思是跳转到物理地址 0xfe05b 处开始执行(回忆下前面说的实模式下的地址计算方式)。

地址 0xfe05b 处开始,便是 BIOS 真正发挥作用的代码了,这块代码会:检测一些外设信息,初始化好硬件,建立中断向量表并填写中断例程。
我们看后面精彩的部分,也就是 BIOS 的最后一项工作:加载启动区。

六、0x7c00 是啥
加载在计算机领域就是指,把某设备上(比如硬盘)的程序复制到内存中的过程。
加载启动区是指,BIOS 程序把启动区的内容复制到了内存中的某个区域。

启动区是符合某种特征的一块区域?先不急,不知道你有没有过设置 BIOS 启动顺序的经历,通常有 U 盘启动、硬盘启动、软盘启动、光盘启动等等,BIOS 会按照顺序,读取这些启动盘中位于 0 盘 0 道 1 扇区的内容。

接着说, 这 0 盘 0 道 1 扇区的内容一共有 512 个字节,如果末尾的两个字节分别是 0x55 和 0xaa,那么 BIOS 就会认为它是个启动区。如果不是,那么按顺序继续向下个设备中寻找位于 0 盘 0 道 1 扇区的内容。如果最后发现都没找到符合条件的,那直接报出一个无启动区的错误。

BIOS 找到这个启动区之后,会把这 512 个字节的内容,全部复制到内存的 0x7c00 这个位置。

启动区内容此时已经被 BIOS 程序复制到了内存的 0x7c00 这个位置。启动区的内容就是我们自己写的代码了,复制到这里之后,开始执行,之后我们的程序就接管了接下来的流程,BIOS 的使命也就结束了。所以复制完之后,接下来是一个跳转指令。PC 寄存器的值变为 0x7c00,指令开始从这里执行

所以,BIOS 把控制权转交给排在第一位的存储设备,可以翻译为:
BIOS 把启动区的 512 字节复制到内存的 0x7c00 位置,并且用一条跳转指令将 pc 寄存器的值指向 0x7c00

为什么非要是 0x7c00 呢?那就是人家 BIOS 开发团队就是这样定的,之后也不好改了,不然不兼容。为什么不好改?我们看一个简单的启动区 512 字节的代码。

; hello-os
; TAB=4

ORG 0x7c00 ;程序加载到内存的 0x7c00 这个位置

;程序主体

entry:
MOV AX,0 ;初始化寄存器
MOV SS,AX
MOV SP,0x7c00
MOV DS,AX ;段寄存器初始化为 0
MOV ES,AX
MOV SI,msg
putloop:
MOV AL,[SI]
ADD SI,1
CMP AL,0 ;如果遇到 0 结尾的,就跳出循环不再打印新字符
JE fin
MOV AH,0x0e ;指定文字
MOV BX,15 ;指定颜色
INT 0x10 ;调用 BIOS 显示字符函数
JMP putloop
fin:
HLT
JMP fin
msg:
DB 0x0a,0x0a ;换行、换行
DB “hello-os”
DB 0x0a ;换行
DB 0 ;0 结尾

RESB 0x7dfe-$ ;填充0到512字节
DB 0x55, 0xaa ;可启动设备标识
我们看第一行:

ORG 0x7c00
这个数字就是刚刚说的启动区加载位置,这行汇编代码简单说就表示把下面的地址统统加上 0x7c00。正因为 BIOS 将启动区的代码加载到了这里,因此有了一个偏移量,所以所有写启动区代码的人就需要在开头写死一个这样的代码,不然全都串位了。

然后正因为所有写操作系统的,启动区的第一行汇编代码都写死了这个数字,那 BIOS 开发者最初定的这个数字就不好改了,否则它得挨个联系各个操作系统的开发厂商,说唉我这个地址改一下哈,你们跟着改改。在公司推动另一个团队改个代码都得大费周折,想想看这样的推动得耗费多大人力。况且即使改了,之前的代码也都不兼容了,这不得被人们骂死啊。

再看最后一行:
DB 0x55, 0xaa
这也验证了我们之前说的这 512 字节的最后两个字节得是 0x55 0xaa,BIOS 才会认为它是一个启动区,才会去加载它,仅此而已。

回过头来说 0x7c00 这个值,它其实就是一个规定死的值,但还是会有人问,那必然有它的合理性吧。其实,我的解释也只能说是人家规定了这个值,后人们替他们解释这个合理性,并不是说当初人家就一定是这样想的,就好比我们做语文的阅读理解题一样。

另一种强行解释:第一个 BIOS 开发团队是 IBM PC 5150 BIOS,当时被认为的第一个操作系统是 DOS 1.0 操作系统,BIOS 团队就假设是为它服务的。但操作系统还没出,BIOS 团队假设其操作系统需要的最小内存为 32 KB。BIOS 希望自己所加载的启动区代码尽量靠后,这样比较“安全”,不至于过早的被其他程序覆盖掉。可是如果仅仅留 512 字节又感觉太悬了,还有一些栈空间需要预留,那扩大到 1 KB 吧。这样 32 KB 的末尾是 0x8000,减去 1KB(0x400) ,刚好等于 0x7c00。

七、启动区里的代码写了啥
启动区里的代码写了啥?就 512 字节就是全部操作系统内容了?
这是一个好问题,512 个字节确实干不了啥,现在的操作系统怎么也得按 M 为单位算吧,512 个字节远远不够呢,那是怎么回事呢?

其实我们可以按照之前的思路猜测,BIOS 用很少的代码就把 512 字节的启动区内容加载到了内存,并跳转过去开始执行。那按照这个套路,这 512 字节的启动区代码,是不是也可以把更多磁盘中存储的操作系统程序,加载到内存的某个位置,然后跳转过去呢?

没错,就是这个套路。所以 BIOS 负责加载了启动区,而启动区又负责加载真正的操作系统内核,这配合默契吧?

由于用于启动盘的磁盘是人家写操作系统的厂商制作的,俗称制作启动盘,所以他也肯定知道操作系统的核心代码存储在磁盘的哪个扇区,因此启动区就把这个扇区,以及之后的好多好多扇区(具体取决于操作系统有多大)都读到内存中,然后跳转到开始的程序开始的位置。跳转到哪里呢?这个就不像 0x7c00 这个数那么经典了,不同的操作系统肯定也不一样,也不用事先规定好,反正写操作系统的人给自己定一个就好了,别覆盖其他关键设备用到的区域就好。

八、操作系统内核写了啥
好了现在经过好几轮跳跳跳,终于跳到内核代码啦,我们来一起回顾一下:

按下开机键,CPU 将 PC 寄存器的值强制初始化为 0xffff0,这个位置是 BIOS 程序的入口地址(一跳)
该入口地址处是一个跳转指令,跳转到 0xfe05b 位置,开始执行(二跳)
执行了一些硬件检测工作后,最后一步将启动区内容加载到内存 0x7c00,并跳转到这里(三跳)
启动区代码主要是加载操作系统内核,并跳转到加载处(四跳)

经过这连续的四次跳跃,终于来到了操作系统的世界了,剩下的内容,可以说是整个操作系统课程所讲述的原理,分段、分页、建立中断、设备驱动、内存管理、进程管理、文件系统、用户态接口等等。

这些名次在操作系统的课程中你可能都或多或少听过,如果你好好学了的话也一定知道大概的原理,不过像笔者这样从头到尾研读过 linux 内核源码的硬核狗来说,这些概念不只是书本上枯燥无味的概念,而是活灵活现在操作系统的每一行代码上,有的展现了作者无比的智慧,有的让我看到了作者由于硬件设定不得已做出的屈服,建议你也找时间读一读,与我交流一下感悟哈哈。

转载:https://new.qq.com/rain/a/20201120a028ap00

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值