六、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 这个位置
;程序主体
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。