系统启动流程
概述
参考:
操作系统被称为 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
- init(初始化)程序的类型
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 运行流程 章节
反馈
此页是否对你有帮助?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.