在实现引导加载器之前,首先我们先了解下在开机之后系统是怎么被引导的,这对于实现引导加载器来说是很重要的。只有知道原理,才能更好的实现嘛。
1 引导过程
1.1 当电源按钮按下时
当我们按下电源按钮是到底发生了什么?当这个按钮被按下后,连接到这个按钮的线缆会向主板发送一个电信号,主板简单的把这个信号转发给电源(PSU )。这个信号只包含1 比特信息。如果是0 ,表示没电(计算机关闭,主板不活动)。如果是1 (活动信号),意味着系统已经加电。当PSU 收到这个活动信号,它开始向系统的其余部分供电。当所有设备都得到正确数量的供电时,就可以确定PSU会持续向它们供电而不发生大的问题。PSU会发送一个“供电正常(power_good )”的信号到主板的基本输入输出系统 (BIOS) 。
1.2 BIOS自检
到那个BIOS 接收到“power_good ”信号,BIOS 开始一个称为POST(Power On Self Test 加电自检) 的初始化过程。POST 通过测试确保供电正确,设备已安装 ( 如:键盘、鼠标、USB 、串口等) ,并确保内存状态良好 ( 通过检测内存损伤) 。POST 向BIOS 交出控制权。POST 将BIOS 加载到内存的末尾( 可能是0xFFFFF0) 并且在内存的第一个字节处放置一个跳转指令。处理器指令指针 (CS:IP) 被设置为0 ,然后处理器得到控制权。什么意思呢?处理器会在地址0x0 处开始执行指令。这里,它是一条POST 程序放置的跳转指令,这条指令跳转到0xFFFFF0 处( 或者其他BIOS 被加载到的地址) ,然后处理器开始执行BIOS 。BIOS 得到控制权……
1.3 BIOS
基本输入输出系统(BIOS) 会做一些工作。它创建一个中断向量表 (IVT), 并提供基本的中断服务。BIOS 然后会做一些检查以确保没有硬件问题。BIOS 也提供一个设置的功能。BIOS 需要找到一个操作系统。根据您在BIOS设置中指定的引导顺序,BIOS 执行0x19 号中断来找出一个可引导设备。如果没有找到可引导设备 (INT 0x19 返回了) ,BIOS 会尝试引导顺序列表中的下一个设备。如果再没有可供尝试的设备,BIOS 会打印一个类似于“操作系统未找到”的信息,并停止系统的运行。
1.4 中断与中断向量表 (IVT)
一个中断是可以被许多不同的程序调用的子程序。这些中断被保存在从地址0x0 开始的被称为中断向量表的空间中。比如,一个常见的中断INT 0x21 被用于DOS 系统。
注意:这儿没有DOS !“只有”BIOS 设置的中断才有效,没有其他的!使用其他的中断会导致系统执行不存在的程序,这将导致你的系统崩溃。
注意:如果切换处理器模式到保护模式,IVT 会变得无效。这意味着,任何的中断(无论软硬件,包括BIOS )全都无效。 对于32 位操作系统,我们不得不这样做。
1.5 BIOS 0x19 中断
该中断由BIOS 执行。它读入磁盘的第一个扇区( 扇区(Sector ) 1, 磁头(Head ) 0, 磁道(Track ) 0) 。如果磁盘可引导,则引导扇区会被加载到0x7C00 , INT 0x19 会跳转到那里,将控制权交给引导加载器。
1.6 引导扇区
由0x19中断读取的第一个扇区正是引导扇区,里面存放了引导加载器的代码。当被读入内存0x7c00后,引导加载器就得到了控制权,开始运行加载系统。
1.7 为什么要引导加载器
为什么不把操作系统的代码直接写到第一个扇区,还要这么麻烦先运行个引导加载器,再把系统加载到内存中,开始我也觉得直接把系统写到第一个扇区更好,可后来发现其实不然。第二种方法就方便了,就像我们在操作文件一样的,可以随时修改系统程序,而第一中我们还要再每次对系统程序修改后用dd命令写入扇区,而且还要好好的计算大小,所以比较的麻烦,也比较容易出错。
2 引导加载器的实现
我的简单引导加载器实现的代码比较简单,就是从装有FAT12文件系统的软盘(软盘镜像文件)中读取一个叫setup的可执行文件,并最终执行这个SETUP程序,SETUP程序会在屏幕上打印preparing to load operating system...,如下图所示:
2.1 制作装有FAT12文件系统的软盘镜像文件
首先制作一个空的1.44M的软盘镜像文件:
dd if=/dev/zero of=bootsect.img bs=512 count=2880
然后用“mkfs.msdos -F 12”命令格式化软盘镜像文件为FAT12文件系统
mkfs.msdos -F 12 bootsect.img
2.2 拷贝SETUP程序到软盘镜像中
这个程序很简单,就打印一条语句,主要是为了测试引导加载程序是否成功加载了SETUP程序,编译可执行文件后怎样把它拷贝到软盘镜像中呢?这个其实很简单,就像其它的文件系统一样,FAT12也可以挂载到linux中的某个目录下,然后将SETUP程序拷贝到这个目录下就可以了。具体操作为:
losetup /dev/loop5 bootsect.img
mount /dev/loop5 floppy/
cp SETUP floppy/
umount floppy/
losetup -d /dev/loop5
不过在我系统里losetup -d /dev/loop5命令执行总是失败,总是提示loop: can't delete device /dev/loop5: Device or resource busy,网上有人说是bug,具体什么原因还不知道。
2.3 编写引导加载程序
引导加载器的思路其实很简单,就是先加载FAT12文件系统的根目录所在的扇区到内存中,然后在根目录中查找文件名为SETUP的目录项,找到后获得SETUP所在的族号。再加载FAT12的FAT表。根据找到的族号来获得SETUP在软盘中的具体位置,并将其加载到内存中,如果SETUP文件较大,一个族不够,可以通过FAT表查找一个族,再将下一个族加载到内存...,等全部加载SETUP到内存后,将控制权交给SETUP程序,然后SETUP程序就开始运行了,也就出现了上面图中的内容。这里的难点可能就是计算目录项在软盘中的位置、SETUP程序在软盘中的位置。
2.4 拷贝引导加载程序到软盘镜像的第一个扇区
对于编译好了的引导加载程序怎样拷贝到软盘镜像的第一个扇区来作为引导扇区呢?还是用linux的好工具dd,具体如下:
dd if=bootsect.img of=bootloader skip=1 seek=1 bs=512 count=2879
bootsect.img为我们前面制作好的软盘镜像,bootloader为我们编译好的引导加载程序。这条命令执行后bootloader就是我们的包含引导加载器的软盘镜像文件了,可以作为虚拟机的镜像文件来引导系统了(相当于我们这里的SETUP程序)。在bochs中运行的画面就是上面给出的图,当然也可以在其他虚拟机上运行,像wmware。
3 总结
实现这个引导加载器,让我学到了很多关于系统启动的东西,如引导扇区的加载过程,FAT12文件系统,软盘镜像的制作等。不过也发现了很多问题,特别是对汇编的不熟练,写这个程序中间一直出现问题,但开始自己也不会调试汇编程序,所以不知所措,后来才慢慢通过学习debug调试,bochs调试来慢慢解决的。不过深感还有很多东西要学习。