操作系统实现之BootLoader引导启动程序
1. 基本原理
-
计算机启动流程
1
2
3
4
5graph LR
A[加电] --> B[BIOS自检]
B --BIOS加载引导扇区--> C[Boot引导程序]
C --Boot加载Loader--> D[Loader引导加载程序]
D --Loader加载内核--> E[操作系统] -
为什么引导程序要分为
boot
和loader
,为什么BIOS不直接加载Loader
?- 一个扇区的空间太小,所以需要两级接力进行初始化和引导。
-
引导扇区是软盘的第
0
磁头0
磁道1
扇区,且以数值0x55
和0xaa
两字节结尾。BIOS
程序即根据这两个特征识别启动设备。 -
BIOS
识别到引导扇区后,会将此扇区数据加载到内存,然后跳转至0x7c00
地址处执行。因此Boot
程序必须以0x7c00
为起始地址。 -
FAT12
文件系统结构
2. 实现过程
简单的boot引导程序
-
程序如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52org 0x7c00 ;程序从0x7c00处开始
BaseofStack equ 0x7c00 ;栈地址
Label_Start:
mov ax,cs
mov ds,ax
mov es,ax
mov ss,ax
mov sp,BaseofStack
;显示引导程序日志信息
;清屏
mov ax,0600h
mov bx,0700h
mov cx,0
mov dx,0184fh
int 10h
;设置光标
mov ax,0200h
mov bx,0000h
mov dx,0000h
int 10h
;在屏幕上显示Start Boot
mov ax,1301h
mov bx,000fh
mov dx,0000h
mov cx,10
push ax
mov ax,ds
mov es,ax
pop ax
mov bp,StartBootMessage
int 10h
;操作磁盘驱动器
;软盘驱动器初始化,将软盘磁头移动至默认位置
xor ah,ah
xor dl,dl
int 13h
;死循环
jmp $
StartBootMessage: db "Start Boot"
;用0填充剩余位置
times 510 - ($-$$) db 0
;以0x55 0xaa结尾,表示扇区为引导扇区
dw 0xaa55 -
使用
nasm
编译boot.asm
程序,得到二进制文件boot.bin
1
nasm boot.asm -o boot.bin
-
使用
bximage
制作软盘boot.img
-
使用
dd
命令将二进制文件boot.bin
写入到软盘镜像的第一个扇区1
dd if=boot.bin of=boot.img bs=512 count=1 conv=notrunc
-
配置好
bochs
之后,启动bochs
,出现如下界面则boot
引导程序正常
加载Loader到内存
-
编译
boot.asm
和loader.asm
1
2nasm boot.asm -o boot.bin
nasm loader.asm -o loader.bin -
将
boot.bin
写入镜像boot.img
中1
dd if=boot.bin of=boot.img bs=512 count=1 conv=notrunc
-
挂载
boot.img
镜像1
2sudo mount boot.img ../boot/ -t vfat -o loop
-t指定文件系统,-o loop表示把此文件描述为磁盘分区 -
将
loader.bin
复制到文件系统中1
2sudo cp loader.bin ../boot/
sync #磁盘同步命令,防止写入信息还在缓存中,没有真正写入磁盘 -
卸载设备
1
sudo umount ../boot/
-
运行结果
loader程序
基本原理
loader
引导加载程序的功能——检测硬件信息、处理器模式切换、向内核传递数据- 检测硬件信息
- 因为
BIOS
上电自检出的信息为实模式下的信息,而内核运行于非实模式下。需要在进入内核之前,将这些信息检测出来作为参数提供给内核程序使用。 - 其中最重要的信息是物理地址空间信息,还有VBE功能获取的显示信息。
- 因为
- 处理器模式切换
Loader
引导加载程序经历三个模式:实模式16位
——保护模式32位
——长模式IA-32e
。- 在各个阶段,程序要手动创建运行各膜式的临时数据,并实现切换。
- 向内核传递数据
- 传递两类信息:控制信息、硬件数据信息
- 控制信息:控制内核启动流程或限制内核的某些功能,是纯软件控制逻辑。
- 硬件数据信息:检测出的硬件信息,如内存信息,VBE信息等
内核
内核执行头程序
- 一小段汇编代码,控制权从
Loader
程序移交到内核之后即先执行内核执行头程序。 - 作用:为操作系统创建段结构和页表结构、设置某些结构的默认处理函数、配置关键寄存器。
内核主程序
- 相当于应用程序的主函数,不会返回,因为内核执行头程序没有提供返回地址
- 作用:调用各个系统模块的初始化函数,将这些模块初始化之后创建第一个进行init,然后将控制权移交给init进程
屏幕显示
编译链接时出现如下错误提示
错误undefined reference to __stack_chk_fail
是因为使用gcc
编译源码到目标文件时,默认会调用__stack_chk_fail
进行栈相关检查,但是使用ld
进行链接时并不会自动链接到__stack_chk_fail
所在的库文件,所以链接时一定会报错。此时在编译时添加-fno-stack-protector
,强制gcc
不进行栈检查即可。
错误undefined reference to strlen
是由于strlen
定义为内联函数,而内联函数嵌入到调用者代码中的操作是一种优化操作,只有进行优化编译时才会进行。如果不是优化编译,则内联函数的代码不会被嵌入到调用者的代码中,而是作为普通函数来处理。此时在编译时添加-O
选项,打开优化编译即可解决问题。
参考