引导启动部分BIOS → boot → loader → kernel_init:
技术点 | 你可以怎么说它的亮点 |
---|---|
MBR + 二级加载器分离 | “因为 BIOS 限制只能加载 512 字节,所以我设计了多阶段加载流程” |
BIOS 中断读取磁盘 | “在 boot 阶段调用 int 0x13 读取扇区,支持 LBA 模式加载 loader” |
函数指针跳转到内核入口 | “我通过 (void (*)(boot_info_t *))e_entry 实现无全局变量传参” |
ELF 文件解析 | “支持 .bss 清零、段映射、加载多个程序段,和 Linux 加载方式一致” |
实模式 → 保护模式切换流程 | “禁中断、打开 A20、加载 GDT、设置 PE 位,完成模式转换” |
GDT 构造 + 平坦模型 | “基址为 0,限长为 4GB,支持扁平寻址 + 段权限控制” |
bios bootloader区别
[BIOS] (硬件开机,里面有写好的固件,上电自检)
↓ 读取硬盘第0扇区 → 加载到内存地址 0x7C00
[boot] (512字节以内的 MBR/bootloader阶段1)(因为只有512字节,太小了所以用了二级引导)
↓ 加载并跳转到更复杂的 loader(通常是 2阶)
[loader] (多段程序,支持文件系统、内核加载等)
↓ 加载操作系统内核(kernel)
[OS内核] (操作系统正式启动)
前三步是我们自己控制不了的
我的代码是从磁盘加载
启动流程(x86 BIOS 启动):
这个第0扇区的代码需要自己去写
首先Bios上电自检,然后将磁盘的第一个扇区512字节放入到内存地址0x7c00,检查是否以0x55 0xaa结束,从而判断这是否是有效的MBR
,如果是的话就进行引导
bios
- 加电启动(Power On)
- BIOS 执行 POST(Power-On Self-Test)
- 搜索可启动设备(硬盘、U盘、光盘…)
- 找到启动设备后:
- 读取该设备的 第 1 个扇区(LBA 0,大小 = 512 字节)
- 把它加载到内存地址
0x0000:0x7C00
(实地址 = 0x7C00)
- 检查最后两个字节是否为
0x55AA
(有效的引导扇区签名) - 如果正确 → 跳转到
0x7C00
执行
在 BIOS 启动模式中,BIOS 会将硬盘的 第 0 扇区(MBR)加载到 0x7C00,并从那里开始执行。
读取磁盘又两种方式
一种是int13(bios)
一种是LAB(复杂一点,在loader中实现)
x86在上电后自动进入实模式,1m内存 无分页机制 寄存器也只能用16位
但是1m内存的话要访问完需要20位地址,我们就是将段基址<<4+偏移构成20位
段基址的值是存在CS/DS/SS/ES/FS/GS(段寄存器)中
这个实模式下1M大小的内存映射情况:
我的boot是在start.s中写的
boot的初始化: 主要就是将段寄存器先赋初值0,简化代码,栈顶指针赋值0x7c00,表示我的boot在0x7c00地址以下的栈区,大概30kb左右是满足这个大小的
boot跳转到loader二级引导:
读取多个磁盘加载到内存地址上:用了bios中断向量表 0x13 从第一个扇区开始 分配64个扇区(大概32kb) 如果读取磁盘正确后进入C环境并跳转到loader(jmp或者call)
.extern C函数名字(boot的一个跳转函数)
boot_loader
cmake的链接器表示我loader 加载到0x8000的地址,start.s放在cmake加入的工程文件的最开头,这样就可以保证加载到0x8000时在start.s
因为512字节显然比较小,没办法完成这么多功能,所以我做了一个二级引导
1.内联汇编显示字符串
2.检测内存容量 0x15(boot_info)
检测10块可用内存区域
3.切换进保护模式
实模式的限制
1.只能访问1MB内存,内核寄存器最大为16位宽
2.所有的操作数最大为16位宽
3.没有任何保护机制
4.没有特权级支持
5.没有分页机制和虚拟内存的支持
如何切换进保护模式
首先保证过程原子性,禁用中断,然后打开A20地址线让其访问1m以上的内存地址,然后初始化加载GDT表保证开启保护模式寄存器值正常,再设置CR0 PE位,开启保护模式。
这个禁用中断的函数我也写了一个函数,保存关中断前的各个寄存器的状态(eflags等),完成实模式到保护模式切换后恢复到原来的状态,这个函数再后面的也可以用到。函数中我也用到了内联汇编函数 sti cli进行开关中断.
加载kernel到内存地址1M
在loader中实现LAB读取磁盘,(一次两字节读取)(通常512字节一次性读取)
#define SYS_KERNEL_LOAD_ADDR (1024*1024) // 内核加载的起始地址(此时打开了A20地址线和保护模式,可以访问1M以上空间
static void read_disk(int sector, int sector_count, uint8_t * buf)
名称 | 含义 | 单位 |
---|---|---|
sector |
起始扇区号(LBA) | 扇区(512 字节) |
sector_count |
连续读取的扇区数量 |