沧海桑田:Linux 系统启动的演进模型与发展变化

Linux 系统启动过程在不断演进和变化,从最初的 SysV init,到如今的 Systemd 初始化进程,Linux 早已发生了翻天覆地的变化。旧时的 SysV init 启动方式已逐渐退出历史舞台,Upstart 也曾大行其道,作为后起之秀的 Systemd 发展迅速,并在 2020 年成为 Linux 启动的主流 init。

但是,经典之所以经典,是因为它的功能虽然被取代,但是包含的思想却永远不会过时,后续的启动方案,都会有它的影子。因此,我们先从 SysV init 入手,讲解最经典的 Linux 启动模型,在此基础上,讲述 Systemd 带来的革新和变化。

0x10 Linux 启动过程

SysV init,即 System V 风格的 init 程序。什么是 System V 呢?System V,曾经也被称为 AT&T System V,是 Unix 操作系统众多版本中的一支 什么是 init 呢?启动一个操作系统,通常从 BIOS(固化在主板上的程序)开始,进入 bootloader,由 bootloader 载入内核,内核进行初始化操作。内核初始化的最后一步就是启动 pid = 1 的 init 进程。因此,init 是系统启动的第一个进行,这一点可以使用 ps -aux/ef 命令得到证实。

# ps -aux
USER        PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root          1  0.0  0.2 160216  5920 ?        Ss   5月11   0:07 /sbin/init splash
root          2  0.0  0.0      0     0 ?        S    5月11   0:00 [kthreadd]

沧海桑田:Linux 系统启动的演进模型与发展变化_第1张图片

图1 SysV 经典 Linux 启动流程

0x11 引导内核

计算机打开电源,BIOS 开机自检,根据 BIOS 的开机启动顺序,启动引导盘,通常是我们的硬盘。硬盘上的操作系统接管硬件之后,首先进入根文件系统的 /boot 目录

root@lys-virtual-machine:/boot# ls -lh
total 107M
-rw-r--r-- 1 root root 231K 1月  16 02:10 config-5.3.0-28-generic
-rw-r--r-- 1 root root 231K 3月  31 10:40 config-5.3.0-46-generic
drwxr-xr-x 5 root root 4.0K 4月  16 09:13 grub
-rw-r--r-- 1 root root  40M 4月  16 09:11 initrd.img-5.3.0-28-generic
-rw-r--r-- 1 root root  40M 4月  16 09:13 initrd.img-5.3.0-46-generic
-rw-r--r-- 1 root root 179K 1月  28  2016 memtest86+.bin
-rw-r--r-- 1 root root 181K 1月  28  2016 memtest86+.elf
-rw-r--r-- 1 root root 181K 1月  28  2016 memtest86+_multiboot.bin
-rw------- 1 root root 4.3M 1月  16 02:10 System.map-5.3.0-28-generic
-rw------- 1 root root 4.3M 3月  31 10:40 System.map-5.3.0-46-generic
-rw-r--r-- 1 root root 8.8M 2月   4 02:37 vmlinuz-5.3.0-28-generic
-rw------- 1 root root 8.8M 3月  31 10:43 vmlinuz-5.3.0-46-generic

启动设备上面安装的 grub 开始引导 Linux,grub 会引导加载初始文件系统,如 initramfs / initrd.imginitrd 是一个内存中的磁盘结构(ramdisk),其中包含必要的工具和脚本,用于在将控制权交给根文件系统上的 init 应用程序之前挂载所需的文件系统。 这样可以完成对硬盘驱动、文件系统的初始化。

0x12 启动初始化进程

内核初始化完成之后,启动的第一个进程是 /sbin/init早期的 Linux 中,该文件就是一个可执行程序,即我们所说的 SysV init

在这里插入图片描述

图 2 Ubuntu 10.10 /sbin/int - SysV init程序

而后来,随着 Linux 的发展,该文件是一个链接文件,指向真正的初始化进程 init。不同时代,init 指向的进程有所不同,如今,init 指向 /lib/systemd/systemd

在这里插入图片描述

图 3 Ubuntu 18.0.4 /sbin/int - 链接程序

/sbin/init 最主要的功能就是准备软件执行的环境,包括系统的主机名、网络设定、语言、文件系统格式及其他服务的启动等。对于经典模型 SysV init 程序首先是需要读取配置文件 /etc/inittab,设置运行级别,启动相应的开机程序。

运行级别说明

许多程序需要开机启动,Windows 中称之为 Service(服务),在 Linux 中叫做 daemon(守护进程)。init 进程一个主要的任务就是运行开机启动程序。Linux 会在不同场合启动不同的开机启动程序,这就诞生了运行级别的概念,即 run level。不同的运行级别对应不同的启动程序,不同的运行级别在不同的 Linux 发行版定义有所差距,在此指出一些通用的说法。

  • 0:系统停机状态,系统默认运行级别不能设为0,否则不能正常启动
  • 1:单用户工作状态,root权限,用于系统维护,禁止远程登陆
  • 2:多用户状态(没有NFS)
  • 3:完全的多用户状态(有NFS),登陆后进入控制台命令行模式
  • 4:系统未使用,保留
  • 5:X11控制台,登陆后进入图形GUI模式
  • 6:系统正常关闭并重启,默认运行级别不能设为6,否则不能正常启动

/etc/inittab 文件描述了系统各种预订的运行级别。通常会有 8 种运行模式,即运行模式 0 到 6 和 S 或者 s,rcS 单用户模式启动脚本。每个运行级别都有一个文件目录对应 /etc/rcN.d,该文件目录下有若干启动程序

沧海桑田:Linux 系统启动的演进模型与发展变化_第2张图片

图 4 运行级别目录

rc 是 run command 的缩写,即运行命令;d 是 directory 的缩写,即目录;不同运行级的目录包含了需要启动的命令,K 是 Kill,即杀死该进程;S 是 Start,即要启动的程序。由于不同运行级别可能会启动一些相同的程序,为了避免资源浪费,rcN.d 目录下的启动程序使用链接的方式,链接到 /etc/initd/ 目录下的程序也就是说,/etc/initd/ 才是存放开启启动程序的真实路径。

在这里插入图片描述

图 5 开机启动程序链接文件

0x13 建立终端

rc 执行完毕后,返回 init。这时基本系统环境已经设置好了,守护进程也已经启动了。接下来是 init 进程会根据 inittab 里的最后几行语句打开 6 个对应的终端

1:2345:respawn:/sbin/mingetty tty1
2:2345:respawn:/sbin/mingetty tty2
3:2345:respawn:/sbin/mingetty tty3
4:2345:respawn:/sbin/mingetty tty4
5:2345:respawn:/sbin/mingetty tty5
6:2345:respawn:/sbin/mingetty tty6

从上面可以看出在2、3、4、5的运行级别中都将以respawn方式运行mingetty程序,mingetty程序能打开终端、设置模式。

同时它会显示一个文本登录界面,这个界面就是我们经常看到的登录界面,在这个登录界面中会提示用户输入用户名,而用户输入的用户将作为参数传给login程序来验证用户的身份。

0x14 用户登录

用户登录的方式有以下三种

  • 命令行登录
  • SSH 登录
  • 图形界面登录

命令行登录:init 进程调用 getty 程序,让用户输入用户名和密码。输入完成之后,调用 login 程序,如果账号和密码正确,就从 /etc/passwd 读取该用户对应的 shell,启动这个 shell。

SSH 登录:远程调用系统 sshd 守护进程,使用 ssh 服务进行登录

图形界面登录:init 调用显示管理器,GNOME 桌面对应的图形管理器是 GDM,如果账号和密码正确,就读取 /etc/gdm3/Xsession,启动用户会话。

0x15 进入 login shell

shell,就是用户最终可以与操作系统进行交互的终端。登录后打开的 shell,即 login shell。shell 有很多种,Debian 默认的 shell 是 Bash,Ubuntu 属于 Debian 系,自然也不例外。用户登录后有一个重要的操作,就是读取 /etc/profile,这是一个重要的文件,主要是系统的环境变量设置。

Linux是一个多用户的操作系统。每个用户登录系统后,都会有一个专用的运行环境。/etc/profile 另外一个作用就是,可以存放用户自己的开机启动程序,一些嵌入式设备的后门可能也会存放在此文件中,注意甄别


0x20 变化

上一章主要说明了 Linux 的传统启动过程,其中也穿插讲解了 init 新的模式。传统的 SysV init 方案,具备简洁清晰的特点,但是,高度依赖 shell 的特性,导致启动过程较慢。

在 Linux 主要应用于服务器和 PC 机的时代,这些都不是问题,因为服务器较为稳定,也不需要经常重启服务器或者某些进程。但是后来,随着嵌入式设备的发展,Linux 更多的用在单板、移动终端设备上,可能会经常性重启,那么 SysV init 带来的启动速度慢成为了一个瓶颈。

为了更加快速的启动系统,Linux 研究人员开始改进启动方式,先后出现了 Upstart 和 Systemd 这两个主要的新一代 init 系统。

0x21 SysV init 读取 /etc/inittab 文件,根据里面内容进行初始化

/etc/inittab 文件内容

id:5:initdefault:
l5:5:wait:/etc/rc.d/rc 5
 
# Boot-time system configuration/initialization script.
# This is run first except when booting in emergency (-b) mode.
#首次执行的脚本,si 是系统初始化的进程
si::sysinit:/etc/init.d/rcS
 
# What to do in single-user mode.
~~:S:wait:/sbin/sulogin
 
 
# /etc/init.d executes the S and K scripts upon change
# of runlevel.
#
# Runlevel 0 is halt.
# Runlevel 1 is single-user.
# Runlevels 2-5 are multi-user.
# Runlevel 6 is reboot.
#每个运行级别对应执行的脚本
l0:0:wait:/etc/init.d/rc 0
l1:1:wait:/etc/init.d/rc 1
l2:2:wait:/etc/init.d/rc 2
l3:3:wait:/etc/init.d/rc 3
l4:4:wait:/etc/init.d/rc 4
l5:5:wait:/etc/init.d/rc 5

l5:5:wait:/etc/rc.d/rc 5 表示以5为参数运行 /etc/rc.d/rc,/etc/rc.d/rc是一个 Shell 脚本,它接受 5 作为参数,去执行 /etc/rc.d/rc5.d/ 目录下的所有的rc启动脚本,/etc/rc.d/rc5.d/ 目录中的这些启动脚本实际上都是一些连接文件,而不是真正的 rc 启动脚本,真正的 rc 启动脚本实际上都是放在 /etc/rc.d/init.d/ 目录下。

0x22 Upstart 读取 /etc/init/rc-sysinit.conf 文件,进行初始化

对于 Upstart 的 init,初始化的时候会读取 rc-sysinit.conf 并执行相关配置和脚本,其主要作用是设置系统默认runlevel(运行级别)

沧海桑田:Linux 系统启动的演进模型与发展变化_第3张图片

图 6 Ubuntu 10.10 初始化进程的配置文件

由代码/etc/init.d/rc $RUNLEVEL 可看出 /etc/init.d/rc 接受参数 $RUNLEVE L来调用 /etc/rc\${runlevel}.d/ 下的脚本

0x23 Systemd 从 /etc/systemd/system/ 读取配置文件

/etc/systemd/system/ 包含了许多开机启动配置文件,其中,default.target.wants 是默认的配置文件,如下图所示

沧海桑田:Linux 系统启动的演进模型与发展变化_第4张图片

图 7 Systemd 默认配置文件

Systemd 初始化进程较为复杂,不再是传统的运行级别,而是提出了 Target 的概念。传统 init 中的 RumLevel 是互斥的,即一种运行级别不可以同时出现,一个用户不能同时启动多个运行级别;而多个 Target 可以同时启动。借助于命令手册,我们可以看到更加直观的说明

man runlevel

可以看到,不同的 Runlevel 对应不同的 targets
沧海桑田:Linux 系统启动的演进模型与发展变化_第5张图片

图 8 Systemd 中的 Target 与传统运行级别的对应关系

虽然新版本 Ubuntu 使用了 Systemd 作为启动方式,但 Ubuntu 仍然保留了大部分 system V init 的外部文件结构 ,如 runlevels 和 /etc/rc?.d 目录, 仅是为了兼容性。

启动方式的变化,又带来了什么样的优缺点呢?

0x30 对比分析

SysV init 的优点

  1. 概念简单。Service 开发人员只需要编写启动和停止脚本,概念非常清楚;将 service 添加/删除到某个 runlevel 时,只需要执行一些创建/删除软连接文件的基本操作;这些都不需要学习额外的知识或特殊的定义语法(UpStart 和 Systemd 都需要用户学习新的定义系统初始化行为的语言)。
  2. 确定的执行顺序:脚本严格按照启动数字的大小顺序执行,一个执行完毕再执行下一个,这非常有益于错误排查。UpStart 和 Systemd 支持并发启动,导致没有人可以确定地了解具体的启动顺序,排错不易。

SysV init 运行时是同步阻塞的。一个脚本运行的时候,后续脚本必须等待。这意味着所有的初始化步骤都是串行执行的,而实际上很多服务彼此并不相关,完全可以并行启动,从而减小系统的启动时间。

当 Linux 内核进入 2.6 时代时,内核功能有了很多新的更新。新特性使得 Linux 不仅是一款优秀的服务器操作系统,也可以被用于桌面系统,甚至嵌入式设备。桌面系统或便携式设备的一个特点是经常重启,而且要频繁地使用硬件热插拔技术。

SysV init 弊端

  1. SysV init 同步阻塞,导致开机速度慢
  2. SysV init 根据不同的运行级别加载不同的启动脚本,但是当一个启动设备(如打印机)没有连接到系统时,init 程序仍然会启动相应的服务(打印机服务)
  3. 新的硬件发现,不能及时启动服务

Upstart 优点

UpStart 解决了之前提到的 SysV init 的缺点。采用事件驱动模型

  1. 更快地启动系统
  2. 当新硬件被发现时动态启动服务
  3. 硬件被拔除时动态停止服务

沧海桑田:Linux 系统启动的演进模型与发展变化_第6张图片

图 9 UpStart 启动流程

事件驱动,即事件在 Upstart 中以通知消息的形式具体存在。一旦某个事件发生了,Upstart 就向整个系统发送一个消息。没有任何手段阻止事件消息被 upstart 的其它部分知晓,也就是说,事件一旦发生,整个 Upstart 系统中所有工作和其它的事件都会得到通知。

Systemd 特点

Systemd 提供了比 UpStart 更激进的并行启动能力,采用了 socket / D-Bus activation 等技术启动服务。一个显而易见的结果就是:更快的启动速度。但是缺点也显而易见,体系庞大,结构臃肿复杂

沧海桑田:Linux 系统启动的演进模型与发展变化_第7张图片

图 10 Systemd 架构

如果说 UpStart 对 SysV init 的改进如下
沧海桑田:Linux 系统启动的演进模型与发展变化_第8张图片

图 11 UpStart 改进部分

UpStart 采用事件驱动机制,不需要的服务可以暂时不启动,当需要的时候才通过事件触发其启动;不相干的服务可以并行启动

那么 Systemd 对 SysV init 的改进更为激进
在这里插入图片描述

图 12 Systemd 改进部分

Systemd 能够更进一步提高并发性,即便对于那些 UpStart 认为存在相互依赖而必须串行的服务,比如 Avahi 和 D-Bus 也可以并发启动。如上图所示,总的启动时间进一步降低。但是 Systemd 原理和启动过程更为复杂,耦合度高。

0x40 总结

Linux 历经多年的发展,日臻完善。内核的代码量不断增加,启动流程也由最初的脚本式顺序执行,变为事件驱动且可以多个脚本并行执行,大大提高系统启动的速度。本文主要讲述经典的 Linux 系统启动流程,附加了近些年来的变化,掌握这些流程可以加深对 Linux 操作系统的理解,也能帮助我们构建一个更加完整的有关 Linux 内核的知识体系。


参考文献

[1] Ask Ubuntu:Where is the inittab file
[2] ArchLinux:SysVinit(简体中文)
[3] 菜鸟教程:Linux 系统启动过程
[4] 阮一峰:Systemd 入门教程
[5] 阮一峰:Linux 的启动流程
[6] 知乎:Linux 启动过程分析
[7] oschina:浅析 Linux 初始化 init 系统:sysvinit
[8] 简书:linux学习之系统启动过程
[9] CSDN:Linux 启动过程分析 (SysV init启动模式)
[10] IBM:浅析 Linux 初始化 init 系统,第 1 部分: sysvinit
[11] IBM:浅析Linux 初始化 init 系统,第 2 部分: UpStart
[12] IBM:浅析 Linux 初始化 init 系统,第 3 部分: Systemd

你可能感兴趣的:(操作系统与内核,嵌入式与Linux,C)