来源于:干货分享 | Systemd 技术原理&实践(上)干货分享 | Systemd 技术原理&实践(下)

一、systemd 介绍

1 systemd 的起源

关于 systemd 的起源,首先要从 Linux 的 init 程序说起。Linux 系统在启动过程中,内核完成初始化以后,由内核第一个启动的程序便是 init 程序,路径为 /sbin/init(为一个软连接,链接到真实的 init 进程),其 PID 为1,它为系统里所有进程的“祖先”,Linux 中所有的进程都由 init 进程直接或间接进行创建并运行,init 进程以守护进程的方式存在,负责组织与运行系统的相关初始化工作,让系统进入定义好的运行模式,如命令行模式或图形界面模式。

init 程序的发展,大体上可分为三个阶段:sysvinit->upstart->systemd,根据 init 进程的发展特性,可以简单理解为如下:

  • sysvinit:init 系统通过 shell 脚本以串行的方式启动系统服务,下一个进程必须等待上一个进程启动完成后才能开始启动,因此系统启动的过程比较慢。
  • upstart:在 sysvinit 的基础上,把一些没有关联的程序并行启动,以提高启动的速度,但是存在依赖关系的程序仍然为串行启动。
  • systemd:通过套接字激活的机制,让所有无论有无依赖关系的程序全部并行启动,并且仅按照系统启动的需要启动相应的服务,最大化提高开机启动速度。

目前优麒麟操作系统使用的就为 systemd。systemd 的意思为 system daemon,意为系统守护进程,由 Lennart Poettering 带头开发,采用更加优秀的服务框架,并且与老的 sysvinit 兼容,其设计目的就是克服 sysvinit 与 upstart 的缺点,进一步地提高启动速度。目前主流的系统中,systemd 的守护进程主要分为系统态(system)与用户态(user),可以在 ps -ef 中看到 systemd 的守护进程,如下:

$ ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 08:04 ?        00:00:20 /sbin/init splash
root         2     0  0 08:04 ?        00:00:00 [kthreadd]
root         3     2  0 08:04 ?        00:00:00 [rcu_gp]
root         4     2  0 08:04 ?        00:00:00 [rcu_par_gp]
root         6     2  0 08:04 ?        00:00:00 [kworker/0:0H-events_highpri]
root         9     2  0 08:04 ?        00:00:00 [mm_percpu_wq]
root        10     2  0 08:04 ?        00:00:00 [rcu_tasks_kthre]

PID 为1的进程/sbin/init 即是 system 态的 systemd,它为一个软链接,指向真实的 systemd 路径,在优麒麟操作系统中一般放在/lib/systemd/目录:

$ ll /sbin/init 
lrwxrwxrwx 1 root root 20 9月   8 02:37 /sbin/init -> /lib/systemd/systemd*

systemd 为进程服务集合的总称,它包含许多的进程,负责控制、管理系统的资源,其中包括 systemd-login,负责用户登录相关信息的创建、修改与删除;systemd-sleep 控制系统的休眠、睡眠状态切换等等。在优麒麟操作系统下,它们主要集中在/lib/systemd/文件目录:

$ ls /lib/systemd/
boot                            systemd-cryptsetup            systemd-pstore          systemd-udevd
catalog                         systemd-dissect               systemd-quotacheck      systemd-update-utmp
libsystemd-shared-245.so        systemd-fsck                  systemd-random-seed     systemd-user-runtime-dir
logind.conf.d                   systemd-fsckd                 systemd-remount-fs      systemd-user-sessions
network                         systemd-growfs                systemd-reply-password  systemd-veritysetup
ntp-units.d                     systemd-hibernate-resume      systemd-resolved        systemd-volatile-root
resolv.conf                     systemd-hostnamed             systemd-rfkill          system-environment-generators
set-cpufreq                     systemd-initctl               systemd-shutdown        system-generators
system                          systemd-journald              systemd-sleep           system-preset
systemd                         systemd-localed               systemd-socket-proxyd   system-shutdown
systemd-ac-power                systemd-logind                systemd-sulogin-shell   system-sleep
systemd-backlight               systemd-makefs                systemd-sysctl          user
systemd-binfmt                  systemd-modules-load          systemd-sysv-install    user-environment-generators
systemd-bless-boot              systemd-networkd              systemd-timedated       user-generators
systemd-boot-check-no-failures  systemd-networkd-wait-online  systemd-timesyncd       user-preset
systemd-cgroups-agent           systemd-network-generator     systemd-time-wait-sync

每个进程的主要用途可以阅读 freedesktop systemd 手册:https://www.freedesktop.org/software/systemd/man/

目前 systemd 占据 init 程序的主导,有统一天下的趋势。

2 systemd 的主要功能

systemd 采用并行的启动方式,并提供按需启动的方式:systemd 在设计之初最关注两件事情:更少的开始,更多的并行。更少的开始,意味着在开机启动阶段,systemd 仅启动系统启动时必要的一些服务,更多其他的服务推迟启动,直到真正需要它的时候,例如优麒麟的蓝牙 bluetooth 与截图相关的服务,开机的时候系统不会用到它;优麒麟的 U 盘启动器相关的服务,可以等到接入 U 盘的时候再启动;如果系统未连接到网络,那那些需要用到网络的相关服务也可以无需启动,直到网络连通后的第一次连接再启动即可。更多的并行,意味着服务的启动不需要像 sysvinit 一样序列化启动,而是同时运行所有需要的服务,以便系统 cpu 资源利用的最大化,因此总的启动时间最小化,后面会介绍 systemd 是如何实现所有服务并行启动。

采用 cgroup 跟踪管理进程的生命周期:cgroup 为控制组,是一个层级结构,类似与文件管理系统的结构。当一个进程创建了子进程,子进程会继承父进程的 cgroup,就好比子进程创建在父进程的目录下,当子进程又创建一个子进程时,这个子进程会继承上一个子进程的 cgroup,也就相当与继承了父进程的 cgroup,好比这个子进程创建在上一个子进程的目录下,也就在父进程的目录下,通过这一机制就可以把父进程与所有的子进程关联起来并进行跟踪,当停止父进程时,可以通过查询 cgroup 找到所有关联的子进程,从而确保干净的停止所有相关服务。

启动挂载与自动挂载:在系统启动过程中,systemd 在初始化时会自身进行一些挂载,如/sys 目录与/run 目录的挂载,这些都是系统启动时至关重要的文件系统。systemd 还能实现动态挂载点的自动挂载,例如不需要经常使用的光盘、U 盘的挂载,只在这些设备接入时,systemd 启动相应的服务并对其进行临时的挂载以便访问其中的内容,当这些设备拔出时,这些挂载点将被取消以便节约资源。

事务的依赖关系管理:系统有很多的服务存在依赖关系,例如麒麟软件商店需要等待网络服务的启动,lightdm 与 systemd 交互需要等待 D-Bus 的启动,大多数服务也需要等待 syslog 的完全启动与初始化。systemd 采用 Unit(配置单元)管理这些服务的依赖关系,维护一个事务的一致性,并保证所有的相关服务不会出现相互依赖而产生死锁的情况,后面会对 Unit 进行详细介绍。

日志:systemd 自带 journalctl 命令来查看系统保存的所有日志信息,并且可以支持通过一些参数来对日志进行过滤,方便用户进行日志分析。

其他:systemd 经过几代的更新,实现的功能已经十分的多了,甚至有人觉得 systemd 管得太多了,导致一些服务都没有了存在的必要。例如 systemd 添加了许多 systemctl 的命令,可以实现系统电源的管理;systemd 还添加了看门狗机制,其他守护进程需要定期 ping systemd 进程,否则会视为失败而重启它等等。详情可以去阅读设计师的博客 http://0pointer.de/blog/projects。

3 systemd 如何实现服务的并行

systemd 的设计理念就是希望让所有的服务并行的启动,以最大化利用硬件资源,提高启动的时间。但是我们知道服务之间存在依赖关系,客户端需要等待服务端的启动才可以建立连接,例如前面提到的,在优麒麟操作系统中,所有的服务都需要等待 syslog 服务的启动,那 systemd 是如何摆脱这同步和序列化过程的呢?

systemd 的设计师认为,对于传统的守护进程,他们真正实际等待另一个守护进程提供的是套接字的准备,需要的是一个文件系统的 socket 套接字描述符,这是它们唯一等待的,因此是否可以设法让这些套接字描述符可以更早的创建用于连接,从而不用等待整个守护进程完整的启动?答案是可以的。

在 C 语言中,一个进程启动另一个进程时,一般是执行系统调用 exec(),systemd 在调用 exec()来启动服务之前,先创建与该服务关联的监听套接字并激活,然后在 exec()启动服务期间把套接字传递给它,因此在服务还在启动的时候,套接字就已经处于可用的状态。通过这一方式,systemd 可以在第一步中为所有的服务创建套接字,然后第二步一次运行所有的服务,如果一个服务需要依赖于另一个服务,由于套接字已经准备好,服务之间可以直接进行连接并继续执行启动,如果遇到了需要同步的请求,不得不等待阻塞的情况,那阻塞的也将只会是一个服务,并且只是一个服务的一个请求,不会影响其他服务的启动,由此实现服务之间不需要再进行序列化的启动。Linux 内核提供了套接字缓冲区功能,帮助 systemd 实现了最大的并行化,还是拿 syslog 服务来说,优麒麟操作系统上大多数服务在启动初期都会先进行日志相关的初始化配置,如果同时启动 syslog 服务与各种 syslog 的客户端服务,由于 syslog 相关套接字在 systemd 执行 exec()启动 syslog 之前已经创建并准备好,客户端可以直接连接到 syslog 的套接字上,如果遇到 syslog 启动比较慢,客户端向 syslog 发送请求消息,syslog 还无法处理的情况,通过内核 socket 缓冲区的机制,请求的消息将会传到 syslog 套接字的缓冲区之中,只要缓冲区未满,客户端就不需要等待并继续往下执行;一旦 syslog 服务完全启动,它就会使所有消息出列并处理他们;当出现另一种情况,缓冲区已满,或者需要同步消息请求的情况,虽然这个时候客户端不得不阻塞等待,但是也只有一个客户端的一个请求被阻塞,并且直到服务端赶上并处理为止。

因此 systemd 先进行套接字的激活,然后开始服务的创建,使得所有的服务可以并行启动,依赖的管理也变得多余,至少可以说是次要的,因为从服务的角度看,只要套接字是激活的,另一个服务有没有启动都没有区别,这样一种方式也使得程序更加地健壮,因为不论服务可用或不可用,甚至是崩溃,套接字都处于可用的状态,不会让客户端注意到丢失连接。

4 systemd 执行单元–Unit 介绍

Unit 是 systemd 管理服务与资源的基本单元,可以简单理解为 systemd 启动后每次需要执行的服务,每次需要处理的资源,都被抽象为一个配置单元 Unit,保存在一个 Unit 文件里面。例如,当用户登录到优麒麟操作系统时,systemd 会执行 systemd-login.service 这个 Unit 文件来启动 login 服务,持续跟踪用户的会话、进程、空闲状态,为用户分配一个 slice 单元;当用户执行睡眠操作时,systemd 会执行 systemd-suspend.service 文件的 Unit,来启动 systemd-sleep 服务执行系统睡眠操作。Unit 文件可以根据其后缀名分为12种不同的类型,systemd 内部给这12种类型的 Unit 定义了不同的全局模板,因此 systemd 的执行流程为:

首先找到对应的 Unit 文件,然后根据 Unit 文件的类型匹配对应的全局模板,再然后根据这个模板解析 Unit 文件,最后执行 Unit 文件里的操作。接下来简单介绍一下12种 Unit 文件类型:

(1)service:这是最明显的单元类型,代表一个后台守护进程,可以启动、停止、重新启动、重新加载守护进程,是最常用的一类 Unit 文件。

(2)socket:这个单元在文件系统或互联网上封装了一个套接字。目前 systemd 支持流、数据报和顺序包类型的 AF_INET、AF_INET6、AF_UNIX 套接字。还支持经典的 FIFO 作为传输。每个套接字单元都有一个匹配的服务单元,相应的服务在第一个连接进入套接字时就会启动,例如:nscd.socket 在传入连接上启动 nscd.service。

(3)device:这个单元封装了 Linux 设备树中的一个设备。如果设备通过 udev 规则为此标记,它将在 systemd 中作为设备单元公开。使用 udev 设置的属性可用作配置源来设置设备单元的依赖关系。

(4)mount:这个单元封装了文件系统层次结构中的一个挂载点。systemd 监控所有挂载点,也可用于挂载或卸载挂载点。systemd 会将/etc/fstab 中的条目都转换为挂载点,并在开机时处理。

(5)automount:这个单元类型在文件系统层次结构中封装了一个自动挂载点。每个自动挂载单元都有一个匹配的挂载单元,当该自动挂载点被访问时,systemd 就会执行挂载点中定义的挂载行为。

(6)target:这种单元类型用于单元的逻辑分组:它本身并不做任何事情,它只是引用其他单元,从而可以一起控制。比如:想让系统进入图形化模式,需要运行许多服务和配置命令,这些操作都由一个个的配置单元表示,将所有这些配置单元组合为一个目标(target),就表示需要将这些配置单元全部执行一遍以便进入目标所代表的系统运行状态。

(7)snapshot:类似于 target 单元,snapshot 本身实际上不做任何事情,它们的唯一目的是引用其他单元。快照可用于保存或回滚 init 系统的所有服务和单元的状态。比如允许用户临时进入特定状态,例如“紧急外壳”,终止当前服务,并提供一种简单的方法返回之前的状态。

(8)swap:和挂载配置单元类似,交换配置单元用来管理交换分区。用户可以使用交换配置单元来定义系统中的交换分区,可以让这些交换分区在启动时被激活。

(9)timer:定时器配置单元用来定时触发用户定义的服务操作,是一种基于定时器的服务激活,这类配置单元取代了 atd、crond 等传统的定时服务。

(10)path:这类配置单元用来监控指定目录或者文件的变化,根据变化触发其他配置单元服务的运行。

(11)scope:这个单元主要表示从 systemd 外部创建的进程。

(12)slice:此单元主要用于封装管理一组进程资源占用的控制组的 slice 单元,也就是主要用于 cgroup,它通过在 cgroup 中创建一个节点实现资源的控制,一般包含 scope 与 service 单元。

下面通过蓝牙服务 bluetooth.service 介绍一下 Unit 文件的结构。

$ cat /usr/lib/systemd/system/bluetooth.service 
[Unit]
Description=Bluetooth service
Documentation=man:bluetoothd(8)
ConditionPathIsDirectory=/sys/class/bluetooth

[Service]
Type=dbus
BusName=org.bluez
ExecStart=/usr/lib/bluetooth/bluetoothd
NotifyAccess=main
#WatchdogSec=10
Restart=on-failure
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
LimitNPROC=1
ProtectHome=true
ProtectSystem=full

[Install]
WantedBy=bluetooth.target
Alias=dbus-org.bluez.service

Unit 文件主要分为以下三个大的部分:

Unit 段: 此部分所有 Unit 文件通用,用来定义 Unit 的元数据、配置以及与其他 Unit 的关系,Description 描述 Unit 文件信息,Documentation 表示指定服务的文档,Condition 表示服务启动的条件,有些 Unit 还包含 wants、before、after、require 字段,这些表示服务的一个依赖关系。

service 段: 服务(Service)类型的 Unit 文件特有的字段,用于定义服务的具体管理和执行动作。其中包括 Type 字段,定义进程的行为,例如使用 fork()启动,使用 dbus 启动等等;ExecStart、ExecStartPre、ExecStartPos、ExecReload、ExecStop 分别表示启动当前服务执行的命令、启动当前服务之前执行的命令、启动当前服务之后启动的命令、重启当前服务时执行的命令、停止当前服务时执行的命令。以上图为例,启动蓝牙服务所需要执行的命令为/usr/lib/bluetooth/bluetoothd

Install 段: 此部分所有 Unit 文件通用,通常指定运行目标的 target,使得服务在系统启动时自动运行。Wantedby、RequiredBy 与 Unit 段 Wants 字段类似,表示依赖关系,Alias 字段表示启动运行时使用的别名。

相关字段更详细的描述可以参考 freedesktop 的 man 手册:https://www.freedesktop.org/software/systemd/man/systemd.unit.html

https://www.freedesktop.org/software/systemd/man/systemd.service.html

以优麒麟操作系统为例,Unit 文件主要的存储路径如下:

system:

/etc/systemd/system/*
/run/systemd/system/*
/lib/systemd/system/*

user:

~/.config/systemd/user/*
/etc/systemd/user/*
$XDG_RUNTIME_DIR/systemd/user/*
/run/systemd/user/*
~/.local/share/systemd/user/*
/usr/lib/systemd/user/*

二、systemd 时代的开机启动流程

在 systemd 作为系统的 init 程序的时代下,Linux 系统的启动流程可以大致分为 6 个阶段:BIOS 自检阶段、GRUB 引导阶段、kernel 内核加载阶段、initrd 虚拟根文件系统阶段、systemd 初始化阶段、终端登录阶段。每个阶段都各司其职,为下一个阶段的进行做铺垫,相互联系,缺一不可。接下来对每个阶段做一下介绍:

1 BIOS 自检阶段

从我们启动计算机从按下电源键开始,计算机开始通电,然后系统就开始加载主板内存上的第一段代码:BIOS,系统进入 BIOS 自检阶段。

BIOS 为基本输入输出系统,全称 Basic Input Output System,它烧录在主板的内存上,其中的内容只能读不能改,如果要进行更改只能重新烧录到主板的内存上。BIOS 在开机阶段最主要的功能为上电自检,它会对主板上接入的硬件设备一个个进行检查,例如检查 CPU、主板、内存、软硬盘系统、键盘、光驱等等硬件是否接入正常,有无故障,当某一些主要的硬件(例如 CPU、内存等)出现问题时,BIOS 就会报错,无法继续启动系统。我们在启动电脑时听到的滴滴的声音就是 BIOS 蜂鸣器发出的声音,当硬件出问题的时候就可以听到它蜂鸣器响两到三声报错,系统就无法进行下一步的启动。

BIOS 检查完所有硬件状态并状态无误的时候,就会按照设置的启动顺序去找相应的启动盘,然后引导系统进入相应的启动盘继续启动系统。有过刷机经验的朋友应该知道在系统启动时按 F12 或者 delete 键就会进入 BIOS 界面,然后就会去选择相应的启动盘进行刷机,启动盘可以是装机 U 盘、光驱,也可以是已经装了系统的磁盘等等,BIOS 可以设置默认的启动顺序,例如:可以设置 U 盘为第一启动项,开机启动时 BIOS 就会引导系统去找 U 盘对应的硬件接口,当找不到 U 盘时,BIOS 会继续尝试第二启动项,当选择好了启动项时,系统进入相应的启动盘,并开始执行启动盘中第一块磁盘第一个扇区的代码,至此 BIOS 自检阶段结束。

2 GRUB 引导阶段

GRUB 是 GRand Unified Bootloader 的缩写,它是一个多重操作系统的管理器,存放在第一个磁盘的第一个扇区的主引导扇区里面,如果你的电脑里面装了多个系统,例如 Linux 系统和 Windows 系统,那么你可以通过 GRUB 来移动光标选择自己想要进入的系统,选择好系统以后 GRUB 就会根据系统分区表里找到对应系统所在的磁盘分区,加载相应的 grub.cfg 配置文件,通过配置文件,加载 /boot 分区的文件系统驱动,然后在文件系统中找到系统内核,把内核加载进来并启动,最后把系统的控制权交给内核,至此 GRUB 引导阶段结束。

GRUB 除了引导系统这一主要功能外,还可以通过 grub.cfg 配置文件来实现其他的一些功能。grub.cfg 配置文件存放在 /boot/grub/目录下,配置文件中,Linux 参数表示系统启动时对应加载的内核,当系统里存放了多个内核、或者在你电脑上重新修改编译了新的内核的时候,可以配置此项来选择相应的内核进行加载;quiet 参数类似于 loglevel 参数,用来配置日志启动的等级;splash 参数用来配置相应的启动动画等等。

3 Kernel 内核加载阶段

在讲解内核的启动之前,先简单介绍一下 Linux 内核。Linux 内核是一种宏内核,运行在单一地址空间的单一的程序,把系统的进程线程管理、内存管理、文件系统、驱动管理、网络管理等一些基本功能集中在一起,内核中的每一个函数都可以访问到其内核的其他部分,不同于微内核(代表:Windows 系统),微内核则是将这些功能独立的划分成不同的服务,服务之间通过通讯接口与中心内核通讯。

在结束了 GRUB 引导阶段,内核拿到系统的控制权后,首先开始初始化系统中各种设备的相关配置工作,其中包括 CPU、I/O 设备、存储设备的初始化等等,其次,内核创建内核态的 kernel_init 的进程,然后找到 initrd 文件并解压,加载 initrd 虚拟根文件系统中的驱动程序完成相关硬件的初始化,最后调用 initrd 虚拟根文件系统的 init 脚本,至此,内核在系统启动过程中的作用基本上就已经完成,内核开始等待 initrd 执行 init 进程,内核加载阶段结束。

4 Initrd 虚拟根文件系统阶段

initrd(Initial RAM Disk)它是一个虚拟的根文件系统,在 GRUB 阶段被拷贝到内存,在内核中被解压,是一个临时的虚拟根文件系统,对其进行解压后可以看到它的目录结构和实际的根文件系统类似,并且包含了些驱动程序,可通过lsinitramfs命令查看initrd.img的内容:

$ lsinitramfs initrd.img-5.10.0-1051-oem | head -n 20
.
kernel
kernel/x86
kernel/x86/microcode
kernel/x86/microcode/AuthenticAMD.bin
kernel
kernel/x86
kernel/x86/microcode
kernel/x86/microcode/.enuineIntel.align.0123456789abc
kernel/x86/microcode/GenuineIntel.bin
.
bin
conf
conf/arch.conf
conf/conf.d
conf/conf.d/zfs
conf/conf.d/zz-resume-auto
conf/initramfs.conf
conf/modules
cryptroot

由于内核为了精简,只保留了最基本的模块,因此没有各种设备硬件的驱动程序,这些驱动程序就存放在了 initrd 里面,内核启动的时候,就从 initrd 中加载必要的驱动模块,完成硬件的初始化工作,接着,内核就开始执行虚拟根文件系统里的 init 程序,即虚拟根文件系统下的 systemd 程序,systemd 就作为内核的子程序,拿到了系统的控制权,开始做一些系统初始化方面的工作。

通过上面的描述,可以总结一下,虚拟根文件系统的阶段可以大致的分为:内核加载 initrd 里面的驱动程序、虚拟根文件系统下的 systemd 程序加载这两个过程,因此也可把虚拟根文件系统阶段分别归到内核加载阶段与 systemd 初始化阶段两个里面,是与上下两个阶段重合的一个阶段。此外,initrd 还提供了美化启动图形界面的功能,用来遮盖系统启动过程中的 log 日志输出,提升用户的体验。当 initrd 下的 systemd 进程完成环境的初始化,系统切换到真正的根文件系统的时候,initrd 阶段结束。

5 systemd 初始化阶段

systemd 是 system deamon 的简称,是一个 Linux 系统基础组件的集合,提供了系统与服务的管理,是 pid 为 1 的 init 进程,是所有进程的父进程。

通过前面的描述,我们可以把 systemd 分为两个阶段:虚拟根文件系统阶段与实根文件系统阶段。内核通过解压 initrd 文件得到虚拟根文件系统,然后执行虚拟根文件系统下的 init 程序来启动 systemd,systemd 作为内核的子进程在虚拟根文件系统下开始运行。虚拟根文件下的 systemd 首先对目前的系统进行一些检查,例如判断系统的运行状态是 user 态还是 system 态,系统是正常的启动状态还是异常出错后的重启状态等等,然后进行一些系统的初始化配置,包括:环境变量的配置、日志的相关配置等,接着对一些关键的文件系统进行挂载,主要包括 /proc、/sys、/dev、/var 这些基本的文件系统目录,到这一步后,systemd 就开始为切换实根文件目录做准备,保存一些已经配置的项目,并进行一些环境的适配之后,systemd 执行 setsid()系统调用脱离内核控制,成为一个完全独立的父进程,至此 systemd 的虚拟根文件系统阶段结束,systemd 进入到实根文件系统阶段。

在实根文件系统阶段,systemd 首先进行一些切换后的环境适配,然后开启日志终端的功能,并把系统启动时临时保存在内核中的日志提取出来,整理后存放到相应的日志文件中,下一步,systemd 开始进行一些系统能力的获取与系统相关的初始化与配置,例如:CPU 亲和力的获取、主机名的配置、系统 ID 的配置,cgroup 控制器的挂载、回环网络的配置等,完成以上的这些所有初始化工作后,systemd 作为 PID 为 1 的守护进程,开始了各个系统服务的创建与管理工作,根据相应 Unit 配置单元文件执行相应的系统服务,通过各个服务逐步完成系统的启动工作。systemd 执行 Unit 的顺序大致可以分为 sysinit.target->basic.target->default.target,其中 sysinit.targetbasic.target 主要用来启动一些系统初始化相关的一些服务与执行一些开机启动早期的一些任务,default.target 则指向不同的“运行级别”target 文件,如果是进入命令行模式则指向 multi-user.target 文件,如果是进入图形界面模式则指向 graphical.target 文件。至此,systemd 开机启动阶段的工作完成。

6 终端登录阶段

在完成了 systemd 初始化阶段以后,系统根据配置的运行级别,进入不同的登录界面,下面主要从图形登录界面进行介绍。在优麒麟操作系统中,systemd 之后的启动流程主要如下:systemd->lightdm->Xorg->ukui-session,在优麒麟的终端通过 pstree 命令可以看到如下两个进程树:

$ pstree
systemd─┬....
        ├─lightdm─┬─Xorg───13*[{Xorg}]
        │         ├─lightdm─┬─ukui-session─┬─agent───2*[{agent}]
        │         │         │              ├─applet.py
        │         │         │              ├─fcitx
        │         │         │              ├─indicator-china───80*[{indicator-china}]
        │         │         │              ├─kmdaemon───5*[{kmdaemon}]
        │         │         │              ├─kylin-nm───10*[{kylin-nm}]
        │         │         │              ├─kylin-printer───11*[{kylin-printer}]
        │         │         │              ├─mktip───9*[{mktip}]
        │         │         │              ├─peony-qt-deskto───10*[{peony-qt-deskto}]
        │         │         │              ├─polkit-ukui-aut───9*[{polkit-ukui-aut}]
        │         │         │              ├─ssh-agent
        │         │         │              ├─ukui-flash-disk───6*[{ukui-flash-disk}]
        │         │         │              ├─ukui-kwin_x11───11*[{ukui-kwin_x11}]
        │         │         │              ├─ukui-menu───9*[{ukui-menu}]
        │         │         │              ├─ukui-panel───10*[{ukui-panel}]
        │         │         │              ├─ukui-power-mana───9*[{ukui-power-mana}]
        │         │         │              ├─ukui-power-mana───3*[{ukui-power-mana}]
        │         │         │              ├─ukui-screensave───4*[{ukui-screensave}]
        │         │         │              ├─ukui-search───9*[{ukui-search}]
        │         │         │              ├─ukui-settings-d───10*[{ukui-settings-d}]
        │         │         │              ├─ukui-sidebar─┬─dbus-monitor
        │         │         │              │              └─13*[{ukui-sidebar}]
        │         │         │              ├─ukui-volume-con───11*[{ukui-volume-con}]
        │         │         │              ├─ukui-window-swi───9*[{ukui-window-swi}]
        │         │         │              ├─update-notifier───3*[{update-notifier}]
        │         │         │              ├─user-guide-daem───5*[{user-guide-daem}]
        │         │         │              └─5*[{ukui-session}]
        │         │         └─2*[{lightdm}]
        │         └─2*[{lightdm}]
        │....

lightdm 是一个全新的、轻量的 Linux 桌面的桌面显示管理器,它首先会拉起 Xorg,Xorg 是一个显示的后台,负责屏幕的绘制,然后 lightdm 还会拉起 lightdm-greeter,lightdm-greeter 是 lightdm 的子进程,它会拉起 ukui-greeter 进程,ukui-greeter 是登录界面进程,因此 ukui-greeter 起来以后,系统进入到登录界面,当输入登录的用户名与密码,用户名与密码效验通过以后,lightdm 建立个人的 ukui-session 桌面窗口管理器进程,至此,终端登录阶段结束,系统完成启动。

三、systemd 相关命令

systemd 提供了 systemctl 相关命令,用于管理系统,下面对一些基础常用命令进行介绍:

1 系统管理命令,控制系统电源状态

# 重启系统
$ sudo systemctl reboot

# 关闭系统,切断电源
$ sudo systemctl poweroff

# 暂停系统,使系统进入睡眠状态
$ sudo systemctl suspend

# 让系统进入冬眠状态
$ sudo systemctl hibernate

# 让系统进入交互式休眠状态
$ sudo systemctl hybrid-sleep

2 systemd-analyze 命令,用于查看启动耗时,可用来分析系统的启动效率

#查看启动耗时
$ systemd-analyze

#查看每个服务的启动耗时
$ systemd-analyze blame

#显示瀑布状的启动过程流
$ systemd-analyze critical-chain

#显示指定服务的启动流
$ systemd-analyze critical-chain atd.service

3 hostnamectl 命令,用于查看当前主机的信息

#显示当前主机的信息
$ hostnamectl

#设置主机名
$ sudo hostnamectl set-hostname rhel7

4 Unit 相关命令,用来管理 Unit 配置单元

#列出正在运行的 Unit
$ systemctl list-units

#列出所有 Unit,包括没有找到配置文件的或者启动失败的
$ systemctl list-units --all

#列出所有没有运行的 Unit
$ systemctl list-units --all --state=inactive

#列出所有加载失败的 Unit
$ systemctl list-units --failed

#列出所有正在运行的、类型为 service 的 Unit
$ systemctl list-units --type=service

#立即启动一个服务
$ sudo systemctl start apache.service

#立即停止一个服务
$ sudo systemctl stop apache.service

#重启一个服务
$ sudo systemctl restart apache.service

#杀死一个服务的所有子进程
$ sudo systemctl kill apache.service

#重新加载一个服务的配置文件
$ sudo systemctl reload apache.service

#重载所有修改过的配置文件
$ sudo systemctl daemon-reload

#显示某个 Unit 的所有底层参数
$ systemctl show httpd.service

#显示某个 Unit 的指定属性的值
$ systemctl show -p CPUShares httpd.service

#设置某个 Unit 的指定属性
$ sudo systemctl set-property httpd.service CPUShares=500

5 日志管理,用来查看和过滤系统日志

#查看所有日志(默认情况下 ,只保存本次启动的日志)
$ sudo journalctl

#查看内核日志(不显示应用日志)
$ sudo journalctl -k

#查看系统本次启动的日志
$ sudo journalctl -b
$ sudo journalctl -b -0

#查看上一次启动的日志(需更改设置)
$ sudo journalctl -b -1

#查看指定时间的日志
$ sudo journalctl --since="2012-10-30 18:17: 16"
$ sudo journalctl --since "20 min ago"
$ sudo journalctl --since yesterday
$ sudo journalctl --since "2015-01-10" --until "2015-01-11 03:00"
$ sudo journalctl --since 09:00 --until "1 hour ago"

#显示尾部的最新 10 行日志
$ sudo journalctl -n

#显示尾部指定行数的日志
$ sudo journalctl -n 20

#实时滚动显示最新日志
$ sudo journalctl -f

#查看指定服务的日志
$ sudo journalctl /usr/lib/systemd/systemd

#查看指定进程的日志
$ sudo journalctl _PID=1

#查看某个路径的脚本的日志
$ sudo journalctl /usr/bin/bash

#查看指定用户的日志
$ sudo journalctl _UID=33 --since today

#查看某个 Unit 的日志
$ sudo journalctl -u nginx.service
$ sudo journalctl -u nginx.service --since today

#实时滚动显示某个 Unit 的最新日志
$ sudo journalctl -u nginx.service -f

#查看指定优先级(及其以上级别)的日志,共有8级
# 0: emerg
# 1: alert
# 2: crit
# 3: err
# 4: warning
# 5: notice
# 6: info
# 7: debug
$ sudo journalctl -p err -b

#日志默认分页输出,--no-pager 改为正常的标准输出
$ sudo journalctl --no-pager

#显示日志占据的硬盘空间
$ sudo journalctl --disk-usage

#指定日志文件占据的最大空间
$ sudo journalctl --vacuum-size=1G

#指定日志文件保存多久
$ sudo journalctl --vacuum-time=1years

关于 systemd 的完整介绍可以参考 systemd 官网手册以及systemd 的官网中文翻译手册

systemd 源码地址:https://launchpad.net/ubuntu/+source/systemd 和 https://github.com/systemd/systemd

Logo

更多推荐