盒子
盒子
文章目录
  1. POST
  2. 主引导记录
  3. 早期环境
  4. 内核
  5. 载入
    1. 传统方式
    2. 简单方法

Boot Sequence

POST

Power-on Self-Test, 定位设备

主引导记录

如果设备的启动扇区的511和512字节是0x550xAA。BIOS会发现这样的启动扇区,载入内存0x0000:0x7c00(有些是0x7c0:0x0000, 同一个物理地址)。让CS:IP在启动扇区的最开始是个好的实践。

执行被移交给刚加载的启动记录。软盘上所有的512字节都会是可执行代码。硬盘上则在偏移为0x0000-0x01bd的主引导记录处放置可执行代码。然后是四个主分区的表条目,每个条目使用16字节(0x01fd-0x01be),还有两字节的签名(0x01ef-0x01ff)

早期环境

早期执行环境高度依赖实现。特定的BIOS有特定的实现。不要做关于寄存器中内容的任何假设。也许被初始化为0,也许包含假的值。

CPU现在在实模式。你不仅要写激活保护模式的代码还要添加测试条件看看有没有激活。

内核

启动载入器把内核载入内存并移交控制权。

载入

我们已经知道要载入什么,但不知道如何载入。

如果从硬盘载入,引导记录(boot record)只有446字节大小。以下是内核镜像启动前必须要做的:

  • 从哪个分区启动
  • 找到启动分区上的内核镜像
  • 将内核镜像载入内存
  • 激活实模式
  • 为内核准备运行时环境

不用按顺序来,但调用kmain()之前做完这些。

To make things worse,gcc只生成保护模式的可执行代码,所以这部分是你用C做不到的。

有几种解决问题的方式:

  • geek loading:把上面列出的一切都挤压到引导记录中。这几乎不可能,会让接下来的特例处理和有用的错误信息无处安放。
  • One-stage loading: 写一个stub来转换,链接到内核镜像之前。引导记录加载内核镜像(在1mb(因为[ES:BX]0xffff x 0xffff = 1114095b约为1mb)内存标志下,实模式的最大内存上限),跳转到stub,stub转换到保护模式并准备运行环境,跳入内核。
  • Two-stage loading: 写一个分开的stub,这个stub载入到1mb内存标志下,然后做以上列表中的所有事。

传统方式

传统,MBR重定位到0x0000:0x0600,找到有用分区的分区表,载入分区表的第一个扇区(分区引导记录)至0x0000:0x7c00,并跳转到该地址。这叫链式载入,想要自己写的引导记录能双启动的话就模仿这种方式。

简单方法

除非你真得想因为教学目的自制启动载入器(记录/stub),推荐使用可用的启动载入器。

其中最突出的是GRUB,一个two-stage启动载入器,不仅提供能链式载入的启动菜单,而且准备好环境(包括保护模式和读取BIOS上有价值的信息),把普通的可执行文件作为内核载入(而不是像很多启动载入器要求flat binary(不包含任何头的二进制文件)),支持可选的内核模块,不同的文件系统,甚至无盘启动(如果配置得当)