前面我们讲到,MBR 代码只有 512 字节,干不了太多事情。因此我们设计了 loader 程序,在 loader 中完成 kernel 的加载。
所以,在 MBR 中我们要完成对 loader 的加载。
为了项目结构清晰,我将工程目录划分如下
- $ tree
- .
- ├── boot
- │ ├── Makefile
- │ └── mbr.S
- ├── loader
- │ ├── loader.S
- │ └── Makefile
- └── Makefile
-
- 2 directories, 5 files
-
完整工程代码:链接
boot 延续使用 mbr.S,在前面的工程中,我们在 boot 阶段没干什么事情,仅仅原地跳转。今天我们要让 boot 完成对 loader 的加载,并且跳转到 loader 执行。
我们计划将 loader 加载到内存的 0x8000 处,
mbr.S
- .code16 # 让汇编器将程序汇编成 16 位的代码(因为 CPU 上电后处于 16 位的实模式)
- .global _start
-
- _start:
- # 加载 loader 到内存
- mov $0x8000, %bx # 要加载到的内存地址
- mov $0x0002, %cx # ch:磁道号(0x00),cl:起始扇区号(0x02),(扇区 0x01 为 MBR)
- mov $0x02, %ah # ah:读磁盘命令
- mov $1, %al # al:读取的扇区数量,必须小于128,暂时设置成 1 个扇区
- mov $0x0080, %dx # dh:磁头号,dl:驱动器号0x80(磁盘1)
- int $0x13
-
- # 跳转到 loader
- mov $0x8000, %bx
- jmp %bx
-
- jmp .
-
- .org 0x1fe
- .byte 0x55, 0xAA
-
-
简单起见,我们上面代码直接使用 BIOS 提供的软中断来完成对硬盘的读取,参考《BIOS int 13H中断介绍》。
我们先写一个简例
loader.S
- .code16
- .global _start
-
- _start:
- jmp _start
-
代码执行完 int $0x13 后,就已经将磁盘上的 loader 代码拷贝到了内存的 0x8000 处,从左侧的 监视 窗口可以看到,0x8000 处的数据已经是 0xeb、0xfe 了,和磁盘镜像文件 os.img 的第二个扇区中的内容一致。
在 jmp %bx 处按 F10 单步,就会跳转到内存 0x8000 处执行(左侧 eip = 0x8000),也就是对应 loader 的代码 _start 处。