Linux 的小伙伴 systemd 详解

小插曲

最近 土豆哥 在捣鼓 Webp Server Go 的时候发咱一张 systemctl status webp 的信息:

咦?咱的 systemctl status webp 为啥子没得 CPU 和 Memory 信息呐?

然后和土豆哥请教了一下,咱也想要。于是土豆哥发咱一篇 systemd – systemctl 不显示内存 CPU 信息 博客,于是拜师学艺就 get 到啦 😋。只需要在 systemd 的配置文件中 /etc/systemd/system.conf 追加

DefaultCPUAccounting=yes
DefaultMemoryAccounting=yes
DefaultTasksAccounting=yes

然后再 systemctl daemon-reload 一把梭就可以啦 😂

╭─root@blog /home/ubuntu
╰─# systemctl status webps
● webps.service - WebP Server
   Loaded: loaded (/opt/webps/webps.service; enabled; vendor preset: enabled)
   Active: active (running) since Sat 2020-03-14 10:32:31 UTC; 4 days ago
     Docs: https://github.com/n0vad3v/webp_server_go
 Main PID: 20691 (webp-server)
    Tasks: 36 (limit: 684)
   Memory: 9.1M
      CPU: 20.421s
   CGroup: /system.slice/webps.service
           └─20691 /opt/webps/webp-server --config /opt/webps/config.json

不过,咱还是要深入地学习一下 systemd ,于是就水了这篇博客 😂

Linux 启动流程

提到 systemd 不得不提一下 Linux 的启动流程,这样才能清楚 systemd 在 Linux 系统中的地位和作用 😋。所以就简明扼要第介绍一下 Linux 启动的流程。

Linux 从按下电源键到进入用户交互界面整个启动流程大致可以分为四个阶段:

  • BIOS 阶段
  • BootLoader 阶段
  • kernel 加载阶段
  • init:systemd/sysvinit 初始化阶段

BIOS/EFI 阶段

BIOS 想必大家都熟悉,也就是做一些基本的硬件自检准备以及加载 bootloader 程序。在按下电源电源键(冷启动)后, CPU 的程序计数器被初始化为一个特定的內存地址,存储在只读存储器(ROM)中的 BIOS 就是从这个特定的內存地址开始执行。所以没有 CPU 是无法启动主板上的 BIOS 的 ,应该(小声。注意:对于嵌入式系统中的 CPU ,将会加载引导区去启动 flash/ROM 中已知地址的程序。

BIOS 启动后就开始执行硬件的基本初始化也称之为(POST: 上电自检),并根据引导设备的优先级将系统控制权交给硬件启动项(比如硬盘/网络/U 盘等)。也就是我们 BIOS 上的启动菜单,这一步是可以被打断的。当我们按下 F12 或者 ESC 键(根据主板芯片组而异)就会弹出选择启动项的界面,而且这些按键高度依赖硬件。

BIOS 选择好硬件启动项之后就开始执行硬件设备上的初级引导程序代码,对于 MBR 硬盘来讲是最开始的一个扇区(512 字节)將被加载到內存,並执行行其中的初始化代码来加载下一阶段的 Bootloader 。

PS:MBR 主引导记录是一个 512 字节的扇区,位于硬盘的第一扇区(0 道 0 柱 1 扇区)。对于 GPT/EFI 来讲~~,有点头大,暂且先不谈 😂。

可以使用 dd 命令读取 MBR 里的内容 dd if=/dev/sda of=mbr.bin bs=512 count=1

使用 od 命令来查看 od -xa mbr.bin

# od -xa mbr.bin
0000000    63eb    0090    0000    0000    0000    0000    0000    0000
          k   c dle nul nul nul nul nul nul nul nul nul nul nul nul nul
0000020    0000    0000    0000    0000    0000    0000    0000    0000
        nul nul nul nul nul nul nul nul nul nul nul nul nul nul nul nul
*
0000120    0000    0000    0000    0000    0000    8000    0800    0000
        nul nul nul nul nul nul nul nul nul nul nul nul nul  bs nul nul
0000140    0000    0000    faff    9090    c2f6    7480    f605    70c2
        nul nul nul nul del   z dle dle   v   B nul   t enq   v   B   p
0000160    0274    80b2    79ea    007c    3100    8ec0    8ed8    bcd0
          t stx   2 nul   j   y   | nul nul   1   @  so   X  so   P   <
0000200    2000    a0fb    7c64    ff3c    0274    c288    bb52    0417
        nul  sp   {  sp   d   |   < del   t stx  bs   B   R   ; etb eot
0000220    07f6    7403    be06    7d88    17e8    be01    7c05    41b4
          v bel etx   t ack   >  bs   }   h etb soh   > enq   |   4   A
0000240    aabb    cd55    5a13    7252    813d    55fb    75aa    8337
……………………

BootLoader 阶段

主引导记录加载完 Bootloader(主要为 GRUB)到 RAM 中之后,GRUB 会根据需求显示一个可用的内核列表(定义在/etc/grub.conf,以及/etc/grub/menu.lst 和/etc/grub.conf 的软连接)。根据 GRUB 的配置加载默认内核镜像和 initrd 镜像到内存中,当所有镜像准备好后,即跳转到内核镜像。

kernel 阶段

BootLoader 阶段完成之后内核镜像加载到内存中,系统的控制权就交给内核镜像,由此内核阶段开始了。内核镜像不是一个可以执行的内核,而是一个被压缩的内核镜像 (zImage 或 bzImage)。在内核镜像的头部有一个小型程序 routine ,其做少量的硬件设置,然后自解压压缩的内核镜像并放到高端内存。如果存在初始磁盘镜像(initrd),routine 将拷贝 initrd 以供稍后安装使用,然后 routine 将调用内核开始内核启动。

在内核引导过程中,初始 RAM 磁盘(initrd)是由 BootLoader 加载到内存中的,它会被复制到 RAM 中并挂载到系统上。这个 initrd 作为 RAM 中的临时根文件系统使用,并允许内核在没有挂载任何物理磁盘的情况下完整地实现引导。由于与外围设备进行交互所需要的模块可是 initrd 的一部分,因此内核可以非常小,但是仍然支持大量可能的硬件配置。在内核启动后,就可以正式装备根文件系统了(通过 pivot_root),此时会将 initrd 根文件系统卸载掉,并挂载真正的根文件系统。
initrd 函数让我们可以创建一个小型的 Linux 内核,其中包括作为可加载模块编译的驱动程序。这些可加载的模块为内核提供了访问磁盘和磁盘上的文件系统的方法,并为其他硬件提供了驱动程序。由于根文件系统是磁盘上的一个文件系统,因此 initrd 函数会提供一种启动方法来获得对磁盘的访问,并挂载真正的根文件系统。在没有硬盘的嵌入式目标中,initrd 可以是最终的根文件系统,或者也可以通过网络文件系统(NFS)来挂载最终的根文件系统。

可以使用 dmesg 来查看从加载内核后的流程,下一篇博客会写从 dmesg 日志分析 Linux 内核启动流程(挖坑

╭─root@blog /home/ubuntu
╰─# dmesg
[    0.000000] Linux version 5.0.0-1031-gcp (buildd@lcy01-amd64-020) (gcc version 7.4.0 (Ubuntu 7.4.0-1ubuntu1~18.04.1)) #32-Ubuntu SMP Tue Feb 11 03:55:48 UTC 2020 (Ubuntu 5.0.0-1031.32-gcp 5.0.21)
[    0.000000] Command line: BOOT_IMAGE=/boot/vmlinuz-5.0.0-1031-gcp root=PARTUUID=9b22aacc-c8b9-497a-9583-a20c1be968c4 ro scsi_mod.use_blk_mq=Y console=ttyS0
[    0.000000] KERNEL supported cpus:
[    0.000000]   Intel GenuineIntel
[    0.000000]   AMD AuthenticAMD
[    0.000000]   Hygon HygonGenuine
[    0.000000]   Centaur CentaurHauls
……………………………………………………………………………………………………
[    2.486896] Write protecting the kernel read-only data: 22528k
[    2.489395] Freeing unused kernel image memory: 2016K
[    2.490937] Freeing unused kernel image memory: 1708K
[    2.500867] x86/mm: Checked W+X mappings: passed, no W+X pages found.
[    2.503126] x86/mm: Checking user space page tables
[    2.513767] x86/mm: Checked W+X mappings: passed, no W+X pages found.
[    2.516258] Run /sbin/init as init process
[    3.992535] systemd[1]: systemd 237 running in system mode. (+PAM +AUDIT +SELINUX +IMA +APPARMOR +SMACK +SYSVINIT +UTMP +LIBCRYPTSETUP +GCRYPT +GNUTLS +ACL +XZ +LZ4 +SECCOMP +BLKID +ELFUTILS +KMOD -IDN2 +IDN -PCRE2 default-hierarchy=hybrid)
[    4.002297] systemd[1]: Detected virtualization kvm.
[    4.004593] systemd[1]: Detected architecture x86-64.
[    4.031531] systemd[1]: Set hostname to <blog>.
[    5.343604] systemd[1]: Reached target Swap.
[    5.355156] systemd[1]: Started Dispatch Password Requests to Console Directory Watch.
[    5.367360] systemd[1]: Created slice User and Session Slice.
[    5.379321] systemd[1]: Set up automount Arbitrary Executable File Formats File System Automount Point.
[    5.395189] systemd[1]: Started Forward Password Requests to Wall Directory Watch.
[    5.411078] systemd[1]: Reached target Local Encrypted Volumes.
[    5.644759] EXT4-fs (sda1): re-mounted. Opts: (null)
[   11.663490] bpfilter: Loaded bpfilter_umh pid 456

init 初始化阶段

一旦内核自解压完成,启动并初始化后,内核启动第一个用户空间应用程序,即 systemd 进程(其是老式 System V 系统的 init 程序的替代品),并转移控制权到 systemd。这是调用的第一个使用标准 C 库编译的程序,在此进程之前,还没有执行任何标准的 C 应用程序。至此整个系统引导过程的结束,kernel 和 systemd 处于运行状态,接下来就由 systemd 来启动各项程序。

从 dmesg 的输出日志我们可以看到,直到 2.516258 秒才开始执行 /sbin/init 命令,而对于采用 systemd 的发行版来说,/sbin/init 是指向 /sbin/init -> /lib/systemd/systemd ,的链接文件,实际上就是在启动 systemd 😂

[    2.516258] Run /sbin/init as init process
[    3.992535] systemd[1]: systemd 237 running in system mode. (+PAM +AUDIT +SELINUX +IMA +APPARMOR +SMACK +SYSVINIT +UTMP +LIBCRYPTSETUP +GCRYPT +GNUTLS +ACL +XZ +LZ4 +SECCOMP +BLKID +ELFUTILS +KMOD -IDN2 +IDN -PCRE2 default-hierarchy=hybrid)

主角 systemd

为什么文章题名为 Linux 的小伙伴 systemd 呐,其实广义上来讲 Linux 是众多 Linux 系统发行版的集合,但严格来讲 Linux 仅仅是一个 OS 的 kernel 而已,仅仅有一个内核是无法组成一个系统的,所以 Linux kernel 还需要他的几个兄弟比如 GNU 、 systemd 、X Window 、GNOME 、 KDE 、Xfce 等等其他用户层面的程序来构建出一套完整的操作系统出来。

还有如果你要是当着自由软件基金会主席 RMS 理查德·斯托曼 说 Linux 的话,他会给你发火哦,你要说 GNU/Linux 才行 😂。

将 Debian 哲学与方法论,GNU 工具集、Linux 内核,以及其他重要的自由软件结合在一起所构成的独特的软件发行版称为 Debian GNU/Linux。当你将 Debian 的安装镜像刻录到 U 盘之后显示的名称就是 Debian GNU/Linux 。关于 GNU 和 Linux 的故事可以去看一哈 操作系统革命 纪录片,以及 RMS 的自传《若为自由故:自由软件之父理查德·斯托曼传》以及 Linus 的自传《只是为了好玩儿》。

systemd 简介

讲完了 Linux 系统启动的流程,接下来就到了本文的主角 systemd 上场啦。剽窃一段 archlinux.org 上的文档来介绍一下 systemd 。(archlinux 的文档是做的最好的哦!

systemd 是一个 Linux 系统基础组件的集合,提供了一个系统和服务管理器,运行为 PID 1 并负责启动其它程序。功能包括:

  • 支持并行化任务;
  • 同时采用 socket 式与 D-Bus 总线式激活服务;
  • 按需启动守护进程(daemon);
  • 利用 Linux 的 cgroups 监视进程; # 本文开篇讲到的 systemctl 显示 CPU 和 Mem 信息就是基于此哦。
  • 支持快照和系统恢复;
  • 维护挂载点和自动挂载点;
  • 各服务间基于依赖关系进行精密控制。
  • systemd* 支持 SysV 和 LSB 初始脚本,可以替代 sysvinit。
  • 除此之外,功能还包括日志进程、控制基础系统配置,维护登陆用户列表以及系统账户、运行时目录和设置,可以运行容器和虚拟机,可以简单的管理网络配置、网络时间同步、日志转发和名称解析等。

还有一段摘自 systemd.io

systemd is a suite of basic building blocks for a Linux system. It provides a system and service manager that runs as PID 1 and starts the rest of the system.

systemd provides aggressive parallelization capabilities, uses socket and D-Bus activation for starting services, offers on-demand starting of daemons, keeps track of processes using Linux control groups, maintains mount and automount points, and implements an elaborate transactional dependency-based service control logic. systemd supports SysV and LSB init scripts and works as a replacement for sysvinit.

Other parts include a logging daemon, utilities to control basic system configuration like the hostname, date, locale, maintain a list of logged-in users and running containers and virtual machines, system accounts, runtime directories and settings, and daemons to manage simple network configuration, network time synchronization, log forwarding, and name resolution.

systemd 设计目标

  • 改进效能。使用二进制代码替换松散的 SYSV 启动脚本,减少频繁的进程创建,库加载,内核/用户切换。
  • 利用 Dbus 进程间通讯与 socket 激活机制,解决任务启动时的依赖问题,实现启动并行化。
  • 实现任务(daemons)的精确控制。使用内核的 cgroup 机制,不依赖 pid 来追踪进程,即使是两次 fork 之后生成的守护进程也不会脱离 systemd 的控制。
  • 统一任务定义。用户不需要自行编写 shell 脚本,而仅依据 systemd 制定的 unit 规则。

systemd 体系架构

  • 最底层:systemd 内核层面依赖 cgroup、autofs、kdbus
  • 第二层:systemd libraries 是 systemd 依赖库
  • 第三层:systemd Core 是 systemd 自己的库
  • 第四层:systemd daemons 以及 targets 是自带的一些基本 unit、target,类似于 sysvinit 中自带的脚本
  • 最上层就是和 systemd 交互的一些工具

systemd unit

systemd 将各种系统启动和运行相关的对象, 表示为各种不同类型的单元 unit, 并提供了 处理不同单元之间依赖关系的能力。

type name 作用
Service unit .service 用于封装一个后台服务进程
Target unit .target 用于将多个单元在逻辑上组合在一起。
Device unit .device 用于定义内核识别的设备,在 sysfs(5) 里面作为 udev(7) 设备树展示
Socket unit .socket 用于标识进程间通信用到的 socket 文件
Snapshot unit .snapshot 管理系统快照
Swap unit .swap 用于标识 swap 文件或设备
Mount unit .mount 用于封装一个文件系统挂载点(也向后兼容传统的 /etc/fstab 文件)
Automount unit .automount 用于封装一个文件系统自动挂载点
Path unit .path 用于根据文件系统上特定对象的变化来启动其他服务。
Time unit .timer 用于封装一个基于时间触发的动作。取代传统的 crond 等任务计划服务
Slice unit *.slice 用于控制特定 CGroup 内所有进程的总体资源占用。

需要注意的是 systemd 只在内存中加载最小化的一组单元。 只有至少满足下列条件之一的单元,才会被加载到内存中:

  • 处于 活动(active)、启动中(activating)、停止中(deactivating)、失败(failed) 状态之一(也就是停止(inactive)之外的状态)
  • 至少有一个作业正在作业队列中
  • 至少有一个其他已经加载到内存中的单元依赖于它
  • 仍然占有某些资源 (例如一个已停止的服务单元的进程忽略了终止请求,仍在逗留)
  • 被 D-Bus 调用以程序化的方式固定到了内存中

只要有需要,systemd 就会自动从磁盘加载所需的单元。 因此实际上用户并不能显而易见的看到某个单元是否已被加载到内存。 使用 systemctl list-units –all 命令可以显示当前已加载到内存中的所有单元。 不满足加载条件(见上文)的单元会被立即从内存中卸载,并且它的记帐数据(accounting data)也会被清空。 不过,因为每当一个单元关闭时,都会生成一条日志记录声明该单元所消耗的资源, 所以这些数据通常不会彻底消失。

systemd unit 配置文件

  • 在 CentOS/RedHat 发行版中 man systemd.unit
Table 1.  Load path when running in system mode (--system).
┌────────────────────────┬─────────────────────────────┐
│Path                    │ Description                 │
├────────────────────────┼─────────────────────────────┤
│/etc/systemd/system     │ Local configuration         │
├────────────────────────┼─────────────────────────────┤
│/run/systemd/system     │ Runtime units               │
├────────────────────────┼─────────────────────────────┤
│/usr/lib/systemd/system │ Units of installed packages │
└────────────────────────┴─────────────────────────────┘
  • 在 Ubuntu/Debian 发行版中 man systemd.unit
Table 1.  Load path when running in system mode (--system).
┌────────────────────┬─────────────────────────────┐
│Path                │ Description                 │
├────────────────────┼─────────────────────────────┤
│/etc/systemd/system │ Local configuration         │
├────────────────────┼─────────────────────────────┤
│/run/systemd/system │ Runtime units               │
├────────────────────┼─────────────────────────────┤
│/lib/systemd/system │ Units of installed packages │
└────────────────────┴─────────────────────────────┘

不同的发行版 systemd unit 的 path 不一样哦,不过 systemd 的配置文件主要位于以下三个目录中

  • /usr/lib/systemd/system/lib/systemd : 使用包管理器安装的软件的 systemd unit 件实际配置文件的存放位置

  • /run/systemd/system:在运行时创建的 s ystemd unit 文件。该目录优先于已安装服务单元文件的目录。

    /etc/systemd/system: 优先级最高,由 systemctl 命令创建的 systemd unit 文件以及为扩展服务而添加的 unit 文件都将启用。

在使用 yum/apt 或其他包管理器,以及 rpm/deb 等软件包安装软件的时候,如果该软件支持 systemd 管理的话,就会自动在 /usr/lib/systemd/system 目录添加一个配置文件。可以用过 systemctl cat name 来查看该软件的 systemd 单元文件,比如查看一下 nginx 的 nginx.service 文件

╭─root@sg-02 ~
╰─# systemctl cat nginx                                                                                         1 ↵
# /lib/systemd/system/nginx.service
# Stop dance for nginx
# =======================
#
# ExecStop sends SIGSTOP (graceful stop) to the nginx process.
# If, after 5s (--retry QUIT/5) nginx is still running, systemd takes control
# and sends SIGTERM (fast shutdown) to the main process.
# After another 5s (TimeoutStopSec=5), and if nginx is alive, systemd sends
# SIGKILL to all the remaining processes in the process group (KillMode=mixed).
#
# nginx signals reference doc:
# http://nginx.org/en/docs/control.html
#
[Unit]
Description=A high performance web server and a reverse proxy server
Documentation=man:nginx(8)
After=network.target

[Service]
Type=forking
PIDFile=/run/nginx.pid
ExecStartPre=/usr/sbin/nginx -t -q -g 'daemon on; master_process on;'
ExecStart=/usr/sbin/nginx -g 'daemon on; master_process on;'
ExecReload=/usr/sbin/nginx -g 'daemon on; master_process on;' -s reload
ExecStop=-/sbin/start-stop-daemon --quiet --stop --retry QUIT/5 --pidfile /run/nginx.pid
TimeoutStopSec=5
KillMode=mixed

[Install]

如果软件没有自带 systemd 配置文件的话,我们可以自己搓一个配置文件出来,并复制到相应的目录下即可。比我我们的 Webp Server Gowebps.service

[Unit]
Description=WebP Server Go
Documentation=https://github.com/webp-sh/webp_server_go
After=nginx.target

[Service]
Type=simple
StandardError=journal
WorkingDirectory=/opt/webps
ExecStart=/opt/webps/webp-server --config /opt/webps/config.json
Restart=always
RestartSec=3s

[Install]
WantedBy=multi-user.target

关于 systemd 的单元配置文件如何书写,因为根据不同的单元类型包含的配置项实在是巨多。不是三言两语就能讲清的,所以大家还是参考一下 systemd 的官方文档/社区翻译文档。比如 systemd (简体中文)

链接文件

当我们使用 reboot 、poweroff 、shutdown 等命令的时候,其实并不是执行该命令本身,背后是调用的 systemctl 命令。systemctl 命令会将 reboot 这些命令作为 $1 参数传递进去。所以执行 reboot 和 systemctl reboot 本质上是一样的。

╭─root@gitlab /sbin
╰─# ls -alh /usr/bin /sbin /bin /usr/local/bin  | grep systemctl
-rwxr-xr-x  1 root root 179K Feb  5 01:07 systemctl
lrwxrwxrwx  1 root root      14 Feb  5 01:07 halt -> /bin/systemctl
lrwxrwxrwx  1 root root      14 Feb  5 01:07 poweroff -> /bin/systemctl
lrwxrwxrwx  1 root root      14 Feb  5 01:07 reboot -> /bin/systemctl
lrwxrwxrwx  1 root root      14 Feb  5 01:07 runlevel -> /bin/systemctl
lrwxrwxrwx  1 root root      14 Feb  5 01:07 shutdown -> /bin/systemctl
lrwxrwxrwx  1 root root      14 Feb  5 01:07 telinit -> /bin/systemctl

还有前文提到的有一点就是绝大多数使用 systemd 的发行版都设置了一个软连接,由 /sbin/init -> /lib/systemd/systemd。另外在 /etc/systemd 下也有很多链接文件,感兴趣的可以去分析一下。

systemd 启动流程

回到 Linux 启动流程,当内核加载到内存中后开始执行 systemd 。根据 dmesg 的日志我们可以了解到 systemd 启动后执行了哪一些操作。

[    2.516258] Run /sbin/init as init process
[    3.992535] systemd[1]: systemd 237 running in system mode. (+PAM +AUDIT +SELINUX +IMA +APPARMOR +SMACK +SYSVINIT +UTMP +LIBCRYPTSETUP +GCRYPT +GNUTLS +ACL +XZ +LZ4 +SECCOMP +BLKID +ELFUTILS +KMOD -IDN2 +IDN -PCRE2 default-hierarchy=hybrid)
[    4.002297] systemd[1]: Detected virtualization kvm.
[    4.004593] systemd[1]: Detected architecture x86-64.
[    4.031531] systemd[1]: Set hostname to <blog>.
[    5.343604] systemd[1]: Reached target Swap.
[    5.355156] systemd[1]: Started Dispatch Password Requests to Console Directory Watch.
[    5.367360] systemd[1]: Created slice User and Session Slice.
[    5.379321] systemd[1]: Set up automount Arbitrary Executable File Formats File System Automount Point.
[    5.395189] systemd[1]: Started Forward Password Requests to Wall Directory Watch.
[    5.411078] systemd[1]: Reached target Local Encrypted Volumes.
[    5.644759] EXT4-fs (sda1): re-mounted. Opts: (null)
[    5.702603] RPC: Registered named UNIX socket transport module.
[    5.704115] RPC: Registered udp transport module.
[    5.705318] RPC: Registered tcp transport module.
[    5.706573] RPC: Registered tcp NFSv4.1 backchannel transport module.
[    5.825727] Loading iSCSI transport class v2.0-870.
[    5.912079] iscsi: registered transport (tcp)
[    5.942499] systemd-journald[196]: Received request to flush runtime journal from PID 1
[    5.973269] systemd-journald[196]: File /var/log/journal/7bc72ce3e0aa559e38159aa4fa0547f9/system.journal corrupted or uncleanly shut down, renaming and replacing.

下面的图表解释了 这些具有特定含义的 target 单元之间的依赖关系 以及各自在启动流程中的位置。图中的箭头表示了单元之间的依赖关系与先后顺序, 整个图表按照自上而下的时间顺序执行。

local-fs-pre.target
            |
            v
   (various mounts and   (various swap   (various cryptsetup
    fsck services...)     devices...)        devices...)       (various low-level   (various low-level
            |                  |                  |             services: udevd,     API VFS mounts:
            v                  v                  v             tmpfiles, random     mqueue, configfs,
     local-fs.target      swap.target     cryptsetup.target    seed, sysctl, ...)      debugfs, ...)
            |                  |                  |                    |                    |
            \__________________|_________________ | `_______________` |____________________/
                                                 \|/
                                                  v
                                           sysinit.target
                                                  |
             `________________________________` /|\________________________________________
            /                  |                  |                    |                    \
            |                  |                  |                    |                    |
            v                  v                  |                    v                    v
        (various           (various               |                (various          rescue.service
       timers...)          paths...)              |               sockets...)               |
            |                  |                  |                    |                    v
            v                  v                  |                    v             *rescue.target
      timers.target      paths.target             |             sockets.target
            |                  |                  |                    |
            v                  \_________________ | `_______________` /
                                                 \|/
                                                  v
                                            basic.target
                                                  |
             `________________________________` /|                                 emergency.service
            /                  |                  |                                         |
            |                  |                  |                                         v
            v                  v                  v                                *emergency.target
        display-        (various system    (various system
    manager.service         services           services)
            |             required for            |
            |            graphical UIs)           v
            |                  |          *multi-user.target
            |                  |                  |
            \_________________ | `_____________` /
                              \|/
                               v
                  *graphical.target
  • systemd 执行的第一个目标是 default.target。但实际上 default.target 是指向 graphical.target 的软链接。Graphical.target 的实际位置是 /usr/lib/systemd/system/graphical.target
╭─root@ ~
╰─# cat /usr/lib/systemd/system/graphical.target
#  This file is part of systemd.
#
#  systemd is free software; you can redistribute it and/or modify it
#  under the terms of the GNU Lesser General Public License as published by
#  the Free Software Foundation; either version 2.1 of the License, or
#  (at your option) any later version.

[Unit]
Description=Graphical Interface
Documentation=man:systemd.special(7)
Requires=multi-user.target
Wants=display-manager.service
Conflicts=rescue.service rescue.target
After=multi-user.target rescue.service rescue.target display-manager.service
AllowIsolate=yes
  • default.target 这个阶段,会启动 multi-user.target 而这个 target 将自己的子单元放在目录 /etc/systemd/system/multi-user.target.wants 里。这个 target 为多用户支持设定系统环境。非 root 用户会在这个阶段的引导过程中启用。防火墙相关的服务也会在这个阶段启动。multi-user.target 会将控制权交给另一层 basic.target
╭─root@docker-230 ~
╰─# cat /usr/lib/systemd/system/multi-user.target
#  This file is part of systemd.
#
#  systemd is free software; you can redistribute it and/or modify it
#  under the terms of the GNU Lesser General Public License as published by
#  the Free Software Foundation; either version 2.1 of the License, or
#  (at your option) any later version.

[Unit]
Description=Multi-User System
Documentation=man:systemd.special(7)
Requires=basic.target
Conflicts=rescue.service rescue.target
After=basic.target rescue.service rescue.target
AllowIsolate=yes
  • basic.target 单元用于启动普通服务特别是图形管理服务。它通过 /etc/systemd/system/basic.target.wants 目录来决定哪些服务会被启动,basic.target 之后将控制权交给 sysinit.target.
╭─root@docker-230 ~
╰─# tree /etc/systemd/system/multi-user.target.wants
/etc/systemd/system/multi-user.target.wants
├── auditd.service -> /usr/lib/systemd/system/auditd.service
├── chronyd.service -> /usr/lib/systemd/system/chronyd.service
├── crond.service -> /usr/lib/systemd/system/crond.service
├── docker.service -> /usr/lib/systemd/system/docker.service
├── firewalld.service -> /usr/lib/systemd/system/firewalld.service
├── gitlab-runsvdir.service -> /usr/lib/systemd/system/gitlab-runsvdir.service
├── irqbalance.service -> /usr/lib/systemd/system/irqbalance.service
├── kdump.service -> /usr/lib/systemd/system/kdump.service
├── NetworkManager.service -> /usr/lib/systemd/system/NetworkManager.service
├── remote-fs.target -> /usr/lib/systemd/system/remote-fs.target
├── rhel-configure.service -> /usr/lib/systemd/system/rhel-configure.service
├── rsyslog.service -> /usr/lib/systemd/system/rsyslog.service
├── sshd.service -> /usr/lib/systemd/system/sshd.service
└── tuned.service -> /usr/lib/systemd/system/tuned.service
  • sysinit.target 会启动重要的系统服务例如系统挂载,内存交换空间和设备,内核补充选项等等。sysinit.target 在启动过程中会传递给 local-fs.target
╭─root@docker-230 ~
╰─# tree /etc/systemd/system/basic.target.wants
/etc/systemd/system/basic.target.wants
├── microcode.service -> /usr/lib/systemd/system/microcode.service
└── rhel-dmesg.service -> /usr/lib/systemd/system/rhel-dmesg.service

0 directories, 2 files
  • local-fs.target,这个 target 单元不会启动用户相关的服务,它只处理底层核心服务。这个 target 会根据 /etc/fstab 和 /etc/inittab 来执行相关操作。
╭─root@docker-230 ~
╰─# cat /usr/lib/systemd/system/sysinit.target
#  This file is part of systemd.
#
#  systemd is free software; you can redistribute it and/or modify it
#  under the terms of the GNU Lesser General Public License as published by
#  the Free Software Foundation; either version 2.1 of the License, or
#  (at your option) any later version.

[Unit]
Description=System Initialization
Documentation=man:systemd.special(7)
Conflicts=emergency.service emergency.target
Wants=local-fs.target swap.target
After=local-fs.target swap.target emergency.service emergency.target

进程树

我们使用 pstree 命令来看一哈进程树的状态,用户空间的进程都挂在 PID 为 1 的 systemd 下。由 systemd 来管理进程优点多多 😂

╭─root@sg-02 ~
╰─# pstree -p
systemd(1)─┬─accounts-daemon(661)─┬─{accounts-daemon}(689)
           │                      └─{accounts-daemon}(729)
           ├─agetty(971)
           ├─agetty(992)
           ├─aria2c(1010)
           ├─atd(653)
           ├─containerd(2915)─┬─{containerd}(2928)
           │                  ├─{containerd}(2929)
           │                  ├─{containerd}(2930)
           │                  ├─{containerd}(2934)
           │                  ├─{containerd}(2935)
           │                  ├─{containerd}(2950)
           │                  ├─{containerd}(2951)
           │                  ├─{containerd}(2978)
           │                  ├─{containerd}(5360)
           │                  └─{containerd}(8299)
           ├─cron(639)───cron(20506)───sh(20507)───monitor.sh(20509)───ffmpeg(20514)
           ├─dbus-daemon(664)
           ├─dockerd(17771)─┬─{dockerd}(17783)
           │                ├─{dockerd}(17784)
           │                ├─{dockerd}(17785)
           │                ├─{dockerd}(17791)
           │                ├─{dockerd}(17793)
           │                ├─{dockerd}(17800)
           │                ├─{dockerd}(17803)
           │                ├─{dockerd}(3030)
           │                └─{dockerd}(3031)
           ├─fail2ban-server(755)─┬─{fail2ban-server}(1059)
           │                      └─{fail2ban-server}(1060)
           ├─iscsid(896)
           ├─iscsid(897)
           ├─lvmetad(392)
           ├─networkd-dispat(622)───{networkd-dispat}(982)
           ├─nginx(3305)───nginx(17700)
           ├─php-fpm7.2(7497)─┬─php-fpm7.2(1295)
           │                  ├─php-fpm7.2(1296)
           │                  └─php-fpm7.2(5611)
           ├─polkitd(808)─┬─{polkitd}(820)
           │              └─{polkitd}(822)
           ├─rngd(981)
           ├─rpcbind(7125)
           ├─rsyslogd(11567)─┬─{rsyslogd}(11598)
           │                 ├─{rsyslogd}(11599)
           │                 └─{rsyslogd}(11600)
           ├─ss-server(17126)───obfs-server(17138)
             ├─sshd(5357)─┬─sshd(15490)───sshd(15598)───zsh(15619)───pstree(20539)
           │            ├─sshd(20413)───sshd(20414)
           │            └─sshd(20517)
           ├─systemd(5715)───(sd-pam)(5718)
           ├─systemd-journal(12866)
           ├─systemd-logind(652)
           ├─systemd-network(12827)
           ├─systemd-resolve(12839)
           ├─systemd-timesyn(12852)───{systemd-timesyn}(12861)
           └─systemd-udevd(8171)

管理 systemd

讲完了 systemd 的基础知识,接下来就开始动手实践,控制 systemd 的主要命令主要有以下几种:

  • systemctl 命令控制 systemd 的管理系统和服务的命令行工具
  • systemsdm 命令控制 systemd 的管理和服务的图形化工具
  • journalctl 命令查詢 systemd 日志系统
  • loginctl 命令控制 systemd 登入管理器
  • systemd-analyze 分析系统启动效能(类似开机时间?

systemctl 基本用法

启动/重启/停止服务

  • systemctl start NAME
  • systemctl restart NAME
  • systemctl stop NAME

查看服务状态

就如文章开头所提到的,需要注意的是最下面那种带时间戳的是输出的日志

  • systemctl status NAME
╭─root@blog /home/ubuntu
╰─# systemctl status webps                                                                                    130 ↵
● webps.service - WebP Server
   Loaded: loaded (/opt/webps/webps.service; enabled; vendor preset: enabled)
   Active: active (running) since Wed 2020-03-18 13:58:00 UTC; 22h ago
     Docs: https://github.com/n0vad3v/webp_server_go
 Main PID: 5732 (webp-server)
    Tasks: 52 (limit: 684)
   Memory: 185.2M
      CPU: 1min 30.092s
   CGroup: /system.slice/webps.service
           └─5732 /opt/webps/webp-server --config /opt/webps/config.json

Mar 19 05:23:20 blog webp-server[5732]: Save to exhaushttps://p.k8s.li/20200109192923891.png.1584538399.webp ok

列出所有单元:

  • systemctl list-unit-files
╭─root@docker-230 ~
╰─# systemctl list-unit-files
UNIT FILE                                     STATE
proc-sys-fs-binfmt_misc.automount             static
dev-hugepages.mount                           static
dev-mqueue.mount                              static

列出激活的单元

  • systemctl list-units 等同于 systemctl
╭─root@docker-230 ~
╰─# systemctl list-units                                                                                      127 ↵
  UNIT                                         LOAD   ACTIVE SUB       DESCRIPTION
  proc-sys-fs-binfmt_misc.automount            loaded active waiting   Arbitrary Executable File Formats File System
  sys-devices-pci0000:00-0000:00:15.0-0000:03:00.0-host2-target2:0:0-2:0:0:0-block-sda-sda1.device loaded active plu
  sys-devices-pci0000:00-0000:00:15.0-0000:03:00.0-host2-target2:0:0-2:0:0:0-block-sda-sda2.device loaded active plu

重新载入 systemd,扫描新的或有变动的单元:

当向 systemd 的目录中添加显得 unit 文件后需要 reload 一下才能生效

  • systemctl daemon-reload

查询服务是否开机启动

  • systemctl is-enabled crond

设置/取消服务开机自启

  • systemctl enable NAME
  • systemctl disable NAME

远程控制其他服务器的 systemd

systemctl 参数中添加 -H <用户名>@<主机名> 可以实现对其他机器的远程控制,该功能使用 SSH 连接。

╭─root@blog /home/ubuntu
╰─# systemctl status nginx -H sg2
● nginx.service - A high performance web server and a reverse proxy server
   Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)
   Active: active (running) since Mon 2020-02-17 08:41:11 UTC; 1 months 0 days ago
     Docs: man:nginx(8)
 Main PID: 3305
    Tasks: 2 (limit: 1152)
   CGroup: /system.slice/nginx.service
           ├─ 3305 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
           └─17700 nginx: worker process

使用 systemctl 命令杀死服务

  • systemctl kill NAME

journal

systemd 的第二个主要部分是 journal 日志系统,类似于 syslog 但也有些显著区别。如果你喜欢用 sed 、grep 、awk 三剑客来处理日志,那么当你面对 journal 日志系统的时候你就准备掀桌儿吧!因为这是个二进制日志,无法使用常规的命令行文本处理工具来解析它 😂

systemd-analyze

╭─root@docker-230 ~
╰─# systemd-analyze
Startup finished in 489ms (kernel) + 1.547s (initrd) + 10.333s (userspace) = 12.369s

推荐/参考

源码

文档

博客