你是自己写的 32 位操作系统是吗 ?
你只要是 2010 年之后买的电脑,基本上都是 UEFI 方式启动的,MBR (boot sector) 根本就不会执行,boot sector 中只有数据部分是有用的。
以 linux 内核为例,x86 boot protocol 中明确讲了 boot sector 和 setup sectors 的布局。这两者位于内核压缩映像开头,主要用于 bootloader 和 linux kernel 之间交换数据。
启动方式大致如下:
系统上电,跳转到 reset vector (0xfffffff0,这是 x86 的规定),这里映射到 UEFI 映像末尾(UEFI 映像在一块 flash 芯片中,通过 SPI 总线连到 CPU),是一条 jmp 指令,跳转到 UEFI 开头执行,然后一路初始化到 64 位 long mode,然后 UEFI 加载 bootloader,例如 grub2。
注意,现在是 identity map,即物理地址等于虚拟地址。
grub2 调用 UEFI 提供的接口,加载 bzImage (/boot/vmlinuz) 到 0x100000 (1M),对应 startup_32/64,移动到合适位置,然后解压缩,跳转到解压缩后的 startup_32/64 (大于等于 16MB 且对齐到 CONFIG_PHYSICAL_ALIGN 的某个地址),继续执行。startup_32 和 startup_64 之间的偏移是固定的 512B。
如果你用虚拟机。那么默认是使用 BIOS 方式,当然可以执行 MBR 了。你把虚拟机启动方式改为 UEFI ,一样会重启。对于 linux,就是执行到 arch/x86/boot/header.S#L72,导致重启。该文件中的数据就是 boot sector 和 setup sectors,布局参考 Documentation/x86/boot.rst。
我简单注释一下导致重启的代码 (arch/x86/boot/header.S 中的所有代码在 UEFI 引导方式下正常启动时都不会执行):
bs_die: # Allow the user to press a key, then reboot xorw %ax, %ax int $0x16 # Keyboard Service。输入 %ah = 0x00 表示从键盘读入字符,输出 %ah 为键盘扫描码,%al 为字符的 ASCII 码 int $0x19 # 将第一个扇区的内容载入 0000:7C00 # int 0x19 should never return. In case it does anyway, invoke the BIOS reset code... ljmp $0xf000, $0xfff0 # 跳转到 UEFI 中的 0xfffffff0
你之所以搞不清楚,是因为你不明白现代的 bootstrap 流程,你在网上找到的几乎所有资料都讲的是 BIOS 时代的事情(就是跳转到 0x7C00 那一套东西),UEFI 时代的启动流程细节基本没有。
在 UEFI+GPT(现代硬盘分区格式) 中,系统上电时,是 UEFI 加载 bootable device (硬盘/U盘) 中的 FAT32 格式的 EFI Part System 中的 bootx64.efi,该文件可以是由 grub2 等 bootloader 编译而来,也可以是开启 efi stub 机制的 linux 内核编译而来。而硬盘的前 32KB 根本没用,你在网上看到的什么 boot.img,core.img 之类的东西都是过时的 (都在硬盘前 32KB)。
UEFI 启动方式下,boot sector 并不是硬盘第一个扇区(MBR),而是内核映像 bzImage (/boot/vmlinuz) 的前 512 字节,后面紧跟着的几个 512 字节是 setup sectors (见前文),再后面是内核 ELF 文件 vmlinux 压缩后的产物。
UEFI 是一个很大的话题,我就不展开了 (以后有时间补)。总之,你不用纠结这个问题,继续用 BIOS 方式即可。学习操作系统编写时,引导加载流程并不是重点,中断机制,内存管理,任务调度,等等才是。尤其 64 位和 32 位有较大的不同,细节资料基本都来自英文文档,需要花一些时间学习。