DeepOs(二)- MBR 主引导记录
这次,我们实现 BIOS 加载 MBR,并在显示器上显示一些信息。
BIOS
BIOS 即 Base Input \& Output System,基本输入输出系统,由于软件大小的缘故所以需要完成的事务很少。在计算机启动后,CPU cs:ip 设置为 0xF000:0xFFF0,此地址便是 BIOS 的入口地址,这里存放着的实际是一条跳转指令,指向 BIOS 代码存放的内存地址。跳转后,BIOS 按照代码检测外设信息,并在内存 0x000-0x3FF 保存这些信息。
实模式和保护模式
Intel 8086 由 20 条地址总线,总共可以访问 1MB 的内存空间,可以使用 32 位指令。但是因为寄存器宽度只有 16 位,因此需要使用特殊的方式来存储地址:使用段基址寄存器和偏移寄存器,如 CS:IP 访问下一条指令。后续 Intel 推出了一些列基于上述处理器的 CPU,即 x86 系列。
80286 处理器引入了地址保护模式的概念,即限制某些地址的访问(中断向量表和内核代码等等),寻址使用 32 位段和偏移量,可以访问 4GB 的段和段内空间。
保护模式下可以通过全局描述符表 GDT 和 局部描述符表 LDT 来访问不同上下文的段。操作系统都必须定义一个 GDT,而每一个程序都需要一个 LDT 来访问自己的段。
实模式下的段宽 16 位,即共 16 个段,段大小 64KB,此时 CPU 直接访问对应的物理内存地址(没有重定位),而这隐患是非常大的。在保护模式下操作系统介入,程序需要地址转换才可以访问内存。再者,CPU 启动时为实模式(兼容),然后可以切换为保护模式,但不允许再切换为 实模式。
0x7C00
BIOS 执行完检测代码后,便会校验 0 盘 0 道 1 扇区的内容,若 512 字节最后两字节的内容为 0xAA55(小端序,即按顺序 0x55、0xAA),便认为这个扇区存放着 MBR,将其加载到物理地址 0x7C00 后执行 MBR 代码。
放在 0x7C00 是因为历史遗留,当时 DOS1.0 要求 32 KB 的内存空间,且 8086CPU 会在 0x000-0x3FF 建立中断向量表,为了给其他程序空出尽可能多的空间,MBR 会被加载到在 32KB 的最后 1KB,又 MBR 会使用栈来服务自己,所以会被加载到 0x7C00。
MBR 1.0
首先给出我们的项目树结构
DeepOs/
├── day1
│ └── mbr.asm
├── deepos.img
├── os_img
└── run.sh –> bash 脚本,用于启动虚拟机
下面代码摘自《操作系统真象还原》。
使用命令
nasm -fbin ./day1/mbr.asm -o source
这里 bin 格式指的是可以直接被 CPU 执行,elf 和 win 格式都有汇编的附加信息。
代码说明
上面 init 标签后的指令负责进行初始化寄存器。通过将 ax 设置为 0 间接将 ds 等寄存器设置为 0,然后初始化栈顶指针为 0x7C00。
后面的三个标签中的代码都是调用了 BIOS 中断提供的功能,这里不过多叙述。
vstart
vstart 和 align 是属于 NASM 中节的属性。之前提过,汇编语言中的 section 和 segement 都是程序员自己划分的逻辑区域,要在可执行程序中附加信息才可以被划分到属性不同的段。vstart 和 align 则是用来调整节的编址信息。
本行指令的地址和节的地址。汇编器会自动计算这些节的地址,通过偏移 \$ 的地址也可以计算,但这些地址是以程序地址为 0 为前提计算。比如
jmp $
源程序第一条指令原意是在开头处暂停,但如果该程序被加载到其他地址(如上面的 0x7C00),则 jmp 指令的地址便不为 0x0,所以该程序仍然会跳转到物理地址 0x0 处,最后会访问不该访问的内容。
至于在 Windows 和 Linux 中可以直接使用这种写法,则是因为加载器的缘故。加载器载入程序到内存后,会重定位其中使用的地址,并和计算后的段对应的基址相加得到要访问的物理地址,这里先按下不表。
想要在裸机上跑上面的程序,则需要使用 vstart 属性,如 MBR1.0 中所写的那样。汇编器会按照程序起始地址为 0x7C00 开始计算各处使用的地址,所以 \$ 会指向正确的位置。至于 align 属性,则是为了代码在内存对齐,方便访问。
为了验证,可以将 jmp $
一行注释掉,并将上一行反注释。上一行我按照物理地址直接运行,放在 qemu 虚拟机发现还是可以正常运行。
运行
我写的 bash 脚本如下(有些简陋 :)
##!/bin/bash
nasm -fbin ./1/2 -o 3
dd bs=512 conv=notrunc if=3 of=./deepos.img skip=4 seek=5
qemu-system-i386 ./deepos.img
会将源码会变为 bin 文件 img_file,然后使用 dd 命令将其写入我们准备好的虚拟磁盘。运行后弹出 qemu 虚拟机,效果如下
可以看到正确加载了 MBR 并得到了我们想要显示的内容。
改进 MBR
上面的代码实际做的工作很少,只是清屏和打印字符串而已,