Skip to content

systemd工具使用及服务管理

1.systemd常用工具

1.1 systemctl

最常用的systemd工具

status: 查看unit状态
cat: 查看unit配置
start/restart/stop: 控制unit当前状态
set-property: 设置unit配置,例如CPUAccounting、CPUShares等
daemon-reload: 重新加载manager配置
list-units: 列出系统中的unit及其状态
list-dependencies:以树状图展示各个unit之间的依赖关系
enable/disable: 设置/取消unit开机启动,实际等价于创建/删除unit file到/etc/systemd/下面的软连接
mask/unmask: mask与disable类似,但是不同的是,service一旦被mask是无法启动的(即使被别的unit require),等价于创建unit file到/dev/null的链接

1.2 journalctl

查询systemd日志

-u: 按unit查询
-b: 只显示启动日志
-k: 显示内核相关日志

例如,需要查看dbus服务相关日志:

journalctl -u dbus.service

journalctl -b命令只能看到该次启动的日志,如果需要查询之前的启动日志,需要通过以下方式(需要将journald日志切换到磁盘存储,否则每次只能看到当此的启动日志,参考《日志管理》)。

# journalctl --list-boots
IDX BOOT ID                          FIRST ENTRY                 LAST ENTRY             
 -9 9d27b37bc8014865b0c20c957429dc31 Wed 2023-01-18 15:44:22 HKT Fri 2023-02-24 10:27:36 HKT
 -8 aa28b72e95074434a4a81b623c400e7e Tue 2023-02-28 16:35:24 HKT Tue 2023-02-28 16:45:08 HKT
 -7 12a71bb5bd9d4dec993d0e03e0cbe636 Mon 2023-03-06 10:46:43 HKT Mon 2023-03-06 11:07:43 HKT
 ...

然后指定查看哪一次的启动日志

journalctl -b -8

1.3 systemd-analyze

系统启动时间分析工具

blame: 分析系统中服务启动时间
plot: 图形化显示系统中服务的启动顺序,可以通过网页或者图形编辑器打开
dump: 查看每个服务的状态,cgroup mask等,比systemctl satus更详细

1.3.1 systemd-analyze blame

该命令主要作用是分析系统中当此启动里服务的启动时间,会默认按时间从长到短列出各服务的启动时间顺序。

# systemd-analyze blame
8.814s kdump.service
2.442s NetworkManager-wait-online.service
 944ms dev-vda1.device
 568ms sshd-keygen@rsa.service
 295ms initrd-switch-root.service
 214ms var-lib-nfs-rpc_pipefs.mount
 206ms dnf-makecache.service
 194ms dracut-initqueue.service
 143ms user@0.service
 135ms ipmi.service
 118ms initrd-parse-etc.service
 103ms chronyd.service
  99ms NetworkManager.service
  99ms systemd-logind.service
  92ms avahi-daemon.service
  88ms auditd.service
  71ms lvm2-monitor.service
  ...

这里方便优化排查系统的启动时间。找到耗时的服务,再结合该服务自己的日志或者其他排查手段排查服务耗时的原因。 例如上面看到kdump服务的启动时间最久,通过strace工具看到它最耗时的过程其实是加载kdump内核。

enter image description here enter image description here#1193px #262px

1.3.2 systemd-analyze plot

通过该方式生成的图像格式,如果系统没有安装图形界面将无法查看,此时可以通过 lrzsz等工具拷贝出环境,在其他环境查看,如

# systemd-analyze plot > boot.svg
# sz boot.svg 
Received - boot.svg  2.12 MB/s Spend: 0 seconds

浏览器打开后可以很详细的看到各服务的启动顺序以及耗时。

enter image description here

1.4 其他工具

systemd-cgls: 以树形态查看系统中cgroup的关系结构
systemd-nspawn: 与chroot类似,但是会挂载proc、sys文件系统

1.3.3 systemd-cgls

systemd-cgls命令用于显示systemd控制组(cgroup)的层次结构。

systemd-cgls 
Control group /:
-.slice
├─user.slice (#1203)
│ → user.invocation_id: 90e555a5089c481bbffcc9bbb8e48019
│ → trusted.invocation_id: 90e555a5089c481bbffcc9bbb8e48019
│ └─user-0.slice (#9785)
│   → user.invocation_id: e4a4009795694a7ca82ad4344ac7e713
│   → trusted.invocation_id: e4a4009795694a7ca82ad4344ac7e713
│   ├─session-22.scope (#10065)
│   │ → user.invocation_id: 183a492473c94efe8e3c4b1e1745f0b0
│   │ → trusted.invocation_id: 183a492473c94efe8e3c4b1e1745f0b0
│   │ ├─33878 sshd: root [priv]
│   │ ├─33892 sshd: root@pts/0
│   │ ├─33893 -bash
│   │ ├─35517 systemd-cgls
│   │ └─35518 less
│   └─user@0.service … (#9865)
│     → user.delegate: 1
│     → trusted.delegate: 1
│     → user.invocation_id: 63e944859fa04569817e2847cedca029
│     → trusted.invocation_id: 63e944859fa04569817e2847cedca029
│     └─init.scope (#9905)
│       ├─33882 /usr/lib/systemd/systemd --user
│       └─33885 (sd-pam)
├─init.scope (#25)
│ └─1 /usr/lib/systemd/systemd --switched-root --system --deserialize 31
└─system.slice (#65)
  ├─rngd.service (#2661)
  │ → user.invocation_id: 4cc6fb282cc54f9bbb09055d771f1dde
  │ → trusted.invocation_id: 4cc6fb282cc54f9bbb09055d771f1dde
  │ └─685 /usr/sbin/rngd -f -x pkcs11 -x nist
  ├─irqbalance.service (#2581)
  │ → user.invocation_id: 5c40d8f7f503434483342f781b534ad3
  │ → trusted.invocation_id: 5c40d8f7f503434483342f781b534ad3
  │ └─683 /usr/sbin/irqbalance --foreground

相比直接去cgroup目录下查看,systemd-cgls会把每个cgroup里的进程都列出来,并且各个层级的关系更加清晰。 所以推荐使用 systemd-cgls来查看系统中的cgroup层级。

1.3.4 systemd-nspawn

chroot使用方式类似,但是进入的目录需要是一个 OS tree形式。否则,会有如下提示:

# systemd-nspawn -D ./rootfs
Directory /root/rootfs doesn't look like it has an OS tree (/usr/ directory is missing). Refusing.

如果目录是正确的 OS tree形式,如下

# ls rootfs/
bin  boot  data  dev  etc  home  lib  lib64  lost+found  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var

那就可以通过 systemd-nspawn创建一个轻量级容器。

# systemd-nspawn -D ./rootfs
Spawning container rootfs on /root/rootfs.
Press ^] three times within 1s to kill container.
[root@rootfs ~]#

如果需要模拟真实环境,还可以加上 -b选项在容器中启动systemd。

2. systemd服务

2.1 服务开机启动

systemd在启动时,会根据 /usr/lib/systemd/system-preset/ 以及 /usr/lib/systemd/user-preset/ 下的preset文件。 当前系统主要默认配置了以下几个配置文件:

文件名 作用
90-default.preset 控制默认启动的系统服务
99-default-disable.preset 默认禁用所有服务
85-display-manager.preset 控制桌面相关的服务是否需要自启动

preset文件按照文件名决定执行顺序,后执行的会覆盖前面的配置,前面的数字越小,优先级越高,越后执行,所以在 99-default-disable.preset里即便disable了所有的服务,还是可以在 90-default.preset85-display-manager.preset中开启部分所需要的服务。

2.2 systemd unit种类

2.2.1 service

这是最常接触的unit,控制系统中的进程运行(包括daemon进程),例如dbus.serivce、docker.serivce等,通常会通过ExecStart=指定程序运行的动作,ExecReload=指定systemctl reload的动作,ExecStop=指定停止unit的动作。

[root@localhost ~]# systemctl cat dbus.service 
# /usr/lib/systemd/system/dbus-broker.service
[Unit]
Description=D-Bus System Message Bus
Documentation=man:dbus-broker-launch(1)
DefaultDependencies=false
After=dbus.socket
Before=basic.target shutdown.target
Requires=dbus.socket
Conflicts=shutdown.target

[Service]
Type=notify
Sockets=dbus.socket
OOMScoreAdjust=-900
LimitNOFILE=16384
ProtectSystem=full
PrivateTmp=true
PrivateDevices=true
ExecStart=/usr/bin/dbus-broker-launch --scope system --audit
ExecReload=/usr/bin/busctl call org.freedesktop.DBus /org/freedesktop/DBus org.freedesktop.DBus ReloadConfig

[Install]
Alias=dbus.service

通过systemctl status dbus.service可以看到service的一些状态信息,然后接着会打印一些该服务相关journal日志

systemctl status sshd
● sshd.service - OpenSSH server daemon
     Loaded: loaded (/usr/lib/systemd/system/sshd.service; enabled; vendor preset: enabled)
     Active: active (running) since Fri 2023-04-21 11:52:56 CST; 2 weeks 4 days ago
       Docs: man:sshd(8)
             man:sshd_config(5)
   Main PID: 1208 (sshd)
      Tasks: 1 (limit: 9183)
     Memory: 8.7M
        CPU: 199ms
     CGroup: /system.slice/sshd.service
             └─1208 "sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups"
  • 服务名前面的点("●"),有不同的颜色和形状,简单表示了该服务当前的状态。如下表格所示
点的颜色形状 表示的状态
"○" "inactive" 或者 "maintenance"
"●"(绿色) "active"
"●"(白色) "deactivating"
"×" "failed" 或者 "error"
"↻" "reloading"
  • “Loaded:“这一行,显示该服务是否被加载进内存,如果是则会显示"loaded",加载失败会显示"error",未找到unit file会显示"not-found",如果unit file里配置错误则会显示"bad-setting",如果服务被mask则会显示"masked"。
  • 后面的路径为加载的unit file的路径
  • 再后面为当前该服务是否设置开机启动(enabled),而 “vendor preset” 则表示发行版厂商是否将该服务预设为开机启动
  • “Active:"这一行,显示该服务的运行状态,
  • 括号前的为当前状态,它可以是以下几种状态之一:
    • active:表示 unit 正在运行。
    • inactive:表示 unit 已经停止运行。
    • activating:表示 unit 正在启动中。
    • deactivating:表示 unit 正在停止中。
    • failed:表示 unit 启动失败。
  • 括号里的为子状态,它可以是以下几种状态之一:
    • running:表示 unit 正在运行。
    • exited:表示 unit 已经停止运行。
    • waiting:表示 unit 正在等待某些条件的发生,例如网络连接、文件系统挂载等等。
    • start-pre:表示 unit 正在启动前的准备工作。
    • start-post:表示 unit 启动后的工作。
    • stop-pre:表示 unit 停止前的准备工作。
    • stop-post:表示 unit 停止后的工作。
  • 后面的时间为该服务最后一次启动的时间

2.2.2 timer

定时器,在固定的时间去触发激活其他的unit(默认是同名的service),通常通过[Timer]字段中的OnActiveSec=/OnCalendar=等配置项控制触发时间(详细可参考man systemd.timer),sysstat-collect中配置的*:00/10意思就是整点开始,每10分钟触发一次

[root@localhost ~]# systemctl cat sysstat-collect.timer 
# /usr/lib/systemd/system/sysstat-collect.timer
# /usr/lib/systemd/system/sysstat-collect.timer
# (C) 2014 Tomasz Torcz <tomek@pipebreaker.pl>
#
# sysstat-12.6.0 systemd unit file:
#        Activates activity collector every 10 minutes

[Unit]
Description=Run system activity accounting tool every 10 minutes

[Timer]
OnCalendar=*:00/10

[Install]
WantedBy=sysstat.service

timer定时器是服务中一个很重要的机制,通过 systemctl status可以看到它相比service,多了Trigger和Triggers字段。Trigger指的是下次触发的时间,以及还有多久到达,Triggers则是显示了该计时器所激活的服务。

systemctl status sysstat-collect.timer
● sysstat-collect.timer - Run system activity accounting tool every 10 minutes
     Loaded: loaded (/usr/lib/systemd/system/sysstat-collect.timer; enabled; vendor preset: disabled)
     Active: active (waiting) since Thu 2023-05-11 20:04:47 CST; 1 week 0 days ago
      Until: Thu 2023-05-11 20:04:47 CST; 1 week 0 days ago
    Trigger: Thu 2023-05-18 20:40:00 CST; 8min left
   Triggers: ● sysstat-collect.service

该例子中,该timer触发的服务是sysstat-collect.service,8分钟后再次触发。

2.2.3 mount

控制系统中的挂载点,通常通过[Mount]字段中的Where=/What=/Type=去控制挂载的位置、设备和类型,正常只需要配置/etc/fstab就行了,systemd-fstab-generator会自动将配置的fstab中的挂载点生成对应的mount unit。

[root@localhost ~]# systemctl cat mnt.mount
# /run/systemd/generator/mnt.mount
# Automatically generated by systemd-fstab-generator

[Unit]
Documentation=man:fstab(5) man:systemd-fstab-generator(8)
SourcePath=/etc/fstab
Before=local-fs.target
Requires=systemd-fsck@dev-vdb1.service
After=systemd-fsck@dev-vdb1.service
After=blockdev@dev-vdb1.target

[Mount]
What=/dev/vdb1
Where=/mnt
Type=ext4
Options=noatime,acl,user_xattr

例如,在/etc/fstab中配置了如下挂载项

# cat /etc/fstab
/dev/vdc1 /data/ ext4 noatime,acl,user_xattr 1 1

那重启(systemd-fstab-generator会在启动时候执行)后,系统中就可以看到有对应的mount服务,服务配置与fstab中配置相对应。

# systemctl list-units -a | grep data
data.mount                                                                                             loaded    active   mounted   /data
# systemctl cat data.mount
# /run/systemd/generator/data.mount
# Automatically generated by systemd-fstab-generator

[Unit]
Documentation=man:fstab(5) man:systemd-fstab-generator(8)
SourcePath=/etc/fstab
Before=local-fs.target
Requires=systemd-fsck@dev-vdc1.service
After=systemd-fsck@dev-vdc1.service
After=blockdev@dev-vdc1.target

[Mount]
What=/dev/vdc1
Where=/data
Type=ext4
Options=noatime,acl,user_xattr

2.2.4 automount

和mount类似,区别是automount仅会在用户访问对应目录的时候去挂载它,而不是像mount一样从启动后一直挂载。 可以通过 autofs使用automount能力。当前环境没有默认安装。下面例子介绍如何使用automount服务。

1 安装autofs服务

dnf install -y autofs

2 然后在 /etc/auto.master 文件中配置 automount 服务。

vim /etc/auto.master

在文件末尾添加以下内容:

/mnt/ /etc/auto.devices --timeout=10

其中, /mnt/ 是挂载点所在的路径(不是设备挂载点), /etc/auto.devices 是设备挂载配置文件的路径, --timeout=10 表示设备挂载超时时间为10秒。

3 在 /etc/auto.devices 文件中配置需要挂载的设备。可以使用以下命令打开 /etc/auto.devices 文件:

vim /etc/auto.devices

在文件中添加需要挂载的设备信息,例如:

mymnt -fstype=auto :/dev/vdb

其中, mymnt 是挂载的目录, -fstype=auto 表示自动检测设备文件系统类型, :/dev/vdb 是设备的路径。

4 重启 autofs 服务

systemctl restart autofs

5 现在,当需要访问 mymnt 设备时,只需要访问 /mnt/ 目录下的 mymnt 的目录, automount 服务会自动将 /dev/vdb 设备挂载到这个目录下。 这里需要注意,进入 /mnt目录时,是看不到 mymnt目录的,并且也无法通过tab补全,但是你可以直接访问它

cd /mnt/mymnt

当访问 /mnt/mymnt 目录时, automount 服务会自动将 /dev/vdb 设备挂载到这个目录下。

# lsblk
NAME   MAJ:MIN RM  SIZE RO TYPE MOUNTPOINTS
sr0     11:0    1 18.8M  0 rom  
zram0  251:0    0    8G  0 disk [SWAP]
vda    252:0    0  100G  0 disk 
└─vda1 252:1    0  100G  0 part /
vdb    252:16   0  200G  0 disk /mnt/mymnt
vdc    252:32   0  300G  0 disk 
└─vdc1 252:33   0  300G  0 part /data

如果一段时间内没有访问该目录, automount 服务会自动卸载设备,以释放系统资源。

2.2.5 target

target unit和上面几个unit不太一样,它本身不执行任何操作,仅仅只是一个“代表”,代表了一个群组的unit,target靠Requires=/After=等unit的依赖配置来控制每个群组unit的依赖关系和启动顺序,通过systemctl list-dependencies可以看到。

multi-user.target
● ├─acpid.service
● ├─arpwatch.service
● ├─atd.service
● ├─auditd.service
● ├─avahi-daemon.service
● ├─chrony-wait.service
● ├─chronyd.service
● ├─crond.service
● ├─gssproxy.service

系统启动时就是通过一个一个的target服务,来按顺序拉起各个子系统所依赖的具体服务。 例如,启动时系统只需要拉起 default.target(本系统中为 multi-user.targetgraphical.target),其他的各个target只要在他们的unit file中指定

[Install]
WantedBy=multi-user.target

就可以实现在 multi-user.target启动前启动。

2.2.6 path

和timer unit类似,主要作用是当监控的路径改变时,触发同名的service,在[Path]字段中的DirectoryNotEmpty=/PathChanged=等去控制监控的路径以及对应的模式。

[root@localhost ~]# systemctl cat systemd-ask-password-console.path
# /usr/lib/systemd/system/systemd-ask-password-console.path
#  SPDX-License-Identifier: LGPL-2.1-or-later
#
#  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=Dispatch Password Requests to Console Directory Watch
Documentation=man:systemd-ask-password-console.path(8)
DefaultDependencies=no
Conflicts=shutdown.target emergency.service
After=plymouth-start.service
Before=paths.target shutdown.target cryptsetup.target
ConditionPathExists=!/run/plymouth/pid

[Path]
DirectoryNotEmpty=/run/systemd/ask-password
MakeDirectory=yes

以下以实例介绍path服务如何使用。

1 创建一个新的 .path 单元文件。在 /etc/systemd/system 目录下创建一个新文件,例如 my-path-monitor.path 。在这个文件中,定义要监视的路径和触发条件。例如:

[Unit]
Description=Monitor changes in /data/testpath directory

[Path]
PathModified=/data/testpath
Unit=my-triggered-service.service

[Install]
WantedBy=multi-user.target

这个 .path 单元文件将监视 /data/testpath 目录的更改。当该目录发生更改时,它将触发名为 my-triggered-service.service 的服务。

2 创建一个与 .path 单元关联的服务单元。在本例中,需要创建一个名为 my-triggered-service.service 的服务文件。在 /etc/systemd/system 目录下创建一个新文件,并定义服务的行为。例如:

[Unit]
Description=Service triggered by path changes

[Service]
Type=oneshot
ExecStart=/usr/bin/echo "123"

这个服务将在 .path 单元触发时执行 echo 123 脚本。

3 重新加载 systemd 配置以识别新的单元文件:

systemctl daemon-reload

4 启动 .path 单元:

systemctl start my-path-monitor.path

5 现在,修改或者删除这个目录

rm -r /data/testpath

6 然后查看 my-triggered-service.service 日志,你会发现,该服务被path服务触发了

# systemctl status  my-triggered-service.service
○ my-triggered-service.service - Service triggered by path changes
     Loaded: loaded (/etc/systemd/system/my-triggered-service.service; static)
     Active: inactive (dead) since Mon 2023-05-29 03:09:15 UTC; 9s ago
TriggeredBy: ● my-path-monitor.path
    Process: 1227670 ExecStart=/usr/bin/echo 123 (code=exited, status=0/SUCCESS)
   Main PID: 1227670 (code=exited, status=0/SUCCESS)
        CPU: 1ms

May 29 03:09:15 localhost.localdomain systemd[1]: Starting my-triggered-service.service - Service triggered by path changes...
May 29 03:09:15 localhost.localdomain echo[1227670]: 123
May 29 03:09:15 localhost.localdomain systemd[1]: my-triggered-service.service: Deactivated successfully.
May 29 03:09:15 localhost.localdomain systemd[1]: Finished my-triggered-service.service - Service triggered by path changes.

2.2.7 socket

socket unit和timer/path一样,也是配合service unit使用的,通过socket unit指定systemd监听的socket,当socket有写入的时候systemd唤醒该unit对应的service unit。

[root@localhost ~]# systemctl cat acpid
# /usr/lib/systemd/system/acpid.service
[Unit]
Description=ACPI Event Daemon
Documentation=man:acpid(8)
Requires=acpid.socket

[Service]
StandardInput=socket
EnvironmentFile=/etc/sysconfig/acpid
ExecStart=/usr/sbin/acpid -f $OPTIONS

[Install]
Also=acpid.socket
WantedBy=multi-user.target
[root@localhost ~]# systemctl cat acpid.socket
# /usr/lib/systemd/system/acpid.socket
[Unit]
Description=ACPID Listen Socket
Documentation=man:acpid(8)

[Socket]
8ListenStream=/run/acpid.socket

[Install]
WantedBy=sockets.target

下面以一个实例介绍如何使用socket服务

1 安装 nc工具

dnf install nc -y

2 为 Python 应用程序创建一个 systemd 服务单元文件。在 /etc/systemd/system 目录下创建一个名为 socket_server.service 的文件,并添加以下内容:

[Unit]
Description=Socket Server

[Service]
ExecStart=nc -l 12345
User=nobody
Group=nobody

3 创建一个 systemd socket 单元文件。在 /etc/systemd/system 目录下创建一个名为 socket_server.socket 的文件,并添加以下内容:

[Unit]
Description=Socket Server Socket

[Socket]
ListenStream=12345
Accept=yes

[Install]
WantedBy=sockets.target

这个 .socket 单元文件将在 12345 端口上创建一个 TCP 套接字,并在接收到连接请求时激活 socket_server.service

4 重新加载 systemd 配置以识别新的单元文件:

systemctl daemon-reload

5 启动 .socket 单元:

systemctl start socket_server.socket

现在,套接字已经创建并开始监听连接。当有客户端连接到端口 12345 时, socket_server.service 将被激活并执行 nc -l 12345

6 使用 telnetnc (netcat)连接到端口 12345:

 telnet localhost 12345

2.2.8 swap

管理swap分区挂载,和mount类似,通常也是通过/etc/fstab配置,然后systemd-fstab-generator自动生成

1 创建一个 swap 文件(如果你还没有一个):

fallocate -l 1G /swapfile

2 将文件设置为 swap:

mkswap /swapfile

3 在 /etc/fstab 文件的末尾添加一行,指定 swap 分区或文件的路径、挂载点(使用 none )、文件系统类型(使用 swap )以及挂载选项(使用 sw )。最后,添加两个数字 0 0 ,表示不需要进行文件系统检查。例如:

/swapfile none swap sw 0 0

现在,你已经成功地在 fstab 中配置了 swap。在每次系统启动时,swap 分区或文件都会自动启用。 重启后看到,已自动创建对应的swap服务

# systemctl list-units -a | grep swap
  swapfile.swap                                                                        loaded    active   active    /swapfile

你可以使用 free -hswapon --show 命令查看 swap 的状态。通过 free命令看到

# free -h
               total        used        free      shared  buff/cache   available
Mem:           7.5Gi       195Mi       7.1Gi         9Mi       210Mi       7.1Gi
Swap:          1.0Gi          0B       1.0Gi

2.2.9 device

是由systemd-udevd根据/sys/和/dev下的设备创建出来的unit,主要作用是控制设备和设备之间以及设备和其他unit之间的依赖关系。

[root@localhost ~]# systemctl list-dependencies mnt.mount
mnt.mount
● ├─-.mount
● ├─dev-vdb1.device
● ├─system.slice
○ └─systemd-fsck@dev-vdb1.service

2.2.10 slice

slice类似target,也是一组unit的代表,但是不同的是slice用来在cgroup中进行资源控制,正常系统默认将unit分为user.slice(用户会话)/system.slice(系统进程)/machine.slice(容器和虚拟机进程)

[root@localhost ~]# tree -d /sys/fs/cgroup/
/sys/fs/cgroup/
├── dev-hugepages.mount
├── dev-mqueue.mount
├── init.scope
├── proc-fs-nfsd.mount
├── proc-sys-fs-binfmt_misc.mount
├── sys-fs-fuse-connections.mount
├── sys-kernel-config.mount
├── sys-kernel-debug.mount
├── sys-kernel-tracing.mount
├── system.slice
│   ├── NetworkManager.service
│   ├── acpid.service
│   ├── arpwatch.service
│   ├── atd.service
│   ├── auditd.service
│   ├── avahi-daemon.service
│   ├── chronyd.service
│   ├── systemd-logind.service
│   ├── systemd-udevd.service
│   │   └── udev
│   ├── systemd-userdbd.service
│   ├── tmp.mount
│   └── var-lib-nfs-rpc_pipefs.mount
└── user.slice
    └── user-0.slice
        ├── session-1702.scope
        ├── session-22304.scope

2.2.11 scope

与slice/target类似,不同的是scope unit仅可以通过systemd dbus的接口去创建,并且scope unit中的进程不会自己去创建子进程。

3. unit配置

systemd支持的unit配置参数数量庞大,大部分问题也都和配置有关系,这里以cpu资源控制相关配置举例,cpu的资源控制配置主要由以下几种方式决定:

3.1 default配置

systemd有一个unit配置表ConfigTableItem,表中的配置项都会有默认初始值,如果后续没有去手动配置,则这些属性都会使用代码中初始值,CPUAccounting是false。可以在src/core/main.c文件中查看

static int parse_config_file(void) {
        const ConfigTableItem items[] = {
                { "Manager", "LogLevel",                     config_parse_level2,                0,                        NULL                              },
                { "Manager", "LogTarget",                    config_parse_target,                0,                        NULL                              },
                .....

3.2 manager配置文件

/etc/systemd/system.conf
/etc/systemd/user.conf
/etc/systemd/system.conf.d/*
/etc/systemd/user.conf.d/*

3.3 unit file

unit自己的配置文件,一般在 /usr/lib/systemd/system/下。可以通过 systemctl cat a.service查看具体内容(这里要注意可能有除了unit file的额外配置文件,所以通过systemctl cat可以看的更全, 通过 systemctl set-property 命令手动设置,这种方式也是最终会在/etc/systemd下面生成对应unit的额外配置文件),如下示例:

# systemctl set-property rsyslog.service CPUQuota=50%
# systemctl show rsyslog.service | grep CPUQuota
...
DropInPaths=/etc/systemd/system.control/rsyslog.service.d/50-CPUQuota.conf

可以看到最终会生成 /etc/systemd/system.control/rsyslog.service.d/50-CPUQuota.conf这个额外配置文件。

# cat /etc/systemd/system.control/rsyslog.service.d/50-CPUQuota.conf
# This is a drop-in unit file extension, created via "systemctl set-property"
# or an equivalent operation. Do not edit.
[Service]
CPUQuota=50%

4. 服务启动顺序

systemd服务的启动顺序,是通过配置相互依赖关系实现的,unit与unit之间的依赖关系分为多种情况。 首先,systemd unit会自动生成依赖,自动生成的依赖分为两种:隐式依赖(Implicit Dependencies)和默认依赖(Default Dependencies)。

  • 隐式依赖通过unit Type和Unit的配置决定,例如dbus.service的Type=dbus,会隐式依赖dbus.socket(等价于Requires= 以及 After=dbus.socket)。
  • 默认依赖和隐式依赖差不多,但是可以通过DefaultDependencies=选项控制开关,例如A.target里配置了Wants= 或者 Requires=B,那A会默认被配置一个After=B。另外,所有的target都会默认配置一个Conflicts=shutdown.target和Before=shutdown.target,为了保证在关机前所有target都已退出。Triggers/TriggeredBy则会默认配置在socket/path/mount等他们对应的service里会。

除此之外,显示配置的依赖关系主要有6个(两个放一起因为他们逻辑相同只不过是行为完全相反):

  • Wants/WantedBy:弱依赖,A Wants= B,A启动的时候会尝试拉起B,如果B启动失败,A会继续启动。(除了直接在unit file里面配置外,还可以通过在unit file外创建.wants目录实现同样的效果)。
  • Requires/RequiredBy:强依赖,A Requires= B,同Wants一样,A启动的时候会尝试拉起B,如果B启动失败,A不会启动。(同Wants一样,也可以通过创建.requires/目录实现相同效果)。
  • BindsTo/BoundBy:更强依赖,与Requires不一样的是,A BindsTo= B,如果A关闭了,B也会关闭,而Requires的话,B不会受影响。
  • Before/After:字面意思,例如A Before= B,这样在A启动后,B才能启动,同样,关闭流程则基于启动流程相反,在A Before= B时,如果A和B都需要关闭,那B先关闭,A才会关闭。
  • PartOf/ConsistsOf:和Requires相似,不一样的是PartOf只影响关闭和重启动作,例如A PartOf= B,那B被关闭或者重启时,A也会被关闭或者重启。
  • Conflicts/ConflictedBy:如字面意思,A Conflicts= B,那启动A就会关闭B。

例如,如果想要查看rsyslog服务是由谁拉起的,可以通过 list-dependencies命令:

# systemctl list-dependencies rsyslog --reverse
rsyslog.service
● └─multi-user.target
○   └─graphical.target

而之所以有如此依赖关系,是因为rsyslog服务的unit file中有如下配置:

systemctl cat rsyslog.service 
# /usr/lib/systemd/system/rsyslog.service
[Unit]
...

[Install]
WantedBy=multi-user.target
...