系统启动流程

概述

参考:

操作系统被称为 The first programme(第一个程序),原因很简单,只有当操作系统启动起来后才能运行我们编写的程序,那么操作系统是怎样启动起来的呢?实际上这个过程就像发射火箭一样有趣

操作系统也是普通程序

首先我们必须意识到这样两点:

  • CPU 执行的是机器指令,编译器将程序翻译后成了机器指令
  • 操作系统本身也是一个程序,这个程序被编译后也会生成一大堆机器指令

现在我们知道了,操作系统本身其实也是一大堆机器指令,既然是机器指令那么它必须得存放在什么地方。

存放在哪里呢?

想想我们编写的程序,编译后生成的是可执行文件,也就是说是以“文件”的形式存放的,并且存放在硬盘上,而操作系统也没什么不同,编译后生成的机器指令同样是以文件的形式存放的,存放在哪里呢?可以存放在任何能存储数据的介质,像 CD、磁盘之类都可以。

我们编写的程序在启动时被加载器——也就是 loader,加载到内存,加载器也是一个程序,这是一个加载其它程序的程序;这么说可能有点拗口,但计算机系统中有大量类似的程序,编译器是一个翻译程序的程序、操作系统是一个运行其它程序的程序、链接器是一个链接程序的程序、解释器是一个执行脚本程序的程序等等。 鸡生蛋蛋生鸡的问题

回到我们的主题,我们写的代码是 loader 加载到内存后运行的,那么操作系统这个程序是也同样的道理,必须得有个什么东西也要把操作系统加载到内存中运行才可以,这个东西不叫 loader,而是叫 boot loader,其本身也是一个程序,它的任务就是加载一个更大的程序,就像这里的操作系统。

此时这里会出现一个鸡生蛋蛋生鸡的,既然我们的程序是被加载器 loader(操作系统的一部分)加载到内存中,而操作系统又是被 boot loader 这个加载程序加载到内存中的,那么又是什么加载器把 boot loader 这个加载程序加载到内存中呢?而又又是什么加载器把上一句中的什么加载器加载内存中呢?而又又又是什么。。?

你会发现这个一个没有出口的无穷递归啊有没有,总得有个什么把前一个程序加载到内存,就好比今天的前一天是昨天、昨天的前一天是前天、前天的前一天是大前天,如果一直这样思考下去那么时间到底在哪里开始的呢?时间到底有没有开始(参考时间简史或相对论)?

时间有没有开始这个问题我不清楚,但操作系统启动的这个问题我知道。

上述关于加载器以及加载加载器等问题全部指向了内存,让我们好好想一想内存有什么特殊性?

内存断电后是无法保存数据

程序员都知道内存只有在加电的情况下才可以保存数据(关于内存的实现原理你可以参考这篇《你管这破玩意叫 CPU?》),那么很显然,当断电后内存中的内容就丢失了,那么又很显然的,当你在按下计算机开关通电时,内存中的内容是未被初始化的,也就是说内存中的内容是无效的,此时的内存里还是一片荒芜,这里没有任何东西可供 CPU 来执行,这就好比大爆炸之前的宇宙。 但我们的计算机总是能启动起来,CPU 必须得执行“一段什么程序”把第一个 boot loader 加载到内存中,由于此时内存中还什么都没有,那么这段程序一定被保存在了其它地方

保存在了哪里呢?

没错,这段程序就被保存在了 BIOS 的非易失性存储 ROM 或者 flash 存储中了,这里的代码在即使断电后也会保存下来,加电后 CPU 开始执行这里代码,把 boot loader 加载到内存中,现在你应该明白第一个 boot loader 是怎样被加载到内存的了吧。 在早期的计算机上甚至专门有一个按钮,让用户自己选择该从哪里,比如打孔纸带、打孔卡片或者硬盘,加载一个更复杂的程序来运行,操作面板上的旋钮可以控制把这些程序加载到内存的什么位置上去:

火箭与操作系统启动

然而现实情况比较复杂,我们刚才提到的 boot loader 这段小程序功能实在是太弱了,此时其能访问的磁盘地址空间有限,不能把完整的内核全部加载到操作系统中,该怎么办呢?

既然 boot loader 比较弱那么就换一个比较牛的 loader 程序来,就这样出现了二阶 boot loader,second stage loader: 二阶 boot loader 功能更为丰富,比如对硬件进行检查、给用户提供选项加载哪个操作系统等等,安装多系统的同学应该知道,启动时会给你一个选项到底是启动 windows 还是 linux,这就是二阶 boot loader 的作用。 最终,操作系统被二阶 boot loader 加载到内存中开始运行。

你会发现这个过程就和发射三级火箭一样,最初一级火箭启动,燃料用尽后二级火箭启动,二级火箭完成使命后三级火箭启动,最终把卫星送到太空,而计算机的启动过程也类似。

最初是 CPU 运行 BIOS 中的一段代码把一级 boot loader 加载到内存中运行,该程序又会把二级 boot loader 加载到内存运行,而二级 boot loader 又会把操作系统加载到内存中,此后控制权被转移到操作系统,(所谓控制权是指 CPU 跳转到操作系统的代码),操作系统开始运行,经过一系列的初始化,比如硬件检测、开启必要的后台进程等等,最终图形界面或者命令行界面呈现出来。 接下来我们把这个过程细化一下。

更详细的启动过程

你在按下电源的瞬间相当于火箭点火,此时一级发动机开始工作。

加电 CPU 重置后开始在地址 0xffff0 处开始执行指令,这个地址其实是 BIOS ROM 的末尾处,该位置其实是一个跳转指令,跳转到 ROM 的一段启动代码上,该代码会进行必要的自检,Power-on self-test (POST),展示 BIOS 启动界面等等,最重要的一步是找到启动设备,所谓启动设备就是指从哪里加载操作系统,比如 CD-ROM、或者磁盘、甚至 U 盘等都可以作为启动设备,早些年流行用 U 盘重新安装系统,其实就是告诉 BIOS 的这段代码从 U 盘中加载操作系统。

通常 BIOS 会把磁盘当做启动设备(大部分情况下),此时 BIOS 中的这段代码开始将磁盘的第 0 号块加载到内存中,那么这第 0 号块中有什么呢?没错,就是第一阶段 boot loader 程序,这第 0 号块也被称之为 Master Boot Record,MBR,肯定有不少同学听说过。

到这里,火箭的一级发动机燃料用尽,二级发动机开始点火,BIOS 中的这段代码把控制权交给加载到内存 boot loader,所谓控制权就是跳转到 boot loader 程序,这样 CPU 终于开始直接与内存交互了,CPU 开始从内存中取出指令然后执行。

MBR 中除了包含一段可执行代码之外还有一个分区表,partition table,这个表的中的每一个条目本质上在说:“操作系统是否在我这个分区,我这个分区有多大”,CPU 在执行 MBR 中的代码时会去检查操作系统存在哪个分区中,定位后开始从相应分区的起始位置读取磁盘数据到内存中,这时的磁盘数据中保存的就是二阶 boot loader,second-stage boot loader,此时一阶 boot loader 把控制权转交给二阶 boot loader,火箭三级发动机开始工作。

2_boot loader 的主要工作将操作系统加载到内存中,此后控制权转交给操作系统,火箭的三级发动机完成使命,到这一时刻,操作系统开始接管计算机,操作系统经过一系列自身的初始化后创建出若干必要进程,至此计算机启动完毕,卫星被成功送到了外太空中。

然而限于篇幅这里依然没有过多涉及细节,操作系统本身的初始化也是一个比较复杂的过程,感兴趣的同学可以去翻阅相关操作系统的资料。

系统启动流程

  • Power-on self-test(POST) # 加电自检通过 ROM 芯片来检测
  • ROM 芯片 # CMOS 然后启动 BIOS 程序
  • Basic Input and Output System(BIOS) # 基本输出输出系统
  • Boot Sequence # 按次序查找各引导设备,第一个有引导程序的设备准备本次启动用到的设备,不管该引导程序是否执行成功,只要有引导程序,就不再找下一个设备了。
    • 读取硬盘的第一个扇区,这个扇区被称为 MBR.这个扇区中存储了一个小程序,叫做 boot loader.MBR 很小,只有 446B. 最常见的一种 boot loader 叫 grub.
    • MBR,前 446 字节为 bootloader,后面 64 字节为 fat(磁盘分区表),后面 2 字节为 55AA 标记
  • Bootloader # 引导加载器,这是一个程序。功能:提供一个菜单,供用户选择要启动的系统或不同的内核版本,把用户选定的内核装载到内存中的特定空间中,解压,展开,并把系统控制权移交给内核
    • boot loader 程序主要做两件事,一个是将内核加载到内存中,另一个是启动一个虚拟文件系统.内核文件是 /boot/vmlinuz*,虚拟文件系统文件是 /boot/initrd*.
    • GRUB(GRand Uniform BootLoader)用来找到操作系统所在的磁盘分区,把内核加载至内存中,还能把控制权正常转交给内核的程序
      • GRUB 0.x 版:GRUB Legacy
      • GRUB 1.X 版:GRUB2
  • Kernel
    • 初始化:
      • 探测硬件
      • 装载硬件的驱动程序(有可能会借助于 ramdisk 加载驱动程序)
      • 挂载根文件系统到 / 目录下
  • 运行用户空间的第一个应用程序
    • init(初始化)程序的类型
      • SysV:第一个进程名 init 早期的系统用的这个程序,通过脚本启动程序,一个启动完了才能启动下一个
      • 配置文件/etc/inittab
    • Upstart:第一个进程名 init
      • 配置文件/etc/inittab、/etc/init/*.conf
    • Systemd:第一个进程名 systemd 所有程序可以并行同时启动
      • 配置文件:/lib/systemd/system,/etc/systemd/system
    • ramdisk

POST –> BootSequence(BIOS) –> Bootloader(MBR) –> kernel(ramdisk) –> rootfs –> init(systemd)

上电、引导

按下开关键,触发主板引脚,开启电源,为主板通电

装载内核

vmlinuz 分析 a. mkdir /tmp/vmlinuz&&cp /boot/vmlinuz-4.4.0-21-generic  /tmp/vmlinuz/ b. cd /tmp/vmlinuz/ c. od -t x1 -A d vmlinuz-4.4.0-21-generic | grep “1f 8b 08” 本质上,vmlinuz-4.4.0-21-generic  是一个 gzip 压缩文件,但是不能直接用 gzip 指令解压,因为在这个文件的开头嵌入了 gzip 的代码. 所以首先用指令 c 找到真正的压缩文件的头部.这个指令的输出形如: 0018864 ac fe ff ff 1f 8b 08 00 00 00 00 00 02 03 ec fd 然后执行下面的指令,其中的 18868 就是 18864+4,这里 4 是指 1f 8b 08 前面有 4 个字节. dd if=vmlinuz-4.4.0-21-generic bs=1 skip=18868 | zcat > vmlinuz-gakki objdump -D vmlinuz-gakki » result 我们可以看到,最终得到的 result 就是一个汇编文件.而 vmlinuz-gakki 文件本质上是一个可执行程序.可以尝试 执行他,chmod +x vmlinuz-gakki &&./vmlinuz-gakki . 显示 Segmentation fault

intrid 分析 首先将/boot/intird.img 文件复制到/tmp 文件夹下 执行 file /tmp/initrd.img-4.4.0-21-generic,得到的结果如下: /tmp/initrd.img-4.4.0-21-generic: gzip compressed data, last modified: Fri Jun 15 13:57:43 2018, from Unix 可以看到本质上这是一个 gzip 格式的压缩文件 cd /tmp mv initrd.img-4.4.0-21-generic initrd.img-4.4.0-21-generic.gz gzip -d initrd.img-4.4.0-21-generic.gz file initrd.img-4.4.0-21-generic 得到的结果为: initrd.img-4.4.0-21-generic: ASCII cpio archive (SVR4 with no CRC) 这是一个 cpio 文件 执行 cpio -idmv < initrd.img-4.4.0-21-generic 这样,我们可以看到它最终生成了一个小型的文件系统.

Systemd 运行

详见 《[Systemd 运行流程](/docs/1.操作系统/3.Systemd%20 系统守护程序/Systemd%20 运行流程.md 系统守护程序/Systemd 运行流程.md)》章节


最后修改 March 16, 2024: update, chroot, process (20992aa0)