目录
1.Systemd service
Systemd是一个专用于Linux操作系统的系统与服务管理器,也是CDOS操作系统的系统服务管理器。当作为启动进程(PID=1)运行时,它将作为初始化系统运行,也就是启动并维护各种用户空间的服务。Systemd将各种系统启动和运行相关的对象, 表示为各种不同类型的单元(unit), 并提供了处理不同单元之间依赖关系的能力。其中,service单元是用于封装一个后台服务进程。通过编写以“.service”结尾的单元(unit)配置文件,允许用户开发的应用程序以系统服务的方式启动和运行。
2.Systemd service编写
2.1.基本结构
Systemd服务的内容主要分为三个部分:控制单元[Unit]的定义、服务[Service]的定义、以及安装[Intall]的定义。
2.2.基本语法
Systemd语法和“.desktop”文件的语法比较像,也比较类似Windows下的“.ini”文件。主要格式见下面service例子,需要注意的如下几点:
- Systemd单元文件中的以 “#” 开头的行后面的内容会被认为是注释;
- Systemd下的布尔值,1、yes、on、true都是开启,0、no、off、false都是关闭;
- Systemd下的时间单位默认是秒,所以要用毫秒(ms)分钟(m)等请显式说明。
[Unit]
Description=Network Manager
Documentation=man:NetworkManager(8)
Wants=network.target
After=network-pre.target dbus.service
Before=network.target
[Service]
Type=dbus
BusName=org.freedesktop.NetworkManager
ExecReload=/bin/kill -HUP $MAINPID
ExecStart=/usr/sbin/NetworkManager --no-daemon
Restart=on-failure
# NM doesn't want systemd to kill its children for it
KillMode=process
CapabilityBoundingSet=CAP_NET_ADMIN CAP_DAC_OVERRIDE CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SETGID CAP_SETUID CAP_SYS_MODULE CAP_AUDIT_WRITE CAP_KILL CAP_SYS_CHROOT
# ibft settings plugin calls iscsiadm which needs CAP_SYS_ADMIN
#CapabilityBoundingSet=CAP_SYS_ADMIN
ProtectSystem=true
ProtectHome=read-only
[Install]
WantedBy=multi-user.target
Also=NetworkManager-dispatcher.service
2.3.定义控制单元[Unit]
单元配置文件中的[Unit],包含与单元类型无关的通用信息。可用的选项(亦称"指令"或"属性")如下:
Description:
对单元进行简单描述的字符串。
Documentation:
一组用空格分隔的文档URI列表,这些文档是对此单元的详细说明。可接受“http://”、“https://”、“file:”、“info:”、“man:”五种URI类型。这些URI应该按照相关性由高到低列出。 比如,将解释该单元作用的文档放在第一个,最好再紧跟单元的配置说明,最后再附上其他文档。可以多次使用此选项,依次向文档列表尾部添加新文档。但是,如果为此选项设置一个空字符串,那么表示清空先前已存在的列表。
Requires:
设置此单元所必须依赖的其他单元。当此单元被启动时,所有这里列出的其他单元也必须被启动。如果此处列出的某个单元启动失败、并且恰好又设置了到这个失败单元的 After 依赖,那么将不会启动此单元。想要添加多个单元,可以多次使用此选项,也可以设置一个空格分隔的单元列表。注意,此选项并不影响单元之间的启动或停止顺序。要想调整单元之间的启动或停止顺序,请使用 After 或 Before 选项。 例如,在 foo.service 中设置了 Requires=bar.service ,但是并未使用 After 或 Before 设定两者的启动顺序,那么,当需要启动 foo.service 的时候,这两个单元会被并行的同时启动。 建议使用 Wants 代替 Requires 来设置单元之间的非致命依赖关系,从而有助于获得更好的健壮性,特别是在某些单元启动失败的时候。
Requisite:
与 Requires 类似。不同之处在于:当单元启动时,这里列出的单元必须已经全部处于启动成功的状态,否则,将不会启动此单元(也就是直接返回此单元启动失败的消息),并且同时也不会启动那些尚未成功启动的单元。
Wants:
此选项是 Requires 的弱化版。当此单元被启动时,所有这里列出的其他单元只是尽可能被启动。但是,即使某些单元不存在或者未能启动成功,也不会影响此单元的启动。推荐使用此选项来设置单元之间的依赖关系。
BindsTo:
与 Requires 类似,但是依赖性更强:如果这里列出的任意一个单元停止运行或者崩溃,那么也会连带导致该单元自身被停止。这就意味着该单元可能因为这里列出的任意一个单元的主动退出、某个设备被拔出、某个挂载点被卸载,而被强行停止。
PartOf:
与 Requires类似,不同之处在于:仅作用于单元的停止或重启。其含义是,当停止或重启这里列出的某个单元时,也会同时停止或重启该单元自身。注意,这个依赖是单向的,该单元自身的停止或重启并不影响这里列出的单元。
Conflicts:
指定单元之间的冲突关系。接受一个空格分隔的单元列表,表明该单元不能与列表中的任何单元共存,也就是说:(1)当此单元启动的时候,列表中的所有单元都将被停止;(2)当列表中的某个单元启动的时候,该单元同样也将被停止。注意,此选项与 After 和 Before 选项没有任何关系。
Before/After:
强制指定单元之间的先后顺序,接受一个空格分隔的单元列表。假定 foo.service 单元包含 Before=bar.service 设置,那么当两个单元都需要启动的时候,bar.service 将会一直延迟到 foo.service 启动完毕之后再启动。 注意,停止顺序与启动顺序正好相反,也就是说,只有当 bar.service 完全停止后,才会停止 foo.service 单元。
After 的含义与 Before 正好相反。假定 foo.service 单元包含 After=bar.service 设置,那么当两个单元都需要启动的时候,foo.service 将会一直延迟到 bar.service 启动完毕之后再启动。注意,停止顺序与启动顺序正好相反,也就是说,只有当 foo.service 完全停止后,才会停止 bar.service 单元。
注意,此二选项仅用于指定先后顺序,而与 Requires、Wants、BindsTo 这些选项没有任何关系。不过在实践中也经常遇见将某个单元同时设置到 After 与 Requires 选项中的情形。可以多次使用此二选项,以将多个单元添加到列表中。 假定两个单元之间存在先后顺序(无论谁先谁后),并且一个要停止而另一个要启动,那么永远是“先停止后启动”的顺序。但如果两个单元之间没有先后顺序,那么它们的停止和启动就都是相互独立的,并且是并行的。对于不同类型的单元来说,判断启动是否已经完成的标准并不完全相同。 特别的,对于设置在 Before/After 中的服务单元来说,只有在服务单元内配置的所有启动命令全部都已经被调用,并且对于每一个被调用的命令,要么确认已经调用失败、要么确认已经成功运行的情况下,才能认为已经完成启动。
OnFailure:
接受一个空格分隔的单元列表。当该单元进入失败“failed”状态时,将会启动列表中的单元。
PropagatesReloadTo/ReloadPropagatedFrom:
接受一个空格分隔的单元列表。PropagatesReloadTo 表示在reload该单元时,也同时reload所有列表中的单元。ReloadPropagatedFrom 表示在reload列表中的某个单元时,也同时reload该单元。
JoinsNamespaceOf:
接受一个空格分隔的单元列表,表示将该单元所启动的进程加入到列表单元的网络及临时文件(/tmp,/var/tmp)的名字空间中。如果单元列表中仅有一个单元处于已启动状态,那么该单元将加入到这个唯一已启动单元的名字空间中。如果单元列表中有多个单元处于已启动状态,那么该单元将随机加入一个已启动单元的名字空间中。此选项仅适用于支持 PrivateNetwork 与/或 PrivateTmp 指令的单元。
RequiresMountsFor:
接受一个空格分隔的绝对路径列表,表示该单元将会使用到这些文件系统路径。所有这些路径中涉及的挂载点所对应的mount单元,都会被隐式的添加到 Requires 与 After 选项中。也就是说,这些路径中所涉及的挂载点都会在启动该单元之前被自动挂载。
OnFailureJobMode:
可设为“fail”、“replace”、“replace-irreversibly”、“isolate”、“flush”、“ignore-dependencies”、“ignore-requirements”之一。 默认值是“replace”。 指定 OnFailure 中列出的单元应该以何种方式排队。
- fail: 表示当新任务与队列中已有的任务冲突时,该命令将失败。所谓"冲突"的含义是导致队列中已有的某个启动操作转变为停止操作,或者相反。
- replace: 表示将队列中冲突的任务替换为新任务。
- replace-irreversibly: 与“replace”类似,不同之处在于将新任务同时标记为“不可撤销”,也就是即使未来与其他新添加的任务发生冲突也不会被撤消。注意,这个“不可撤销”的任务,仍然可以使用 cancel 命令显式的撤消。应该将此模式应用于所有包含在 shutdown.target 中的任务。
- isolate: 仅用于启动操作,表示在该单元启动之后,所有其他单元都会被停止。当使用 isolate 命令的时候,这是默认值,且不能更改。
- flush: 表示撤消队列中已有的全部任务,然后加入新任务。
- ignore-dependencies: 表示忽略新任务的所有依赖关系(包括先后顺序依赖),立即执行请求的操作。如果成功,那么所有被依赖的单元及先后顺序都将被忽略。
- ignore-requirements: 类似于”ignore-dependencies“,表示仅忽略必需的依赖(但依然遵守单元之间的先后顺序)。
IgnoreOnIsolate:
接受一个布尔值。设为yes表示在执行 systemctl isolate 命令时,此单元不会被停止。对于service单元来说,默认值是false。
StopWhenUnneeded:
如果设为yes,那么当此单元不再被任何已启动的单元依赖时,将会被自动停止。默认值 no 的含义是,除非此单元与其他即将启动的单元冲突,否则即使此单元已不再被任何已启动的单元依赖,也不会自动停止它。
RefuseManualStart/RefuseManualStop:
如果设为yes,那么此单元将拒绝被手动启动(RefuseManualStart)或拒绝被手动停止(RefuseManualStop)。也就是说,此单元只能作为其他单元的依赖条件而存在,只能因为依赖关系而被间接启动或者间接停止,不能由用户以手动方式直接启动或者直接停止。设置此选项是为了禁止用户意外的启动或者停止某些特定的单元。默认值是no。
AllowIsolate:
如果设为yes,那么此单元将允许被 systemctl isolate 命令操作,否则将会被拒绝。
DefaultDependencies:
默认值 yes 表示为此单元隐式地创建默认依赖。对于service单元来说,默认的依赖关系是指:(1)开机时,必须在基础系统初始化完成之后才能启动该服务;(2)关机时,必须在该服务完全停止后才能关闭基础系统。通常,只有那些在系统启动的早期就必须启动的单元,以及那些必须在系统关闭的末尾才能关闭的单元,才可以将此选项设为no。
JobTimeoutSec/JobRunningTimeoutSec/JobTimeoutAction/JobTimeoutRebootArgument:
当该单元的一个任务(job)进入队列的时候,JobTimeoutSec 用于设置从该任务进入队列开始计时、到该任务最终完成,最多可以使用多长时间,JobRunningTimeoutSec 用于设置从该任务实际运行开始计时、到该任务最终完成,最多可以使用多长时间。 如果上述任意一个设置超时,那么超时的任务将被撤销,并且该单元将保持其现有状态不变(而不是进入 "failed" 状态)。 对于非device单元来说,DefaultTimeoutStartSec 选项的默认值是 ”infinity“(永不超时), 而JobRunningTimeoutSec 的默认值等于 DefaultTimeoutStartSec 的值。 注意,此处设置的超时不是指单元自身的超时(例如 TimeoutStartSec 就是指单元自身的超时), 而是指该单元在启动或者停止等状态变化过程中,等候某个外部任务完成的最长时限。换句话说,适用于单元自身的超时设置(例如 TimeoutStartSec)用于指定单元自身在改变其状态时,总共允许使用多长时间;而此处设置的超时则是设置此单元在改变其状态的过程中,等候某个外部任务完成所能容忍的最长时间。
JobTimeoutAction 用于指定当超时发生时,将触发什么样的额外动作。 可接受的值与 StartLimitAction 相同,默认值为none。
JobTimeoutRebootArgument 用于指定传递给reboot系统调用的字符串参数。
StartLimitIntervalSec/StartLimitBurst:
设置单元的启动频率限制。 默认情况下,一个单元在10秒内最多允许启动5次。 StartLimitIntervalSec= 用于设置时长, 默认值等于 DefaultStartLimitIntervalSec 的值(默认为10秒),设为0表示不作限制。StartLimitBurst= 用于设置在一段给定的时长内,最多允许启动多少次,默认值等于 DefaultStartLimitBurst 的值(默认为5次)。 虽然此选项通常与service的 Restart 一起使用, 但实际上,此选项作用于任何方式的启动(包括手动启动), 而不仅仅是由 Restart 触发的启动。 注意,一旦某个设置了 Restart 自动重启逻辑的单元 触碰到了启动频率限制,那么该单元将再也不会尝试自动重启;不过,如果该单元后来又被手动重启成功的话,那么该单元的自动重启逻辑将会被再次激活。注意,systemctl reset-failed 命令能够重置单元的启动频率计数器。系统管理员在手动启动某个已经触碰到了启动频率限制的单元之前,可以使用这个命令清除启动限制。注意,因为启动频率限制位于所有单元条件检查之后,所以基于失败条件的启动不会计入启动频率限制的启动次数之中。
StartLimitAction:
设置单元在触碰到了启动频率限制(上文的 StartLimitIntervalSec 与 StartLimitBurst 选项)时,将会执行什么动作。可以设为none、reboot、reboot-force、reboot-immediate、poweroff、poweroff-force、poweroff-immediate 之一。
- none: 表示除了禁止再次启动外,什么也不做;
- reboot/poweroff: 表示按照常规的关机流程重启/关闭整个系统, 相当于执行 systemctl reboot|poweroff 命令。
- reboot-force/poweroff-force: 表示强制杀死所有进程之后强制重启/关机,虽然可能会造成应用程序数据丢失,但是不会造成文件系统不一致,相当于执行 systemctl reboot|poweroff -f 命令。
- reboot-immediate/poweroff-immediate: 表示强制立即执行 reboot 系统调用重启/关机,可能会造成数据丢失以及文件系统不一致。默认值是 none。
RebootArgument:
当 StartLimitAction 或 FailureAction 触发关机动作时, 此选项的值就是传递给 reboot 系统调用的字符串参数。 相当于 systemctl reboot 命令接收的可选参数。
Condition系列:
这组选项用于在启动单元之前,首先测试特定的条件是否为真。若为真则开始启动,否则将会“悄无声息”地跳过此单元(仅是跳过,而不是进入“failed”状态)。注意,即使某单元由于测试条件为假而被跳过,那些由于依赖关系而必须先于此单元启动的单元并不会受到影响(也就是会照常启动)。可以使用条件表达式来跳过那些对于本机系统无用的单元,比如那些对于本机内核或运行环境没有用处的功能。
- ConditionArchitecture: 检测是否运行于特定的硬件平台: x86、x86-64、ppc、ppc-le、ppc64、ppc64-le、ia64、parisc、parisc64、s390、s390x、sparc、sparc64、mips、mips-le、mips64、mips64-le、alpha、arm、arm-be、arm64、arm64-be、sh、sh64、m68k、tilegx、cris、arc、arc-be、native(编译 systemd 时的目标平台)。 可以在这些关键字前面加上感叹号“!”前缀表示逻辑反转。
- ConditionVirtualization: 检测是否运行于特定的虚拟环境中: qemu、kvm、zvm、vmware、microsoft、oracle、xen、bochs、uml、openvz、lxc、lxc-libvirt、systemd-nspawn、docker、rkt、vm(某种虚拟机)、container(某种容器)、private-users(用户名字空间)、yes(某种虚拟环境)、no(物理机)。
- ConditionHost: 检测系统的 hostname 或者 machine ID 。参数可以是一个主机名字符串(首尾可加引号界定), 或者是一个machine ID格式的字符串(首尾不可加引号)。可以在字符串前面加上感叹号“!”前缀表示逻辑反转。
- ConditionKernelCommandLine: 检测是否设置了某个特定的内核引导选项。参数可以是一个单独的单词,也可以是一个“var=val”格式的赋值字符串。如果参数是一个单独的单词,那么以下两种情况都算是检测成功:(1)恰好存在一个完全匹配的单词选项;(2)在某个“var=val”格式的内核引导选项中等号前的“var”恰好与该单词完全匹配。如果参数是一个“var=val”格式的赋值字符串,那么必须恰好存在一个完全匹配的“var=val”格式的内核引导选项,才算检测成功。可以在字符串前面加上感叹号“!”前缀表示逻辑反转。
- ConditionSecurity: 检测是否启用了特定的安全模块selinux、apparmor、ima、smack、audit。可以在这些关键字前面加上感叹号“!”前缀表示逻辑反转。
- ConditionCapability: 检测 systemd 的 capability 集合中是否存在特定的 capabilities。 参数应设为例如“CAP_MKNOD”这样的 capability 名称。注意,此选项不是检测特定的 capability 是否实际可用,而是仅检测特定的 capability 在绑定集合中是否存在。可以在名称前面加上感叹号“!”前缀表示逻辑反转。
- ConditionACPower: 检测系统是否正在使用交流电源。 yes 表示至少在使用一个交流电源,或者更本不存在任何交流电源。 no 表示存在交流电源,但是没有使用其中的任何一个。
- ConditionNeedsUpdate: 可设为 /var 或 /etc 之一,用于检测指定的目录是否需要更新。
- ConditionFirstBoot: 可设为 yes 或 no 。用于检测 /etc 目录是否处于未初始化的原始状态(重点是 /etc/machine-id 文件是否存在)。此选项可用于系统出厂后(或者恢复出厂设置之后),首次开机时执行必要的初始化操作。
- ConditionPathExists: 检测指定的路径是否存在,必须使用绝对路径。可以在路径前面加上感叹号“!”前缀表示逻辑反转。
- ConditionPathExistsGlob: 与 ConditionPathExists 类似,唯一的不同是支持通配符。
- ConditionPathIsDirectory: 检测指定的路径是否存在并且是一个目录,必须使用绝对路径。可以在路径前面加上感叹号“!”前缀表示逻辑反转。
- ConditionPathIsSymbolicLink: 检测指定的路径是否存在并且是一个软连接,必须使用绝对路径。可以在路径前面加上感叹号“!”前缀表示逻辑反转。
- ConditionPathIsMountPoint: 检测指定的路径是否存在并且是一个挂载点,必须使用绝对路径。可以在路径前面加上感叹号“!”前缀表示逻辑反转。
- ConditionPathIsReadWrite: 检测指定的路径是否存在并且可读写(rw),必须使用绝对路径。可以在路径前面加上感叹号“!”前缀表示逻辑反转。
- ConditionDirectoryNotEmpty: 检测指定的路径是否存在并且是一个非空的目录,必须使用绝对路径。可以在路径前面加上感叹号“!”前缀表示逻辑反转。
- ConditionFileNotEmpty: 检测指定的路径是否存在并且是一个非空的普通文件,必须使用绝对路径。可以在路径前面加上感叹号“!”前缀表示逻辑反转。
- ConditionFileIsExecutable: 检测指定的路径是否存在并且是一个可执行文件,必须使用绝对路径。可以在路径前面加上感叹号“!”前缀表示逻辑反转。
- ConditionUser: 检测 systemd 是否以给定的用户身份运行。 参数可以是数字形式的“UID”、或者字符串形式的UNIX用户名、或者特殊值 “@system”(表示属于系统用户范围内)。此选项对于系统服务无效,因为管理系统服务的 systemd 进程总是以 root 用户身份运行。
- ConditionGroup: 检测 systemd 是否以给定的用户组身份运行。参数可以是数字形式的“GID”或者字符串形式的UNIX组名。
Assert系列:
- AssertArchitecture
- AssertVirtualization
- AssertHost
- AssertKernelCommandLine
- AssertSecurity
- AssertCapability
- AssertACPower
- AssertNeedsUpdate
- AssertFirstBoot
- AssertPathExists
- AssertPathExistsGlob
- AssertPathIsDirectory
- AssertPathIsSymbolicLink
- AssertPathIsMountPoint
- AssertPathIsReadWrite
- AssertDirectoryNotEmpty
- AssertFileNotEmpty
- AssertFileIsExecutable
- AssertUser
- AssertGroup
SourcePath:
指定生成此单元时所参考的配置文件。仅用于单元生成器标识此单元生成自何处。普通的单元不应该使用它。
2.4.定义服务本体[Service]
每个服务单元文件都必须包含一个 [Service] 小节,本节列出了专用于系统服务service的配置选项,可用选项如下:
Type:
设置进程的启动类型,必须设为 simple、forking、oneshot、dbus、notify、idle 之一。
- simple: 如果设为simple(设置了 ExecStart 但未设置 BusName 时的默认值),那么表示 ExecStart 进程就是该服务的主进程。如果此进程需要为其他进程提供服务,那么必须在该进程启动之前先建立好通信渠道(例如套接字),以加快后继单元的启动速度。
- forking: 如果设为forking,那么表示 ExecStart 进程将会在启动过程中使用fork()系统调用。这是传统UNIX守护进程的经典做法。也就是当所有的通信渠道都已建好、启动亦已成功之后,父进程将会退出,而子进程将作为该服务的主进程继续运行。对于此种进程,建议同时设置 PIDFile= 选项,以帮助systemd准确定位该服务的主进程,进而加快后继单元的启动速度。
- oneshot: oneshot(未设置 ExecStart 时的默认值)与 simple 类似,不同之处在于该进程必须在systemd启动后继单元之前退出。此种类型通常需要设置 RemainAfterExit 选项。
- dbus: dbus(既设置了 ExecStart 也设置了 BusName 时的默认值)与 simple 类似,不同之处在于该进程需要在D-Bus上获得一个由 BusName 指定的名称。Systemd将会在启动后继单元之前,首先确保该进程已经成功的获取了指定的D-Bus名称。设为此类型相当于隐含的依赖于 dbus.socket 单元。
- notify: notify 与 simple 类似,不同之处在于该进程将会在启动完成之后通过 sd_notify 之类的接口发送一个通知消息。Systemd将会在启动后继单元之前,首先确保该进程已经成功的发送了这个消息。如果设为此类型,那么下文的 NotifyAccess 将只能设为非 none 值。如果 NotifyAccess 未设置,或者已经被明确设为 none ,那么将会被自动强制修改为 main 。注意,目前 Type=notify 尚不能在 PrivateNetwork=yes 的情况下正常工作。
- idle: idle 与 simple 类似,不同之处在于该进程将会被延迟到所有活动的任务都完成之后再执行。这样可以避免控制台上的状态信息与shell脚本的输出混杂在一起。注意:(1) idle仅可用于改善控制台输出,切勿将其用于不同单元之间的排序工具; (2)延迟最多不超过5秒,超时后将无条件的启动服务进程。
RemainAfterExit:
当该服务的所有进程全部退出之后,是否依然将此服务视为活动(active)状态。默认值为 no。
GuessMainPID:
在无法明确定位该服务主进程的情况下,systemd是否应该猜测主进程的PID(可能不正确)。 该选项仅在设置了 Type=forking 但未设置 PIDFile 的情况下有意义。如果PID猜测错误,那么该服务的失败检测与自动重启功能将失效。默认值为 yes。
PIDFile:
守护进程的PID文件,必须是绝对路径。强烈建议在 Type=forking 的情况下明确设置此选项。Systemd将会在此服务启动后从此文件中读取主守护进程的PID 。Systemd不会写入此文件,但会在此服务停止后删除它(若存在)。
BusName:
设置与此服务通信所使用的D-Bus名称。在 Type=dbus 的情况下,必须明确设置此选项。
ExecStart:
在启动该服务时需要执行的命令行(命令+参数)。
除非 Type=oneshot,否则必须且只能设置一个命令行。仅在 Type=oneshot 的情况下,才可以设置任意个命令行(包括零个),多个命令行既可以在同一个 ExecStart= 中设置,也可以通过设置多个 ExecStart 来达到相同的效果。如果设为一个空字符串,那么先前设置的所有命令行都将被清空。如果不设置任何 ExecStart 指令, 那么必须确保设置了 RemainAfterExit=yes 指令,并且至少设置一个 ExecStop 指令。 同时缺少 ExecStart 与 ExecStop 的服务单元是非法的(也就是必须至少明确设置其中之一)。
命令行必须以一个绝对路径表示的可执行文件开始,并且其后的那些参数将依次作为“argv[1] argv[2] …”传递给被执行的进程。
ExecStartPre/ExecStartPost:
设置在执行 ExecStart 之前/后执行的命令行。语法规则与 ExecStart 完全相同。如果设置了多个命令行,那么这些命令行将以其在单元文件中出现的顺序依次执行。
如果某个无“-”前缀的命令行执行失败,那么剩余的命令行将不会被继续执行,同时该单元将变为失败(failed)状态。仅在所有无“-”前缀的 ExecStartPre 命令全部执行成功的前提下,才会继续执行 ExecStart 命令。
ExecStartPost 命令仅在 ExecStart 中的命令已经全部执行成功之后才会运行,判断的标准基于 Type 选项。具体说来,对于 Type=simple 或 Type=idle 就是主进程已经成功启动;对于 Type=oneshot 来说就是最后一个 ExecStart 进程已经成功退出;对于 Type=forking 来说就是初始进程已经成功退出;对于 Type=notify 来说就是已经发送了 "READY=1" ;对于 Type=dbus 来说就是已经取得了 BusName 中设置的总线名称。
注意,不可将 ExecStartPre 用于需要长时间执行的进程。因为所有由 ExecStartPre 派生的子进程都会在启动 ExecStart 服务进程之前被杀死。
注意,如果在服务启动完成之前,任意一个 ExecStartPre、ExecStart、ExecStartPost 中无“-”前缀的命令执行失败或超时,那么,ExecStopPost 将会被继续执行,而 ExecStop 则会被跳过。
ExecReload:
这是一个可选的指令,用于设置当该服务被要求重新载入配置时所执行的命令行。语法规则与 ExecStart 完全相同。
ExecStop:
这是一个可选的指令,用于设置当该服务被要求停止时所执行的命令行。语法规则与 ExecStart 完全相同。执行完此处设置的所有命令行之后,该服务将被视为已经停止,此时,该服务所有剩余的进程将会根据 KillMode 的设置被杀死。 如果未设置此选项,那么当此服务被停止时,该服务的所有进程都将会根据 KillSignal 的设置被立即全部杀死。
一般来说,不应该仅仅设置一个结束服务的命令而不等待其完成。因为当此处设置的命令执行完之后,剩余的进程会被按照 KillMode 与 KillSignal 的设置立即杀死,这可能会导致数据丢失。因此,这里设置的命令必须是同步操作,而不能是异步操作。
注意,仅在服务确实启动成功的前提下,才会执行 ExecStop 中设置的命令。如果服务从未启动或启动失败(例如,任意一个 ExecStart、ExecStartPre、ExecStartPost 中无“-”前缀的命令执行失败或超时), 那么 ExecStop 将会被跳过。如果想要无条件的在服务停止后执行特定的动作,那么应该使用 ExecStopPost 选项。
应该将此选项用于那些必须在服务干净的退出之前执行的命令。当此选项设置的命令被执行的时候,应该假定服务正处于完全正常的运行状态,可以正常的与其通信。如果想要无条件的在服务停止后“清理尸体”,那么应该使用 ExecStopPost 选项。
ExecStopPost:
这是一个可选的指令,用于设置在该服务停止之后所执行的命令行。语法规则与 ExecStart 完全相同。注意,与 ExecStop 不同,无论服务是否启动成功,此选项中设置的命令都会在服务停止后被无条件的执行。
应该将此选项用于设置那些无论服务是否启动成功都必须在服务停止后无条件执行的清理操作。此选项设置的命令必须能够正确处理由于服务启动失败而造成的各种残缺不全以及数据不一致的场景。由于此选项设置的命令在执行时,整个服务的所有进程都已经全部结束,所以无法与服务进行任何通信。
注意,此处设置的所有命令在被调用之后都可以读取如下环境变量:$SERVICE_RESULT(服务的最终结果), $EXIT_CODE(服务主进程的退出码), $EXIT_STATUS(服务主进程的退出状态)。
RestartSec:
设置在重启服务(Restart)前暂停多长时间。 默认值是100毫秒(100ms)。如果未指定时间单位,那么将视为以秒为单位。
TimeoutStartSec:
设置该服务允许的最大启动时长。如果守护进程未能在限定的时长内发出“启动完毕”的信号,那么该服务将被视为启动失败,并会被关闭。如果未指定时间单位,那么将视为以秒为单位。例如设为“20”等价于设为“20s”。 设为“infinity”则表示永不超时。 当 Type=oneshot 时,默认值为“infinity”(永不超时), 否则默认值等于 DefaultTimeoutStartSec 的值。
TimeoutStopSec:
设置该服务允许的最大停止时长。如果该服务未能在限定的时长内成功停止,那么将会被强制使用 SIGTERM 信号关闭,如果依然未能在相同的时长内成功停止,那么将会被强制使用 SIGKILL 信号关闭。如果未指定时间单位,那么将视为以秒为单位。设为 “infinity”则表示永不超时。默认值等于 DefaultTimeoutStopSec 的值。
TimeoutSec:
一个同时设置 TimeoutStartSec 与 TimeoutStopSec 的快捷方式。
RuntimeMaxSec:
允许服务持续运行的最大时长。如果服务持续运行超过了此处限制的时长,那么该服务将会被强制终止,同时将该服务变为失败(failed)状态。注意,此选项对 Type=oneshot 类型的服务无效,因为它们会在启动完成后立即终止。默认值为“infinity”(不限时长)。
WatchdogSec:
设置该服务的看门狗(watchdog)的超时时长。看门狗将在服务成功启动之后被启动。该服务在运行过程中必须周期性的以“WATCHDOG=1”(“keep-alive ping”)调用 sd_notify 函数。如果在两次调用之间的时间间隔大于这里设定的值,那么该服务将被视为失败(failed)状态,并会被强制使用 SIGABRT 信号关闭。
通过将 Restart 设为 on-failure、on-watchdog、on-abnormal、always 之一,可以实现在失败状态下的自动重启该服务。这里设置的值将会通过 WATCHDOG_USEC 环境变量传递给守护进程,这样就允许那些支持看门狗的服务自动启用“keep-alive ping”。如果设置了此选项,那么 NotifyAccess 将只能设为非 none 值。如果 NotifyAccess 未设置,或者已经被明确设为 none,那么将会被自动强制修改为 main。如果未指定时间单位,那么将视为以秒为单位。默认值“0”表示禁用看门狗功能。
Restart:
当服务进程正常退出、异常退出、被杀死、超时的时候,是否重新启动该服务。所谓“服务进程”是指 ExecStartPre、ExecStartPost、ExecStop、ExecStopPost、ExecReload 中设置的进程。当进程是由于systemd的正常操作(例如 systemctl stop|restart)而被停止时,该服务不会被重新启动。所谓“超时”可以是看门狗的“keep-alive ping”超时,也可以是 systemctl start|reload|stop 操作超时。
该选项的值可以取 no、always、on-success、on-failure、on-abnormal、on-watchdog、on-abort 之一。no(默认值) 表示不会被重启。always 表示会被无条件的重启。on-success 表示仅在服务进程正常退出时重启,所谓“正常退出”是指: 退出码为“0”,或者进程收到 SIGHUP、SIGINT、SIGTERM、SIGPIPE 信号之一,并且退出码符合 SuccessExitStatus 的设置。on-failure 表示仅在服务进程异常退出时重启,所谓“异常退出”是指: 退出码不为“0”,或者进程被强制杀死(包括 “core dump”以及收到 SIGHUP、SIGINT、SIGTERM、SIGPIPE 之外的其他信号),或者进程由于看门狗或者systemd的操作超时而被杀死。
SuccessExitStatus:
额外定义附加的进程“正常退出”状态。可以设为一系列以空格分隔的数字退出码或者信号名称。
例如,SuccessExitStatus值可以为 1、2、8、SIGKILL。表示当进程的退出码是 1、2、8 或被 SIGKILL 信号终止时,都可以视为“正常退出”。注意,退出码“0”以及 SIGHUP、SIGINT、SIGTERM、SIGPIPE 信号是标准的“正常退出”,不需要在此特别定义。
如果多次使用此选项,那么最终的结果将是多个列表的合并。如果将此选项设为空,那么先前设置的列表将被清空。
RestartPreventExitStatus:
可以设为一系列以空格分隔的数字退出码或信号名称,当进程的退出码或收到的信号与此处的设置匹配时,无论 Restart 是如何设置的,该服务都将无条件的禁止重新启动。
例如,RestartPreventExitStatus值可以为 1、6、SIGABRT。可以确保退出码 1、6 与 SIGABRT 信号不会导致该服务被自动重启。默认值为空,表示完全遵守 Restart 的设置。如果多次使用此选项,那么最终的结果将是多个列表的合并。如果将此选项设为空,那么先前设置的列表将被清空。
RestartForceExitStatus:
可以设为一系列以空格分隔的数字退出码或信号名称,当进程的退出码或收到的信号与此处的设置匹配时,无论 Restart 是如何设置的,该服务都将无条件的被自动重新启动。默认值为空,表示完全遵守 Restart 的设置。如果多次使用此选项,那么最终的结果将是多个列表的合并。如果将此选项设为空,那么先前设置的列表将被清空。
PermissionsStartOnly:
设为 yes 表示所有与权限相关的执行选项仅对 ExecStart 中的程序有效,而对 ExecStartPre、ExecStartPost、ExecReload、ExecStop、ExecStopPost中的程序无效。默认值 no 表示所有与权限相关的执行选项,对所有 Exec* 系列选项中的程序都有效。
RootDirectoryStartOnly:
设为 yes 表示根目录仅对 ExecStart 中的程序有效,而对 ExecStartPre、ExecStartPost、ExecReload、ExecStop、ExecStopPost 中的程序无效。默认值 no 表示根目录对所有 Exec* 系列选项中的程序都有效。
NonBlocking:
是否为所有基于套接字启动传递的文件描述符设置非阻塞标记(O_NONBLOCK)。设为 yes 表示除了通过 FileDescriptorStoreMax 引入的文件描述符之外,所有 ≥3 的文件描述符(非 stdin、stdout、stderr 文件描述符)都将被设为非阻塞模式。该选项仅在与socket单元(systemd.socket)联用的时候才有意义。对于那些先前已经通过 FileDescriptorStoreMax 引入的文件描述符则毫无影响。默认值为 no。
NotifyAccess:
设置通过 sd_notify 访问服务状态通知套接字的模式。可以设为 none(默认值)、main、exec、all 之一。none 表示不更新任何守护进程的状态,忽略所有状态更新消息。main 表示仅接受主进程的状态更新消息。exec 表示仅接受主进程以及 Exec* 进程的状态更新消息。all 表示接受该服务cgroup内所有进程的状态更新消息。当设置了 Type=notify 或 WatchdogSec= 的时候,此选项将只能设为非 none 值。如果 NotifyAccess 未设置,或者已经被明确设为 none,那么将会被自动强制修改为 main。
注意,服务单元的 sd_notify() 通知能够正常工作的前提,是必须满足如下两个条件之一: (1)在 PID=1 的进程处理通知消息时,发送该通知的进程依然在运行;(2)发送该通知的进程是 systemd 派生的子进程(也就是匹配 main 或 exec 的进程)。如果服务单元中的某个辅助进程在发送了 sd_notify() 通知之后就立即退出了,那么systemd将有可能来不及将该通知关联到这个服务单元上。在这种情况下,即使明确设置了 NotifyAccess=all ,该通知也可能会被忽略掉。
Sockets:
设置一个 socket 单元的名称,表示该服务在启动时应当从它继承套接字文件描述符。通常并不需要明确设置此选项,因为所有与该服务同名(不算后缀)的 socket 单元的套接字文件描述符,都会被自动的传递给派生进程。
注意: (1)同一个套接字文件描述符可以被传递给多个不同的进程(服务)。 (2)当套接字上有流量进入时,被启动的可能是另一个不同于该服务的其他服务。换句话说就是:套接字单元中的 Sockets 所指向的服务单元中的 Sockets 未必要反向指回去。如果多次使用此选项,那么最终的结果将是多个socket单元的合集。如果将此选项设为空,那么先前设置的所有socket单元都将被清空。
FailureAction:
当该服务进入失败(failed)状态时所触发的动作。取值范围及值的含义都与 StartLimitAction(参见 [Unit]) 完全相同。默认值为 none。
FileDescriptorStoreMax:
允许在systemd中最多为该服务存储多少个使用“FDSTORE=1”消息(sd_pid_notify_with_fds)的文件描述符。默认值为“0”(不存储)。通过将服务重启过程中不应该关闭的套接字与文件描述符使用这种方法保存起来,就可以实现让服务在重启(正常重启或崩溃重启)之后不丢失其状态。进程的状态可以被序列化为一个文件之后保存在“/run”中,或者保存在一个 memfd_create 内存文件描述符中(这是更好的选择)。所有被systemd暂存的文件描述符都将在该服务重启之后交还给该服务的主进程。所有被systemd暂存的文件描述符都将在遇到如下两种情况时被自动关闭: (1)收到 POLLHUP 或 POLLERR 信号; (2)该服务被彻底停止,并且没有任何剩余的任务需要处理。
USBFunctionDescriptors:
设为一个包含 USB FunctionFS 描述符的文件路径,以实现 USB gadget 支持。仅与配置了 ListenUSBFunction 的 socket 单元一起使用。该文件的内容将被写入 ep0 文件。
USBFunctionStrings:
设为一个包含 USB FunctionFS 字符串的文件路径。其行为与上面的 USBFunctionDescriptors 类似。
2.5.定义安装服务[Install]
[Install]包含了单元的启用信息。Systemd在运行时并不使用此小节。 只有 systemctl 的 enable 与 disable 命令在启用/停用单元时才会使用此小节。可用的选项如下:
Alias:
启用时使用的别名,可以设为一个空格分隔的别名列表。每个别名的后缀(也就是单元类型)都必须与该单元自身的后缀相同。如果多次使用此选项,那么每个选项所设置的别名都会被添加到别名列表中。在启用此单元时,systemctl enable 命令将会为每个别名创建一个指向该单元文件的软连接。
WantedBy/RequiredBy:
接受一个空格分隔的单元列表,表示在使用 systemctl enable 启用此单元时,将会在每个列表单元的 .wants/ 或 .requires/ 目录中创建一个指向该单元文件的软连接。这相当于为每个列表中的单元文件添加了 Wants=此单元 或 Requires=此单元 选项。 这样当列表中的任意一个单元启动时,该单元都会被启动。
Also:
设置此单元的附属单元,可以设为一个空格分隔的单元列表。表示当使用 systemctl enable 启用 或 systemctl disable 停用 此单元时,也同时自动的启用或停用附属单元。
DefaultInstance:
仅对模板单元有意义,用于指定默认的实例名称。如果启用此单元时没有指定实例名称,那么将使用这里设置的名称。
2.6.示例
例 1. 简单服务
下面的单元文件创建了一个运行“/usr/sbin/foo-daemon”守护进程的服务。未设置 Type 等价于 Type=simple 默认设置。Systemd执行守护进程之后,即认为该单元已经启动成功。
[Unit]
Description=简单的Foo服务
[Service]
ExecStart=/usr/sbin/foo-daemon
[Install]
WantedBy=multi-user.target
注意,本例中的“/usr/sbin/foo-daemon”必须在启动后持续运行到服务被停止。如果该进程只是为了派生守护进程,那么应该使用 Type=forking。
因为没有设置 ExecStop 选项,所以在停止服务时,systemd将会直接向该服务启动的所有进程发送 SIGTERM 信号。若超过指定时间依然存在未被杀死的进程,那么将会继续发送 SIGKILL 信号。
默认的 Type=simple 并不包含任何通知机制(例如通知“服务启动成功”)。要想使用通知机制,应该将 Type 设为其他非默认值,Type=notify 可用于能够理解systemd通知协议的服务;Type=forking 可用于能将自身切换到后台的服务;Type=dbus 可用于能够在完成初始化之后获得一个D-Bus名称的单元。
例 2. 一次性服务
Type=oneshot 用于那些只需要执行一次性动作而不需要持久运行的单元,例如文件系统检查或者清理临时文件。此类单元,将会在启动后一直等待指定的动作完成,然后再回到停止状态。下面是一个执行清理动作的单元:
[Unit]
Description=清理老旧的 Foo 数据
[Service]
Type=oneshot
ExecStart=/usr/sbin/foo-cleanup
[Install]
WantedBy=multi-user.target
注意,在“/usr/sbin/foo-cleanup”执行结束前,该服务一直处于“启动中”(activating)状态,而一旦执行结束,该服务又立即变为“停止”(inactive)状态。也就是说,对于 Type=oneshot 类型的服务,不存在“活动”(active)状态。这意味着,如果再一次启动该服务,将会再一次执行该服务定义的动作。注意,在先后顺序上晚于该服务的单元,将会一直等到该服务变成“停止”(inactive)状态后,才会开始启动。
Type=oneshot 是唯一可以设置多个 ExecStart 指令的服务类型。多个 ExecStart 指令将按照它们出现的顺序依次执行,一旦遇到错误,就会立即停止,不再继续执行,同时该服务也将进入“失败”(failed)状态。
例 3. 可停止的一次性服务
有时候,单元需要执行一个程序以完成某个设置(启动),然后又需要再执行另一个程序以撤消先前的设置(停止),而在设置持续有效的时段中,该单元应该视为处于“活动”(active)状态,但实际上并无任何程序在持续运行。网络配置服务就是一个典型的例子。此外,只能启动一次(不可多次启动)的一次性服务,也是一个例子。
可以通过设置 RemainAfterExit=yes 来满足这种需求。在这种情况下,systemd将会在启动成功后将该单元视为处于“活动”(active)状态(而不是“停止”(inactive)状态)。RemainAfterExit=yes 虽然可以用于所有 Type 类型,但是在实践中主要用于 Type=oneshot 和 Type=simple 类型。对于 Type=oneshot 类型, systemd一直等到服务启动成功之后,才会将该服务置于“活动”(active)状态。所以,依赖于该服务的其他单元必须等待该服务启动成功之后,才能启动。但是对于 Type=simple 类型,依赖于该服务的其他单元无需等待,将会和该服务同时并行启动。下面的类似展示了一个简单的静态防火墙服务:
[Unit]
Description=简单的静态防火墙
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/local/sbin/simple-firewall-start
ExecStop=/usr/local/sbin/simple-firewall-stop
[Install]
WantedBy=multi-user.target
因为服务启动成功后一直处于“活动”(active)状态,所以再次执行 systemctl start 命令不会有任何效果。
例 4. 传统的服务
多数传统的守护进程(服务)在启动时会转入后台运行。Systemd通过 Type=forking 来支持这种工作方式。对于这种类型的服务,如果最初启动的进程尚未退出,那么该单元将依然处于“启动中”(activating)状态。当最初的进程成功退出,并且至少有一个进程仍然在运行(并且 RemainAfterExit=no),该服务才会被视为处于“活动”(active)状态。
对于单进程的传统服务,当最初的进程成功退出后,将会只剩单独一个进程仍然在持续运行,systemd将会把这个唯一剩余的进程视为该服务的主进程。仅在这种情况下,才将可以在 ExecReload、ExecStop … 之类的选项中使用 $MAINPID 变量。
对于多进程的传统服务,当最初的进程成功退出后,将会剩余多个进程在持续运行,因此,systemd无法确定哪一个进程才是该服务的主进程。在这种情况下,不可以使用 $MAINPID 变量。然而,如果主进程会创建传统的PID文件,那么应该将 PIDFile 设为此PID文件的绝对路径,以帮助 systemd 从该PID文件中读取主进程的PID,从而帮助确定该服务的主进程。 注意,守护进程必须在完成初始化之前写入PID文件, 否则可能会导致 systemd 读取失败(读取时文件不存在)。
下面是一个单进程传统服务的示例:
[Unit]
Description=一个单进程传统服务
[Service]
Type=forking
ExecStart=/usr/sbin/my-simple-daemon -d
[Install]
WantedBy=multi-user.target
例 5. DBus 服务
对于需要在D-Bus系统总线上注册一个名字的服务,应该使用 Type=dbus 并且设置相应的 BusName 值。该服务不可以派生任何子进程。一旦从D-Bus系统总线成功获取所需的名字,该服务即被视为初始化成功。下面是一个典型的D-Bus服务:
[Unit]
Description=一个简单的 DBus 服务
[Service]
Type=dbus
BusName=org.example.simple-dbus-service
ExecStart=/usr/sbin/simple-dbus-service
[Install]
WantedBy=multi-user.target
对于基于D-Bus启动的服务来说,不可以包含“[Install]”小节,而是应该在对应的D-Bus service文件中设置 SystemdService 选项, 例如(/usr/share/dbus-1/system-services/org.example.simple-dbus-service.service):
[D-BUS Service]
Name=org.example.simple-dbus-service
Exec=/usr/sbin/simple-dbus-service
User=root
SystemdService=simple-dbus-service.service
例 6. 能够通知初始化已完成的服务
Type=simple 类型的服务非常容易编写,但是无法将“启动成功”的消息及时通知给systemd是一个重大缺陷。Type=notify 可以弥补该缺陷,它支持将“启动成功”的消息及时通知给systemd。下面是一个典型的例子:
[Unit]
Description=Simple notifying service
[Service]
Type=notify
ExecStart=/usr/sbin/simple-notifying-service
[Install]
WantedBy=multi-user.target
注意,该守护进程必须支持systemd通知协议,否则systemd将会认为该服务一直处于“启动中”(activating)状态,并在超时后将其杀死。
3.常用命令
3.1.分析系统状态
显示 系统状态:
$ systemctl status
输出激活的单元:
$ systemctl
以下命令等效:
$ systemctl list-units
输出运行失败的单元:
$ systemctl --failed
所有可用的单元文件存放在 /usr/lib/systemd/system/ 和 /etc/systemd/system/ 目录(后者优先级更高)。查看所有已安装服务:
$ systemctl list-unit-files
3.2.使用系统服务单元
立即激活单元:
# systemctl start <单元>
立即停止单元:
# systemctl stop <单元>
重启单元:
# systemctl restart <单元>
重新加载配置:
# systemctl reload <单元>
输出单元运行状态:
$ systemctl status <单元>
检查单元是否配置为自动启动:
$ systemctl is-enabled <单元>
开机自动激活单元:
# systemctl enable <单元>
检查单元是否配置为自动启动:
$ systemctl is-enabled <单元>
设置单元为自动启动并立即启动这个单元:
# systemctl enable --now unit
取消开机自动激活单元:
# systemctl disable <单元>
禁用一个单元(禁用后,间接启动也是不可能的):
# systemctl mask <单元>
取消禁用一个单元:
# systemctl unmask <单元>
显示单元的手册页(必须由单元文件提供):
# systemctl help <单元>
重新载入 systemd,扫描新的或有变动的单元:
# systemctl daemon-reload