实验一:显示字符的 toy bootloader
实验目标
操作系统是一个软件,也需要通过某种手段加载并运行它。在这里我们将通过另外一个更加简单的软件-bootloader 来完成这些工作。为此,我们需要完成一个能够切换到 x86 的保护模式并显示字符的 bootloader,为将来启动操作系统做准备。proj1 提供了一个非常小的 bootloader,整个 bootloader 的大小小于 512 个字节,这样才能放到硬盘的主引导扇区中。
这里对 x86 的保护模式不必太在意,后续会进一步讲解
通过分析和实现这个 bootloader,读者可以了解到:
- 与操作系统原理相关
- I/O 设备管理:设备管理的基本概念,涉及简单的信息输出
- 内存管理:基于分段机制的存储管理,x86 的实模式/保护模式以及切换到保护模式的方法
- 计算机系统和编程
- 硬件
- PC 加电后启动 bootloader 的过程
- 通过串口/并口/CGA 输出字符的方法
- 软件
- bootloader 的文件组成
- 编译运行 bootloader 的过程
- 调试 bootloader 的方法
- 在汇编级了解栈的结构和处理过程
- 硬件
proj1 概述
实现描述
proj1 实现了一个简单的 bootloader,主要完成的功能是初始化寄存器内容,完成实模式到保护模式的转换,在保护模式下通过 PIO 方式控制串口、并口和 CGA 等进行字符串输出。
项目组成
[要点(非 OSP):bootloader 的编译生成过程] lab1 中包含的第一个工程小例子是 proj1:一个可以切换到保护模式并显示字符串的 bootloader。proj1 的整体目录结构如下所示:
proj1 /
| - boot
| | - asm.h
| | - bootasm.S
| ` - bootmain.c
| - libs
| | - types.h
| ` - x86.h
| - Makefile
` - tools
| - function.mk
| - gdbinit
` - sign.c
3 directories, 9 files
其中一些比较重要的文件说明如下:
- bootasm.S :定义并实现了 bootloader 最先执行的函数 start,此函数进行了一定的初始化,完成了从实模式到保护模式的转换,并调用 bootmain.c 中的 bootmain 函数。
- bootmain.c:定义并实现了 bootmain 函数实现了通过屏幕、串口和并口显示字符串。
- asm.h:是 bootasm.S 汇编文件所需要的头文件,主要是一些与 X86 保护模式的段访问方式相关的宏定义。
- types.h:包含一些无符号整型的缩写定义。
- x86.h:一些用 GNU C 嵌入式汇编实现的 C 函数(由于使用了 inline 关键字,所以可以理解为宏)。
- Makefile 和 function.mk:指导 make 完成整个软件项目的编译,清除等工作。
- sign.c:一个 C 语言小程序,是辅助工具,用于生成一个符合规范的硬盘主引导扇区。
- gdbinit:用于 gdb 远程调试的初始命令脚本
从中,我们可以看出 bootloader 主要由 bootasm.S 和 bootmain.c 组成,当你完成编译后,你会发现这个 bootloader 只有区区的 3 百多字节。下面是编译运行 bootloader 的过程。
【提示】bootloader 是一个超小的系统软件,在功能上与我们一般的应用软件不同,主要用于硬件简单初始化和加载运行操作系统。在编写 bootloader 的时候,需要了解它所处的硬件环境(比如它在内存中的起始地址,它的储存空间的位置和大小限制等)。而这些是编写应用软件不太需要了解的,因为操作系统和编译器帮助应用软件考虑了这些问题。
编译运行
【实验 编译运行 bootloader】
在 proj1 下执行 make,在 proj1/bin 目录下可生成一个 ucore.img。ucore.img 是一个包含了 bootloader 或 OS 的硬盘镜像,通过执行如下命令可在硬件虚拟环境 qemu 中运行 bootloader 或 OS:
make //生成 bootloader 和对应的主引导扇区
make qemu //通过 qemu 硬件模拟器来运行 bootloader
make clean //清除生成的临时文件,bootloader 和对应的主引导扇区
我们除了需要了解 bootloader 的功能外,还需要进一步了解 bootloader 的编译链接和最终执行码的生成过程,从而能够清楚生成的代码是否是我们所期望的。proj1 中的 Makefile 是一个配置脚本,make 软件工具能够通过 Makefile 完成管理 bootloader 的 C/ASM 代码生成执行码的整个过程。Makefile 的内容比较复杂,不过读者在前期只需会执行 make [参数]来生成代码和清除代码即可。对于本实验的 make 的执行过程如下所示:
1. gcc -O2 -o tools/sign tools/sign.c
2. i386-elf-gcc -fno-builtin -Wall -MD -ggdb -m32 -fno-stack-protector -O -nostdinc -Iinclude -Iinclude/x86 -c bootloader/bootmain.c -o obj/bootmain.o
3. i386-elf-gcc -fno-builtin -Wall -MD -ggdb -m32 -fno-stack-protector -nostdinc -Iinclude -Iinclude/x86 -c bootloader/bootasm.S -o obj/bootasm.o
4. i386-elf-ld -N -e start -Ttext 0x7C00 -o obj/bootblock.o obj/bootasm.o obj/bootmain.o
5. i386-elf-objdump -S obj/bootblock.o > obj/bootblock.asm
6. i386-elf-objcopy -S -O binary obj/bootblock.o obj/bootblock.out
7. sign.exe obj/bootblock.out obj/bootblock
obj/bootblock.out size: 380 bytes
build 512 bytes boot sector: obj/bootblock success!
8. dd if=/dev/zero of=obj/ucore.img count=10000
10000+0 records in
10000+0 records out
5120000 bytes (5.1 MB) copied, 0.509 s, 10.1 MB/s
9. dd if=obj/bootblock of=obj/ucore.img conv=notrunc
1+0 records in
1+0 records out
512 bytes (512 B) copied, 0.011 s, 46.5 kB/s
这 9 步的含义是:
1. 编译生成 sign 执行程序,用于生成一个符合规范的硬盘主引导扇区;
2. 用 gcc 编译器编译 bootmain.c,生成 ELF 格式的目标文件 bootmain.o;
3. 用 gas 汇编器(gcc 只是一个包装)编译 bootasm.S,生成 ELF 格式的目标文件 bootasm.o;
4. 用 ld 链接器把 bootmain.o 和 bootasm.o 链接在一起,形成生成 ELF 格式的执行文件 bootblock.o;
5. 目标文件信息导出工具 objdump 反汇编 bootblock.o,生成 bootblock.asm,通过查看 bootlock.asm 内容,可以了解 bootloader 的实际执行代码;
6. 文件格式转换和拷贝工具 objcopy 把 ELF 格式的执行文件 bootblock.o 转换成 binary 格式的执行文件 bootblock.out;
7. 通过 sign 执行程序,把 bootblock.out(本身大小需要小于 510 字节)扩展到 512 字节,形成一个符合规范的硬盘主引导扇区 bootblock;
8. 设备级转换与拷贝工具 dd 生成一个内容都为“0”的磁盘文件 ucore.img;
9. 设备级转换与拷贝工具 dd 进一步把 bootblock 覆盖到 ucore.img 的前 512 个字节空间中,这样就可以把 ucore.img 作为一个可启动的硬盘被硬件模拟器 qemu 使用。
如果需要了解 Makefile 中的内容,需要进一步看看附录“ucore 实验中的常用工具”一节。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论