「Linux」- 系统启动流程

  CREATED BY JENKINSBOT

整理这篇笔记的原因

当修改系统启动配置后,进行操作系统重启。然而此时操作系统没有如期启动,而是在屏幕上显示grub>提示符。如果不了解GRUB在系统启动流程中是如何工作的,就无法处理这种系统启动问题。

当升级操作系统内核后,进行操作系统重启以加载新内核。然而此时操作系统没有如期启动,而是在屏幕上显示(initramfs)提示。如果不了解在系统启动流程中内核是如何加载的,就无法处理该启动问题。

如果不了解系统启动流程,就不知道:如何在BIOS下从GPT中引导启动;如何从LVM中引导启动;引导加载程序(如GRUB、LILO等)参数含义以及如何配置;如何使用GRUB实现多操作系统的引导盘。

因此熟知系统启动流程对于理解原理、调整配置、排查问题、实现功能是非常有帮助的。

涵盖在笔记中的内容

本部分将描述系统启动流程。我们基于GNU/Linux环境,从启动按钮按下的那刻开始,到显示器终端提示用户登录结束。

本部分涉及极少的硬件知识。由于硬件不是我们的兴趣所在,因此我们在硬件方面投入的研究较少,自然也不是我们的专长(当然这不是在说硬件技术无聊)。对于硬件方面,我们只需熟悉它们的组成部分、工作原理即可(因为硬件的特性影响驱动及软件的实现,在软件中会看到硬件的相关概念)。

本部分笔记以理论知识为主。重点讲述系统启动流程,参与系统启动的组件,组建的详细信息,组件之间的协作。

本部分笔记存在完整性问题。由于知识体系之庞大,无法单次完成,需要要日积月累,不断完善,在此为内容完整性深表歉意。

本部分笔记存在准确性问题。此外由于我们知识有限,部分内容可能有失准确,我们会及时更正。

本部分笔记存在连贯性问题。由于篇幅有限且内容较多,所以本文负责概述流程,相关细节可参考附属章节。

系统启动流程的概览

常见的系统启动流程

我们读过很多介绍系统启动流程的文章,多数基于1.BIOS => 2.MBR => 3.BOOTLOADER => 4.KERNEL => 5.INIT流程展开介绍。通过该流程可以概览系统启动过程,但是却存在若干问题:

	**主引导记录(MBR)与引导加载程序(BOOTLOADER)的分界不明显。**参与系统启动的是在主引导记录中的引导代码,而引导代码属于引导加载程序,通过命令安装到主引导记录中。导致在讲述第二步的主引导记录时,需要涉及第三步的引导加载程序的引导代码,引发笔记结构混乱问题。

为了规避这些问题,我们决定从新角度来解释系统启动流程。

从程序执行角度观察

因为我们跳过硬件原理介绍,那么对于我们来说 系统启动过程 就是 程序执行过程,所以我们可以从程序文件执行的角度来观察系统启动流程。有哪些镜像(程序文件)参与系统启动?它们保存在什么位置?它们的加载顺序是怎样的?它们负责完成哪些任务?

从程序执行角度看,系统启动可以分为如下步骤:1.BIOS => 2.GRUB => 3.KERNEL => 4.INIT

第一步、System startup/Hardware initilazation (BIOS/System start)

当计算机开机或重置时,首先执行 BIOS (Basic Input/Output System) 代码,运行系列称之为 POST 的诊断。

按照固件配置顺序定位到软盘、光驱、硬盘等引导设备时,该阶段即将接近尾声结束。

此时 BIOS 开始检查可引导设备的引导签名(魔数),引导签名位于零号扇区,在第 510 与 511 字节包含 0x55, 0xAA 字节序列。

当 BIOS 找到该扇区时,将该扇区加载到内存 0x0000:0x7c00(segment 0, address 0x7c00)。但是,某些 BIOS 加载到 0x07c0:0x0000(segment 0x07c0, offset 0),虽然解析为相同的物理地址,但这可能令人惊讶。较好的做法是在启动扇区的开始里强制制定 CS:IP。

在加载结束后,执行被传递给已载入内存的引导记录

第二步、Boot loader Stage 1 (MBR loading)

在软盘中,整个引导扇区的 512 字节全为可执行代码(除了引导签名);在磁盘中,主引导记录的 0x0000 – 0x01bd 含有可执行代码,后面为四个分区表,每个分区表十六字节,以及两字节引导签名。

主引导记录代码执行的“环境”

这种早期环境是高度实现定义的(即由特定 BIOS 实现定义的),具有很多不确定性,切勿对寄存器的内容做任何假设:可能被初始化为零,也可能包含伪值。包括 FLGAS Register 与 SP Register,可能也没有有效的栈!唯一能够确定是 DL Register 存有引导代码(来自引导代码加载地方)。

当前处理器处于 Real Mode(除非你使用某些罕见 BIOS 帮你激活 Protected Mode),就是说需要编写代码激活以激活其他硬件的 Protected Mode,还要测试是否已经激活保护模式。这些都是主引导记录代码需要处理的问题。

主引导记录代码的工作任务

这些都是引导代码需要完成的工作,除此之外它还要完成其他工作(少量):

	1)决定从哪个分区引导启动,或者显示菜单以让用户选择引导分区
	2)确定内核位置以载入内存(要么检测文件系统,要么从固定位置加载)
	3)将操作系统内核载入内存(需要磁盘读写)
	4)启用保护模式
	5)准备内核的运行环境(比如设置栈空间)

虽然不必按以上顺序完成这些任务,但是这些任务是必须完成的(历史遗留问题要求我们完成这些任务)。

完成引导工作任务的几种途径

第一种,极客式,MBR only
在主引导记录中完成所有事情。然而这几乎不可能,并且没有办法处理特殊场景以及显示错误信息(空间无法容纳更多程序)。

第二种,单阶段,MBR => Stub Program + Kernel
编写“桩程序(Stub Program)”,链接到内核镜像前面。引导记录加载内核映像(低于 1MiB 内存标记,因为在实模式下,这是内存上限!),跳转到桩程序,桩程序负责切换到保护模式并进行准备工作,正确跳转到内核。

第三种,两阶段,MBR => Stub Program => Kernel
编写分离的“桩程序(Stub Program)”(即不与内核链接),加载到 1MiB 内存标记以下,然后完成上述工作。

当前实现方案(以 GRUB 为例)

如果实现方式不同,那么 MBR 的工作内容也有所不同。

我们以 GRUB 为例,它提供 主引导记录代码 与 桩程序,主引导记录代码完成以下工作:

TODO 整理 GRUB 主引导记录代码的任务

关于编写引导加载程序

棘手问题:由于 GCC 只能生成保护模式的可执行代码,所以用于该早期环境的的代码不能使用 C 编写。

在传统上,MBR 会将自己重新定位到 0x0000:0x0600 地址,从分区表上确定活动分区,将该活动分区的第一扇区加载到 0x0000:0x7c00 地址,并跳转到该地址。这被成为 Chain Loading。如果希望自己写的引导记录能够双重引导,例如 Windows,那它应该模仿这种行为。

最简单的实现方式是使用现有的引导程序,比如 GRUB,两阶段引导,不仅提供 Chain Loading 功能,还将环境初始化为良好定义的状态(包括 Protected Mode 与 从 BIOS 读取各种需要信息),可将通用可执行文件当作内核镜像加载(其他引导程序可能需要固定二进制文件),支持可选内核模块,多种文件系统,无盘启动。

第三步、Boot loader Stage 2 (GRUB Boot loader)

然后启动加载器会读取磁盘中的配置文件,向用户显示可配置菜单来选择启动项。

在用户选择后,启动加载器会从磁盘加载配置的kernel、initramfs镜像,并载入内存中。镜像initramfs是经过gzip的cpio归档,其中包含在启动时所有必要硬件的内核模块、初始化脚本等等。在RHEL 7中,initramfs包含自身可用的整个系统。

第四步、Kernel (Linux OS)

第五步、INIT process (Run levels)

systemd 会查找从内核命令行传递或系统中配置的默认目标,然后启动单元,以符合目标的配置,从而解决单元之间的依赖问题。本质上systemd目标是一组应在激活后达到所需系统状态的单元。这些Target通常至少包含生成文本登录或图形登录的屏幕。

第六步、User prompt (User commands)

相关链接

How Computers Boot Up
The Kernel Boot Process

参考文献

6 Stages of Linux Boot Process (Startup Sequence)
Stages of Linux booting process – explanation, step by step tutorial
RH124 Chapter 13
OSDev.org/Boot Sequence