BIOS主导
BIOS —— 基本输入输出系统 计算机启动过程: BIOS按照“启动顺序”,把控制权转交给排在第一位的存储设备:硬盘。然后在硬盘里寻找主引导记录的分区,这个分区告诉电脑操作系统在哪里,并把操作系统被加载到内存中,然后你就能看到经典的启动界面了,这个开机过程也就完成了。
内存映射
利用内存映射文件技术,系统可以在内存空间中为文件保留一部分空间,并将文件映射到这块保留空间,一旦文件被映射后,操作系统将管理页映射缓冲以及高速缓冲等任务,而不需要调用分配、释放内存块和文件输入/输出的API函数,也不需要自己提供任何缓冲算法
实模式下的内存分布
从BIOS程序开始执行
BIOS 里的信息被映射到了内存 0xC0000 - 0xFFFFF (BIOS自有空间)位置,其中最为关键的系统 BIOS 被映射到了 0xF0000 - 0xFFFFF 位置。那么为什么一开机就执行了这一段,而非从头执行?
CPU 从内存的PC 寄存器中取出地址值并执行 。BIOS 程序的入口地址也就是开始地址是 0xFFFF0(人家就那么写的),也就是开机键一按下,一定有一个神奇的力量,将 pc 寄存器中的值变成 0xFFFF0,然后 CPU 就开始马不停蹄地跑了起来。没错,接下来这句话,可能就是你找了很久的答案,请做好准备:
在你开机的一瞬间,CPU 的 PC 寄存器被强制初始化为 0xFFFF0。如果再说具体些,CPU 将段基址寄存器 cs 初始化为 0xF000,将偏移地址寄存器 IP 初始化为 0xFFF0,根据实模式下的最终地址计算规则,将段基址左移 4 位,加上偏移地址,得到最终的物理地址也就是抽象出来的 PC 寄存器地址为 0xFFFF0。
BIOS中的程序
BIOS 程序里到底写了啥?
我们分析一些主要的。入口地址是 0xFFFF0,实模式下内存的下边界就是 0xFFFFF。入口地址处是个跳转指令,跳到一个更大范围的空间去执行自己的任务。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 这个位置。怎么复制的?当然是指令啦。哪些指令呢?这里我只能简单说指令集中是有 in 和 out 的,用来将外设中的数据复制到内存,或者将内存中的数据复制到外设,用这两个指令,以及外设给我们提供的读取方式,就能做到这一点啦。
启动区内容此时已经被 BIOS 程序复制到了内存的 0x7c00 这个位置,然后呢?这个其实也不难猜测,启动区的内容就是我们自己写的代码了,复制到这里之后,就开始执行呗,之后我们的程序就接管了接下来的流程,BIOS 的使命也就结束啦。所以复制完之后,接下来应该是一个跳转指令吧!没错,正是这样,PC 寄存器的值变为 0x7c00,指令开始从这里执行。
咦?不知道你有没有发现,我们似乎不知不觉又把之前的一句魔法语言翻译成人话了,开头我们说:
BIOS 把控制权转交给排在第一位的存储设备。
所以这句话是什么意思呢?就是 BIOS 把启动区的 512 字节复制到内存的 0x7c00 位置,并且用一条跳转指令将 pc 寄存器的值指向 0x7c00。你看,这不是也没多几个字嘛,就把这个问题说得明明白白,简简单单。
哦,对了,现在似乎就剩下一个问题了,为什么非要是 0x7c00 呢?好问题,当然答案也很简单,那就是人家 BIOS 开发团队就是这样定的,之后也不好改了,不然不兼容。为什么不好改?我们看一个简单的启动区 512 字节的代码。
; hello-os
; TAB=4
ORG 0x7c00 ;程序加载到内存的 0x7c00 这个位置
; FAT12格式软盘专用
JMP entry
DB 0x90 ;
DB "HELLOIPL" ;启动区名称(8字节)
DW 512 ;每个扇区大小(必须为512字节)
DB 1 ;簇大小(必须为1个扇区)
DW 1 ;FAT的起始位置(一般从第一个扇区开始)
DB 2 ;FAT的个数(必须为2)
DW 224 ;根目录大小(一般为224项)
DW 2880 ;该磁盘的大小(必须为2880扇区)
DB 0xf0 ;磁盘的种类(必须是0xf0)
DW 9 ;FAT的长度(必须是9扇区)
DW 18 ;1个磁道有几个扇区(必须为18)
DW 2 ;磁头数(必须是2)
DD 0 ;不使用分区,必须是0
DD 2880 ;重写一次磁盘大小
DB 0,0,0x29 ;意义不明,固定
DD 0xffffffff ;卷标号码
DB "HELLO-OS " ;磁盘的名称(11字节)
DB "FAT12 " ;磁盘格式名称(8字节)
RESB 18 ;空出18个字节
;程序主体
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才会认为它是一个启动区,才会去加载它。
启动区中的代码
BIOS 用很少的代码就把 512 字节的启动区内容加载到了内存,并跳转过去开始执行。那这 512 字节的启动区代码,是不是也可以把更多磁盘中存储的操作系统程序,加载到内存的某个位置,然后跳转过去呢?
没错,BIOS 负责加载了启动区,而启动区又负责加载真正的操作系统内核。
由于用于启动盘的磁盘是人家写操作系统的厂商制作的,俗称制作启动盘,所以他也肯定知道操作系统的核心代码存储在磁盘的哪个扇区,因此启动区就把这个扇区,以及之后的好多好多扇区(具体取决于操作系统有多大)都读到内存中,然后跳转到开始的程序开始的位置。跳转到哪里呢?这个就不像 0x7c00 这个数那么经典了,不同的操作系统肯定也不一样,也不用事先规定好,反正写操作系统的人给自己定一个就好了,别覆盖其他关键设备用到的区域就好。
启动过程回顾
- 按下开机键,CPU 将 PC 寄存器的值强制初始化为 0xffff0,这个位置是 BIOS 程序的入口地址
- 该入口地址处是一个跳转指令,跳转到 0xfe05b 位置,开始执行
- 执行了一些硬件检测工作后,最后一步将启动区内容加载到内存 0x7c00,并跳转到这里
- 启动区代码主要是加载操作系统内核,并跳转到加载处
经过这连续的四次跳跃,终于来到了操作系统的世界了,剩下的内容,可以说是整个操作系统课程所讲述的原理
|