启动流程
在机器加电后,BIOS 会进行自检以及初始化等操作,接下来BIOS根据保存在CMOS中的引导设备信息决定了是从哪个引导设备(磁盘、USB、网络)开始引导,如果决定从硬盘启动OS,BIOS就会加载安装在硬盘的MBR上的GRUB的Stage 1模块,进而把整个GRUB加载起来,GRUB可以看作是一个小型的操作系统,其中内置了Shell以及一系列功能,不过GRUB的本质还是一个多重引导器,基于GRUB,用户可以具体选择引导哪个操作系统(Ubuntu还是CentOS还是Windows),如果选择了Linux,那么GRUB会加载Linux的内核映像vmlinuz,至此操作权就交给了真正的操作系统。
详细过程
从加电到BIOS
由于CPU只能从内存读取指令而不能从硬盘、U盘等地方读取指令,因此为了能让CPU在开机时读取到指令、硬件工程师设计 CPU 时,硬性地规定在加电的瞬间,强制将 CS 寄存器的值设置为 0XF000,IP 寄存器的值设置为 0XFFF0,这样一来,CS:IP 就指向了 0XFFFF0 这个物理地址(实模式下段偏移)。在这个物理地址上连接了主板上的一块小的 ROM 芯片。这种芯片的访问机制和寻址方式和内存一样,只是它在断电时不会丢失数据,在常规下也不能往这里写入数据,它是一种只读内存,BIOS 程序就被固化在该 ROM 芯片里,现在,CS:IP 指向了 0XFFFF0 这个位置,这意味着CPU开始正式执行BIOS处的指令
从BIOS到GRUB
BIOS 一开始会初始化 CPU,接着检查并初始化内存,然后将自己的一部分复制到内存,最后跳转到内存中运行。BIOS 的下一步就是枚举本地设备进行初始化,并进行相关的检查,检查硬件是否损坏,这期间 BIOS 会调用其它设备上的固件程序,如显卡、网卡等设备上的固件程序。
当设备初始化和检查步骤完成之后,BIOS 会在内存中建立中断表和中断服务程序:BIOS 会从内存地址(0x00000)开始用 1KB 的内存空间(0x00000~0x003FF)构建中断表,在紧接着中断表的位置,用 256B 的内存空间构建 BIOS 数据区(0x00400~0x004FF),接下来在 0x0e05b 的地址加载 8KB 大小的与中断表对应的中断服务程序。其中中断表中有 256 个条目,每个条目占用 4 个字节,其中两个字节是 CS 寄存器的值,两个字节是 IP 寄存器的值。每个条目(CS:IP)本质上都指向一个具体的中断服务程序,在中断发生时CPU会通过中断表寄存器+中断号找到对应的中断描述条目并执行对应的中断服务程序(CPU在硬件逻辑电路中针对中断信号会与call
指令一样自动压栈当前执行的PC以方便中断结束后继续运行原来的指令流,但是执行上下文:通用寄存器、数据寄存器等的压栈保存与出栈恢复则是交由中断处理程序自行处理的了)。
接下来为了启动外部储存器中的程序,BIOS 会搜索可引导的设备,搜索的顺序是由 CMOS 中的设置信息决定的(也就是平时所谓的在 BIOS 中设置的启动设备顺序)。一个是软驱,一个是光驱,一个是硬盘上,还可以是网络上的设备甚至是一个 usb 接口的 U 盘,都可以作为一个启动设备。Linux 通常是从硬盘中启动的。硬盘上的第 1 个扇区(每个扇区 512 字节空间),被称为 MBR(主启动记录),其中包含有基本的 GRUB 启动程序和分区表,安装 GRUB 时会自动写入到这个扇区,当 MBR 被 BIOS 装载到 0x7c00 地址开始的内存空间中后,BIOS 就会将控制权转交给了 MBR。在当前的情况下,其实是交给了 GRUB。
GRUB启动
BIOS 只会加载硬盘上的第 1 个扇区,但是一个扇区仅有 512 字节,这 512 字节中还有 64 字节的分区表加 2 字节的启动标志,很显然,剩下 446 字节的空间,是装不下 GRUB 这种大型通用引导器的,因此GRUB的加载分为了多个步骤,而GRUB也分成了多个文件,其中有几个重要的文件:boot.img、core.img和kernel.img。
boot.img会被GRUB的安装程序写入到硬盘的MBR中,而在boot.img中记录了core.img文件所占用的第一个扇区号,进而可以通过boot.img跳转到core.img中执行
core.img 文件是由 GRUB 安装程序根据安装时环境信息,用其它 GRUB 的模块文件动态生成。如果是从硬盘启动的话,core.img 中的第一个扇区的内容就是 diskboot.img 文件,它会读取 core.img 中剩余的部分到内存中,由于这时 diskboot.img 文件还不识别文件系统,所以core.img 文件的全部位置,都会用文件块列表的方式保存到 diskboot.img 文件中。这样就能确保 diskboot.img 文件找到 core.img 文件的剩余内容,最后将控制权交给 kernel.img 文件。
由于grub内置了Shell以及一系列功能,因此它可以看作是一个小型的操作系统,而kernel.img 文件相当于它的内核,控制权交给kernel.img后,会调用对应的grub_main函数进而调用grub_load_modules函数加载各个mod模块,加载各个mod后,grub就支持文件系统了,进而grub就能访问boot/grub及其子目录了。
kernel.img接着调用grub_load_normal_mode加载normal模块,normal模块读取解析文件grub.cfg,并开始展示对应的启动菜单交由用户选择启动哪个操作系统,进而加载 Linux 系统的 vmlinuz 内核文件。
vmlinuz文件
vmlinuz 存在于/boot目录下,这个vmlinuz文件其实就是由 Linux 编译生成的 bzImage 文件复制而来的,其中生成 bzImage 文件主要包含了两个部分分别是:setup.bin、vmlinux.bin,其中setup.bin是操作系统的入口进行初始化,而vmlinux.bin则包含了操作系统的功能实现
参考
- 《操作系统实战45讲》