systemd是为了替换Linux系统中init进程而提出来的,是为系统的启动和管理提供直接服务的,而从它的名字也可以看出来,作为一个守护进程,systemd就是用来守护整个系统的。在systemd提供的一整套工具中,最常用的就是systemctl命令。
systemd要解决的问题
init进程是Linux用来启动服务的传统进程。一般需要自己配置/etc/init.d
的服务,都是使用init进程启动的。
使用init来启动服务不是那么容易的,首先init服务是串行的,不是并行的,不能同时运行多个进程。其次,init进程是通过脚本来启动服务,所以脚本的编写就会变得十分复杂。
systemd成功的解决了init进程的这些问题,虽然systemd引入了一个十分庞大复杂的架构体系。systemd目前在大多数的Linux发行版中都得到了应用,并且取代了init进程成为了系统的1号进程(PID: 1)。我们日常最经常用到的功能就是systemd中的系统管理功能,也就是systemctl命令。
除了systemctl命令以外,systemd还提供了许多其他的命令,用来管理系统的每个方面。例如systemd-analyze
可以用来分析系统启动耗时,hostnamectl
可以用来查看主机信息。
可以被管理的系统资源
作为系统的1号进程,可以管理系统中的所有资源,systemd将这些资源划分成了不同的Unit,具体可见下表。
Unit名称 | 对应系统资源 | 文件命名后缀 |
---|---|---|
Service Unit | 系统服务 | .service |
Target Unit | 由多个Unit组成的一个组 | .target |
Device Unit | 硬件设备 | .device |
Mount Unit | 文件系统的挂载点 | .mount |
Automount Unit | 自动挂载点 | .automount |
Path Unit | 文件或路径 | .path |
Scope Unit | 不由systemd启动的外部进程 | .scope |
Slice Unit | 资源控制组 | .slice |
Snapshot Unit | systemd快照 | |
Socket Unit | 进程间通信的套接字 | .socket |
Swap Unit | 交换区文件 | .swap |
Timer Unit | 任务计划 | .timer |
其实Unit文件在系统中就是ini格式的纯文本文件,其中书写了所有Unit对象的信息。
Unit资源加载路径
systemd会从一组在编译是就已经设定好的路径中加载Unit文件,并且不同的路径的优先级不一样,高优先级目录中的Unit文件会覆盖地优先级目录中的同名文件。鉴于systemd可以以系统实例和用户实例两种模式运行,所以会有两种Unit文件加载顺序。
以系统实例模式(--system
)运行时:
系统单元目录 | 目录所包含内容 | 优先级 |
---|---|---|
/etc/systemd/system.control |
通过dbus API创建的永久系统单元 | 0 |
/etc/systemd/system.control |
通过dbus API创建的临时系统单元 | 1 |
/etc/systemd/transient |
动态配置的临时单元(系统与全局用户共用) | 2 |
/etc/systemd/generator.early |
生成的高优先级单元(系统与全局用户共用) | 3 |
/etc/systemd/system |
本地配置的系统单元 | 4 |
/run/systemd/system |
运行时配置的系统单元 | 5 |
/run/systemd/generator |
生成的中优先级系统单元 | 6 |
/usr/local/lib/systemd/system |
本地软件包安装的系统单元 | 7 |
/usr/lib/systemd/system |
发行版软件包安装的系统单元 | 8 |
/run/systemd/generator.late |
生成的低优先级系统单元 | 9 |
当systemd以用户实例模式(--user
)运行时,所使用的目录的基本上与以系统实例模式类似,只是system
目录一般都换成user
目录,系统单元也替换成用户单元,并且会受到环境变量$XDG_CONFIG_HOME
和$XDG_RUNTIME_HOME
的影响。
一般来说,需要开机不登录就可以运行的程序,都需要存放在系统服务里,即/usr/lib/systemd/system
目录里,如果需要用户登录以后才可以运行的程序,可以放在相应的用户实例模式目录中,即/usr/lib/systemd/usr
目录里。
所有的优先级描述,数字越小优先级越高。
systemctl命令功能
systemctl命令就是针对这些Unit进行管理。
命令格式
|
|
正如systemctl命令格式中所描述的一样,systemctl命令的功能是通过不同的子命令来实现的。
列举Unit
列举Unit可以采用以下命令格式:
|
|
直接运行这个命令可以列出目前已经存在于内存中的Unit,如果需要继续列出其他的Unit,那么就需要增加pattern
了。常用的pattern
有以下这些。
--all
,所有的Unit,包括没有找到配置文件和启动失败的。--failed
,仅列出加载失败的Unit。--type
,仅列出指定类型的Unit。这里的type
值可以使用以上表格中的Unit名称(不带Unit字样且使用小写)。--state
,列出拥有指定状态的Unit。可以使用的状态有:active
reloading
inactive
failed
activating
deactivating
其他相似的列举命令还有:
list-sockets
,列举所有内存中的套接字Unit。list-timers
,列举所有内存中的定时任务Unit。list-unit-files
,列举目前系统中已经安装的Unit文件。is-active
, 列举所有已经激活的Unit。is-failed
,列举所有加载失败的Unit。
查看Unit状态
查看Unit状态需要使用status
命令,格式为:
|
|
这里的pattern
则是各个Unit文件的名称了,但是不需要包括文件后缀。例如systemctl status bluetooth
。status
命令将会列出指定Unit的全部信息。对于Unit的状态是通过Unit名称前面的图标来表现的。
启用和禁用Unit
支持systemd程序在安装的时候,一般都会在/usr/lib/systemd/system
目录中添加一个Unit配置文件,这时就可以使用systemctl提供的命令来完成启用和禁用的任务。
systemctl中用于支持启用和禁用Unit的命令是以下两个:
enable
,启用Unit配置。disable
,禁用Unit配置。
这两个命令在使用的时候均只需要书写Unit配置文件的文件名即可,不需要书写文件名后缀。这两个命令的主要是操作/etc/systemd/system
目录中的符号链接。当调用enable
命令的时候,systemctl会在/etc/systemd/system
目录中创建一个指向/usr/lib/systemd/system
中文件的符号链接。而调用disable
命令的时候,systemctl会撤销/etc/systemd/system
目录中的符号链接。
开机的时候,systemd只会执行
/etc/systemd/system
目录中的配置文件。所以如果要修改系统服务的配置,只需要修改这个目录中的文件即可。
控制Unit状态
控制Unit的状态应该是日常使用systemctl命令最频繁的地方了。常用的Unit状态控制命令主要有以下这些:
start
,启动Unit。stop
,停止Unit。reload
,重新加载Unit的配置。restart
,重新启动Unit。try-restart
,尝试重新启动Unit,如果Unit启动失败,不会报错。reload-or-restart
,重新加载Unit的配置,如果Unit不支持reload
,那么则以重新启动代替。try-reload-or-restart
,尝试重新加载Unit的配置,如果Unit既不能重新加载配置也不能重新启动,那么便什么也不做。kill
,强行终止Unit的运行。clean
,清理Unit的配置、缓存等一切运行痕迹。freeze
,挂起并冻结Unit的所有进程。thraw
,解封被冻结的Unit。
编写一个Unit配置文件
要编写一个Unit配置文件,最快的学习方法就是参考系统本身已经存在的配置文件。要查看Unit配置文件,并不需要实地去保存有配置文件的目录中使用编辑器打开,而是可以借助systemctl提供的命令:systemctl cat [Unit]
。
Unit配置文件就是一个ini格式的文本文件,其中可以分为三个区块:Unit、Sevice和Install。每个区块都是以下形式的键值对。
|
|
Unit区块
Unit区块通常是配置文件的第一个区块,主要用来定义Unit的元信息和与其他Unit的关系。例如会形成以下这样的配置项。
|
|
以上示例表示这个Unit描述是A Sample Service
,依赖于sshd.service
运行。在Unit区块里可以用来定义Unit元信息的项目主要有以下这些。
Description
,Unit的简短描述。Documentation
,Unit的文档所在位置。Required
,当前Unit所依赖于的其他Unit,只有在这些Unit启动之后,当前的Unit才会开始启动。如果列在这里被依赖的Unit没有启动,那么这些Unit会先被启动。Wants
,与当前Unit配合的其他Unit,如果哪些Unit不存在,当前Unit也不会出现启动失败的情况。Requesite
,与Required
类似,但是如果列在这里的Unit没有启动,那么当前的Unit会启动失败。BindsTo
,当前Unit所要绑定到的其他Unit,会随目标Unit一同启动,也会随之停止。PartOf
,与Required
类似,但是如果列在这里的Unit发生了停止或者重启事件,那么当前Unit也会跟随做相同的操作。Before
,指示当前Unit必须在指定Unit之前启动。After
,指示当前Unit必须在指定Unit之后启动。Conflicts
,指示当前Unit不能与指定Unit一同运行。OnFailure
,指示当当前Unit处于failed
状态的时候,需要启动哪些Unit来进行处理。FailureAction
,SuccessAction
,指示当当前Unit处于相应的状态时,要激活哪些系统或者用户活动,可以取以下值。none
,不做任何动作,可在用户模式中使用。reboot
,重启系统。reboot-force
,强制重启系统。reboot-immediate
,强制立刻重启系统。poweroff
,关闭系统。poweroff-force
,强制隔壁系统。exit
,退出程序,可在用户模式中使用。exit-force
,强制退出,可在用户模式中使用。
除了以上这些配置项以外,还有一系列的配置项是用来定义Unit的启动条件的,这些配置项通过对一些条件的定义确定了Unit的启动时机。常用的条件配置项都是Condition...
前缀格式的。,默认情况下配置项中所列举的条件都是与的关系,但是也可以使用Condition...=|...
格式来将配置项变为或的关系,如果在配置值前使用了!
,那么这个条件就会成为一个否定条件。
Condition...
配置项可以常用的条件后缀主要有以下这些:
Architecture
,系统架构,常用值有x86
、x86-64
、arm
、arm-be
、arm64
等。Virtualization
,判断系统是否运行在虚拟化环境中,常用值有qemu
、kvm
、vmware
、microsoft
、oracle
、docker
、podman
、rkt
等。Host
,判断系统的Hostname是否是指定值。KernelVersion
,判断系统内核的版本,可以使用<
、>
等关系比较符。Environment
,判断制定的环境变量是否已经设置。Security
,判断指定的安全技术已经在系统中被启用,常用的值有selinux
、apparmor
、audit
等。Capability
,判断服务管理器是否拥有指定的能力。ACPower
,判断系统是否使用的是外接电源。FirstBoot
,判断系统是否是第一次启动。PathExists
,判断指定的路径是否存在。PathIsDirectory
,判断指定的路径是否是一个目录。PathIsSymbolicLink
,判断指定路径是否是一个符号链接。PathIsMountPoint
,判断指定路径是否是一个挂载点。PathIsReadWrite
,判断指定路径是否是可读写的。PathIsEncrypted
,判断指定路径是否已被加密。DirectoryNotEmpty
,判断指定路径是否非空。FileNotEmpty
,判断指定文件是否非空。FileIsExecutable
,判断指定文件是可执行的。Memory
,判断系统中所安装的内存容量是否满足要求,可以使用<
、>
等关系比较符。CPUs
,判断系统中所安装的CPU数量是否满足要求,可以使用<
、>
等关系比较符。
如果一个配置项目需要有多个值,那么可以直接用空格分隔书写。
通过对于Unit元信息的配置,可以确定Unit的启动条件,使Unit在启动的时候不至于因为缺少必要的资源而出现启动失败。
Service区块
Service区块是Service Unit专有的配置区块,其中配置的是要启动何种程序以及程序要如何启动、如何停止、如何重启等。常用的配置项有以下这些。
Type
,程序以何种方式启动,可以取以下值。simple
,普通方式。forking
,程序以fork()
方式启动。oneshot
,程序只执行一次,systemd会等待程序执行结束再继续启动其他服务。dbus
,程序会等待DBus信号以后启动。notify
,程序启动后会发出通知信号,systemd才会去继续启动其他服务。idle
,程序会等待其他任务都执行结束才会开始执行。
ExecStart
,要启动的程序及其参数。配置值前可以添加以下符号来实现一些特殊的功能,这些符号中功能不相近的可以同时使用。@
,指示将第二个参数映射给argv[0]
。-
,报错信息将会被压制,只会被记录。:
,环境变量不会被应用进去。+
,程序会拥有最大权限。!
,程序会使用提升的权限执行。
ExecStartPre
,ExecStartPost
,指示在主程序启动之前或者之后启动其他的程序。ExecReload
,指定执行systemctl reload
的时候如何激活程序的重载功能,命令中可以使用$MAINPID
来引用程序的PID。ExecStop
,指定执行systemctl stop
时使用何命令来停止程序的运行。ExecStopPost
,指定在程序停止之后还需要执行的命令。RestartSec
,指定重新启动程序的时间间隔。TimeoutStartSec
,指定启动程序之前需要等待的时间。TimeoutStopSec
,指定停止程序之前需要等待的时间。Restart
,程序的重启策略,可取值有no
、on-success
、on-failure
、on-abnormal
、on-watchdog
、on-abort
和always
。OOMPolicy
,设定程序出现Out-Of-Memory状态时系统可以采取的策略,可取值有continue
、stop
、kill
。
Install区块
Install区块是被systemctl enable
和systemctl diasble
命令使用确定Unit要被如何安装和卸载的。这个区块中可以使用的配置项有以下这些。
Alias
,定义Unit的别名。Unit的别名可以在其他的Unit定义文件中使用,但是要求一个Unit的别名必须拥有相同的后缀。并且如果一个Unit拥有多个别名,那么在执行systemctl enable
命令的时候,将会创建多个符号链接。WantedBy
,RequiredBy
,会在.wants
和.requires
命名的Target目录中创建当前Unit的符号链接,使当前Unit可以被指定的Target所包含。Also
,设置与当前Unit一同被安装和卸载的Unit。
常见问题
设置的服务在启动的时候报Default-Start contains no runlevels
这种错误在比较旧的Linux系统中很少出现,反而在比较新的Linux系统中更容易出现。一旦出现了这种错误,服务将不会启动。但其实这种错误也十分容易解决,这种错误一般表示服务的启动脚本中没有定义Default-Start
所可以使用的runlevel
。
要解决这个错误,只需要在服务的启动脚本开头的位置加入以下代码即可。
|
|