内核启动 init(第一个用户空间进程)的时刻意义重大--不仅仅是因为内存和 CPU 终于可以正常运行系统了,还因为在这个时刻,你可以看到系统的其他部分是如何作为一个整体建立起来的。在此之前,内核的执行路径都是由相对较少的软件开发人员定义好的。用户空间的模块化和可定制程度要高得多,而且也很容易看到用户空间的启动和运行过程。如果你喜欢冒险,可以利用这一点,因为了解和改变用户空间的启动不需要底层编程。
用户空间的启动顺序大致如下
init
基本的底层服务,如 udevd 和 syslogd
网络配置
中高级服务(cron、打印等)
登录提示、图形用户界面和高级应用程序,如网络服务器
6.1 init 简介
init和Linux系统中的其他程序一样,是一个用户空间程序,你可以在/sbin中找到它和许多其他系统二进制文件。它的主要用途是启动和停止系统中的基本服务进程。
在当前所有主流 Linux 发行版中,init 的标准实现是 systemd。本章主要介绍 systemd 的工作原理以及如何与之交互。
在旧版系统中,你可能会遇到另外两种 init。System V init 是一种传统的序列化 init(Sys V,通常发音为 “sys-five”,源于 Unix System V),存在于 7.0 版之前的 Red Hat Enterprise Linux (RHEL) 和 Debian 8 上。 Upstart 是 15.04 版之前的 Ubuntu 发行版上的 init。
还有其他版本的 init,尤其是在嵌入式平台上。例如,Android 有自己的 init,而名为 runit 的版本在轻量级系统上很流行。BSD 也有自己的 init 版本,但你不太可能在当代 Linux 机器上看到它们。(有些发行版还修改了 System V 的 init 配置,使其与 BSD 风格相似)。
为了解决 System V init 的几个缺陷,人们开发了不同的 init 实现。要了解这些问题,请先看一下传统 init 的内部工作原理。基本上,init 会依次运行一系列脚本,一次一个。每个脚本通常会启动一个服务或配置系统的一个单独部分。在大多数情况下,解决依赖关系相对容易,而且可以通过修改脚本灵活地满足不寻常的启动要求。
不过,这种方案也有一些明显的局限性。这些限制可归纳为 “性能问题 ”和 “系统管理麻烦”。其中最重要的问题如下:
systemd 的总体目标是简化依赖顺序并加快启动时间。套接字单元等资源单元提供了一种与按需启动类似的方法。我们仍然会有一个服务单元和一个代表服务单元所提供资源的辅助单元,只不过在这种情况下,systemd 会在激活辅助单元后立即启动服务单元,而不是等待请求。
采用这种方案的原因是,systemd-journald.service 等必要的启动时服务单元需要一些时间才能启动,而且许多其他单元也依赖于它们。但是,systemd 可以快速提供一个单元(如套接字单元)的基本资源,然后它不仅可以立即激活该基本单元,还可以立即激活依赖于它的任何单元。一旦基本单元准备就绪,它就会控制该资源。
图 6-2 显示了这在传统的顺序系统中是如何工作的。在此启动时间线中,服务 E 提供了基本资源 R。服务 A、B 和 C 依赖于此资源(但不相互依赖),必须等待服务 E 启动。由于系统在启动前一个服务之前不会启动新的服务,因此需要很长时间才能启动服务 C。
图 6-3 显示了一个可能的等效 systemd 启动配置。服务由单元 A、B、C 和 E 代表,新单元 R 代表单元 E 提供的资源。由于 systemd 可以在单元 E 启动时为单元 R 提供接口,因此单元 A、B、C 和 E 可以同时启动。有趣的是,单元 A、B 或 C 在完成启动前可能并不需要访问单元 R 提供的资源。我们要做的是为它们提供尽快开始访问资源的选项。
如果深入研究,你会发现其中一些程序是相对简单的封装程序。它们的功能是运行标准的系统实用程序,并将结果通知 systemd。systemd-fsck 就是一个例子。
如果你在 /lib/systemd 中看到一个无法识别的程序,请查看手册页面。很有可能它不仅描述了该实用程序,还描述了它要增强的单元类型。
6.4 系统 V 运行级别
在了解了 systemd 及其工作原理之后,让我们换个角度来看看传统的 System V init 的某些方面。在 Linux 系统中,任何时候都有一组基本进程(如 crond 和 udevd)在运行。在 System V init 中,机器的这种状态称为运行级别,用 0 到 6 之间的数字表示。系统大部分时间都处于一个运行级别,但当你关闭机器时,init 会切换到不同的运行级别,以便有序地终止系统服务,并告诉内核停止运行。
你可以使用 who -r 命令检查系统的运行级别,如下所示:
# who -r
run-level 3 2024-06-28 20:21
复制代码
输出结果会告诉我们当前的运行级别是 3,以及建立运行级别的日期和时间。
运行级别有多种用途,但最常见的用途是区分系统启动、关机、单用户模式和控制台模式状态。例如,传统上大多数系统的文本控制台使用运行级别 2 至 4;运行级别 5 表示系统启动图形用户界面登录。
但运行级别已成为过去式。尽管 systemd 支持运行级别,但它认为运行级别作为系统的结束状态已经过时,而更倾向于目标单元。对于 systemd 来说,运行级别主要是用来启动只支持 System V init 脚本的服务。
6.5 System V init
System V init 是 Linux 上最古老的实现之一,其核心理念是通过精心设计的启动顺序,支持有序启动到不同的运行级别。System V init 现在在大多数服务器和桌面安装中已不常见,但在 7.0 版之前的 RHEL 版本以及嵌入式 Linux 环境(如路由器和电话)中可能会遇到。此外,一些旧软件包可能只提供为 System V init 设计的启动脚本;systemd 可以通过兼容性模式处理这些脚本,我们将在第 6.5.5 节中讨论。我们将在此介绍一些基础知识,但请记住,你可能不会遇到本节所涉及的任何问题, 所以以下内容省略。
6.6 关闭系统
init 控制着系统关闭和重启的方式。无论运行哪个版本的 init,关闭系统的命令都是一样的。关闭 Linux 机器的正确方法是使用 shutdown 命令。
使用 shutdown 有两种基本方法。如果你停止系统,它就会关闭机器并使其继续运行。要使机器立即停止运行,请运行以下命令:
# shutdown -h now
复制代码
在大多数机器和 Linux 版本中,停止系统会切断机器电源。您也可以重新启动机器。重启时,使用 -r 而不是 -h。
关机过程需要几秒钟。应避免在关机过程中重置或关闭机器电源。
在上例中,现在就是关机时间。包含时间参数是必须的,但也有多种指定方法。例如,如果希望机器在未来某个时间关机,可以使用 +n,其中 n 是关机前应等待的分钟数。有关其他选项,请参阅 shutdown(8) 手册。
要让系统在 10 分钟后重启,请输入
# shutdown -r +10
复制代码
在 Linux 上,shutdown 会通知任何登录的人机器正在宕机,但它几乎不做实际工作。如果指定的时间不是现在,shutdown 命令会创建一个名为 /etc/nologin 的文件。该文件存在时,系统禁止除超级用户以外的任何人登录。
当系统关闭时间最终到来时,shutdown 会告诉 init 开始关闭进程。在 systemd 中,这意味着激活关机单元,而在 System V init 中,这意味着将运行级别更改为 0(停止)或 6(重启)。无论使用哪种 init 实现或配置,程序一般都是这样的:
init 会要求每个进程干净利落地关闭。
当系统出现问题时,你的第一选择通常是使用发行版的 “实时 ”镜像启动系统,或使用专用的应急镜像(如 SystemRescueCD)启动系统。实时镜像是指无需安装程序就能启动和运行的 Linux 系统;大多数发行版的安装镜像都兼具实时镜像的功能。修复系统的常见任务包括以下内容:
系统崩溃后检查文件系统。
重置遗忘的密码。
修复关键文件中的问题,如 /etc/fstab 和 /etc/passwd。
系统崩溃后从备份中恢复。
另一种快速启动到可用状态的方法是单用户模式。其原理是让系统快速启动到 root shell,而不是通过一大堆乱七八糟的服务。在 System V init 中,单用户模式通常是运行级别 1。在 systemd 中,它由 rescue.target 表示。通常使用引导加载程序的 -s 参数进入该模式。可能需要输入 root 密码才能进入单用户模式。
单用户模式的最大问题是它没有提供很多便利。网络几乎肯定不可用(即使可用也很难使用),没有图形用户界面,终端甚至可能无法正常工作。因此,实时图像几乎总是被认为是更好的选择。
6.9 展望未来
现在,你已经了解了 Linux 系统的内核和用户空间启动阶段,以及 systemd 如何在服务启动后对其进行跟踪。接下来,我们将深入研究用户空间。有两个领域需要探索,首先是一些系统配置文件,所有 Linux 程序在与用户空间的某些元素交互时都会用到这些文件。然后,我们将看到 systemd 启动的基本服务。