操作系统实现(二):一级引导程序boot
本节背景
计算机开机启动后,位于ROM中的BIOS即开始上电自检,这个过程BIOS会检测硬件设备是否存在问题。如果检测无误的话,将根据BIOS的启动项配置选择引导设备。
当检测到所选的引导设备的第一个扇区以被称为魔法数(0x55和0xaa)的两个字节结尾时,BIOS即认为该扇区是一个引导扇区(boot sector),则该引导设备是可引导的(bootable)。
随即BIOS将此扇区的数据复制到物理内存地址0x7c00处,随后将处理器的执行权移交给这段程序(跳转至0x7c00地址处执行),此即为第一次控制权转移。
用于引导内核启动的程序叫做引导程序(bootloader),引导程序需要完成引导扇区自启动、文件系统识别、硬件检测、处理器模式切换、页表配置等工作,并最终将控制权交给系统内核。
由于引导扇区被限制为一个扇区大小(512B),而对于现代操作系统的引导程序(需要设置显示模式、切换处理器模式及设置页表等)来说显然不够,因此将引导程序分为两段是现代操作系统开发中更常见的做法。
将引导程序分为两级后,一级引导程序boot用于寻找并加载二级引导程序进内存,并将控制权转移给二级引导程序;而二级引导程序loader则用于实现引导程序的主要任务,并加载内核程序进入内存,最终实现控制权转移给内核程序。
本节目的
- 完成一级引导程序boot.asm的编写,并在其中实现FAT12文件系统。
- 制作操作系统(启动)盘。
- 进行第一次虚拟机启动。
实现
首先对一些常量进行定义,并规定程序起始地址。
(此部分只面向汇编器,汇编后不占用任何存储空间,)
; boot.asm |
FAT12
对于加载Loader程序,最理想的方法自然是从文件系统中把Loader程序加载到内存里。考虑到代码的易实现性,本操作系统将选用逻辑简单的FAT12文件系统来装载Loader程序和内核程序。
要将系统盘格式化为FAT12文件系统,需要在引导程序的开头使用FAT12文件系统结构,这样的引导扇区被称为FAT12文件系统引导扇区结构,包括跳转指令(3B)、FAT12格式信息(59B)、引导代码(448B)及结束标志魔法数(2B),共计512B。(关于FAT12的相关补充知识见文章末尾)
; FAT12格式 |
start
在程序的起始部分立即对部分寄存器进行初始化,由于代码段寄存器cs已经在控制权进行转移时被赋值,且其他段寄存器我们尚且用不到,因此这里只对数据段寄存器中的ds、栈段寄存器ss以及栈指针寄存器sp进行赋值(关于寄存器的相关补充知识见文章末尾)。
完成寄存器的初始化后即调用BIOS中断进行清屏操作并显示启动字符串信息。(关于BIOS中断相关补充知识自行搜索)
最后就是boot程序的主要任务——搜索二级引导程序loader。
start: |
搜索loader.bin
这里我们使用三层循环在根目录扇区对loader.bin文件名进行搜索。
LoadRootDirSec: |
文件未找到
如果未找到loader文件,则显示错误信息。
FileNotFound: |
加载loader
如果找到,则加载loader进入内存。
根据根目录项中得到的文件起始簇号在FAT表及数据区依次读取进loader.bin文件中的所有内容至物理地址0x10000(自己规定)处。
FileFound: |
跳转至loader
在将loader加载进内存后即可进行跳转,将控制权交给loader去完成引导程序的余下工作。
Loader: |
函数实现
对上述代码中出现的函数进行实现:
读磁盘扇区函数
; 读磁盘扇区(1个)函数 |
消息显示函数
; 显示信息函数 |
余下数据内容
将全局变量及消息数据等放在代码段(虽然这里并未明确区分)后面。
Msg: |
魔法数
在扇区的最后放置引导扇区标志。
times 510-($-$$) db 0 |
至此,boot.asm的全部内容已经完成。
成果
首先使用Linux的dd命令创建一个空的虚拟软盘镜像文件bootloader.img(也可以使用bochs自带的bximage命令创建):
> dd if=/dev/zero of=bootloader.img bs=512 count=2880 |
接着使用nasm汇编器对boot.asm进行汇编,得到二进制程序文件boot.bin:
> nasm boot.asm -o boot.bin |
得到的boot.bin文件大小应该恰好是512B,并且使用十六进制阅读器会发现其中的最后两个字节为0x55和0xaa。
汇编结束后,便可将生成的二进制程序文件写入(而不是复制)到虚拟软盘镜像文件内。
由于此时还不存在文件管理系统,因此只能使用dd命令把引导程序强制写入到虚拟软盘的固定扇区中(而复制一词则涉及到文件系统的操作,是后续装载loader和kernel的流程)。
> dd if=boot.bin of=bootloader.img conv=notrunc |
最后即可在bochs虚拟机中启动该虚拟软盘镜像查看效果:
- 如果是在Linux的命令行中直接运行
bochs命令,则选择其中的begin simulation后再选择刚刚创建的镜像文件进行模拟。 - 如果是Windows的图像化bochs窗口,则在
Edit Options栏中选择Disk & Boot进行输入镜像文件配置,配置好后即可点击Start开始模拟。 - 这里我们使用统一的终端命令,首先找到/生成/创建/编写自己的bochs配置文件(形如bochsrc.bxrc,类似文件即可,完全可以直接copy。),然后运行:
bochs -f bochsrc.bxrc |
- 注:bochs.bxrc是你自己的bochs配置文件,里面包含了运行系统环境、虚拟软盘镜像文件等信息,实在找不到也不会创建可以自行上网搜。但上述所有的方案基本配置都应类似于:floppya: type=1_44, 1_44=”.\bootloader.img”, status=inserted, write_protected=0。
虚拟机运行结果如图:

由于此时还没有编写并装载loader程序进虚拟软盘,因此会显示File Not Found!错误信息,但其中的Hello, MyOS!则表明我们已经成功运行了我们的一级引导程序!
完结撒花 !!!
总结
- 完成了一级引导程序的编写
- 实现FAT12文件系统格式化
- 完成了系统启动盘的制作
- 成功利用bochs虚拟机对虚拟软盘镜像进行模拟,并成功运行
补充
FAT(File Allocation Table)文件分配表
- FAT文件系统以簇为单位来分配数据区的存储空间(扇区),数据区的簇号与FAT表的表项是一一对应关系。即使文件的长度只有一个字节,FAT也会为它分配一个簇的磁盘存储空间。
- FAT文件系统的结构是由引导扇区、FAT表、根目录区、数据区组成
- FAT表中的表项位宽与FAT类型有关,例如,FAT12文件系统的表项位宽为12 bit、FAT16文件系统的表项位宽为16 bit、FAT32文件系统的表项位宽为32 bit。
- 由于FAT12的表项位宽为12位, 故两个表项共用3个字节, 且数据是有交叉的,第二个字节的高4位是下个簇号的低4位, 第二个字节的低4位是上个簇号的高4位
- FAT的前两个表项FAT[0]和FAT[1]是保留表项,索引值从0开始, FAT表项值具有一下含义:
- 0x000: 可用簇
- 0x002~0xfef: 已用簇, 值表示文件下一个簇的簇号
- 0xff0~0xff6: 保留簇
- 0xff7: 坏簇
- 0xff8~0xfff: 表明这是文件的最后一个簇
- 根目录项是一个由32B组成的结构体,其中记录着文件名字(8B文件名+3B拓展名,共11B)、文件长度以及数据起始簇号等信息
寄存器
X表示16位拓展,H表示高8位,L表示低8位, 前缀E表示32位寄存器,R表示64位寄存器- 虽然
AX等可以作为通用寄存器, 在特定用途下使用时会被编译为不同长度的指令, 如add指令与AX寄存器搭配时会被编译为3字节码, 而与CX搭配时则会被编译为4字节码命令 - 只有
BX、BP、SI、DI可以作为间接寻址的基址寄存器, 而AX、CX、DX、SP则不能 - 通用寄存器:
AX累加寄存器BX基址寄存器CX计数寄存器DX数据寄存器
- 段寄存器:
段寄存器出现的原因在于8086中CPU的数据总线(即ALU算数逻辑单元)宽度为16位, 但地址总线宽度为20位, 为了能够访问1MB的内存空间, 采用了段地址+偏移地址的方式, 通过段寄存器存储段地址, 通过偏移地址存储偏移地址。
不论指定什么内存地址, 使用的都是段:基址的方式。在实模式下表示段址*16+基址, 16对应4位二进制数(1位16进制数),即在16位模式下最多进行1MB(实际为1114095, 略大于1M)寻址。而在保护模式或长模式下段寄存器中存储的则是段选择子。 若[]中未指定段寄存器, 则默认为DS。CS代码段寄存器, 与ip搭配使用, 作为代码段的段基址。取值时, 通过CS:IP的值(实际为段寄存器缓存区中的基地址base+ip)来确定下一条指令的地址, 然后ip+所读取的指令长度, 从而指向下一条指令, 然后重复上述过程DS数据段寄存器, 一般与bx、bp、si、di或立即数搭配使用, 作为数据段的段基址SS栈段寄存器, 与sp搭配使用, 作为栈段的段基址ES拓展段寄存器, 作为辅助段寄存器, 当前程序使用附加数据段的段基址,该段是串操作指令中目的串所在的段FS标志段寄存器, 80386起增加的两个辅助段寄存器之一,在这之前只有一个辅助段寄存器ES, 指向当前活动线程的TEB结构(线程结构)GS全局段寄存器, 80386起增加的两个辅助段寄存器之一
BP基址指针寄存器IP指令指针寄存器SP栈指针寄存器SI源变址寄存器DI目的变址寄存器CR0~4控制寄存器0~4
