Bootloader 定义
Bootloader 是计算机加电之后加载的第一个程序(除去 Bios),这个程序被放在二级存储(比如硬盘)的第一个扇区,用于加载复杂的操作系统到内存中。之所以不直接加载操作系统,而加载 bootloader 来加载操作系统,是因为操作系统体积庞大,无法存储在第一扇区(只有 512 字节)。
过程简述
- 计算机加电,进行自检等一系列操作,这些操作由硬件供应商保证,操作系统开发人员一般不需太过关心;
- 随后 Bios 会读取硬盘第一个扇区(sector)的数据(此处存疑),加载到 0x7c00 的物理地址;
ip
和cs
寄存器被设置(cs = 0x00, ip = 0x7c00
),并开始执行 bootloader;- Bootloader 进行一些必要的初始化(比如 enable a20 gate,保护模式等等);
- 在特定位置寻找操作系统(很可能是第二扇区),并根据一定的约定(比如 elf 可执行文件格式),加载操作系统;
- 跳转到操作系统入口处执行,进入操作系统。
实现参考
放置 Bootloader 到存储设备
假定 bootloader 已有,在 Linux 下硬盘被映射为文件,可以使用 dd
来将 bootloader 写入硬盘扇区。暂时先写入成为一个镜像文件,通过 qemu 虚拟机启动:
dd if=bootloader of=nros.img conv=notrunc
qemu-system-i386 -hda nros.img
不过忧心的是你目前还没有 bootloader 呢,所以我们自己写一个吧。
实现细节
要理解 bootloader 还是要理解链接与加载地址。对于程序生成的二进制代码,物理地址、线性地址,和逻辑地址这些概念都是透明的。程序只知道 (virtual) memory address(VMA) 和 load memory address(LMA),即程序看到的地址,和看到的地址真正被放置的地址。VMA 和 LMA 是 elf 格式引入的概念,对于硬件而言太高级了,难以实现。硬件只会简单的将第一扇区的 512 字节放在内存 0x7c00
的位置(相当于LMA),因此你必须安排链接器,让他把代码的地址链接到 0x7c00 的位置(参考 GNU 文档),如下:
/*
* setup.ld
*
* Linker script for the i386 setup code
*/
OUTPUT_ARCH("i386");
OUTPUT_FORMAT("binary", "binary", "binary");
SECTIONS
{
. = 0x7C00;
.text : {
*(.text);
}
. = 0x7DFE;
.signature : {
SHORT(0xAA55);
}
/DISCARD/ : {
*(.eh_frame);
*(.eh_frame_hdr);
}
}