译自:http://www.brokenthorn.com/Resources/OSDev3.html
第3 章:引导加载器
by Mike, 2008, 2009
本系列文章旨在向您展示并说明如何从头开发一个操作系统。
介绍
欢迎!这正是您等待的一篇教程,在这一章包括的主题有:
准备好了吗?
引导过程
当电源按钮按下时
当我们按下电源按钮是到底发生了什么?当这个按钮被按下后,连接到这个按钮的线缆会向主板发送一个电信号,主板简单的把这个信号转发给电源 (PSU )。
这个信号只包含1 比特信息。如果是0 ,表示没电(计算机关闭,主板不活动)。如果是1 (活动信号),意味着系统已经加电。
为了更好的理解,记住计算机的基础是二值逻辑。8 “比特”仅仅表示8 条可以导电的“线缆”,0 代表线上没有电流,而1 表示线上有电流。这些与逻辑门,一起构成了数字逻辑点了的基础,而在此之上构建的整个计算机。
当PSU 收到这个活动信号,它开始向系统的其余部分供电。当所有设备都得到正确数量的供电时,就可以确定PSU 会持续向它们供电而不发生大的问题。
PSU 会发送一个“供电正常(power_good )”的信号到主板的基本输入输出系统 (BIOS) 。
BIOS POST
到那个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 得到控制权……
BIOS
基本输入输出系统(BIOS) 会做一些工作。它创建一个中断向量表 (IVT), 并提供基本的中断服务。BIOS 然后会做一些检查以确保没有硬件问题。BIOS 也提供一个设置的功能。
BIOS 需要找到一个操作系统。根据您在BIOS 设置中指定的引导顺序,BIOS 执行0x19 号中断来找出一个可引导设备。
如果没有找到可引导设备 (INT 0x19 返回了) ,BIOS 会尝试引导顺序列表中的下一个设备。如果再没有可供尝试的设备,BIOS 会打印一个类似于“操作系统未找到”的信息,并停止系统的运行。
中断与中断向量表 (IVT)
一个中断是可以被许多不同的程序调用的子程序。这些中断被保存在从地址0x0 开始的被称为中断向量表的空间中。比如,一个常见的中断INT 0x21 被用于DOS 系统。
注意:这儿没有DOS !“只有”BIOS 设置的中断才有效,没有其他的!使用其他的中断会导致系统执行不存在的程序,这将导致你的系统崩溃。
注意:如果切换处理器模式,IVT 会变得无效。这意味着,任何的中断(无论软硬件,包括BIOS )全都无效。 对于32 位操作系统,我们不得不这样做。
BIOS 0x19 中断
INT 0x19 ——引导程序加载器
通过“热重启”重启系统,不会清空内存,也不会恢复中断向量表。
该中断由BIOS 执行。它读入磁盘的第一个扇区( 扇区(Sector ) 1, 磁头(Head ) 0, 磁道(Track ) 0) 。
扇区Sectors
扇区即一个512 字节的组,扇区1 表示磁盘最前面的512 字节数据。
磁头Heads
磁头( 或“面”) 表示磁盘的一面。磁头0 是正面,磁头1 是背面. 多数磁盘值由一个面,因此只有一个磁头。
磁道Tracks
为了理解磁道,看下面的图:
图中的磁盘代表硬盘或软盘,我们看到的是磁头0 (正面),并且每扇区512 字节。磁道是扇区的集合。
注意:记住1 扇区是512 字节,软盘的1 磁道又18 扇区,这一点在加载文件时很重要。
如果磁盘可引导,则引导扇会被加载到0x7C00 , INT 0x19 会跳转到哪里,将控制权交给一点加载器。
注意:引导加载器会被加载扫0x7C00 ,这很重要!
注意:有些系统在按下“热重启”按钮,会在地址0x0040:0072 处放置一个0x1234 再跳转到0xFFFF:0 。冷重启则会用0x0 代替。
现在,我们的1337 引导加载器得到控制权!
引导加载器理论
关于引导加载器我们已经看了不少。现在,把其中重要的部分放在一起。
引导加载器是……
如你所想,我们不能在512 字节里做很多事情。我们要做什么呢?
在汇编语言中我们很容易超过512 字节。尽管代码看起来不错,可是只有一部分 在内存中,比如,想想下面的例子:
mov ax, 4ch
inc bx ; 512 byte
mov [var], bx ; 514 byte
在汇编语言中,从上往下执行。但,要记得当向内存加载文件时,以扇区为单位。 每扇区512 字节,因此只能复制几个512 字节到内存中。
如果只有第一个扇区被加载到内存,我们仅仅复制到第512 字节 (inc bx 那一行) 。这样最后的mov 指令仍在磁盘上,不在内存中 !
那当处理器执行完inc bx 之后会做什么呢?处理器会继续执行第514 字节。但是我们的代码不在内存中,它会跨过文件的末尾! 结果呢?崩溃。
但是,再加载第二(或更多)扇区到指定地址并执行是可能的。这样文件中剩余的数据就会加载到内存中,会工作的很好。
这种方法是可行的,但难以使用。常见的方法是将引导加载器控制在512 字节,用来搜索、加载和执行第二段引导加载器。这个我们会在后面看到。
硬件异常Hardware Exceptions
硬件异常与软件异常相似不过它由处理器 而不是软件执行。
有时,我们必须避免所有异常的产生。比如,在我们切换处理器状态后,整个中断向量表失效,此时,任何的软硬件中断都会使我们的系统崩溃 。我们在后面详细说明。
CLI 和 STI 指令
您可以使用STI 和CLI 指令允许或禁止所有的中断,大部分系统不允许应用程序执行这两条指令,因为这可能会带来大问题(尽管系统可以模拟它们)。
cli ; 禁止中断
; do something...
sti ; 允许中断——我们前面禁止了!
双重错误(Double Fault )的硬件异常
如果处理器在处理异常的时候发现了问题(如,非法指令,除0 ,等),处理器会执行双重错误处理程序,即0x8 中断。
我们在后面会看到一个双重错误。如果处理器在双重错误之后还不能恢复,它会执行一个三重错误(Triple Fault )。
三重错误Triple Fault
前面我们见到过这个条目吗?CPU 的三重错误意味着系统重启。
在早期阶段,比如在引导加载过程中,你代码中的错误,会产生一个三重错误。这表明你的代码有问题。
开发一个简单的引导加载器
总算到了我们等的了!
让我们在看看我们的列表:
打开任意的文本编辑器(我使用Visual Studio 2005 ), 但Notepad (记事本)就足够了。
这里是引导加载器 (Boot1.asm)...
;*********************************************
; Boot1.asm
; - A Simple Bootloader
;
; Operating Systems Development Tutorial
;*********************************************
org 0x7c00 ; 我们已经被BIOS 加载到 0x7C00
bits 16 ; 我们在16 位实模式
Start:
cli ; 禁止中断
hlt ; 系统停机
times 510 - ($-$$) db 0 ; 我们得有512 字节,将剩余的部分清零
dw 0xAA55 ; 引导标志
这些并没有什么令人兴奋的,下面我们一行行分析:
org 0x7c00
记住:BIOS 把我们加载到0x7C00 ,上面的代码告诉 NASM 确保相对地址为0x7C00 。这表示,第一条指令在0x7C00 处
bits 16
还记得第2 章吗?在那一章里,我解释了x86 系列向后兼容老DOS 系统。因为老DOS 系统是16 位的,所有的x86 兼容机引导时为16 位模式 ,也就是:
我们会在后面将计算机切换到32 位模式。
times 510 - ($-$$) db 0
我希望这里有文档可以参考。在NASM 中,美元符($) 表示当前行的地址。$$ 表示第一条指令的地址(0x7C00) 。所以,$-$$ 返回当前行到起点共有多少字节 ( 这里就是程序的大小) 。
dw 0xAA55
这需要一些解释。
BIOS INT 0x19 会搜索可引导磁盘。那它怎么知道一个磁盘是否可以引导呢?因为引导标志。如果511 字节是0xAA 且512 字节是0x55 ,INT 0x19 会加载它,并执行引导加载器。
因为引导表示必须是引导扇的最后两个字节。我们使用times 关键字填充到第510 个字节,而不是第512 个字节。
使用NASM 汇编
NASM 是命令行汇编器,因此必须通过命令行或批处理脚本执行。汇编Boot1.asm 这么做:
nasm -f bin Boot1.asm -o Boot1.bin
-f 选项用于告诉 NASM 生成哪种类型的文件,这里是二进制文件。
-o 选项由于给出输出文件名,这里是Boot1.bin
汇编之后你会得到一个名为"Boot1.bin"512 字节的文件。
注意:因为一些原因Windows 文件浏览器会显示文件的大小为1 KB ,但查看文件属性时你会发现它确实是512 字节。
使用VFD ( 虚拟软盘驱动器)
我们使用VFD 来创建我们的操作系统要保存的虚拟软盘。下面解释如何使用它。
你会看到这个:
确保Media Type (媒体类型)是st 和ard 3.5" 1.44 MB floppy (标准的3.5" 1.44 MB 软盘),并且类型是RAM 。同样的,确保Write Protect (写保护)打开(不选中)。点击 "Create".
到“我的电脑”( 在“您”的计算机上) 您会见到一个新的软盘驱动器。
在驱动器图标上右击-> 属性,格式化软盘。在VFD 标签处会有一个格式化选项。
PartCopy ——复制引导加载器
好,现在我们有了自己的引导加载器,怎么把它复制到硬盘中呢?你可能知道,Windows 不允许我们将文件直接复制到磁盘的第一个扇区上,所以我们用一个命令来完成。
在第1 章中我们见到了debug 命令,如果你决定使用那个命令,请跳过这一节。
PartCopy 是一个命令行出现,它使用下面的语法:
partcopy file first_byte last_byte drive
PartCopy 不仅仅用于复制文件,它可以将制定的字节复制到扇区或从扇区复制出来,感谢它的语法(见上面),这是一个安全的方法。
因为你可以模拟软驱,你可以使用驱动器号(如A: )来代表驱动器。
要复制引导驱动器,这么做:
partcopy Boot1.bin 0 200 -f0
f0 代表0 号软驱。你可以使用f0 或f1 等,这要根据你的软盘在那个驱动器里。Boot1.bin 是我们要复制的文件。从第一个字节(0x0) 复制到最后一个字节 (0x200, 十进制的512) 。注意partcopy 只接受16 进制的数据。
警告:如果使用不小心,可能会导致磁盘数据损坏。上面的命令值适用于软盘,不要在硬盘上尝试。
Bochs: 测试引导加载器
Bochs 是一个32 位PC 仿真器,我们使用它来调试和测试。
Bochs 使用配置文件来描述要仿真的硬件。如下例,是我使用的配置文件:
# ROM 和 VGA BIOS images ---------------------------------------------
romimage: file=BIOS-bochs-latest, address=0xf0000
vgaromimage: VGABIOS-lgpl-latest
# boot from floppy using our disk image -------------------------------
floppya: 1_44=a:, status=inserted # Boot from drive A
# logging 和 reporting -----------------------------------------------
log: OSDev.log # All errors and info logs will output to OSDev.log
error: action=report
info: action=report
配置文件使用# 注释。它试图从一个在驱动器A 中的软盘引导。
ROM BIOS 和VGA BIOS 映像文件是和Bochs 一起的,所以别为它担心。
定位BIOS ROM
配置文件中的大部分都很简单。有一行需要再看看:
romimage: file=BIOS-bochs-latest, address=0xf0000
这行告诉Bochs 把BIOS 放到内存的什么位置。要知道BIOS 的大小是可变的,BIOS 必须放在1MB 的末尾,即BIOS 的最后一个字节必须在0xFFFFF 。
因此你可能需要改变BIOS 的位置。这可以通过获得BIOS 映像的大小(它在Bochs 文件夹下的BIOS-bochs-latest )。这个大小以字节为单位 。
这样从0xFFFFF 减去BIOS 文件的大小(以字节为单位)。这就是新的BIOS 地址,更新这一行的address ,把BIOS 移到一个新位置。
你可能不需要这一步。如果你被告知“BIOS 必须在0xFFFFF 结束”时,你就要这么做了。
如何使用Bochs
使用Bochs:
一个新的窗口会打开,你会看到:
如果Bochs 退出并重启了
……你有了一个三重错误的经历。返回到代码,找找哪里出了错。如果你需要帮助,联系我吧。
如果窗口出现,但什么也没发生
恭喜!这是我们的cli 和hlt 指令使系统停止了,我们的引导加载器在执行了。
构建步骤——总结
和我们在前一章里提到的构建步骤相比较,一旦你跟着做了,你会发现这很简单。
从此往后,我们将步骤详细重复这个构建步骤。
下次见