Linux 开机流程详解

对于任何系统, 开机不仅仅是接通电源这么简单的事情, 在这短短的几秒或几十秒内将会发生许多事情, 了解这个完整的流程将是解决任何开机问题或提高开机速度的前提. 接下来, 我们就详细了解一下Linux的开机流程: 

1. 加载 BIOS 的硬件信息并进行自我测试,并依据取得第一个可开机的装置;
2. 读取并执行第一个开机装置内 MBR 的 Boot Loader (如 grub, spfdisk 等程序);
3. 依据 Boot Loader 的设定加载 kernel, kernel 会开始侦测硬件并加载驱劢程序;
4. 在硬件驱动成功后, kernel 会调用 init 程序,而init 会取得 run-level 信息;
5. init 执行 /etc/rc.d/rc.sysinit 文件来准备软件运行的环境 (如网络、时区等);
6. init 执行 run-level 的各个服务启动 (script 方式);
7. init 执行 /etc/rc.d/rc.local 文件;
8. init 执行终端机仿真程序 mingetty 来启动 login 程序,最后用户就可以登陆了;

接下来对以上步骤进行详细的讲述:
1. 加载 BIOS 的硬件信息并进行自我测试,并依据取得第一个可开机的装置;
在X86平台下,如果像让系统跑起来,就必须首先让系统去加载BOIS, 并通过BOIS程序去加载CMOS信息. 通过CMOS内的设置定取得主机的各项硬件配置: 例如 CPU 与接口设备的沟通频率、开机装置的搜寻顺序、硬盘的大小与类型啊、 系统时间、各周边总线的是否启动 Plug and Play (PnP, 即
插即用装置) 、 各接口设备的 I/O 地址、以及与 CPU 沟通的 IRQ 岔断等等的信息。
在这里我们有必要了解BOIS和CMOS的概念:
BOIS: 基本输入输出(Basic Input Output System). 其内容集成在主板上的一个ROM芯片上,主要保存系统最重要的基本输入输出程序, 包括:终端服务程序, 系统设置程序, 开机上电自检程序和系统启动自举程序.

CMOS: 互补金属氧化物半导体存储器(Complementary Metal Oxide Semiconductor). 是主板上一块可读写的RAM芯片,主要用拿过来保存当前系统的硬件配置和操作者对某些参数的设定(通俗的来说,就保存了你的电脑的硬件配置信息和你对自己电脑的参数设置等信息). CMOS RAM芯片由系统通过一块后备电池供电, 因此无论在关机状态, 还是遇到系统掉电情况,CMOS信息都不会丢失.(这也是放电破解开机密码的原理^-^)

BOIS和CMOS的联系:
CMOS是存放BOIS设定系统参数的介质, BOIS是修改CMOS的手段.

接着上面的内容继续介绍, BOIS加载CMOS信息后, 开始进行开机自检, 然后开始执行硬件的初始化, 并设定PnP, 之后再定义开机的装置顺序, 接下来就从指定的开机装置中读取数据. 至此,第一步就已完成.

2. 读取并执行第一个开机装置内 MBR 的 Boot Loader (如 grub, spfdisk 等程序);
第一步完成后, 此时系统开始从第一个开机装置中读取MRB, 并执行Boot Loader.在此,我们简单解释一下MBR和Boot Loader的概念:
MRB: 主引导记录区(Master Boot Record). 位于整个硬盘的0磁道0柱面1扇区. 不过在总共512字节的主引导扇区中, MBR只占用起其中446字节, 另外的64个字节交给了 DPT(Disk Partition Table硬盘分区表),最后两个字节“55, AA”是分区的结束标志。这个整体构成了硬盘的主引导扇区。

Boot Loader: BOIS指定开机装置后, 我们就可以读取该装置上的系统文件了, 然而不同的操作系统, 其文件格式一般不相同, 所以我们必须以一个开机管理程序来处理核心文件加载问题, 这个开机管理程序就是Boot Loader. 该程序被安装在开机装置的第一个扇区内, 即MBR中. 也就是说, MBR是存放Boot Loader的地方.

通过对MBR和Boot Loader的介绍, 我们知道Boot Loader的存放位置总是一定的,所以当BIOS识别磁盘后, 就可以通过INT 13号中断跳到制定的位置, 从而执行Boot Loader程序加载操作系统的核心文件. 至此, 第二步就已完成.

3. 依据 Boot Loader 的设定加载 kernel, kernel 会开始侦测硬件并加载驱动程序;
第二步完成后, Boot Loader就可以从指定磁盘上加载kernel, 讲kernel解压缩到内存中, 利用kernel的功能, 开始测试与驱动硬件设备. 同时, kernel还会重新侦测一次硬件, 而不一定会使用BIOS侦测到的硬件信息. 至此, kernel开机接管BIOS后的工作.在此, 我们需要简单介绍一下Linux的kernel.
在Linux中, kernel存放在/boot 分区, 并且命名为vmlinuz. 
接着上面的介绍, 在这一步中, kernel会加载驱动程序, 但是会有一个问题, 这些驱动程序一般存放在/lib/modules/ 目录中, 而kernel要识别磁盘, 必须先加载磁盘的驱动程序, 而磁盘的驱动程序存储在磁盘中(/lib/modules), 就陷入了一种进退两难的地步, 导致系统始终无法继续启动, 对于这种问题, Linux的前辈们, 想出了一种绝妙的方式-----虚拟文件系统来解决这个问题, 在此我们简单介绍一下这个虚拟文件系统.
虚拟文件系统(Initial RAM Disk) 一般使用的文件名为initrd, 被存储在/boot/目录下, 这个文件的特点是: 它能够透过Boot Loader来加载到内存中, 然后这个文件被解压缩到内存中被仿真成一个根目录, 且此仿真根目录在内存中的文件系统能够提供一个可执行的程序, 透过该程序来加载开机过程中所需要的核心模块, 通常是USB, RAID, LVM, SCSI等文件系统与磁盘接口的驱动程序, 载入完成后, kernel便会重新加载/sbin/init来开始后续的正常开机流程. 至此, 第三步便完成了.

initrd文件内容如下:
drwxrwxr-x 10 freeman freeman     4096 10月 18 14:50 ./
drwxrwxrwt 16 root    root        4096 10月 18 14:47 ../
drwxr-xr-x  2 freeman freeman     4096 10月 18 14:50 bin/
drwxr-xr-x  3 freeman freeman     4096 10月 18 14:50 conf/
drwxr-xr-x  7 freeman freeman     4096 10月 18 14:50 etc/
-rwxr-xr-x  1 freeman freeman     7237 10月 18 14:50 init*
-rw-r--r--  1 freeman freeman 51981312 10月 18 14:46 initrd
drwxr-xr-x  7 freeman freeman     4096 10月 18 14:50 lib/
drwxr-xr-x  2 freeman freeman     4096 10月 18 14:50 lib64/
drwxr-xr-x  2 freeman freeman     4096 10月 18 14:50 run/
drwxr-xr-x  2 freeman freeman     4096 10月 18 14:50 sbin/
drwxr-xr-x  7 freeman freeman     4096 10月 18 14:50 scripts/
是不是很像Linux文件系统的目录结构(^-^)

4. 在硬件驱动成功后, kernel 会调用 init 程序初始化软件的运行环境
第三步完成后, kernel开始调用init程序, 也是系统中第一个程序, 所以其PID=1, 那么为什么要调用这个程序呢? 在此, 我们需要了解一下这个init程序的作用:
init 程序的最主要功能是准备软件运行的环境,  其中包括系统的主机名, 网络设定, 语言选择, 文件系统格式及其他服务的启动等. 而这些所有的动作都会透过init的配置文件, 也就是/etc/inittab来规划, 而inittab内还有一个很重要的设定项目, 那就是默认的runlevel(开机执行等级). 所以说, /etc/inittab 文件来指导init程序来执行具体的动作.

下面我们看看/etc/inittab文件的内容:

freeman@freeman-H55M-S2:~$ cat /etc/inittab
id:3:initdefault:
#设置默认运行等级.init 首先读取这一行来确定运行级别.


si::sysinit:/etc/rc.d/init.d/rc sysinit
#执行/etc/rc.d/init.d/rc.sysinit脚本, 用于设置主机名, 挂在文件系统, 启动交换分区等.


0:0:wait:/etc/rc.d/init.d/rc 0
1:S1:wait:/etc/rc.d/init.d/rc 1
2:2:wait:/etc/rc.d/init.d/rc 2
3:3:wait:/etc/rc.d/init.d/rc 3
4:4:wait:/etc/rc.d/init.d/rc 4
5:5:wait:/etc/rc.d/init.d/rc 5
6:6:wait:/etc/rc.d/init.d/rc 6
#对应于/etc/rc.d/rc[0-6].d中的脚本.


#配置行的排列语法为:
# id:runlevels:action:process [argument]
#id为标志字段,一般为2至4个字符.
#runlevel定义本行适用的运行等级.一般如下
#---------------------------------------------------------------------
#运行等级   |                                      说明
#---------|------------------------------------------------------------
#  0       | Halt,关闭系统.
#  1       | 单用户,在grub启动时加上为kernel加上参数single即可进入此运行等级
#  2       | 无网络多用户模式.
#  3       | 有网络多用户模式.
#  4       | 有网络多用户模式.
#  5       | X模式
#  6       | reboot重启系统
#  S/s    | 同运行等级1
#  a,b,c | 自定义等级,通常不使用.
#---------------------------------------------------------------------
#action定义采取的行动.
#---------------------------------------------------------------------
#    行动      |              说明
#---------------------------------------------------------------------
#   respawn   |只要进程一停止,该进程就重新启动.
#    wait     |进程只运行一次,init将一直等待它结束,再执行其它命令.
#    once     |进程只运行一次.
#    boot     |系统引导进程中,进程运行,init将忽略运行等级这段.
#   bootwait |系统引导过程中,进程运行,init将等待进程结束.
#    off     |不采取任何行动,功能相当于将这行用#注释掉.
# ondemand   |只要init调用a,b,c中的任何一种运行等级时,进程便运行.
#initdefault|系统设置默认运行级别.process字段被忽略.
#   sysinit   |只要系统引导,该进程便运行,优先于boot与bootwait.
# powerwait |当init接收到SIGPWR信号时进程开始运行,一般为电源故障时运行.
# powerfail |与powerwait相同,但init不会等待进程完成.
#powerokwait|当电源故障修复时运行.
# ctrialdel |当init收到SIGNT信号时(按下ctrl+alt+del)时,进程运行.
# kbrequest |当init收到键盘处理KeyboardSignal信号时,进程运行.
#----------------------------------------------------------------------


ca:12345:ctrlaltdel:/sbin/shutdown -t1 -a -r now
#在1-5运行等级中按下ctrl+alt+del时便重新启动系统.


su:S016:once:/sbin/sulogin
#在S,0,1,6等级下会运行sulogin.
#一般而言,如果在用grub或lilo启动时,如果为kernel加上single参数时,会直接进入单用户模式,而无需密码.造成一定安全隐患.加上这一行即可解决问题.


1:2345:respawn:/sbin/agetty -I '\033(K' tty1 9600
2:2345:respawn:/sbin/agetty -I '\033(K' tty2 9600
3:2345:respawn:/sbin/agetty -I '\033(K' tty3 9600
4:2345:respawn:/sbin/agetty -I '\033(K' tty4 9600
5:2345:respawn:/sbin/agetty -I '\033(K' tty5 9600
6:2345:respawn:/sbin/agetty -I '\033(K' tty6 9600
#设置tty控制台数量,一般为tty[1-6],X窗口系统一般运行在tty7

通过查看/etc/inittab中的内容, 我们很清楚的了解了init将通过执行 /etc/rc.d/rc.sysinit 脚本来准备软件运行的环境 (如网络、时区等). 至此, 第四步便已完成.

5. init 执行 run-level 的各个服务启动 (script 方式);
第四步完成后, init 还是根据inittab文件中指定的run-level, 来启动相应的服务. 具体有哪些服务, 我们可以根据不同的run-level对应的脚本文件来查看,
每个run-level对应的脚本文件在inittab中都有说明, 如下所示:
0:0:wait:/etc/rc.d/init.d/rc 0
1:S1:wait:/etc/rc.d/init.d/rc 1
2:2:wait:/etc/rc.d/init.d/rc 2
3:3:wait:/etc/rc.d/init.d/rc 3
4:4:wait:/etc/rc.d/init.d/rc 4
5:5:wait:/etc/rc.d/init.d/rc 5
6:6:wait:/etc/rc.d/init.d/rc 6


至此, 我们以run-level为5简单看一下对应的脚本文件内容:

freeman@freeman-H55M-S2:/etc/rc5.d$ ls -l
lrwxrwxrwx 1 root root  20  9月 20 18:00 S20kerneloops -> ../init.d/kerneloops
lrwxrwxrwx 1 root root  15  9月 20 18:00 S20rsync -> ../init.d/rsync
lrwxrwxrwx 1 root root  27  9月 20 18:00 S20speech-dispatcher -> ../init.d/speech-dispatcher
lrwxrwxrwx 1 root root  15  9月 20 18:00 S50saned -> ../init.d/saned
lrwxrwxrwx 1 root root  19  9月 20 18:00 S70dns-clean -> ../init.d/dns-clean
lrwxrwxrwx 1 root root  18  9月 20 18:00 S70pppd-dns -> ../init.d/pppd-dns
lrwxrwxrwx 1 root root  21  9月 20 18:00 S99grub-common -> ../init.d/grub-common
lrwxrwxrwx 1 root root  18  9月 20 18:00 S99ondemand -> ../init.d/ondemand
lrwxrwxrwx 1 root root  18  9月 20 18:00 S99rc.local -> ../init.d/rc.local
简单说明一下这个文件的命名含义: S表示Start, K表示Kill, 后面的数字表示运行的顺序, 再后面的单词就是运行的程序名.
至此, 第五步便已完成.

6. init 执行 /etc/rc.d/rc.local 文件;
在第五步完成后, 系统基本上可以说已经跑起来了, 对于这一步, 只是执行一些在系统启动之前的用户任务, 所以, 如果用户想在进入系统前, 做一些工作,
那么就可以在rc.local文件中动手脚.
 
7. init 执行终端机仿真程序 mingetty 来启动 login 程序,最后用户就可以登陆了;
在第六步完成后, 系统缩需要的服务已启动完成, 接下来Linux就会启动终端或X Window. 至此, 整个系统开机流程完毕.

参考资料:
鸟哥的私房菜(第三版)

BIOS与CMOS的区别

/etc/inittab文件分析


郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。