上电

我们知道,现在使用的计算机都是存储程序计算机(冯·诺依曼计算机)。计算机运行时一边从内存中读取指令,一边执行指令。进一步,可能也知道对于8086 CPU,段寄存器 CS指令寄存器 IP共同构成的地址[CS:IP]指向当前执行的指令。

但计算机上电开机之后,CS与IP中内容均为空,其它寄存器、内存的内容也为空。类似牛顿力学体系中的第一推动力一样,计算机上电后第一步该如何开始呢?牛顿给的答案是上帝提供了第一推动力。类似的,计算机启动第一步也只能通过体系外的某种方式解决。

上电后执行软件前只能靠硬件。CPU的硬件设计为上电后默认将CS置为0xF000, IP置为0xFFF0,即上电后默认执行的第一条指令位于[CS:IP]=0xFFFF0处的。而这个地址处存储的是BIOS。

BIOS

我们都知道计算机启动后会运行BIOS(basic input output system),进一步可能也知道BIOS存储在ROM中,当然也知道相对RAM,ROM掉电后信息不会丢失。计算机上电后,硬件将指令地址置为BIOS所在的0xFFFF0处,开始执行BIOS内容。

BIOS会先进行加电自检(POST, Power-On Self-Test),测试各项硬件是否正常等。

加电自检小插曲

我前一阵(2019年底)组装了一台AMD的电脑(AMD, YES!!!)。不考虑打游戏,只用来沉迷于学习,所以预算里没有考虑显卡。等所有零件到了之后迫不及待装好之后上电,然后显示器黑屏,主板上有一个标有VGA的LED红灯常亮。

想了一小会立即意识到,现在Intel的CPU基本都带了核显(大多数人觉得很鸡肋),但AMD的CPU并没有。BIOS加电自检挂这里过不去了。

后来没办法,买了一张二手的GTX960作为亮机卡。

BIOS中断

BIOS还会在内存中起始的4K处0x00000-0x003FF建立中断向量表,以及位于0x0E05B-0x0FFFE处对应的中断服务程序

BIOS提供的中断服务可以查阅网上资料,参考资料中也提供了一份。我们会用到如下几个:

中断号 描述
int 10h 显示服务
int 13h 低级磁盘服务
int 19h 加电自检后载入操作系统

调用BIOS中断时,可能需要设置一些参数。比如int 10h中断用来在屏幕上显示信息,就需要指定显示的字符串内容,显示的颜色,位置等信息。中断信息如何设置,BIOS对此都有详细的说明。

加载bootsec

BIOS加电自检后使用int 19h中断将软盘中的第一个扇区(512 byte)中的内容复制到内存0x7C00h处,然后开始执行。在Linux中,第1个扇区的内容被称为bootsec。

对于OS来说,我们可以认为计算机加电后直到将bootsec中的内容载入内存并开始执行,这些都是自动完成的,我们只需要关注从bootsec代码开始执行之后的部分就可以了。

bootsec之后的事情我们也可以大致设想一下:依靠bootsec这512个字节的代码,我们会继续读取软盘上更多的代码,然后依赖读入的代码完成更多的工作,直至整个系统运行起来。

一个简单的bootsec样例

CPU硬件设计师,BIOS系统开发等费了九牛二虎之力,终于在上电之后把OS自身最开始的512个字节的bootsec给运行起来了。作为“全村的希望”,当然是希望我们再接再励把OS运行起来的。但这里,我们不管这么多,先写一个hello world再说吧。

org 07c00h         ; 告诉编译器程序加载到7c00处
;----直接往显存中写数据
mov ax, 0xb800
mov gs, ax
mov byte [gs:0x00],'h'
mov byte [gs:0x02],'e'
mov byte [gs:0x04],'l'
mov byte [gs:0x06],'l'
mov byte [gs:0x08],'o'

times 510-($-$$) db 0 ; 填充剩下的空间,使生成的二进制代码恰好为512字节
dw 0xaa55             ; 结束标志

int10

上图中,0xb800是显存开始的地址。显存中,每2个字节对应显示字符,第1个字节为字符对应ASCII码,第2个字节为字符对应的颜色。屏幕每行有80个字符,共24行。这里,我们我们只修改了屏幕左上角开始的前5个字符对应的ASCII码,所以在左上角显示出了hello5个字符。

0xAA55

是不是将任意一张软盘插入计算机中,上电之后就会自动执行第1扇区的代码呢?其实并不是这样的。

软件中经常会约定使用一个魔数进行标识某个系统,比如在Java中,将文件中前4个字节固定为CAFEBABE用来标识这是一个class文件。同样,在BIOS中,约定只有当第一个扇区最后两个字节为0xAA55时才表示这是一个启动扇区。

运行bootsec样例

工具安装及使用可以参考附录中内容。

  • 编译代码

使用如下命令将以上汇编代码编译为二进制

nasm boot.asm -o boot.bin
  • 使用bximage制作软盘镜像文件。

bximage

  • 将二进制写入软盘
dd if=boot.bin of=a.img bs=512 count=1 conv=notrunc
  • 使用bochs运行bootsec

输入bochs启动虚拟机,然后在终端中输入c继续运行虚拟机,之后就可以看到如下画面了。

bochs

bochs

参考资料