systemd.exec 中文手册

译者:金步国


版权声明

本文译者是一位开源理念的坚定支持者,所以本文虽然不是软件,但是遵照开源的精神发布。

其他作品

本文译者十分愿意与他人分享劳动成果,如果你对我的其他翻译作品或者技术文章有兴趣,可以在如下位置查看现有的作品集:

联系方式

由于译者水平有限,因此不能保证译文内容准确无误。如果你发现了译文中的错误(哪怕是错别字也好),请来信指出,任何提高译文质量的建议我都将虚心接纳。


手册索引 . 指令索引systemd-235

名称

systemd.exec — 执行环境配置

大纲

service.service, socket.socket, mount.mount, swap.swap

描述

本手册列出了 service, socket, mount, swap 单元所共有的、 用于定义进程执行环境的配置选项(亦称"配置指令"或"单元属性")。

根据单元类型的不同, 这些共有的选项(亦称"指令"或"属性")分别位于单元文件的 [Service], [Socket], [Mount], [Swap] 小节。 通用于所有单元类型的配置选项位于 systemd.unit(5) 手册。 上述四种单元各自专用的配置选项分别位于 systemd.service(5), systemd.socket(5), systemd.swap(5), systemd.mount(5) 手册。

此外,通过 cgroup 控制资源占用的选项位于 systemd.resource-control(5) 手册中。它们是对本文所列选项的补充。

隐含依赖

某些选项会导致自动添加额外的依赖关系:

  • 设置了 WorkingDirectory=, RootDirectory=, RootImage=, RuntimeDirectory=, StateDirectory=, CacheDirectory=, LogsDirectory=, ConfigurationDirectory= 之一的单元, 将自动获得对访问指定路径所需的所有 mount 单元的 Requires=After= 依赖。 这等同于将这些 mount 单元明确的在 RequiresMountsFor= 中列出来。

  • 类似的,设置了 PrivateTmp=yes 的单元,将自动获得对访问 /tmp/var/tmp 所需的所有 mount 单元的 Requires=After= 依赖。此外,还将自动获得对 systemd-tmpfiles-setup.service(8)After= 依赖。

  • 如果单元的标准输出(StandardOutput=)或标准错误(StandardError=)中含有 journal, syslog, kmsg 之一, 那么该单元将会自动获得 After=systemd-journald.socket 依赖。

选项

WorkingDirectory=

设置进程的工作目录。 既可以设为特殊值 "~" 表示 User= 用户的家目录,也可以设为一个以 RootDirectory= 为基准的绝对路径。 例如当 RootDirectory=/sysroot 并且 WorkingDirectory=/work/dir 时,实际的工作目录将是 /sysroot/work/dir 。 当 systemd 作为系统实例运行时,此选项的默认值是 / ; 当 systemd 作为用户实例运行时,此选项的默认值是对应用户的家目录。 如果给目录加上 "-" 前缀,那么表示即使此目录不存在,也不算致命错误。 如果未设置 RootDirectory=/RootImage= 选项,那么为 WorkingDirectory= 设置的绝对路径 将以主机(或容器)的根目录(也就是运行 systemd 的系统根目录)为基准。 注意,设置此选项将会导致自动添加额外的依赖关系(见上文)。

RootDirectory=

设置以 chroot(2) 方式执行进程时的根目录。 必须设为一个以主机(或容器)的根目录(也就是运行 systemd 的系统根目录)为基准的绝对路径。 如果设置了此选项,必须确保进程及其辅助文件在 chroot() 监狱中确实可用。 注意,设置此选项将会导致自动添加额外的依赖关系(见上文)。

MountAPIVFS= 以及 PrivateUsers=RootDirectory= 一起使用时特别有意义。详见下文。

RootImage=

可设为一个块设备节点或者一个普通文件。 此设置的含义与 RootDirectory= 相同,不同之处在于是从块设备或回环文件挂载一个文件系统, 而不是直接使用一个现成的目录。需要注意的是,块设备或回环文件中必须包含合法的文件系统,同时还需要满足以下条件之一: (1)不包含任何分区表;(2)仅包含单独一个 Linux 能够识别的 MBR/MS-DOS 或 GPT 分区; (3)包含一组完全遵守 Discoverable Partitions Specification 规范的 GPT 分区。

MountAPIVFS=

接受一个布尔值。设为 yes 表示为单元内的进程创建私有的挂载名字空间, 并在其中挂载 /proc, /sys, /dev 虚拟文件系统(除非它们已经被挂载了)。此设置仅在与 RootDirectory=/RootImage= 一起使用时才有意义,因为宿主机上一般已经挂载了这三个虚拟文件系统; 同时,除非切换到了不同的根目录,否则私有的挂载名字空间将会完全按原样从宿主机复制一份, 这其中当然也就包含了这三个挂载点。注意,当 PrivateDevices=no (默认值)时, /dev 将会以绑定(--bind)的方式从宿主机上挂载。 要想让服务运行在一个私有的最小化 /dev/ 环境中,请务必联合使用合适的 PrivateDevices= 设置。

User=, Group=

设置进程在执行时使用的用户与组。 既可以设为一个数字形式的 UID/GID 也可以设为一个字符串形式的名称。 对于系统服务(由 PID=1 的 systemd 系统实例管理)以及由 root 运行的用户服务(由 root 用户启动的 systemd --user 用户实例管理), User= 的默认值是 "root" ,同时亦可明确将 User= 设为其他用户。 对于普通用户运行的用户服务,User= 的默认值就是该用户自身,并且禁止将 User= 切换为其他用户。 如果没有明确设置 Group= 选项,则使用 User= 所属的默认组。 此选项不影响带有 "+" 前缀的命令。

注意,为了避免歧义以及确保在不同 Linux 系统之间的兼容性, 用户与组的名称必须满足以下规则: (1)仅可包含 a-z, A-Z, 0-9, "_", "-" 字符; (2)首字母只能是 a-z, A-Z, "_" 之一(也就是禁止使用数字与 "-" 字符); (3)字符串长度必须介于 1~31 之间。

当与 DynamicUser=yes 一起使用时, 指定的用户与组将在服务启动时动态分配,并在服务停止时自动释放,除非它们已经被静态的创建了(见下文)。 当与 DynamicUser=no 一起使用时, 指定的用户与组必须在服务启动之前就已经被静态的创建了, 例如在系统启动或安装软件包时,使用 sysusers.d(5) 机制所创建的用户与组。

DynamicUser=

设置是否动态分配用户。默认值为 "no" 。 设为 yes 表示在该单元启动时,为其动态分配一个 user/group 对,并在该单元停止时释放。 动态分配的 user/group 不会被添加到 /etc/passwd/etc/group 文件中, 而是由 glibc 的 NSS 插件 nss-systemd(8) 进行维护,以确保能够从系统的 user/group 数据库中查询到动态分配的 user/group 对。 可以通过上文的 User=Group= 明确指定动态分配的 user/group 的名称。 若未明确指定名称,则自动根据单元的名称生成: 如果单元名(不含类型后缀)恰好符合用户名规则,那么就直接使用单元名,否则将使用单元名的哈希值。 如果 User=Group= 恰好指定了一个已经静态存在的名称, 那么将直接使用已经静态存在的 user/group 而不是动态分配。因为动态分配的 UID/GID 范围在 61184-65519 之间, 所以静态存在的 UID/GID 应该避免使用这个范围。在任意时间点上, 每一个动态分配的 UID/GID 只能对应最多一个动态分配的 user/group 。 因为动态分配的 UID/GID 在单元停止后会被回收,并且会被反复循环使用, 所以使用动态分配用户的单元不应该在其停止后遗留下任何属于动态分配用户的文件或目录, 否则其他单元有可能在未来取得相同的 UID/GID ,从而成为这些遗留文件或目录的拥有者。 当 DynamicUser=yes 时,也同时隐含的设置了 RemoveIPC=yesPrivateTmp=yes ,从而确保将单元的 IPC 对象与临时文件的生存期 与单元自身的生存期、为该单元动态分配的 user/group 的生存期绑定在一起。 因为除 /tmp/var/tmp 之外,通常不存在其他全局可写的目录, 所以,这通常也确保了使用动态分配 user/group 的单元,不可能在单元停止之后还遗留任何文件或目录。 进一步,DynamicUser=yes 还同时隐含了 ProtectSystem=strictProtectHome=read-only ,以禁止单元写入文件系统上的敏感路径。 如果想要允许单元写入某些特定的路径, 那么必须将这些路径使用 ReadWritePaths= 白名单明确列出。 注意,必须小心使用这个白名单,以避免由于循环使用 UID/GID 带来的安全问题。 可以使用下文的 RuntimeDirectory= 设置一个运行时的写入目录, 该目录的拥有者将会被自动设为动态分配的 user/group ,并会在单元停止后被自动删除。 可以使用下文的 StateDirectory=, CacheDirectory=, LogsDirectory= 来设置一组专门用途的可写目录,以避免由于循环使用 UID/GID 带来的安全问题。

SupplementaryGroups=

设置进程在执行时使用的附加组。 值是一个空格分隔的组名或组ID列表。 可以多次使用此选项,以添加更多的附加组。 若设为空,则表示清空先前已设置的列表。 注意,此选项并不覆盖系统现有的附加组, 而只是在现有的附加组基础上进行扩展。 此选项不影响带有 "+" 前缀的命令。

RemoveIPC=

接受一个布尔值。 表示是否在单元停止时删除该单元所拥有的所有 IPC 对象。 对于使用相同用户/组的多个单元来说,仅在最后一个使用此 IPC 对象的单元停止之后,该 IPC 对象才会被删除。 特别地,将会删除 System V 信号量(semaphore)、 System V 与 POSIX 共享内存(shared memory segment)与消息队列(message queue)。 此设置仅在至少明确使用了 User=, Group=, DynamicUser= 之一的情况下才有效。并且对 root 用户的 IPC 对象无效。 当 DynamicUser=yes 时,此设置的默认值为 "yes" ,否则默认值为 "no" 。

Nice=

设置进程的默认谦让值。 可以设为 -20(最高优先级) 到 19(最低优先级) 之间的整数值。详见 setpriority(2) 手册。

OOMScoreAdjust=

设置进程因内存不足而被杀死的优先级。 可设为 -1000(禁止被杀死) 到 1000(最先被杀死)之间的整数值。 详见 proc.txt 文档。

IOSchedulingClass=

设置进程的IO调度类型。 可设为 0 到 3 之间的数字或对应的 none, realtime, best-effort, idle 字符串。详见 ioprio_set(2) 手册。

IOSchedulingPriority=

设置进程的IO调度优先级。 可设为 0(最高优先级) 到 7(最低优先级) 之间的数字。 实际可用的优先级取决于 IOSchedulingClass= 的设置。参见 ioprio_set(2) 手册。

CPUSchedulingPolicy=

设置进程的CPU调度策略。 可设为 other, batch, idle, fifo, rr 之一。 详见 sched_setscheduler(2) 手册。

CPUSchedulingPriority=

设置进程的CPU调度优先级。 有效值范围取决于 CPUSchedulingPolicy= 的设置。 例如对于实时调度策略(fifo, rr)来说, 可以设为 1(最低优先级) 到 99(最高优先级) 之间的整数。 详见 sched_setscheduler(2) 手册。

CPUSchedulingResetOnFork=

是否为派生(fork)的子进程重置CPU调度策略与优先级。 若设为 yes 则表示:当父进程的CPU调度策略与优先级高于默认值时, 将重置派生的子进程的CPU调度策略与优先级为默认值。 这样就可以阻止派生的子进程继承不应有的高优先级。详见 sched_setscheduler(2) 手册。 默认值为 no 。

CPUAffinity=

设置进程的CPU亲和性。 值是一个空格或逗号分隔的CPU编号与CPU范围列表。 CPU范围可以用"编号下限-编号上限"格式表示。 若多次设置此选项, 则表示将多个选项值的CPU亲和性以掩码的方式相融合。 若设为空,则表示重置掩码并清空先前设置的所有CPU编号列表。 详见 sched_setaffinity(2) 手册。

UMask=

设置文件创建掩码。详见 umask(2) 手册。默认值为 0022

Environment=

设置进程的环境变量, 值是一个空格分隔的 VAR=VALUE 列表。 可以多次使用此选项以增加新的变量或者修改已有的变量 (同一个变量以最后一次的设置为准)。 若设为空, 则表示清空先前所有已设置的变量。 注意: (1)不会在字符串内部进行变量展开(也就是"$"没有特殊含义); (2)如果值中包含空格或者等号, 那么必须在字符串两边使用双引号(")界定。

例如:

Environment="VAR1=word1 word2" VAR2=word3 "VAR3=$word 5 6"

设置了 "VAR1", "VAR2", "VAR3" 三个变量,其值分别为 "word1 word2", "word3", "$word 5 6"

详见 environ(7) 手册。

EnvironmentFile=

Environment= 类似, 不同之处在于此选项是从文本文件中读取环境变量的设置。 文件中的空行以及以分号(;)或井号(#)开头的行会被忽略, 其他行的格式必须符合 VAR=VALUE 的shell变量赋值语法。 行尾的反斜杠(\)将被视为续行符, 这与shell语法类似。 若想在变量值中包含空格, 则必须在值的两端加上双引号(")界定。

文件必须用绝对路径表示(可以包含通配符)。 但可在路径前加上"-"前缀表示忽略不存在的文件。 可以多次使用此选项, 以从多个不同的文件中读取设置。 若设为空, 则表示清空所有先前已经从文件中读取的环境变量。

这里列出的文件将在进程启动前的瞬间被读取, 因此可以由前一个单元生成配置文件, 再由后一个单元去读取它。

从文件中读取的环境变量会覆盖 Environment= 中设置的同名变量。 文件的读取顺序就是它们出现在单元文件中的顺序, 并且对于同一个变量,以最后读取的文件中的设置为准。

PassEnvironment=

将某些 systemd 系统服务管理器进程(PID=1)所持有的环境变量传递给该单元中的进程。 接受一个空格分隔的变量名列表。可以多次使用此选项以传递更多变量。 若设为空,则表示清空先前已设置的所有变量。 如果此处设置的变量并不是系统服务管理器进程(PID=1)所持有的环境变量,那么将会被悄无声息的忽略掉。 注意,此选项仅可用于传递PID=1的 systemd 系统服务管理器进程所持有的环境变量, 因为系统服务单元默认并不自动继承PID=1进程所持有的环境变量。 又因为用户服务单元默认就会自动继承PID≠1的 systemd 用户服务管理器进程的所有环境变量, 所以此选项对于用户服务管理器没有意义。

注意,通过此选项传递过来的环境变量的值会被 Environment=EnvironmentFile= 选项中的同名变量所覆盖。

例如:

PassEnvironment=VAR1 VAR2 VAR3

传递了 "VAR1", "VAR2", "VAR3" 三个变量,其值等于PID=1进程所持有的值。

参见 environ(7) 以了解更多环境变量的细节。

UnsetEnvironment=

明确撤销该单元的特定环境变量。 接受一个空格分隔的变量名("NAME")与特定值变量("NAME=VALUE")列表。 可以多次使用此选项以撤销更多变量。 若设为空,则表示清空先前已设置的所有列表。 特定值变量("NAME=VALUE")表示仅当变量"NAME"的值恰好等于"VALUE"时才会撤销"NAME"变量。 而普通的变量名("NAME")则表示无论变量"NAME"的值是什么,都会无条件的撤销"NAME"变量。 注意,因为 UnsetEnvironment= 的撤销操作发生在向单元内进程传递环境变量前的最后一步, 所以此选项可以撤销来自各种渠道的环境变量,包括: (1)由 Environment=EnvironmentFile= 设置的环境变量; (2)继承自服务管理器全局设置的环境变量(参见 systemd-system.conf(5) 手册中的 DefaultEnvironment= 选项); (3)根据 PassEnvironment= 继承的环境变量; (4)由服务管理器设置的环境变量(例如 $NOTIFY_SOCKET 之类); (5)由 PAM 模块设置的环境变量(如果已设置 PAMName= 的话)。

参见 environ(7) 以了解更多有关环境变量的详细介绍。

StandardInput=

设置进程的标准输入(STDIN)。 可设为 null, tty, tty-force, tty-fail, socket, fd 之一。

null 表示 /dev/null , 也就是所有的读取都只会得到一个文件结束标记(EOF)。

tty 表示 TTY(由 TTYPath= 设置), 也就是该进程将会成为终端的控制进程。 若终端已被其他进程控制, 则一直等到其他进程释放为止。

tty-forcetty 类似, 不同之处在于, 该进程将会强制立即取得终端的控制权, 并剥夺其他进程的控制权。

tty-failtty 类似, 不同之处在于,若终端已被其他进程控制, 则会导致该进程自身启动失败。

socket 仅可用于基于套接字启动的服务单元, 并且要求在套接字单元文件 (systemd.socket(5)) 中只设置了一个套接字。 主要用于兼容那些依赖于传统 inetd(8) 的守护进程。

fd 表示将标准输入(STDIN)连接到一个由 socket 单元提供的文件描述符。 可以通过 "fd:foobar" 格式 明确指定文件描述符的名称。 描述符名称的默认值为 "stdin" ,也就是 "fd" 等价于 "fd:stdin" 。 必须明确使用 Sockets= 选项 提供至少一个定义了文件描述符名称的 socket 单元。 注意,文件描述符的名称不一定和定义它的 socket 单元的名称一致。 如果出现了多个匹配,那么以第一个为准, 详见 systemd.socket(5) 手册对 FileDescriptorName= 选项的讲解。

默认值是 null

StandardOutput=

设置进程的标准输出(STDOUT)。 可设为 inherit, null, tty, journal, syslog, kmsg, journal+console, syslog+console, kmsg+console, socket, fd 之一。

inherit 表示使用 StandardInput= 设置的值。

null 表示 /dev/null , 也就是所有写入都会被丢弃。

tty 表示 TTY(由 TTYPath= 设置), 如果仅用于输出, 那么进程将无需取得终端的控制权, 亦无需等待其他进程释放终端控制权。

journal 表示 systemd 日志服务(通过 journalctl(1) 访问)。 注意,所有发到 syslogkmsg 的日志都会 隐含的复制一份到 journal 中。

syslog 表示 syslog(3) 日志服务。 注意,此时所有日志都会隐含的复制一份到 journal 中。

kmsg 表示内核日志缓冲区(通过 dmesg(1) 访问)。 注意,此时所有日志都会隐含的复制一份到 journal 中。

journal+console, syslog+console, kmsg+console 与上面三个值类似, 不同之处在于所有日志都会再复制一份到系统的控制台上。

socket 的解释与 StandardInput= 中的解释完全相同。

fd 表示将标准输出(STDOUT)连接到一个由 socket 单元提供的文件描述符。 可以通过 "fd:foobar" 格式 明确指定文件描述符的名称。 描述符名称的默认值为 "stdout" ,也就是 "fd" 等价于 "fd:stdout" 。 必须明确使用 Sockets= 选项 提供至少一个定义了文件描述符名称的 socket 单元。 注意,文件描述符的名称不一定和定义它的 socket 单元的名称一致。 如果出现了多个匹配,那么以第一个为准, 详见 systemd.socket(5) 手册对 FileDescriptorName= 选项的讲解。

如果单元的标准输出(StandardOutput=)或标准错误(StandardError=)中含有 journal, syslog, kmsg 之一, 那么该单元将会自动隐含的获得 After=systemd-journald.socket 依赖(见上文)。

此选项的默认值等于 systemd-system.conf(5) 中的 DefaultStandardOutput= 选项的值(默认为 journal)。 注意,设置此选项将会导致自动添加额外的依赖关系(见上文)。

StandardError=

设置进程的标准错误(STDERR)。 取值范围及含义与 StandardOutput= 相同。 但有如下例外:(1) inherit 表示使用 StandardOutput= 的值。 (2) fd 的文件描述符名称的默认值为 "stderr"

此设置的默认值是 systemd-system.conf(5)DefaultStandardError= 的值 (默认为 inherit)。 注意,设置此选项将会导致自动添加额外的依赖关系(见上文)。

TTYPath=

设置用于 STDIN, STDOUT, STDERR 的终端设备节点文件(参见前面三个选项)。 默认值是 /dev/console

TTYReset=

是否在进程执行前与进程退出后,重置 TTYPath= 终端设备。 默认值是 "no"

TTYVHangup=

是否在进程执行前与进程退出后, 断开所有已打开 TTYPath= 终端设备的客户端。 默认值是 "no"

TTYVTDisallocate=

TTYPath= 是一个虚拟控制台终端时, 是否尝试在进程执行前与进程退出后,释放该终端, 以确保清空屏幕与回滚缓冲区。 默认值是 "no"

SyslogIdentifier=

设置日志标识符(发送日志消息时加在行首的字符串)。 默认值是进程的名称。 此选项仅在 StandardOutput=StandardError= 的值包含 syslog(+console), journal(+console), kmsg(+console) 之一时才有意义。

SyslogFacility=

设置 syslog(3) 的 facility 值。 可设为 kern, user, mail, daemon, auth, syslog, lpr, news, uucp, cron, authpriv, ftp, local0, local1, local2, local3, local4, local5, local6, local7 之一。仅在将 StandardOutput=StandardError= 设为 syslog 时有意义。 默认值是 daemon

SyslogLevel=

设置默认的 syslog(3) 日志级别。 可设为 emerg, alert, crit, err, warning, notice, info, debug 之一。仅在将 StandardOutput=StandardError= 设为 syslogkmsg 时有意义。 注意,进程可以在某些日志行的头部附加一个自定义的日志级别标记, 以覆盖此处设置的默认级别。 但是可以通过设置 SyslogLevelPrefix=no 来禁止识别自定义的日志级别(见后)。 参见 sd-daemon(3) 手册。 默认值是 info

SyslogLevelPrefix=

此选项仅在 StandardOutput=StandardError=syslog, kmsg, journal 之一时有意义。 设为 yes 表示按照日志行头部附加的日志级别发送日志 (在实际发送时,头部的日志级别标记将被移除)。 设为 no 则表示将日志行按原样发送而不作任何解析。 参见 sd-daemon(3) 手册。 默认值是 yes

TimerSlackNSec=

设置进程的定时器粒度。 详见 prctl(2) 手册。 定时器的粒度大小控制着进程被操作系统定时器唤醒的时间精度(也就是最小时间片)。 如果仅设为一个整数而没有单位,那么单位是纳秒。 也可以在整数后面加上时间单位后缀: "ms"(毫秒), "s"(秒), "min"(分钟), "h"(小时), "d"(天)

LimitCPU=, LimitFSIZE=, LimitDATA=, LimitSTACK=, LimitCORE=, LimitRSS=, LimitNOFILE=, LimitAS=, LimitNPROC=, LimitMEMLOCK=, LimitLOCKS=, LimitSIGPENDING=, LimitMSGQUEUE=, LimitNICE=, LimitRTPRIO=, LimitRTTIME=

设置进程的各种软/硬资源限制。详见 setrlimit(2) 手册。 这些指令的值有两种表示法,一个单独的 value 值表示将软硬两种限制设为同一个值。 而冒号分隔的 soft:hard 值表示分别设置软限制与硬限制(例如 LimitAS=4G:16G)。 特殊值 infinity 表示没有限制。 对于以字节为单位的选项,可以使用以1024为基数的 K, M, G, T, P, E 后缀(例如 LimitAS=16G)。 对于时间限制,可以加上 "ms"(毫秒), "s"(秒), "min"(分钟), "h"(小时), "d"(天), "w"(周) 等明确的时间单位后缀(systemd.time(7))。 如果仅设置了一个整数而没有单位,那么对于 LimitCPU= 来说默认单位是秒。 而对于 LimitRTTIME= 来说默认单位是微秒(百万分之一秒)。 注意,这些资源限制值的实际效果可能会受到各自有效粒度的影响。 例如 LimitCPU= 所设置的时间会被向上取整到一秒钟的整数倍。 LimitNICE= 的值有两种表示法: 可以设为带有 "+" 或 "-" 前缀的谦让值(介于 -20 到 19 之间)。 也可以设为无前缀的原始资源限制参数(介于 1 到 40 之间)。

注意,对进程的资源限制是针对单个进程的, 当父进程派生出一个子进程的时候, 子进程便获得了一个全新的资源集(重新计算资源限制), 而不是共享父进程的资源限制,这可能会导致资源超限。 而且 LimitRSS= 在Linux平台是没有意义的(因为没有被Linux实现)。 建议使用 systemd.resource-control(5) 中的资源限制方法,而不是此处这些针对单个进程的限制指令, 因为前者的限制是针对整个单元的,并且可以在运行时动态调整,所以是更好的选择。 例如 MemoryLimit= 就是对 LimitRSS= 的上佳替代。

对于系统单元来说,可以自由地设置资源限制。 而对于用户单元(也就是由 systemd(1) 用户实例运行的单元)来说, 只能在操作系统分配给该用户的全部资源范围内,进一步限制单元的资源消耗。

对于一个单元来说,未明确限制的资源的默认值由 systemd-system.conf(5) 中的 DefaultLimitCPU=, DefaultLimitFSIZE=, … 系列选项的值确定。 而这一系列选项的默认值则取决于底层操作系统的配置(全系统或针对单个用户)。

表 1. 资源限制指令、对应的 ulimit 命令、单位

指令等价的 ulimit 命令单位
LimitCPU=ulimit -t
LimitFSIZE=ulimit -f字节
LimitDATA=ulimit -d字节
LimitSTACK=ulimit -s字节
LimitCORE=ulimit -c字节
LimitRSS=ulimit -m字节
LimitNOFILE=ulimit -n文件描述符的数量
LimitAS=ulimit -v字节
LimitNPROC=ulimit -u进程的数量
LimitMEMLOCK=ulimit -l字节
LimitLOCKS=ulimit -x锁的数量
LimitSIGPENDING=ulimit -i信号队列的长度(排队的信号数量)
LimitMSGQUEUE=ulimit -q字节
LimitNICE=ulimit -e谦让度
LimitRTPRIO=ulimit -r实时优先级
LimitRTTIME=不存在微秒

PAMName=

设置建立PAM会话所使用的PAM服务名称。 此选项仅在与 User= 连用时才有意义(否则将被忽略)。 若设置,那么将以设置的名称为进程注册一个PAM会话。 若未设置,那么将不会为进程打开任何PAM会话。详见 pam(8) 手册。

注意,对于每一个使用了此选项的单元, 都会在其生存期内维护一个PAM会话处理进程(作为该单元的一部分),以确保在整个单元的生存期内,PAM机制始终可以正常工作。 此PAM会话处理进程名为 "(sd-pam)" 并且始终作为该单元的主进程的直接子进程存在。

使用此选项非常可能(取决于PAM的配置)导致在该单元启动时, 将该单元的主进程迁移到自己的会话 scope 单元中,从而使得同一个主进程被关联到两个单元: (1)启动该主进程的单元(也就是配置了 PAMName= 选项的单元); (2)与该单元对应的会话 scope 单元。 例如,将此选项与 NotifyAccess=all 一起使用时,就会导致上述结果。 在这种情况下,该主进程的所有子进程将会被仅关联到对应的会话 scope 单元, 从而导致这些子进程无法通过通知消息反映原始服务单元的状态变化(因为这些通知消息仅属于会话 scope 单元,而非原始服务单元)。 因此,不应该将 PAMName=NotifyAccess=all 一起使用。

CapabilityBoundingSet=

设置进程的 capability 集合(bounding, effective, permitted, inheritable)中应该包含哪些 capabilities(7) 。 选项值是一个空格分隔的 capability 名称列表, 例如 CAP_SYS_ADMIN, CAP_DAC_OVERRIDE, CAP_SYS_PTRACE 。 列表中的 capabilities 将会被包含在 capability 集合中, 而所有其他不在列表中的 capabilities 则会被剔除。 如果列表以 "~" 符号开头,那么表示取反, 也就是所有列表之外的 capabilities 将会被包含在 capability 集合中。 若未设置此选项,则表示不修改进程的 capability 集合。 若多次设置此选项,则表示将多个设置的 capability 集合合并: 一般用 AND 逻辑合并,但以"~"开头的行用 OR 逻辑合并。 若设为空,则表示清空所有已设置的 capability 集合。 若设为一个单独的 "~" 字符, 则表示清空先前的所有设置,并将 capability 集合重置为包含所有的 capabilities 。 此选项不影响带有 "+" 前缀的命令。

例子:如果一个单元拥有如下设置:

CapabilityBoundingSet=CAP_A CAP_B
CapabilityBoundingSet=CAP_B CAP_C

那么表示 CAP_A, CAP_B, CAP_C 全部被设置。 如果在第二行前面加上 "~" 前缀:

CapabilityBoundingSet=CAP_A CAP_B
CapabilityBoundingSet=~CAP_B CAP_C

那么表示仅有 CAP_A 被设置。

AmbientCapabilities=

设置进程的 ambient capability 集合中应该包含哪些 capabilities(7) 。 选项值是一个空格分隔的 capability 名称列表, 例如 CAP_SYS_ADMIN, CAP_DAC_OVERRIDE, CAP_SYS_PTRACE 。 若多次设置此选项,则表示合并多个已设置的 ambient capability 集合(参见上文 CapabilityBoundingSet= 的例子)。 如果列表以 "~" 符号开头,那么表示取反, 也就是所有列表之外的 capabilities 将会被包含在 ambient capability 集合中。 若设为空,则表示清空所有已设置的 ambient capability 集合。 若设为一个单独的 "~" 字符,则表示清空先前的所有设置,并将 ambient capability 集合重置为包含所有的 capabilities 。 注意,添加到 ambient capability 集合中的 capabilities 也会被添加到进程的 inherited capability 集合中。 你可以使用 ambient capability 集合给以普通用户身份运行的进程赋予某些 capabilities 。 注意,此时 keep-caps 将被自动添加到 SecureBits= 中, 以确保此处设置的 capabilities 不受用户设置的影响。 此选项不影响带有 "+" 前缀的命令。

SecureBits=

设置进程的安全位。 值是一个空格分隔的列表。 可用列表项如下: keep-caps, keep-caps-locked, no-setuid-fixup, no-setuid-fixup-locked, noroot, noroot-locked 。 可以多次使用此选项,以合并(OR)多个安全位。 若设为空,则表示将安全位重置为"0"。 此选项不影响带有 "+" 前缀的命令。 参见 capabilities(7) 手册。

ReadWritePaths=, ReadOnlyPaths=, InaccessiblePaths=

为进程设置一个新的文件系统名字空间,也就是限制进程可访问的文件系统范围。 每个选项的值都是一个空格分隔的绝对路径列表。 注意,这里所说的"绝对路径"实际上是以主机或容器根目录(也就是运行 systemd 的系统根目录)为基准的绝对路径。 注意,如果路径是一个软连接,那么在追踪软连接时将以 RootDirectory=/RootImage= 设置的根目录为基准。

对于 ReadWritePaths= 中列出的路径,进程从名字空间内访问与从外部访问的权限是一样的。 对于 ReadOnlyPaths= 中列出的路径, 即使进程从外部访问时拥有写入权限,从名字空间内访问时,进程也依然只能拥有只读权限。 可将 ReadWritePaths= 嵌套于 ReadOnlyPaths= 内, 以实现在只读目录内嵌套可写子目录的功能。 当 ProtectSystem=strict 时,可以使用 ReadWritePaths= 设置可写入路径的白名单。 对于 InaccessiblePaths= 中的路径, 进程从名字空间内访问时没有任何权限(既不能读取也不能写入)。

注意,如果在这些路径下的子目录中又挂载了新的文件系统, 那么这些选项所设置的限制并不会传递到这些新挂载的文件系统上。这里所说的"路径"可以是目录、文件、软连接。 可以多次使用这些选项,以在文件系统名字空间内增加更多的受限路径。 若将某选项设为空,则表示撤消该选项先前设置的所有路径列表。

可以在路径前加上 "-" 前缀,表示忽略不存在的路径。 也可以在路径前加上 "+" 前缀,表示路径是以单元的 RootDirectory=/RootImage= 为基准(而不是以主机或容器根目录为基准[见上文])。如果想要在同一个路径上同时使用 "-" 与 "+" 前缀,那么必须确保 "-" 在 "+" 之前。

注意,使用这些选项后, 文件系统的挂载将无法从主机向服务传递(但从服务向主机的传递依然有效)。 因此,不可将这些选项用于需要在主机名字空间中挂载文件系统的服务。 注意,特权进程可以撤销这些设置的效果。为了给单元设置一个有效的沙盒环境, 建议将这些设置与 CapabilityBoundingSet=~CAP_SYS_ADMINSystemCallFilter=~@mount 一起使用。

BindPaths=, BindReadOnlyPaths=

设置专用于该单元的绑定挂载点(bind)。 通过"绑定挂载",可以将该单元原本不可见的挂载点,放入该单元的可见范围内。 注意,通过此选项创建的绑定挂载点专属于该单元,对运行该单元的主机并不可见。 这些选项接受一个空格分隔的绑定挂载点的定义列表。列表中的每一项定义都遵守 "原路径[:目标路径[:挂载选项]]" 格式(中括号表示后两项为可选)。 目标路径的默认值是原路经,挂载选项只能设为 "rbind"(递归) 或 "norbind"(不递归) 之一。 如果省略了目标路径,那么必须同时一起省略挂载选项。

BindPaths= 用于创建普通的可读写的绑定挂载点(除非原挂载点本来就是只读的)。 BindReadOnlyPaths= 用于创建只读的绑定挂载点。 可以多次使用这些选项,以设置更多的绑定挂载点。 注意,若将某个选项设为空,则表示撤消所有先前设置的绑定挂载点列表, 包括普通的可读写的绑定挂载点以及只读的绑定挂载点。 也就是任意一个选项都会一次性撤销两个选项先前的全部设置。

这些选项一般和 RootDirectory=/RootImage= 一起使用, 此时,原路径一般是主系统上的某个挂载点, 而目标路径则是该单元根目录下的某个路径。

PrivateTmp=

设为 yes 表示在进程的文件系统名字空间中挂载私有的 /tmp/var/tmp 目录, 也就是不与名字空间外的其他进程共享临时目录。 这样做会增加进程的临时文件安全性,但同时也让进程之间无法通过 /tmp/var/tmp 目录进行通信。 同时,当服务停止之后,所有先前在临时目录中创建的文件都将被删除。 可以通过 JoinsNamespaceOf= 选项(参见 systemd.unit(5) 手册)将多个单元 运行在同一个名字空间的私有 /tmp/var/tmp 中。 当 DynamicUser=yes 时,此设置的默认值为 yes ,否则默认值为 no 。 注意,与 ReadOnlyPaths= 一样,使用此选项之后,文件系统的挂载将无法从主机向服务传递(但从服务向主机的传递依然有效)。 开启此选项的同时也隐含的在该单元中添加了对 /tmp/var/tmp 挂载点 的 Requires=After= 依赖, 以及对 systemd-tmpfiles-setup.service(8) 单元的 After= 依赖。

注意,因为此选项的设置有可能在实际上无法落实(例如挂载名字空间不可用), 所以,在编写单元文件的时候,不应该将单元的安全依赖于此选项必然生效的假定。

PrivateDevices=

设为 yes 表示为进程设置一个全新的 /dev 挂载点, 并仅在其中添加诸如 /dev/null, /dev/zero, /dev/random, /dev/ptmx, /dev/pts/ … 之类的虚拟设备。 注意,并不包括 /dev/sda, /dev/md0, /dev/mem, /dev/port … 之类的物理设备。 这样可以有效的关闭进程对物理设备的访问。 设为 yes 将会安装一个禁止使用底层I/O系统调用(@raw-io)的过滤器、 强行从 CapabilityBoundingSet= 中移除 CAP_MKNODCAP_SYS_RAWIO 项、 强行设置 DevicePolicy=closed (详见 systemd.resource-control(5) 手册)。因此,不可将该选项用于需要在主机名字空间中挂载文件系统的服务。 注意:(1)这个全新的 /dev 挂载点将以 "ro,noexec" 选项挂载(只读,不可执行)。 (2)"noexec"可能会导致某些老旧的程序故障,因为这些程序企图通过对 /dev/zero 使用 mmap(2) 来设置可执行内存,而不是使用新式的 MAP_ANON 方法。 注意,与 ReadOnlyPaths= 一样,使用此选项之后,文件系统的挂载将无法从主机向服务传递(但从服务向主机的传递依然有效)。 如果开启此选项,并且该单元运行于用户模式或者缺少 CAP_SYS_ADMIN capability 的系统模式(例如明确将 User= 设为普通用户), 那么将自动隐含 NoNewPrivileges=yes 的设置。

注意,因为此选项的设置有可能在实际上无法落实(例如挂载名字空间不可用), 所以,在编写单元文件的时候,不应该将单元的安全依赖于此选项必然生效的假定。

PrivateNetwork=

设为 yes 表示为进程设置一个新的网络名字空间, 并在其中仅配置一个 "lo" 本地回环设备(没有任何物理网络设备)。 这可以有效关闭进程对实际物理网络的访问。 可以通过 JoinsNamespaceOf= 选项(参见 systemd.unit(5) 手册)将多个单元运行在同一个私有的网络名字空间中。 注意,此选项将会从主机断开所有套接字(包括 AF_NETLINK 与 AF_UNIX), 同时, 进程亦无法访问那些位于抽象套接字名字空间中的AF_UNIX套接字 (不过依然可以访问位于文件系统上的AF_UNIX套接字)。 默认值是 no 。

注意,因为此选项的设置有可能在实际上无法落实(例如网络名字空间不可用), 所以,在编写单元文件的时候,不应该将单元的安全依赖于此选项必然生效的假定。

PrivateUsers=

设为 yes 表示为进程设置一个新的用户名字空间, 并仅在其中保留最小化的 user/group 映射。具体说来就是仅在该名字空间内保留 "root" 用户与组、 单元自身的用户与组,同时将所有其他用户与组统一映射到 "nobody" 用户与组。 这样就可以安全的将该单元所使用的 user/group 数据库从主机系统中剥离出来, 从而为该单元创建一个有效的沙盒环境。所有不属于 "root" 或该单元自身用户的 文件、目录、进程、IPC 对象……等资源,在该单元内部依然可见, 但是它们将会变为全部属于 "nobody" 用户与组。开启此选项之后, 无论单元自身的用户与组是否为 "root" ,在主机的名字空间内,该单元内的所有进程都将以非特权用户身份运行。 特别地,这意味着该单元内的进程在主机的名字空间内没有任何 capability , 但是在该单元的用户名字空间内部,仍然拥有所有全部的 capability 。 诸如 CapabilityBoundingSet= 之类的设置,将仅在该单元的用户名字空间内部有意义(不能突破到主机的名字空间)。 默认值是 no 。

此设置在与 RootDirectory=/RootImage= 一起使用时比较有意义, 因为在此场景中仅需要映射 "root", "nobody" 以及单元自身的用户与组。

注意,因为此选项的设置有可能在实际上无法落实(例如用户名字空间不可用), 所以,在编写单元文件的时候,不应该将单元的安全依赖于此选项必然生效的假定。

ProtectSystem=

可设为布尔值或 "full" 或 "strict" 之一。 若设为 yes 则表示为该单元以只读模式挂载 /usr/boot 目录。 若设为 "full" 则表示为该单元以只读模式挂载 /etc/usr/boot 目录。 若设为 "strict" 则表示为该单元以只读模式挂载 除虚拟文件系统 /dev, /proc, /sys 之外的所有其他目录(对这些目录可以使用 PrivateDevices=, ProtectKernelTunables=, ProtectControlGroups= 进行保护)。 这样可以有效禁止该单元对操作系统、配置文件、本地挂载点进行任何修改。 推荐为所有需要长时间运行的服务开启此选项,除非该单元确实需要对系统进行修改。 开启此选项之后,还可以使用 ReadWritePaths= 来将某些特定的目录改为读写模式。 当 DynamicUser=yes 时,此设置的默认值为 "yes" ,否则默认值为 "no" 。 注意,与 ReadOnlyPaths= 一样,使用此选项之后, 文件系统的挂载将无法从主机向服务传递(但从服务向主机的传递依然有效)。

ProtectHome=

可设为布尔值或 "read-only" 。 若设为 yes 则表示对该单元屏蔽 /home, /root, /run/user 目录(内容为空且不可写入)。 若设为 "read-only" 则表示 /home, /root, /run/user 目录对该单元仅为只读(不可写入)。 推荐为所有需要长时间运行的服务开启此选项(特别是面向网络的服务),以确保其无法访问隐私数据。 当 DynamicUser=yes 时,此设置的默认值为 "yes" ,否则默认值为 "no" 。 注意,与 ReadOnlyPaths= 一样,使用此选项之后, 文件系统的挂载将无法从主机向服务传递(但从服务向主机的传递依然有效)。

ProtectKernelTunables=

设置是否保护内核变量。设为 yes 表示对于该单元内的进程,所有通过 /proc/sys, /sys, /proc/sysrq-trigger, /proc/latency_stats, /proc/acpi, /proc/timer_stats, /proc/fs, /proc/irq 访问的内核变量,将全部变为只读。通常,仅在系统启动的时候设置内核变量, 比如通过 sysctl.d(5) 配置。 因为只有极少数服务需要在运行时修改内核变量,所以建议为绝大多数服务单元开启此选项。 注意,与 ReadOnlyPaths= 一样,使用此选项之后,文件系统的挂载将无法从主机向服务传递(但从服务向主机的传递依然有效)。 默认值为 "no" 。如果将此选项设为 yes 并且该单元运行在用户模式, 或者运行在没有 CAP_SYS_ADMIN capability 的系统模式(也就是明确设置了 User= 为非特权用户), 那么将自动隐含 NoNewPrivileges=yes 的设置。 注意,此选项不能阻止通过 IPC 调用其他进程的方式间接修改内核变量。 不过可以使用 InaccessiblePaths= 来屏蔽 IPC 对象。 注意, ProtectKernelTunables=yes 隐含的设置了 MountAPIVFS=yes

ProtectKernelModules=

设置是否保护内核模块。默认值为 no 。 设为 yes 表示禁止明确的加载或卸载内核模块。 对于绝大多数不需要使用特别的文件系统或内核模块才能工作的服务, 建议明确将此选项设为 yes 。 开启此选项之后,将会删除该单元的 CAP_SYS_MODULE capability 并且添加一个系统调用过滤器,以禁止加载或卸载内核模块, 同时,禁止访问 /usr/lib/modules 目录。 注意,与 ReadOnlyPaths= 一样,使用此选项之后, 文件系统的挂载将无法从主机向服务传递(但从服务向主机的传递依然有效)。 注意,将此选项设为 yes ,既不会阻止自动加载用户明确配置为自动加载的内核模块, 也不会阻止内核自身为了完成特定功能而自动加载的内核模块。 要想彻底禁止内核模块的自动加载功能,可以参考 sysctl.d(5) 中的 kernel.modules_disabled 参数,以及讲解 /proc/sys/kernel/modules_disabled 的文档。 如果开启了此选项,并且该服务运行在用户模式或者没有 CAP_SYS_ADMIN capability 的系统模式(例如明确将 User= 设为普通用户),那么将自动隐含 NoNewPrivileges=yes 的设置。

ProtectControlGroups=

设置是否保护 cgroups 。默认值为 no 。 设为 yes 表示仅允许以只读模式访问 Linux Control Groups (cgroups(7)), 也就是仅允许以只读模式访问 /sys/fs/cgroup 。 除了容器管理程序,其他服务不应该对 cgroups 拥有控制权。因此,建议为绝大多数服务单元开启此选项。 注意,与 ReadOnlyPaths= 一样,使用此选项之后, 文件系统的挂载将无法从主机向服务传递(但从服务向主机的传递依然有效)。 注意, ProtectControlGroups=yes 隐含了 MountAPIVFS=yes

MountFlags=

设置文件系统的挂载传递标记,可设为 shared, slave, private 之一。 这些标记控制着文件系统挂载点的挂载和卸载动作如何在主机与服务单元之间传递。 参见 mount(2) 手册。 shared 表示挂载和卸载将会在主机名字空间和单元名字空间之间同步(双向可见); slave 表示单元内的挂载和卸载不会传递到主机,但主机中挂载的文件系统依然对单元内的进程可见(单向可见)。 private 表示主机的挂载和卸载不会传递到单元,同时单元中的挂载亦不会传递到主机中(双向不可见)。 如果将此选项设为 slaveprivate ,那么所有派生进程创建的挂载, 都将在完成 ExecStartPre=, ExecStartPost=, ExecStart=, ExecStopPost= 命令行之后被卸载。 注意,slave 意味着在主机名字空间内卸载文件系统时, 有可能因为在单元名字空间内该文件系统依然被使用,从而造成"设备忙"的故障。 本选项的默认值一般是 shared ,但如果使用了 PrivateTmp=, PrivateDevices=, ProtectSystem=, ProtectHome=, ProtectKernelTunables=, ProtectControlGroups=, ReadOnlyPaths=, InaccessiblePaths=, ReadWritePaths= 选项之一, 那么本选项的默认值将会自动从 shared 降级到 slave , 因为这些选项要求挂载和卸载必须不能从单元传递到主机。

UtmpIdentifier=

设置一个4字符长度的标识符, 用于在 utmp(5) 与 wtmp 中标识自己。 若长度超过4个字符,则截取4个结尾字符。 该选项应该仅用于 agetty(8) 之类的服务, 以及好像是被 getty 进程运行的服务 (见下文 UtmpMode= 选项), 因为它必须在执行前和退出后在 utmp/wtmp 中创建和清除记录。 该选项可以识别 %I 风格的字符串。 默认值为空, 也就是不在 utmp/wtmp 中创建和清除日志。

UtmpMode=

此选项仅在设置了 UtmpIdentifier= 时才有意义, 表示为此服务生成哪种类型的 utmp(5) 与 wtmp 记录。 "init" 表示仅生成 INIT_PROCESS 项, 被调用的进程必须实现与 getty 兼容的 utmp/wtmp 逻辑。 "login" 表示依次生成 INIT_PROCESSLOGIN_PROCESS 项, 被调用的进程必须实现与 login(1) 兼容的 utmp/wtmp 逻辑。 "user" 表示依次生成 INIT_PROCESS, LOGIN_PROCESS, USER_PROCESS 项, 被调用的进程必须作为一个会话首进程(session leader)运行。 默认值是 "init" 。

SELinuxContext=

设置进程的 SELinux 安全上下文。 设置此选项会覆盖自动域名转换,不过,安全策略依然需要对转换进行授权。 此选项仅在 SELinux 确实被开启的情况下才有意义。 若加上 "-" 前缀则表示忽略一切错误。 此选项不影响带有 "+" 前缀的命令。 详见 setexeccon(3) 文档。

AppArmorProfile=

设置进程的 profile 名称,进程将在启动时切换到此 profile 。 设置的 profile 必须已经加载到内核中,否则该单元将无法启动。 此选项仅在 AppArmor 确实被开启的情况下才有意义。 若加上 "-" 前缀则表示忽略一切错误。 此选项不影响带有 "+" 前缀的命令。

SmackProcessLabel=

设置进程的 SMACK64 安全标签。 进程将以设定的标签启动, SMACK 将会根据此标签决定是否允许该进程启动。 若允许,进程将继续以此标签运行, 除非该可执行文件有其自身的 SMACK64EXEC 标签 (此时将会切换到其自身标签运行)。 默认值是 systemd 的运行时标签。 该选项仅在 SMACK 已启用的情况下才有意义。

若加上 "-" 前缀则表示忽略一切错误。 设为空表示撤消先前的设置。 此选项不影响带有 "+" 前缀的命令。

IgnoreSIGPIPE=

默认值 yes 表示忽略发送给进程的 SIGPIPE 信号, 因为该信号通常仅对shell管道有意义。

NoNewPrivileges=

设为 yes 表示该服务的所有进程与子进程都不能通过 execve() 调用获得任何新权限(例如通过 setuid/setgid 位或者文件系统 capability)。 该选项是最简单也是最有效的防止进程提升权限的方法。 默认值为 no 。但是,当 SystemCallFilter=, SystemCallArchitectures=, RestrictAddressFamilies=, RestrictNamespaces=, PrivateDevices=, ProtectKernelTunables=, ProtectKernelModules=, MemoryDenyWriteExecute=, RestrictRealtime= 之一被开启的时候,此选项将会被自动强制设为 yes 。

SystemCallFilter=

设置进程的系统调用过滤器。 值是一个空格分隔的系统调用名称列表(默认为白名单)。 如果进程使用了列表之外的系统调用,将会立即被 SIGSYS 信号杀死。 可以在列表开头添加 "~" 字符表示反转(变成黑名单), 也就是仅禁止使用列表中列出的系统调用。 如果以用户模式运行或者以不含 CAP_SYS_ADMIN capability 的系统模式运行(例如设置了 User=nobody), 那么将自动隐含 NoNewPrivileges=yes 的设置。 该选项依赖于内核的 Secure Computing Mode 2 接口("seccomp filtering"),常用于强制建立一个最小化的沙盒环境。 注意,execve, exit, exit_group, getrlimit, rt_sigreturn, sigreturn 以及查询系统时间与暂停执行(sleep)的系统调用是默认隐含于白名单中的。 可以多次使用此选项以融合多个过滤器。 若设为空,则表示清空先前所有已设置的过滤器。 此选项不影响带有 "+" 前缀的命令。

注意,在支持混合ABI的系统上(例如 x86/x86-64),建议关闭次要的ABI(例如关闭 x86 以使用纯 x86-64 环境), 以确保进程无法通过次要ABI接口绕过此处设置的限制。 我们强烈建议将此选项与例如 SystemCallArchitectures=native 这样的设置一起使用。

注意,过于严苛的系统调用过滤器有可能会对服务单元的正常进程执行与故障处理机制产生不良影响。 特别地,如果 execve 系统调用被屏蔽, 那么将无法启动服务进程。 此外,如果服务进程启动失败(例如未找到可执行文件), 那么故障处理机制有可能需要额外访问更多的系统调用,以确保可以正常的收拾残局与记录错误日志。 为了调试是否是因为系统调用过滤器过于严苛而导致的单元故障,可以临时禁用系统调用过滤器。

如果同时设置了白名单和黑名单, 那么以先出现的名单为基准, 后出现的名单将基于第一个名单对列表项进行加减。 例如, 首个列表为白名单, 包含 read, write 两项, 随后又是一个仅包含一个 write 的黑名单, 那么最终结果将是一个仅包含 read 的白名单。

因为系统调用的数量非常巨大, 所以 systemd 预定义了一些以 "@" 字符开头的系统调用的集合, 以方便使用。

表 2. 预定义的系统调用集合

集合名字描述
@aio异步 I/O 操作 (io_setup(2), io_submit(2), …)
@basic-io基本的 I/O 操作:读取、写入、寻址、文件描述符的复制与关闭 (read(2), write(2), …)
@chown更改文件归属 (chown(2), fchownat(2), …)
@clock更改系统时钟 (adjtimex(2), settimeofday(2), …)
@cpu-emulation CPU 模拟 (vm86(2) …)
@debug调试、性能监控、跟踪 (ptrace(2), perf_event_open(2) …)
@file-system文件系统操作:打开文件/目录、创建文件/目录、读写文件/目录、重命名文件/目录、删除文件/目录、读取文件属性、创建硬连接/软连接
@io-event事件循环 (poll(2), select(2), epoll(7), eventfd(2) …)
@ipc管道, SysV IPC, POSIX 消息队列, 其他IPC (mq_overview(7), svipc(7))
@keyring内核密钥环 (keyctl(2) …)
@memlock将内存锁定在 RAM 中 (mlock(2), mlockall(2) …)
@module加载/卸载内核模块 (init_module(2), delete_module(2) …)
@mount挂载/卸载文件系统 (mount(2), chroot(2), …)
@network-ioSocket I/O (包括 AF_UNIX): socket(7), unix(7)
@obsolete不常用/反对使用/未实现的系统调用 (create_module(2), gtty(2), …)
@privileged所有需要特权的调用 (capabilities(7))
@process进程控制、执行、名字空间操作 (clone(2), kill(2), namespaces(7), …
@raw-io原始 I/O 端口访问 (ioperm(2), iopl(2), pciconfig_read(), …
@reboot重新启动与准备重启 (reboot(2), kexec(), …)
@resources更改资源限制、内存使用、进程调度 (setrlimit(2), setpriority(2), …)
@setuid更改 UID/GID 凭证 (setuid(2), setgid(2), setresuid(2), …)
@signal操纵与处理进程信号 (signal(2), sigprocmask(2), …)
@swap挂载与卸载 swap 设备 (swapon(2), swapoff(2))
@sync将内存中的缓存刷写到磁盘上 (fsync(2), msync(2), …)
@timer按时间编排执行计划 (alarm(2), timer_create(2), …)


当内核增加新的系统调用时, 上述集合的内容可能会随之发生变化。 此外,集合的内容也会跟随内核版本以及 编译 systemd 的目标架构的不同而变化。可以使用 systemd-analyze syscall-filter 精确的列出每个过滤器中的系统调用。

推荐将文件系统名字空间相关的选项与 SystemCallFilter=~@mount 一起使用,以阻止单元内的进程改变挂载属性。 文件系统名字空间相关的选项有: PrivateTmp=, PrivateDevices=, ProtectSystem=, ProtectHome=, ProtectKernelTunables=, ProtectControlGroups=, ReadOnlyPaths=, InaccessiblePaths=, ReadWritePaths=

SystemCallErrorNumber=

如果将此选项设为一个 "errno" 名称 (EPERM, EACCES, EUCLEAN), 那么当进程触犯 SystemCallFilter= 规则时, 将会得到一个这里设定的 "errno" , 而不是被 SIGSYS 信号杀死。 若未设置此选项或设为空, 则按常规使用 SIGSYS 信号杀死违规进程。

SystemCallArchitectures=

设置进程可以使用哪些体系结构的系统调用,选项值是一个空格分隔的体系结构标识符列表。 可以使用的标识符与 ConditionArchitecture= 相同(参见 systemd.unit(5) 手册), 此外还包括 x32, mips64-n32, mips64-le-n32, native 。 该选项可用于强制仅允许执行特定体系结构的二进制程序。 比如禁止在 x86-64 机器上执行32位的 x86 二进制程序。 特殊值 native 表示编译 systemd 时的目标架构。 如果以用户模式运行或者以不含 CAP_SYS_ADMIN capability 的系统模式运行(例如设置了 User=nobody),那么将自动隐含 NoNewPrivileges=yes 的设置。 该选项的默认值为空,表示不作任何限制。 但若设为非空,则 native 将被隐含的包含在列表中。

注意,系统调用过滤器并非在所有体系结构上都完全一致。例如,由于 ABI 的限制, 无法在 x86 上对网络套接字相关的系统调用进行过滤,但是 x86-64 却不存在这个问题(也就是可以过滤)。 在支持混合ABI的系统上(例如 x86/x86-64), 建议关闭次要的ABI(例如关闭 x86 以使用纯 x86-64 环境), 以确保进程无法通过次要ABI接口绕过系统调用过滤器的限制。在实践中,明确设置 SystemCallArchitectures=native 是一种非常好的做法。

还可以通过 SystemCallArchitectures= 在全局范围内限制可使用的ABI架构。参见 systemd-system.conf(5) 手册。

RestrictAddressFamilies=

限制进程可以访问的套接字类型。值是一个空格分隔的地址族列表(默认为白名单)。 例如 AF_UNIX, AF_INET, AF_INET6 … 列表默认解释为白名单。 但也可以在列表开头添加 "~" 字符表示反转(变成黑名单),也就是仅禁止使用列出的套接字。 注意,此限制仅作用于 socket(2) 系统调用。 通过其他方式传递给进程的套接字(例如,基于套接字启动的单元,参见 systemd.socket(5))、 以及由 socketpair() 创建的 AF_UNIX 套接字,不受此选项影响。 此外,此选项在 32-bit x86, s390, s390x, mips, mips-le, ppc, ppc-le, pcc64, ppc64-le 架构上是无效的(注意,在 x86-64 上是有效的)。 在支持混合ABI的系统上(例如 x86/x86-64),建议关闭次要的ABI(例如关闭 x86 以使用纯 x86-64 环境), 以确保进程无法通过次要ABI接口绕过此处的限制。在实践中, 明确设置 SystemCallArchitectures=native 是一种非常好的做法。 如果以用户模式运行或者以不含 CAP_SYS_ADMIN capability 的系统模式运行(例如设置了 User=nobody),那么将自动隐含 NoNewPrivileges=yes 的设置。 该选项默认不作任何限制。明确设为空字符串表示撤销先前所有的限制(也就是重置为默认值)。 此选项不影响带有 "+" 前缀的命令。

此选项旨在限制对进程的远程访问,特别是通过例如 AF_PACKET 这样敏感的协议。 注意,在大多数情况下,应该将 AF_UNIX 包含在白名单中, 因为包括通过 syslog(2) 发送日志之类的本地进程间通信需要使用它。

RestrictNamespaces=

限制进程对名字空间的访问。可设为一个布尔值,或者一个空格分隔的名字空间类型标识符列表。 有关 Linux 名字空间的详细解说,参见 namespaces(7) 手册。 默认值 no 表示不对名字空间的创建和切换做任何限制。 设为 yes 表示禁止访问任何类型的名字空间。 可使用的名字空间类型标识符如下: cgroup, ipc, net, mnt, pid, user, uts 。 列表默认为白名单,表示仅允许访问明确列出的名字空间类型。 可以在列表开头添加 "~" 字符表示反转(变成黑名单), 也就是仅禁止访问明确列出的名字空间类型。 设为空字符串等价于重置为默认值 no (不作任何限制)。 在系统内部,此处的设置实际上是限制可以对 unshare(2), clone(2), setns(2) 系统调用使用那些标记参数。 注意,如果使用此选项限制了对特定类型的名字空间的创建与切换,那么同时也将禁止对 setns() 系统调用使用空标记参数。 此选项仅在 x86, x86-64, mips, mips-le, mips64, mips64-le, mips64-n32, mips64-le-n32, ppc64, ppc64-le, s390, s390x, 架构上有意义。 如果以用户模式运行或者以不含 CAP_SYS_ADMIN capability 的系统模式运行(例如设置了 User=nobody),那么将自动隐含 NoNewPrivileges=yes 的设置。

Personality=

当进程调用 uname(2) 时, 应该返回哪种体系结构标识符,可设为 x86, x86-64, ppc, ppc-le, ppc64, ppc64-le, s390, s390x 之一。 必须按照硬件的实际情况设置此选项。 通常,64位架构都隐含支持与其对应的32位架构。 例如 x86-64 同时支持 x86-64x86 。 这主要用于在 x86-64 平台上运行 32-bit 服务的场合。 若未设置,则返回未经修改的原始值(取决于主机的内核)。

LockPersonality=

接受一个布尔值,表示是否锁定 personality(2) 系统调用。设为 yes 表示禁止更改内核的执行域,也就是只能使用默认值或 Personality= 设置的值,这有助于提升系统的安全性。 如果以用户模式运行或者以不含 CAP_SYS_ADMIN capability 的系统模式运行(例如设置了 User=), 那么将自动隐含 NoNewPrivileges=yes 的设置。

KeyringMode=

控制如何设置服务单元的内核会话密钥环(参见 session-keyring(7) 以详细了解会话密钥环的更多详情)。可设为 inherit, private, shared 之一。 设为 inherit 表示不对密钥环做特别的设置,直接使用内核的默认行为。 设为 private 表示每调用一个服务进程,都会为其分配一个不与任何用户密钥环连接的全新会话密钥环。 建议系统服务使用此设置,以确保使用同一个用户身份(通常是 root)运行的多个不同服务之间不会共享各自的密钥。 设为 shared 表示与 private 类似,也会为每一个服务进程分配一个会话密钥环, 但不同之处在于,User= 用户的密钥环将会被连接到这个新分配的密钥环之中, 从而允许单元中的进程请求分配给 User= 用户的密钥。 除非设为 inherit 模式,并且将单元的 $INVOCATION_ID 值按照 "invocation_id" 名称, 作为一个受保护的密钥,添加到新创建的会话密钥环中; 否则,在 shared 模式下,使用同一个用户身份运行的多个不同服务之间会共享各自的密钥。 对于系统管理器(PID=1)来说,默认值是 private ;对于用户管理器(PID≠1)来说,默认值是 inherit

RuntimeDirectory=, StateDirectory=, CacheDirectory=, LogsDirectory=, ConfigurationDirectory=

这些选项接受一个空白符分隔的目录名称列表。 目录名称必须是相对路径,并且不能在其中包含 "." 或 ".." 成分。 如果设置了这里的选项,那么在单元启动时: (1)对于系统服务,将会分别在 /run, /var/lib, /var/cache, /var/log, /etc 目录中,创建一系列指定的目录。 (2)对于用户服务,将会分别在 $XDG_RUNTIME_DIR, $XDG_CONFIG_HOME, $XDG_CACHE_HOME, $XDG_CONFIG_HOME/log, $XDG_CONFIG_HOME 目录中,创建一系列指定的目录。

对于 RuntimeDirectory= 来说, 除非 RuntimeDirectoryPreserve= 被设为 restartyes , 否则,当单元停止时,将会自动删除已经创建的目录(实际仅删除最末级的目录)。 但是对于 StateDirectory=, CacheDirectory=, LogsDirectory=, ConfigurationDirectory= 来说,即使单元已经停止,也不会删除已经创建的目录。

对于除 ConfigurationDirectory= 之外的其他选项来说, 在创建指定的目录时,将会以 User=Group= 作为拥有者(实际仅为最末级的目录)。 如果指定的目录已经存在,并且这些目录自身并不符合 User=Group= 的设置, 那么将会强制按照 User=Group= 的设置,递归的修改这些目录。 作为一种优化措施,如果指定的目录已经存在,并且这些目录自身已经符合 User=Group= 的设置, 那么即使这些目录下的内容并不符合 User=Group= 的设置,也会按原样保持这些目录下的内容不变。 对指定的目录(实际仅为最末级的目录),将会分别按照 RuntimeDirectoryMode=, StateDirectoryMode=, CacheDirectoryMode=, LogsDirectoryMode=, ConfigurationDirectoryMode= 强制设置其访问权限。

ConfigurationDirectory= 之外的其他目录,都被隐含的添加到了 ReadWritePaths= 之中。当这些选项与 RootDirectory=RootImage= 一起使用时,这些路径将始终驻留在宿主系统中, 并且会被挂载到该单元的文件系统名字空间中。如果将 DynamicUser=yesRuntimeDirectory=, StateDirectory=, CacheDirectory=, LogsDirectory= 一起使用, 那么这些选项中指定的目录将会在非特权用户无法访问的 /run/private, /var/lib/private, /var/cache/private, /var/log/private 目录中分别创建, 从而确保这些目录不会因为UID循环而被意外访问,同时也会通过创建软连接的方法来消除这种做法造成的路径差异。 这样一来,无论是从宿主系统的视角还是从单元内部的视角, 指定的目录将始终直接位于 /run, /var/lib, /var/cache, /var/log 目录中。

使用 RuntimeDirectory= 来管理单元的运行时目录, 可以将运行时目录与单元的生命周期绑定在一起, 以确保这些运行时目录在单元启动时自动创建、并在单元停止时自动删除。 这非常适合于那些没有权限在 /run 中创建运行时目录的非特权守护进程。 对于需要更复杂配置或者生存期保证的运行时目录,可以考虑使用 tmpfiles.d(5) 功能。

例如,如果在一个系统服务单元中存在如下设置:

RuntimeDirectory=foo/bar baz

那么,当该服务启动时,将会自动创建 /run/foo/bar/run/baz 目录,并且将 /run/foo/bar/run/baz 的拥有者设置为 User=Group= 的值;当该服务停止时,这两个目录也会被自动删除。

RuntimeDirectoryMode=, StateDirectoryMode=, CacheDirectoryMode=, LogsDirectoryMode=, ConfigurationDirectoryMode=

分别设置 RuntimeDirectory=, StateDirectory=, CacheDirectory=, LogsDirectory=, ConfigurationDirectory= 目录的访问权限(必须是八进制形式)。 默认值是 0755 。对于权限位的含义,可参见 path_resolution(7) 手册的 "Permissions" 小节。

RuntimeDirectoryPreserve=

是否保留运行时目录(RuntimeDirectory=), 可设为布尔值或特殊值 restart 。 默认值 no 表示在单元停止或重启时无条件的删除运行时目录。 设为 restart 表示仅在单元重启时保留运行时目录。 注意,所谓"重启"包括由 Restart= 定义的自动重启,以及由 systemctl restart foo.service 命令触发的手动重启。 设为 yes 表示始终无条件的保留运行时目录(即使单元已经被停止)。 注意,因为 /run 是一个 "tmpfs" 文件系统, 所以 RuntimeDirectory= 总是会在系统重启之后消失。

MemoryDenyWriteExecute=

设为 yes 表示: 禁止创建可写可执行的内存映射、 禁止将已存在的内存映射修改为可执行、 禁止将共享内存段映射为可执行。 具体说来就是将会拒绝: 同时设置了 PROT_EXECPROT_WRITEmmap(2) 系统调用、 设置了 PROT_EXECmprotect(2) 系统调用、 设置了 SHM_EXECshmat(2) 系统调用。 注意,此选项与那些会在运行时动态生成可执行代码的程序与库有冲突, 包括JIT(运行时编译执行)执行引擎、可执行堆栈、以及利用了C编译器"trampoline"特性生成的可执行程序。 此选项可用于提升服务的安全性,因为它使得利用软件漏洞来动态改变运行时代码变得困难。 注意, x86-64 完整支持此特性,但 x86 仅部分支持。 特别地,在 x86 上不能使用 shmat() 保护。 在支持混合ABI的系统上(例如 x86/x86-64),建议关闭次要的ABI(例如关闭 x86 以使用纯 x86-64 环境), 以确保进程无法通过次要ABI接口绕过此处的限制。 在实践中,明确设置 SystemCallArchitectures=native 是一种非常好的做法。 如果以用户模式运行或者以不含 CAP_SYS_ADMIN capability 的系统模式运行(例如明确将 User= 设为普通用户), 那么将自动隐含 NoNewPrivileges=yes 的设置。

RestrictRealtime=

设为 yes 表示禁止实时调度单元中的进程。 也就是禁止访问 SCHED_FIFO, SCHED_RR, SCHED_DEADLINE 这些实时任务调度策略(参见 sched(7) 手册)。 如果以用户模式运行或者以不含 CAP_SYS_ADMIN capability 的系统模式运行(例如明确将 User= 设为普通用户),那么将自动隐含 NoNewPrivileges=yes 的设置。 实时调度策略可能会导致CPU被长时间独占, 进而导致系统失去响应或导致拒绝服务攻击。 应该仅允许个别确实需要实时调度的单元使用实时调度策略。 默认值为 no

环境变量

进程在被 systemd 启动的时候,会拥有多个不同来源的环境变量。 由系统实例(PID=1)启动的进程仅从 systemd(PID=1) 继承 PassEnvironment= 中列出的环境变量; 由用户实例(PID≠1)启动的进程则会自动从 systemd(PID≠1) 继承全部环境变量。

对于每一个被 systemd 启动的进程,都会拥有来自下列多个不同来源的环境变量:

  • (1)通过 DefaultEnvironment= 设置的全局环境变量(参见 systemd-system.conf(5) 手册)、 (2)通过内核引导选项 systemd.setenv= 设置的全局环境变量(参见 systemd(1) 手册)、 (3)通过 systemctl set-environment 设置的全局环境变量(参见 systemctl(1) 手册)。

  • 由 systemd 为进程设置的环境变量(参见下文的列表)

  • 从 systemd 继承的环境变量:(1)由用户实例启动的进程继承全部环境变量;(2)由系统实例启动的进程仅继承 PassEnvironment= 中列出的环境变量。

  • 通过单元文件中的 Environment= 选项设置的环境变量

  • 通过单元文件中的 EnvironmentFiles= 选项设置的环境变量

  • 设置了 PAMName= 选项之后,由 PAM 模块设置的环境变量。详见 pam_env(8) 手册。

如果上述多个来源都设置了同一个环境变量,那么将按照上述列表的顺序,以最后一个来源为准。 注意,上述所有环境变量都会在启动进程之前的最后一步,按照 UnsetEnvironment= 的设置进行清除。

systemd 将会为每一个被启动的进程设置下列环境变量:

$PATH

冒号分隔的目录列表(绝对路径), 此值固定为 /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

$LANG

本地化设置。可以通过 locale.conf(5) 文件设置,也可以通过内核引导选项(参见 systemd(1)kernel-command-line(7))设置。

$USER, $LOGNAME, $HOME, $SHELL

用户名, 用户名, 家目录, 登录shell 。 这几个变量仅对设置了 User= 选项的单元有效(包括 systemd 用户实例)。参见 passwd(5) 手册。

$INVOCATION_ID

在单元每次启动时随机生成的一个 128bit 唯一标识符,表现为一个32字符的十六进制字符串。 在单元每一次从 inactive 状态变 activating 或 active 状态时,这个标识符都会发生变化, 因此可以用于标识单元的每一个生命周期,特别是在存储例如日志这样的离线数据的时候。 同一个单元内的进程,在单元的同一个生命周期内,都将收到相同的标识符。

$XDG_RUNTIME_DIR

保存运行时数据的目录。 此变量针对 systemd 用户实例以及用户会话设置。 参见 pam_systemd(8) 手册。

$XDG_SESSION_ID, $XDG_SEAT, $XDG_VTNR

会话ID, 席位名称, 会话虚拟终端。 由 pam_systemd(8) 对登录会话设置。$XDG_SEAT 仅在会话被附加到一个席位时才设置; $XDG_VTNR 仅在会话被附加到一个虚拟终端时才设置。

$MAINPID

单元主进程的PID(如果能确定的话)。 仅对由 ExecReload= 之类启动的控制进程设置。

$MANAGERPID

systemd 用户实例的PID , 仅为从 systemd 用户实例派生的进程设置。

$LISTEN_FDS, $LISTEN_PID, $LISTEN_FDNAMES

传递给服务的基于套接字启动的文件描述符的相关信息。 参见 sd_listen_fds(3) 手册。

$NOTIFY_SOCKET

sd_notify() 所使用的套接字。参见 sd_notify(3) 手册。

$WATCHDOG_PID, $WATCHDOG_USEC

关于看门狗 keep-alive 通知的相关信息。参见 sd_watchdog_enabled(3) 手册。

$TERM

终端类型。 仅为连接到终端的单元设置(StandardInput=tty, StandardOutput=tty, StandardError=tty) 。参见 termcap(5) 手册。

$JOURNAL_STREAM

如果进程的标准输出(STDOUT)或标准错误(STDERR)被连接到了日志(例如 StandardError=journal), 那么 $JOURNAL_STREAM 将是形如 "device:inode" 格式的字符串, 其中 "device" 是连接的文件描述符所在的设备,而 "inode" 则是连接的文件描述符的 inode 号(以十进制数表示)。 这将允许进程安全的检测其标准输出(STDOUT)或标准错误(STDERR)是否被连接到了日志。 进程应该将自己实际使用的文件描述符的 "device:inode" 值与环境变量中的值进行对比, 以检测进程的标准输出(STDOUT)或标准错误(STDERR)是否依然连接在日志上。 注意,仅检查是否设置了 $JOURNAL_STREAM 环境变量是远远不够的, 因为服务进程有可能调用外部进程,并在未撤销此环境变量的情况下, 替换这些外部进程的标准输出(STDOUT)或标准错误(STDERR)。

如果进程的标准输出(STDOUT)与标准错误(STDERR)都通过流套接字连接到了日志, 那么此环境变量将只包含标准错误(STDERR)所使用的流套接字信息, 因为错误信息通常是日志记录的首要目的。 不过因为通常都会使用同一个流套接字来连接标准输出(STDOUT)与标准错误(STDERR), 所以其实这两者所使用的流套接字信息实际上是完全一样的。

此环境变量主要用于让服务单元将日志协议升级到 systemd 日志协议(使用 sd_journal_print(3) 等函数)。 如果服务进程的标准输出(STDOUT)或标准错误(STDERR)被连接到了日志文件, 那么就可以伴随日志消息一起保存相关的结构化元数据。

$SERVICE_RESULT

仅在 service 单元中存在。此环境变量将会传递给所有 ExecStop=ExecStopPost= 进程,其中包含了该服务的"结果"。 当前定义了如下这些值:

表 3. 已定义的 $SERVICE_RESULT

含义
"success"服务启动成功之后又干净的退出了
"protocol"违反协议:服务没有按照单元的配置执行必需的步骤(特别是违反了 Type= 的设置)
"timeout"某个步骤操作超时
"exit-code"服务进程的退出码非零。可以通过下面的 $EXIT_CODE 获取具体的退出码
"signal"服务进程被某个信号异常终止且未进行内存转储。可以通过下面的 $EXIT_STATUS 获取具体的终止信号。
"core-dump"服务进程被某个信号异常终止且已进行内存转储。可以通过下面的 $EXIT_STATUS 获取具体的终止信号。
"watchdog"为服务开启了看门狗并且发生了喂狗超时
"start-limit-hit"因为超出了该单元的启动频率限制而启动失败。参见 systemd.unit(5) 手册中的 StartLimitIntervalSec=StartLimitBurst= 选项以了解详情。
"resources"总括性的表示操作失败

这些环境变量可以用于监视服务是否成功的终止了。 虽然这些变量对 ExecStop=ExecStopPost= 都可见, 但是将监视工具添加到后者是更好的选择, 因为前者仅在服务启动成功的情况下才会被调用, 而后者则在服务启动失败和运行中失败的情况下都会被调用。

$EXIT_CODE, $EXIT_STATUS

仅在 service 单元中存在。此环境变量将会传递给所有 ExecStop=, ExecStopPost= 进程,其中包含了该服务主进程的退出码/退出状态。 要想查看退出码/退出状态的详细定义,可以查看 wait(2) 手册。 $EXIT_CODE 总是 "exited", "killed", "dumped" 之一。如果 $EXIT_CODE 是 "exited" , 那么 $EXIT_STATUS 将包含字符串形式表示的退出状态(实际上是对退出码含义的解释),否则将包含终止信号的名称。 注意,这些环境变量仅在 systemd 成功定位到服务单元主进程的情况下才会被设置。

表 4. 服务终止状态变量表

$SERVICE_RESULT$EXIT_CODE$EXIT_STATUS
"success""exited""0"
"protocol"未设置未设置
"exited""0"
"timeout""killed""TERM", "KILL"
"exited""0", "1", "2", "3", …, "255"
"exit-code""exited""1", "2", "3", …, "255"
"signal""killed""HUP", "INT", "KILL", …
"core-dump""dumped""ABRT", "SEGV", "QUIT", …
"watchdog""dumped""ABRT"
"killed""TERM", "KILL"
"exited""0", "1", "2", "3", …, "255"
"start-limit-hit"未设置未设置
"resources"上述任意一个上述任意一个
注意:服务进程有可能被 systemd 之外的其他进程发送信号而终止。特别地,进程可以给自身发送任意信号(包括不可屏蔽的信号)。尽管如此,在上面的 "timeout" 与 "watchdog" 行,仅包括 systemd 发送的信号。此外,可以使用 SuccessExitStatus= 指定其他表示干净退出的状态(不在此表中)。

进程退出码

如果 systemd 在启动单元进程时,未能成功应用上文各种选项所设置的执行参数, 那么已经启动的服务进程将会直接以非零退出码终止,并且不会执行任何命令行参数。 具体说来就是, 服务进程将会在被 fork(2) 系统调用创建之后、进一步使用 execve(2) 系统调用之前,直接以本小节列出的各种非零退出码终止。这些退出码的定义来自: (1)系统的标准C库;(2)LSB规范;(3) systemd 独有的规范。

下面是系统的标准C库定义的退出码:

表 5. 标准C库定义的退出码

退出码符号名称解释
0EXIT_SUCCESS通用的成功代码
1EXIT_FAILURE通用的失败代码

下面是 LSB specification 定义的退出码:

表 6. LSB规范定义的服务退出码

退出码符号名称解释
2EXIT_INVALIDARGUMENT参数无效或超量
3EXIT_NOTIMPLEMENTED功能尚未实现
4EXIT_NOPERMISSION用户权限不足
5EXIT_NOTINSTALLED程序尚未安装
6EXIT_NOTCONFIGURED程序尚未配置
7EXIT_NOTRUNNING程序尚未运行

因为LSB规范建议 200 及以上的退出码可以自由定义。 所以 systemd 就定义了下列退出码:

表 7. systemd 定义的服务退出码

退出码符号名称解释
200EXIT_CHDIR切换进程的工作目录失败。参见上文的 WorkingDirectory= 选项。
201EXIT_NICE设置进程的调度优先级(谦让值)失败。参见上文的 Nice= 选项。
202EXIT_FDS关闭进程不需要的文件描述符失败,或者调整传递过来的文件描述符失败。
203EXIT_EXEC实际进程执行失败(也就是 execve(2) 系统调用失败)。这通常是因为可执行文件不存在或者没有执行权限造成的。
204EXIT_MEMORY内存不足导致操作失败
205EXIT_LIMITS调整进程的资源限制失败。参见上文的 LimitCPU= 等资源限制选项。
206EXIT_OOM_ADJUST调整进程的 OOM 设置失败。参见上文的 OOMScoreAdjust= 选项。
207EXIT_SIGNAL_MASK设置进程的信号屏蔽失败
208EXIT_STDIN设置进程的标准输入失败。参见上文的 StandardInput= 选项。
209EXIT_STDOUT设置进程的标准输出失败。参见上文的 StandardOutput= 选项。
210EXIT_CHROOT切换进程的根目录(chroot(2))失败。参见上文的 RootDirectory=/RootImage= 选项。
211EXIT_IOPRIO设置进程的IO调度优先级失败。参见上文的 IOSchedulingClass=/IOSchedulingPriority= 选项。
212EXIT_TIMERSLACK设置进程的定时器粒度失败。参见上文的 TimerSlackNSec= 选项。
213EXIT_SECUREBITS设置进程的安全位失败。参见上文的 SecureBits= 选项。
214EXIT_SETSCHEDULER设置进程的CPU调度优先级失败。参见上文的 CPUSchedulingPolicy=/CPUSchedulingPriority= 选项。
215EXIT_CPUAFFINITY设置进程的CPU亲和性失败。参见上文的 CPUAffinity= 选项。
216EXIT_GROUP检测或修改进程的用户组失败。参见上文的 Group=/SupplementaryGroups= 选项。
217EXIT_USER检测或修改进程的用户身份失败、或者设置用户名字空间失败。参见上文的 User=/PrivateUsers= 选项。
218EXIT_CAPABILITIES修改进程的 capability 集合失败。参见上文的 CapabilityBoundingSet=/AmbientCapabilities= 选项。
219EXIT_CGROUP设置服务单元的控制组失败
220EXIT_SETSID未能创建新的进程会话
221EXIT_CONFIRM执行过程被用户取消。详见 kernel-command-line(7) 手册中的 systemd.confirm_spawn= 内核引导选项。
222EXIT_STDERR设置进程的标准错误失败。参见上文的 StandardError= 选项。
224EXIT_PAM设置进程的PAM会话失败。参见上文的 PAMName= 选项。
225EXIT_NETWORK设置进程的网络名字空间失败。参见上文的 PrivateNetwork= 选项。
226EXIT_NAMESPACE设置进程的文件系统名字空间失败。参见上文的 ReadOnlyPaths= 等文件系统相关选项。
227EXIT_NO_NEW_PRIVILEGES未能禁用进程的新权限。参见上文的 NoNewPrivileges=yes 选项。
228EXIT_SECCOMP设置进程的系统调用过滤器失败。参见上文的 SystemCallFilter= 等相关选项。
229EXIT_SELINUX_CONTEXT检测或设置进程的 SELinux 安全上下文失败。参见上文的 SELinuxContext= 选项。
230EXIT_PERSONALITY设置进程的执行域(体系结构)失败。参见上文的 Personality= 选项。
231EXIT_APPARMOR_PROFILE无法更改进程的 AppArmor profile 。参见上文的 AppArmorProfile= 选项。
232EXIT_ADDRESS_FAMILIES限制进程可以访问的套接字类型失败。参见上文的 RestrictAddressFamilies= 选项。
233EXIT_RUNTIME_DIRECTORY设置进程的运行时目录失败。参见上文的 RuntimeDirectory= 等相关选项。
235EXIT_CHOWN修改套接字的拥有者失败。仅用于 socket 单元。
236EXIT_SMACK_PROCESS_LABEL设置进程的 SMACK64 安全标签失败。参见上文的 SmackProcessLabel= 选项。
237EXIT_KEYRING设置内核密钥环失败
238EXIT_STATE_DIRECTORY设置单元的状态目录失败。参见上文的 StateDirectory= 选项。
239EXIT_CACHE_DIRECTORY设置单元的缓存目录失败。参见上文的 CacheDirectory= 选项。
240EXIT_LOGS_DIRECTORY设置单元的日志目录失败。参见上文的 LogsDirectory= 选项。
241EXIT_CONFIGURATION_DIRECTORY设置单元的配置目录失败。参见上文的 ConfigurationDirectory= 选项。

参见

systemd(1), systemctl(1), systemd-analyze(1), journalctl(8), systemd.unit(5), systemd.service(5), systemd.socket(5), systemd.swap(5), systemd.mount(5), systemd.kill(5), systemd.resource-control(5), systemd.time(7), systemd.directives(7), tmpfiles.d(5), exec(3)