banner
EthanYang

Sail's Blog

github

Linux系统启动时

之前看到了 Linux 的启动流程的大概描述:

image

我想知道更具体的细节,所以搜寻了一些资料,做一个总结。

一、CPU Reset#

当我们按下电源按钮后,电源就开始给设备供电,一开始供电并不稳定,主板检测到后,就会一直给 CPU 发送RESET 信号,此时的 CPU 就会清除寄存器上的残留数据并进行寄存器的设置:

image

在这之中最重要的寄存器就是CS 和 EIP,CS 中隐式的 Base 会和 RIP 相加,即ffff0000H+fff0H=fffffff0Hffff 0000H + fff0H = ffff fff0 H,存放在这个地址 (也称为重置向量) 的是一条跳转指令,其跳转的目的地址是BIOS 的入口地址

为什么重置向量不直接设为 BIOS 入口地址,而是要跳转呢?
X86 芯片一开始是运行在实模式下的,实模式只有 20 位寻址空间(1M),而我们的重置向量是 fff0H,到 ffffH 只有很短的距离,根本放不下 BIOS 程序,所以要跳转。
至于为什么要把重置向量放在地址空间的高地址处,是为了给内存腾出更大的空间。

实模式下的内存布局

image

多核系统下的 CPU Reset#

多核计算机下,一开始要执行某个协议,系统将选取一个 CPU 来执行,其他 CPU 都处于等待状态,这个被选取的主 CPU 叫 Bootstrpping CPU(BSP),只有 BSP 继续执行,其它 CPU 等待 BSP 给它们发指令。

二、BIOS 执行#

BIOS 是固定在 EPROM 中的程序,一般由硬件厂商写死,其负责引导系统的启动。

POST 加电自检#

BIOS 首先执行的程序就是 POST,其用于计算机刚接通电源时对硬件部分的检测,如果自检中发现不是很严重的错误,系统会根据检测代码给出提示信息或鸣笛警告

为什么选择鸣笛的方式来发出警报?
因为以前没有核显,显卡就一定是外设,POST 执行时,显卡还没有完成初始化,所以无法将错误信息显示在屏幕上,只能通过声音来报警。

检测的过程是逐一进行的,BIOS 厂商对每个设备都提供了一个POST CODE,当对某个设备进行检测时,就会把这个 POST CODE 装到诊断的端口,如果没通过,这个 CODE 就会被保留,进行报警。

初始化设备#

POST 结束后,BIOS 还会调用各外设的 BIOS 进行自检和初始化,比如显卡的 BIOS。所有的设备都是这个时候初始化并启动的。
除了初始化设备,BIOS 这个时候还会初始化中断向量表。

启动 Bootloader#

在检测和初始化各种设备之后,BIOS 会查找用户自定义的启动顺序,默认是磁盘,当然也可以是光盘和 U 盘(以前重装操作系统时就会用到光盘和 U 盘):

image

根据顺序,BIOS 会把排在最前面的设备的MBR(主引导记录),即第 0 磁道第一个扇区的 512 字节,读到RAM 绝对地址 0x7C00 处,并跳转到这个地址。

MBR 格式
MBR 不属于任何一个操作系统,其 512 字节的内容如下:

  • 启动代码 (446B):检测分区表的准确性,并将系统控制转移给硬盘上的 Bootloader 程序(比如 grub)
  • 磁盘分区表 (16 X 4B):DPT,由 4 个分区表组成,每个 16B,说明磁盘分区情况。
  • 结束标志 (2B)

UEFI
现在说 BIOS,主要指的是 UEFI,而不是传统的 BIOS。不管是传统 BIOS 还是 UEFI,都是经过 ROM→RAM→BOOT 的过程,主干是没有区别的,那么我们为什么需要 UEFI 呢?
可以参考这个回答:UEFI 引导与 BIOS 引导在原理上有什么区别?

三、Bootloader#

所谓的 Bootloader 程序就是用来加载操作系统内核文件到内存,这类程序的主要功能为:

  1. 从实模式到保护模式,从 16 位寻址空间到 32 位寻址空间,使能段机制。
  2. 从硬盘读取 ELF 格式的 Kernel(就是跟在 MBR 后面的扇区)并放到内存中的固定位置,这个过程一般分两步,最终是执行 boot 指令,即加载系统引导菜单 (/boot/grub/menu.lst或 grub.lst),内核 vmlinuz 和 RAM 磁盘 initrd。
    我们这里以 GNU 的grub为例。grub 可用于选择操作系统分区上的不同内核,也可用于向这些内核传递启动参数。grub 被载入一般包括两个步骤:
  3. 装载基本引导程序 - stage1,其主要功能是装载第二引导程序
  4. 装载第二引导程序 - stage2,这第二引导装载程序用于引出更高级的功能,以允许用户装载入一个特定的操作系统,在 grub 中,这步是给用户一个显示菜单或者让用户输入命令,该阶段引导的最终状态是执行 boot 命令,将操作系统内核加载进入内存中,进而将控制权转交给内核。
    当内核加载完成后,内存被映射为:

image

四、Linux 内核设置和启动#

Linux 内核启动后,执行的一系列检测,检测完成后跳转到 start_kernel 函数,这个函数会依次初始化各个模块,比如页表、中断向量等,然后变成 0 号进程。

0 号进程会 fork 出 1 号 kernel_init 进程,kernel_init 会执行 init 程序,大致过程如下:

init 程序

  1. init 进程读取/etc/inittab文件,作用是设定 Linux 的运行等级,决定进程运行模式。
  2. Linux 执行第一个用户层文件/etc/rc.d/rc.sysinit,该文件功能包括:设定 PATH 运行变量、设定网络配置、启动 swap 分区、设定 /proc、系统函数等.
  3. 读取/etc/modules.conf/etc/modules.d目录下的文件来加载系统内核模块。
  4. 启动一些服务,之后执行/etc/rc.d/rc.local文件。
  5. 最后执行 /bin/login 程序,启动到登录界面,让用户输入用户名和密码。

完成用户的启动后,0 号进程进入 cpu_idle, 变成 idle 进程。到这 Linux 差不多就启动好了。

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。