BIOS 时期

在主板上,有一个东西叫 ROM(Read Only Memory,只读存储器)。这和咱们平常说的内存 RAM(Random Access Memory,随机存取存储器)不同。ROM 是只读的,上面早就固化了一些初始化的程序,也就是 BIOS(Basic Input and Output System,基本输入输出系统)。

在 x86 系统中,实模式下,将 1M 空间最上面的 0xF0000 到 0xFFFFF 这 64K 映射给 ROM,也就是说,到这部分地址访问的时候,会访问 ROM。当电脑刚加电的时候,会做一些重置的工作,将 CS 设置为 0xFFFF,将 IP 设置为 0x0000,所以第一条指令就会指向 0xFFFF0(CS << 4 + IP),正是在 ROM 的范围内。在这里,有一个 JMP 命令会跳到 ROM 中做初始化工作的代码,于是,BIOS 开始进行初始化的工作。

初始化中,首先要检查硬件,其次建立一个中断向量表和中断服务程序,此外还要展示当前运行状态,即内存空间映射显存的空间,在显示器上显示一些字符。

bootloader 时期

操作系统一般都会在安装在硬盘上,在 BIOS 的界面上会看到一个启动盘的选项。启动盘一般在第一个扇区,占 512 字节,而且以 0xAA55 结束。这是一个约定,当满足这个条件的时候,就说明这是一个启动盘,在 512 字节以内会启动相关的代码。

在 Linux 里面有一个工具,叫 Grub2,全称 Grand Unified Bootloader Version 2。顾名思义,就是搞系统启动的。使用 grub2-install /dev/sda,可以将启动程序安装到相应的位置。

grub2 第一个要安装的就是 boot.img。它由 boot.S 编译而成,一共 512 字节,正式安装到启动盘的第一个扇区。这个扇区通常称为 MBR(Master Boot Record,主引导记录 / 扇区)。BIOS 完成任务后,会将 boot.img 从硬盘加载到内存中的 0x7c00 来运行。

由于 512 个字节实在有限,boot.img 做不了太多的事情。它能做的最重要的一个事情就是加载 grub2 的另一个镜像 core.img。core.img 由 lzma_decompress.img、diskboot.img、kernel.img 和一系列的模块组成,功能比较丰富,能做很多事情。2b8573bbbf31fc0cb0420e32d07b196a.webp
boot.img 先加载的是 core.img 的第一个扇区。如果从硬盘启动的话,这个扇区里面是 diskboot.img,对应的代码是 diskboot.S。boot.img 将控制权交给 diskboot.img 后,diskboot.img 的任务就是将 core.img 的其他部分加载进来,先是解压缩程序 lzma_decompress.img,再往下是 kernel.img,最后是各个模块 module 对应的映像。这里需要注意,它不是 Linux 的内核,而是 grub 的内核。

lzma_decompress.img 对应的代码是 startup_raw.S,本来 kernel.img 是压缩过的,现在执行的时候,需要解压缩。由于实模式下内存空间过小,所以在真正的解压缩之前,lzma_decompress.img 会调用 real_to_prot,切换到保护模式,这样就能在更大的寻址空间里面,加载更多的东西。

扩展阅读: https://www.cnblogs.com/shiqi17/p/12650640.html

实模式切换到保护模式

切换到保护模式要干很多工作,大部分工作都与内存的访问方式有关

第一项是启用分段,就是在内存里面建立段描述符表,将寄存器里面的段寄存器变成段选择子,指向某个段描述符,这样就能实现不同进程的切换了。第二项是启动分页。能够管理的内存变大了,就需要将内存分成相等大小的块。

保护模式需要做一项工作,那就是打开 Gate A20,也就是第 21 根地址线的控制线。在实模式 8086 下面,一共就 20 个地址线,可访问 1M 的地址空间。如果超过了这个限度怎么办呢?当然是绕回来了。在保护模式下,第 21 根要起作用了,于是我们就需要打开 Gate A20。

因为 8086 只有 20 根地址线,即 A0-A19。而段寄存器却只有 16 位,导致无法直接寻址 20 位的内存空间,因此 intel 使了个小把戏,即 CS<<4: offset(16位) 作为拼凑为 20 位的地址,而 FFFF 左移 4 位后与 FFFF 相加的值为 10FFEF,结果为 21 位,最高位1被丢弃,从而100000-10FFEF 回卷为 00000-0FFEF。因此 00000-0FFEF 对应有两个逻辑地址,很多 8086 的程序员利用这个回卷特点进行编程。但是到 80286 时,地址线变为了 24 根,而 CS 中存放的也不再是段基地址,而是段选择子,通过段选择子拿到的段基地址可以很方便的扩充到 24 位,因此就可以访问整个 16M 地址空间了,但是为了不得罪老用户,intel 必须兼容 8086,因此就加了个 A20 与门,当处于实模式时,关闭 A20 开关,即与门输出为 0,这就等价于 8086 丢弃最高 20 位的 1 了,从而地址仍然会进行回卷,这样旧的软件就可以继续跑了。同时,当我们在保护模式下运行基于 80286 的新程序时,就开启 A20 开关,即 A20 与门的输出与地址线 A20 保持一致,从而可以直接访问更宽的地址了。切换保护模式的函数 DATA32 call real_to_prot 会打开 Gate A20,也就是第 21 根地址线的控制线。

有了大量的空间以后就要对压缩过的 kernel.img 进行解压缩,然后跳转到 kernel.img 开始运行。kernel.img 对应的代码是 startup.S 以及一堆 c 文件,在 startup.S 中会调用 grub_main,这是 grub kernel 的主函数。在这个函数里面,grub_load_config() 开始解析 grub.conf 文件里的配置信息。

如果是正常启动,grub_main 最后会调用 grub_command_execute (“normal”, 0, 0),最终会调用 grub_normal_execute() 函数。在这个函数里面,grub_show_menu() 会显示出让你选择的那个操作系统的列表。
883f3f5d4227a593228e1bcb93f67297.webp

总结延伸

0a29c1d3e1a53b2523d2dcab3a59886b.webp

  • 实模式只有 1MB 内存寻址空间(X86)
  • 加电, 重置 CS 为 0xFFFF , IP 为 0x0000, 对应 BIOS 程序
  • 0xF0000-0xFFFFF 映射到 BIOS 程序(存储在ROM中), BIOS 做以下三件事:
    • 检查硬件
    • 提供基本输入(中断)输出(显存映射)服务
    • 加载 MBR 到内存(0x7c00)
  • MRB: 启动盘第一个扇区(512B, 由 Grub2 写入 boot.img 镜像)
  • boot.img 加载 Grub2 的 core.img 镜像
  • core.img 包括 diskroot.img, lzma_decompress.img, kernel.img 以及其他模块
  • boot.img 先加载运行 diskroot.img, 再由 diskroot.img 加载 core.img 的其他内容
  • diskroot.img 加载解压缩程序 lzma_compress.img, 由 lzma_compress.img 切换到保护模式

  • 切换到保护模式需要做以下三件事:
    • 启用分段, 辅助进程管理
    • 启动分页, 辅助内存管理
    • 打开其他地址线
  • lzma_compress.img 解压运行 grub 内核 kernel.img, kernel.img 做以下四件事:
    • 解析 grub.conf 文件
    • 选择操作系统
    • 例如选择 linux16, 会先读取内核头部数据进行检查, 检查通过后加载完整系统内核
    • 启动系统内核

扩展阅读 :grub2