CentOS 系统启动流程

前言

Linux 系统的启动及初始化是 Linux 学习过程中非常重要的一个点,只有洞悉系统启动过程、初始化了哪些内容,才能对 Linux 有更加深入的了解。本文主要描述 CentOS 系统( PC 架构)从电脑开机到初始化完成整个过程,包括 cpu 加电自检、 bios 寻找启动项、 bootloader 加载系统内核、系统启动并初始化等等。

总览

POST –> Boot Sequence –> Boot Loader –> Kernel –> rootfs –> switchroot –> /sbin/init –> ( /etc/inittab , /etc/init/*.conf ) –> 设定默认运行级别 –> 系统初始化脚本 –> 关闭或启动对应级别下的服务 –> 启动终端

第一步:加电自检( POST

系统加电之后,首先进行的硬件自检,一但通电后主板会自动读取主板 ROM (只读)中的程序,进行加载,计算机会首先加载 BIOS 系统。然后通过加载 CMOS 检查各种硬件设备是否完整存在,如内存,硬盘,显示, IO 设备等。这个时候机器不能获取外部的存储或者网络信息,一些重要的值(日期、时间、其他外部值)都是从CMOS里读取。如果有硬件故障的话将按两种情况理:对于严重故障(致命性故障)则停机,此时由于各种初始化操作还没完成,不能给出任何提示或信号;对于非严重故障则给出提示或声音报警信号,等待用户处理。如果没有故障, POST 完成自己的接力任务,将尾部工作交接给 BIOS 处理。

第二步:系统启动引导( Boot Sequence

BIOS 会按次序查找各引导设备,第一个有引导程序的设备即为本次启动要用到的设备(也可以人工介入进行选择)。决定启动介质按照 BIOS 所设定的系统启动流程,根据引导次序( Boot Sequence )自上而下的寻找对应存储设备上操作系统的 MBR 。

MBR 是启动硬盘上第 0 磁道第一个扇区,也就是 Master Boot Record ,即主引导记录,它的大小仅有 512 bytes ,但里面却存放了引导区、分区表等信息。可分为三部分:

  1. 446 bytes :引导区( bootloader )
  2. 64 bytes :分区表( PARTITION TABLE )
  3. 2 bytes :55AA(分区有效性标记, 55AA 为有效,其他为无效)

POST 过程结束后,系统的控制权从 BISO 转交到 bootloader 。 Bootloader 一般存储在系统的硬盘上(传统的 BIOS/MBR 系统)。

第三步:启动加载器( Boot Loader

Bootloader 的功能:

  1. 提供一个菜单,允许用户选择要启动的系统或不同的内核版本;
  2. 把用户选定的内核装载到 RAM 的特定空间中,解压、展开,而后把系统控制权移交给内核;

各个系统的 bootloader 加载器程序也不同:

  • Windows:ntloader
  • Linux:
    • LILO:LIinux LOader(无法支持大磁盘,1024 柱面后的内容不能加载,现一般用于移动设备)
    • GRUB:Grand Uniform Bootloader
      • GRUB 0.X:Grub Legacy(CentOS 5/6)
      • GRUB 1.X:Grub2(CentOS 7)
      • Grub 0.X 和 Grub 1.X 是两种几乎完全不一样的版本。

上面说过, MBR 中给予 bootloader 的空间非常少,只有 446 bytes ,对于要实现多功能的 bootloader 来说,非常不利于扩展和改造,所以 Grub 将程序分为两段:

  1. bootloader(放于 MBR);

    1.5. :存放在 MBR 之后的扇区,让第一段中的 bootloader 能识别第二段所在的分区上的文件系统;

  2. 存放于 /boot/grub (主要负责调用内核及实现其他功能);

Grub 配置文件示例图:

Grub 配置文件示例图

图片摘自:http://linux.it.net.cn/CentOS/fast/2016/0103/19557.html

注意:除了 MBR 架构,还有 UEFI 、 GPT 等架构,此次主要说 MBR 。

第四步:加载内核( Booting The Kernel

Bootloader 加载到内核后,内核会在 ramdisk 中装载(详见《Linux 内核简介》),并进行自身初始化。

初始化流程为:

  1. 探测可识别到的所有硬件设备;
  2. 加载硬件驱动程序;(有可能会借助于 ramdisk 加载驱动);
  3. 以只读方式挂载根文件系统;(确保启动没问题后,再改为读写);
  4. 运行用户空间的第一个应用程序 /sbin/init 进行系统初始化;

一旦内核借助于 ramdisk 提供的临时根完成加载真正的根文件系统所在的设备,下一步就装载根文件系统,内核会自动把根文件系统所在的设备挂载至根上,所以说根是在内核中就是这个原因所在。

在挂载根文件系统时为了避免内核中有 bug 或操作过程中有 bug 导致根文件系统被损坏,先只读挂载根文件系统,加载完成后才读写挂载,完成整个挂载根文件系统后,直接去找 /sbin/init 程序,即开始运行用户空间的第一个程序。

至此,内核级别的启动流程就完成了, /sbin/init 会接手接下来的系统启动流程。

第五步:系统初始化( /sbin/init

简介

init 程序的类型分为三种, SysV initUpstartSystemdCentOS 5 及之前的系统用的是 SysV init ,而 CentOS 6 用的是 Upstart ,现在 CentOS 7 用的为 Systemd

运行级别

init 为了系统的运行或维护等目的,设定了 7 个运行级别:

  1. 0 :关机, shutdown ;
  2. 1 :单用户模式( single user ), root 用户登录,无须认证,属于维护模式;
  3. 2 :多用户模式( multi user ),会启动网络功能,但不会启动 NFS ,属于维护模式;
  4. 3 :多用户模式( mutli user ),完全功能模式,文本界面;
  5. 4 :预留级别:目前无特别使用目的,但习惯以同 3 级别功能使用;
  6. 5 :多用户模式( multi user ),完全功能模式,图形界面;
  7. 6 :重启, reboot ;

默认级别: 35

级别切换: init n

级别查看: who -rrunlevel

配置文件

各类型的 init 配置文件存放路径为:

  1. SysV init/etc/inittab
  2. Upstart/etc/inittab/etc/init/*.conf
  3. Systemd/usr/lib/systemd/system//etc/systemd/system/

以当前使用比较广泛的 /etc/inittab 和文件为例,inittab 的配置格式为:

每行定义一种 action 以及与之对应的 process 。

1
2
3
4
5
6
7
8
9
10
id:runlevels:action:process

# id :一个任务的标识符;
# runlevels :在哪些级别启动此任务;#,###,也可以为空,表示所有级别;
# action :在什么条件下启动此任务;
# wait :等待切换至此任务所在的级别时执行一次;
# respawn :一旦此任务终止,就自动重新启动之;
# initdefault :设定默认运行级别;此时, process 省略;
# sysinit :设定系统初始化方式,此处一般为指定 /etc/rc.d/rc.sysinit 脚本(此脚本主要用来完成整个系统初始化);
# process :任务;

例如:

1
2
3
4
5
6
7
8
9
10
11
# 设定默认运行级别为 3
id:3:initdefault:

# 设定系统初始化方式
si::sysinit:/etc/rc.d/rc.sysinit

# 每个运行级别切换时,执行对应的 rc 脚本
l0:0:wait:/etc/rc.d/rc 0
l1:1:wait:/etc/rc.d/rc 1
...
l6:6:wait:/etc/rc.d/rc 6

/etc/rc.d/rc.sysinit

rc.sysinit 是系统初始化脚本,由 init 进行优先调用以进行系统初始化,其主要实现的功能为:

  1. 设置主机名;

  2. 设置欢迎信息;

  3. 激活 udev 和 selinux ;

  4. 挂载 /etc/fstab 文件中定义的所有文件系统;

  5. 检测根文件系统,并以读写方式重新挂载根文件系统;

  6. 设置系统时钟;

  7. 根据 /etc/sysctl.conf 文件来设置内核参数;

  8. 激活 lvm 及软 raid 设备;

  9. 激活 swap 设备;

  10. 加载额外设备的驱动程序;

  11. 清理操作;

chkconfig

很多应用在安装时,都会生成一个服务脚本到 /etc/init.d/ 目录下,以便使用 service 命令进行控制。

chkconfig ,就是管控 /etc/init.d/ 下每个服务脚本在各级别下的启动或关闭状态的命令。

能被添加进自启动服务的服务脚本,需要在开头添加 chkconfig 定义,比如:

1
2
3
4
5
#!/bin/sh
# chkconfig: 2345 80 05
# 2345 为启动 level
# 80 为启动优先权; 05 为停止优先权
# description: Starts and Stops the tomcatSvr daemon.

定义好的服务脚本,在执行 chkconfig name [on|off] 时,会按脚本内的启动 level ,创建超链接到各自的 /etc/rc.d/rc${level}.d/ 目录中。( K 开头为要停止的服务,后面的数字为优先级,数字越小,越是优先关闭,依赖的服务先关闭,而后关闭被依赖的; S 开头的为要启动的服务,后面的数字为优先级,数字越小,越是优先启动,被依赖的服务先启动,而依赖的服务后启动。)

chkconfig 常用参数:

1
2
3
4
5
6
7
8
9
10
11
# 查看
chkconfig --list

# 让服务受 chkconfig 管理(添加管理后, chkconfig 可对 level 进行修改)
chkconfig --add name

#chkconfig 管理中删除服务
chkconfig --del name

# 管理服务
chkconfig [--level <levels>] [--type <type>] <name> <on|off|reset|resetpriorities>

第六步:启动应用服务( /etc/rc.d/rc

通过前面的说明可以知道, /etc/rc.d/rc${level}.d/ 目录中存放了各种需要在不同的 level 下需要启动或关闭的服务脚本超链接,在系统初始化结束后,便会调用 /etc/rc.d/rc 脚本,带入运行级别,去执行各 level 目录下的脚本。

其脚本实现框架为:

1
2
3
4
5
6
7
8
for  srv  in  /etc/rc.d/rc${level}.d/K*; do
$srv stop
done
for srv in /etc/rc.d/rc${level}.d/S*; do
$srv start
done

# 其中 level 参数为外部带入。

正常级别下(2345),系统执行完 /etc/rc.d/rc${level}.d/ 目录下的脚本后,都会去执行 /etc/rc.d/rc.local 脚本,因此,不便或不需写为服务脚本的程序期望能开机自动运行时,直接放置于此脚本文件中即可。

1
2
3
4
5
6
[hzz@magedu ~]$ find /etc/rc.d/ -name "*local" -type l -exec ls -l {} \; 
lrwxrwxrwx. 1 root root 11 824 09:59 /etc/rc.d/rc5.d/S99local -> ../rc.local
lrwxrwxrwx. 1 root root 11 824 09:59 /etc/rc.d/rc2.d/S99local -> ../rc.local
lrwxrwxrwx. 1 root root 11 824 09:59 /etc/rc.d/rc3.d/S99local -> ../rc.local
lrwxrwxrwx. 1 root root 11 824 09:59 /etc/rc.d/rc4.d/S99local -> ../rc.local
[hzz@magedu ~]$

第七步:启动终端

系统初始化完成后, init 给出用户登录提示符( login )或者图形化登录界面,用户输入用户和密码登陆后,系统会为用户分配一个用户 ID ( uid )和组 ID ( gid ),这两个 ID 是用户的身份标识,用于检测用户运行程序时的身份验证。登录成功后,整个系统启动流程运行完毕!

其他

前面所说的初始化,大多是以 SysV init 为蓝本的,因为其兼容性最高,但也说到 CentOS 6/7 使用的是完全不同的机制,现在简要说说区别。

CentOS 6 :

使用的 init 程序是 upstart ,但其执行脚本依然命名为 /etc/init

其配置文件: /etc/init/*.conf*.confupstart 风格的配置文件), /etc/inittab (仅用于定义默认运行级别)。

CentOS 7 :

init 程序为 systemd

配置文件: /usr/lib/systemd/system//etc/systemd/system/

虽然 init 程序和配置文件已经是完全不同的两种风格了,但是完全兼容 SysV 脚本机制。

因此, service 命令依然可用,不过建议使用 systemctl 命令来控制服务:

1
systemctl {start|stop|restart|status} name[.service]