Linux 系统启动过程在不断演进和变化,从最初的 SysV init,到如今的 Systemd 初始化进程,Linux 早已发生了翻天覆地的变化。旧时的 SysV init 启动方式已逐渐退出历史舞台,Upstart 也曾大行其道,作为后起之秀的 Systemd 发展迅速,并在 2020 年成为 Linux 启动的主流 init。
但是,经典之所以经典,是因为它的功能虽然被取代,但是包含的思想却永远不会过时,后续的启动方案,都会有它的影子。因此,我们先从 SysV init 入手,讲解最经典的 Linux 启动模型,在此基础上,讲述 Systemd 带来的革新和变化。
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]
计算机打开电源,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.img
。initrd 是一个内存中的磁盘结构(ramdisk),其中包含必要的工具和脚本,用于在将控制权交给根文件系统上的 init 应用程序之前挂载所需的文件系统。 这样可以完成对硬盘驱动、文件系统的初始化。
内核初始化完成之后,启动的第一个进程是 /sbin/init
,早期的 Linux 中,该文件就是一个可执行程序,即我们所说的 SysV init。
而后来,随着 Linux 的发展,该文件是一个链接文件,指向真正的初始化进程 init。不同时代,init 指向的进程有所不同,如今,init 指向 /lib/systemd/systemd
/sbin/init
最主要的功能就是准备软件执行的环境,包括系统的主机名、网络设定、语言、文件系统格式及其他服务的启动等。对于经典模型 SysV init 程序首先是需要读取配置文件 /etc/inittab
,设置运行级别,启动相应的开机程序。
许多程序需要开机启动,Windows 中称之为 Service(服务),在 Linux 中叫做 daemon(守护进程)。init 进程一个主要的任务就是运行开机启动程序。Linux 会在不同场合启动不同的开机启动程序,这就诞生了运行级别的概念,即 run level。不同的运行级别对应不同的启动程序,不同的运行级别在不同的 Linux 发行版定义有所差距,在此指出一些通用的说法。
/etc/inittab
文件描述了系统各种预订的运行级别。通常会有 8 种运行模式,即运行模式 0 到 6 和 S 或者 s,rcS 单用户模式启动脚本。每个运行级别都有一个文件目录对应 /etc/rcN.d
,该文件目录下有若干启动程序
rc 是 run command 的缩写,即运行命令;d 是 directory 的缩写,即目录;不同运行级的目录包含了需要启动的命令,K 是 Kill,即杀死该进程;S 是 Start,即要启动的程序。由于不同运行级别可能会启动一些相同的程序,为了避免资源浪费,rcN.d 目录下的启动程序使用链接的方式,链接到 /etc/initd/ 目录下的程序。也就是说,/etc/initd/ 才是存放开启启动程序的真实路径。
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程序来验证用户的身份。
用户登录的方式有以下三种
命令行登录:init 进程调用 getty 程序,让用户输入用户名和密码。输入完成之后,调用 login 程序,如果账号和密码正确,就从 /etc/passwd 读取该用户对应的 shell,启动这个 shell。
SSH 登录:远程调用系统 sshd 守护进程,使用 ssh 服务进行登录
图形界面登录:init 调用显示管理器,GNOME 桌面对应的图形管理器是 GDM,如果账号和密码正确,就读取 /etc/gdm3/Xsession,启动用户会话。
shell,就是用户最终可以与操作系统进行交互的终端。登录后打开的 shell,即 login shell。shell 有很多种,Debian 默认的 shell 是 Bash,Ubuntu 属于 Debian 系,自然也不例外。用户登录后有一个重要的操作,就是读取 /etc/profile
,这是一个重要的文件,主要是系统的环境变量设置。
Linux是一个多用户的操作系统。每个用户登录系统后,都会有一个专用的运行环境。/etc/profile
另外一个作用就是,可以存放用户自己的开机启动程序,一些嵌入式设备的后门可能也会存放在此文件中,注意甄别。
上一章主要说明了 Linux 的传统启动过程,其中也穿插讲解了 init 新的模式。传统的 SysV init 方案,具备简洁清晰的特点,但是,高度依赖 shell 的特性,导致启动过程较慢。
在 Linux 主要应用于服务器和 PC 机的时代,这些都不是问题,因为服务器较为稳定,也不需要经常重启服务器或者某些进程。但是后来,随着嵌入式设备的发展,Linux 更多的用在单板、移动终端设备上,可能会经常性重启,那么 SysV init 带来的启动速度慢成为了一个瓶颈。
为了更加快速的启动系统,Linux 研究人员开始改进启动方式,先后出现了 Upstart 和 Systemd 这两个主要的新一代 init 系统。
/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/ 目录下。
对于 Upstart 的 init,初始化的时候会读取 rc-sysinit.conf
并执行相关配置和脚本,其主要作用是设置系统默认runlevel
(运行级别)
由代码/etc/init.d/rc $RUNLEVEL 可看出 /etc/init.d/rc 接受参数 $RUNLEVE L来调用 /etc/rc\${runlevel}.d/
下的脚本
/etc/systemd/system/ 包含了许多开机启动配置文件,其中,default.target.wants 是默认的配置文件,如下图所示
Systemd 初始化进程较为复杂,不再是传统的运行级别,而是提出了 Target 的概念。传统 init 中的 RumLevel
是互斥的,即一种运行级别不可以同时出现,一个用户不能同时启动多个运行级别;而多个 Target 可以同时启动。借助于命令手册,我们可以看到更加直观的说明
man runlevel
可以看到,不同的 Runlevel 对应不同的 targets
虽然新版本 Ubuntu 使用了 Systemd 作为启动方式,但 Ubuntu 仍然保留了大部分 system V init 的外部文件结构 ,如 runlevels 和 /etc/rc?.d 目录, 仅是为了兼容性。
启动方式的变化,又带来了什么样的优缺点呢?
SysV init 的优点
SysV init
运行时是同步阻塞的。一个脚本运行的时候,后续脚本必须等待。这意味着所有的初始化步骤都是串行执行的,而实际上很多服务彼此并不相关,完全可以并行启动,从而减小系统的启动时间。
当 Linux 内核进入 2.6 时代时,内核功能有了很多新的更新。新特性使得 Linux 不仅是一款优秀的服务器操作系统,也可以被用于桌面系统,甚至嵌入式设备。桌面系统或便携式设备的一个特点是经常重启,而且要频繁地使用硬件热插拔技术。
SysV init 弊端
Upstart 优点
UpStart
解决了之前提到的 SysV init 的缺点。采用事件驱动模型
事件驱动,即事件在 Upstart 中以通知消息的形式具体存在。一旦某个事件发生了,Upstart 就向整个系统发送一个消息。没有任何手段阻止事件消息被 upstart 的其它部分知晓,也就是说,事件一旦发生,整个 Upstart 系统中所有工作和其它的事件都会得到通知。
Systemd 特点
Systemd
提供了比 UpStart 更激进的并行启动能力,采用了 socket / D-Bus activation 等技术启动服务。一个显而易见的结果就是:更快的启动速度。但是缺点也显而易见,体系庞大,结构臃肿复杂。
那么 Systemd 对 SysV init 的改进更为激进
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