📘systemd 单元文件

[[systemd]]

systemd 单元文件

systemd 的核心设计思想是将系统里的一切资源都抽象成“单元”(Unit)。单元是 systemd 管理的基本对象,涵盖了服务、套接字、设备、挂载点乃至系统状态等。

单元文件就是用来定义一个单元的纯文本配置文件。它清晰地描述了这个单元的属性、行为以及它与其他单元之间的关系。这些文件采用类似 INI 的语法,易于阅读和编写。

文件位置与优先级

systemd 会从多个目录中加载单元文件,并遵循一套明确的优先级规则

  • 系统级(只读包):/usr/lib/systemd/system/
  • 系统级(本地管理员覆写):/etc/systemd/system/
  • 运行时(临时,重启丢失):/run/systemd/system/
  • 优先级:/etc 覆盖 /run 覆盖 /usr/lib。同名时,高优先级目录生效。

推荐使用 systemctl edit <unit> 生成 drop-in 覆盖,避免直接改发行文件。

  • /etc/systemd/system/:最高优先级。系统管理员存放自定义或修改后的单元文件的地方。这里的配置会覆盖其他位置的同名文件。
  • /run/systemd/system/:第二优先级。通常由程序在运行时动态创建单元文件。系统重启后该目录内容会丢失。
  • /usr/lib/systemd/system/:最低优先级。通常由软件包管理器(如 aptyum`)安装的默认单元文件存放于此。不建议直接修改这里的文件

核心原则:当需要修改一个默认单元文件时(例如 sshd.service),推荐使用 systemctl edit --full sshd.service 命令。它会自动将文件从 /usr/lib/systemd/system/ 复制到 /etc/systemd/system/ 并打开编辑器,确保你的修改具有最高优先级且不会在软件包升级时被覆盖。

systemd unit

定义行为、依赖与生命周期

管理的对象:服务、套接字、计时器、挂载点等。

常见类型:servicesockettargettimerpathmountautomountdeviceslicescope

单元命名与实例化

文件名格式:name.type,例如:nginx.servicesshd.socket

模板单元:name@.type,实例化时用 name@instance.type,适合 per-connection 或 per-device。

你当前使用的是模板服务 dropbear@.service 配合 dropbear.socket 实现每连接派生。

单元文件结构

一个单元文件通常由几个配置段(Section)组成,每个段由 [方括号] 标识。其中,[Unit][Install] 是几乎所有单元类型都通用的。

  • [Unit]: 通用元信息、依赖、顺序。
  • [Service]/[Socket]/[Timer]/…:类型专属的配置段。
  • [Install]: 启用时的附着点(WantedBy/RequiredBy/Also)。

[Unit] 段:通用元数据与依赖关系

此段定义了单元的通用属性,它不关心单元的类型。

  • Description=: 一段描述性文本,用于在 systemctl status 等命令中直观地展示单元的用途。
  • Documentation=: 提供文档链接,可以是 man 手册页或 URL,如 man:sshd(8)https://example.com/docs
  • After=: 定义启动顺序。此单元将在 After 列出的单元启动完成之后再启动。这是最常用的顺序依赖。
  • Before=: 定义启动顺序。此单元将在 Before 列出的单元启动之前先启动。
  • Wants=: 定义一种弱依赖关系。此单元启动时,systemd尝试启动 Wants 列出的单元。但即使后者启动失败,也不影响此单元的启动。
  • Requires=: 定义一种强依赖关系。此单元启动时,systemd 必须成功启动 Requires 列出的单元。如果后者启动失败,此单元也将被停用。
  • Conflicts=: 定义冲突关系。如果此单元启动,Conflicts` 中列出的单元将会被停止。反之亦然。

[Install] 段:启用与开机自启行为

此段定义了当用户使用 systemctl enable/disable 命令时,该单元应如何表现。

  • WantedBy=: 最常用的指令。表示此单元是 WantedBy 所指定的 .target 单元的“一部分”。执行 systemctl enable 时,systemd 会在 /etc/systemd/system/ 目录下创建一个指向此单元文件的符号链接,链接位于 multi-user.target.wants/ 这样的目录中。
  • RequiredBy=: 类似于 WantedBy,但表示一个更强的依赖。如果 RequiredBy 指定的 .target 启动,此单元必须成功启动。
  • Also=: 指定当此单元被启用/禁用时,Also` 列出的其他单元也应被一同启用/禁用。

主要单元文件类型详解

Service Unit (.service)

这是最核心、最常用的单元类型,用于定义一个系统服务(守护进程)。

主要用途:启动、停止、重启和管理后台服务进程。

核心配置段[Service]

常用指令

  • Type=: 定义服务的启动类型。
    • simple (默认): ExecStart 后的主进程就是服务进程。
    • forking: ExecStart 启动的进程会 fork() 一个子进程作为真正的服务进程,父进程会退出。systemd 会等待父进程退出后认为服务启动完成。
    • oneshot: 类似于 simple,但 systemd 会等待主进程退出后才认为服务启动完成。适用于一次性任务(如内核模块加载、文件系统检查)。
    • notify: 服务启动后会通过 sd_notify() 函数向 systemd 发送“准备就绪”的信号。
    • dbus: 服务需要获取一个 D-Bus 名称。
  • ExecStart=: 指定启动服务时要执行的命令。
  • ExecStop=: 指定停止服务时要执行的命令(可选)。
  • ExecReload=: 指定重载服务配置时要执行的命令(可选)。
  • Restart=: 定义服务在何种情况下应被自动重启。常用值有 no (默认)、on-successon-failureon-abnormalalways
  • User= / Group=: 指定运行服务的用户和用户组。出于安全考虑,推荐使用非 root 用户。
  • WorkingDirectory=: 指定进程的工作目录。
  • Environment= / EnvironmentFile=: 为服务进程设置环境变量或从文件中加载环境变量。

Socket Unit (.socket)

用于定义一个网络套接字(TCP/UDP)或本地 IPC 套接字。它是 systemd 实现按需启动 (Socket Activation) 的关键。

主要用途:代替服务进程监听端口。当有连接请求时,systemd 会激活并启动相应的 .service 单元来处理该连接。

核心配置段[Socket]

常用指令

  • ListenStream=: 监听一个 TCP 套接字。值是端口号。
  • ListenDatagram=: 监听一个 UDP 套接字。
  • ListenFIFO=: 监听一个 FIFO (命名管道)。
  • Accept=: 布尔值。false (默认) 表示 systemd 接受连接后,会启动一个服务实例并将该连接传递给它,然后该服务负责处理后续所有连接。yes 表示 systemd 会为每一个进来的连接都启动一个新的服务实例(如 dropbear@.service 模板)。
  • Service=: 指定当此套接字有活动时要激活的服务名。如果 socket 文件名是 foo.socket,默认激活的服务就是 foo.service

Target Unit (.target)

用于对其他单元进行逻辑分组,本身不执行任何操作。它取代了传统 SysVinit 中的“运行级别”(Runlevel)。

主要用途:定义系统状态的同步点,管理一组相关的单元。

核心配置段:无专属配置段,主要通过 [Unit] 段的 WantsRequires 来聚合其他单元。

常用 Targets

  • multi-user.target: 类似于运行级别 3,用于启动所有网络服务,进入多用户命令行模式。
  • graphical.target: 类似于运行级别 5,依赖 multi-user.target,并额外启动图形界面服务。
  • network.target: 表示网络已就绪。
  • sockets.target: 所有 .socket 单元都应属于此目标。
  • reboot.target: 用于重启系统。

Timer Unit (.timer)

用于定义定时器,是 cron 的现代化替代品,可以触发任何 .service 单元。

主要用途:定时或周期性地执行任务。

核心配置段[Timer]

常用指令

  • OnCalendar=: 基于日历的绝对时间。语法灵活,如 daily, weekly, *-*-* 12:00:00 (每天中午12点)。
  • OnActiveSec=: 相对于 timer 自身被激活的时间。
  • OnBootSec=: 相对于系统启动完成的时间。
  • OnUnitActiveSec=: 相对于它所激活的单元最后一次被激活的时间。
  • Unit=: 指定此定时器要触发的单元名。默认是同名的 .service 文件。
  • Persistent=: 布尔值。如果设为 true,当系统关机时错过的执行任务,会在下次开机后立即补上。

Path Unit (.path)

用于监控文件或目录的变化,当事件发生时触发另一个单元。

主要用途:实现基于文件系统事件的按需服务启动。

核心配置段[Path]

常用指令

  • PathExists=: 当指定路径存在时触发。
  • PathChanged=: 当文件内容发生变化时触发。
  • PathModified=: 当文件内容或元数据(如权限、时间戳)发生变化时触发。
  • DirectoryNotEmpty=: 当指定目录从空变为非空时触发。
  • Unit=: 指定要触发的单元。

Mount Unit (.mount) & Automount Unit (.automount)

.mount: 用于以声明式的方式管理文件系统的挂载点,可以替代 /etc/fstabsystemd 会在启动时自动将 /etc/fstab 条目转换为 .mount 单元。

.automount: 用于实现文件系统的按需挂载。它会监听一个挂载点目录,只有当该目录被访问时,systemd 才会真正执行挂载操作。

Slice Unit (.slice)

用于对一组单元进行资源限制。它基于 Linux 内核的 cgroups (控制组) 功能,本身不包含进程,而是作为一个资源控制的层级。

主要用途:对系统、用户或特定服务的 CPU、内存、I/O 等资源进行分组和限制。

核心配置段[Slice],但资源限制通常直接在此段中定义。

常用指令CPUWeight=, MemoryMax=, IOWeight= 等。

默认 Slicesystem.slice (系统服务), user.slice (用户会话), machine.slice (虚拟机和容器)。

Scope Unit (.scope)

.service 类似,也用于管理一组进程。但 .scope 单元不是通过 systemd 启动进程,而是用于管理由外部进程(如用户登录、容器运行时)创建的进程。

Device Unit (.device)

systemd-udevd 服务自动创建,用于响应内核的设备热插拔事件。通常用户不需要手动创建。

Swap Unit (.swap)

用于定义和管理交换空间(swap分区或文件),可替代 /etc/fstab 中的 swap 条目。

Snapshot Unit (.snapshot)

一种特殊的单元,用于保存 systemd 管理器的当前状态(所有正在运行的单元)。可以用于临时进入一个不同的状态,然后再恢复到快照时的状态。

依赖与顺序的要点

  • Requires= 强依赖;失败会导致依赖者失败。
  • Wants= 软依赖;失败不导致依赖者失败。
  • Before=/After= 仅控制顺序,不建立依赖。

套接字激活时,socket 与 service 的依赖/顺序由 systemd 根据命名自动处理。

条件与断言

ConditionPathExists=ConditionKernelCommandLine= 等,条件不满足时跳过激活且不视为失败。 Assert*= 不满足时视为失败。

环境变量与配置注入

  • Environment="KEY=VAL"
  • EnvironmentFile=/etc/default/foo`(常见 Debian/BusyBox 习惯)

建议把可变参数放 EnvironmentFile,便于 OTA/不同机型差异化。

进程模型与 Exec

  • Type=simpleExecStart 进程为主进程
  • Type=forking:适配传统 daemonize 程序(会 fork)
  • Type=notify:进程通过 sd_notify 报告就绪
  • ExecStartPre=/ExecStartPost=:前后钩子
  • TimeoutStartSec=` 避免卡启动

资源限制与安全沙箱

限制

  • User=Group=SupplementaryGroups=
  • LimitNOFILE=MemoryMax=TasksMax=
  • CPUQuota=IOSchedulingClass=

沙箱

  • ProtectSystem=full/strict
  • ProtectHome=truePrivateTmp=truePrivateDevices=true
  • NoNewPrivileges=true
  • CapabilityBoundingSet=AmbientCapabilities=
  • RestrictAddressFamilies=RestrictSUIDSGID=LockPersonality=`

对嵌入式建议:尽量开启 NoNewPrivileges、收窄 capability 集、文件系统 Protect、限制文件句柄与内存。

systemd 单元文件管理速查表

命令 用途
systemctl status <unit> 查看单元的当前状态、日志摘要等详细信息。
journalctl -fu <unit> 实时跟踪(follow)指定单元的日志输出。
systemctl cat <unit> 查看单元的最终合成配置(包含所有 drop-in 文件)。
systemctl list-dependencies <unit> 查看单元依赖的其他单元。
systemctl list-dependencies --reverse <unit> 查看依赖此单元的其他单元。
systemctl start <unit> 立即启动一个单元。只影响当前会话,不设置开机自启。
systemctl stop <unit> 立即停止一个单元。只影响当前会话,不影响开机自启设置。
systemctl restart <unit> 重启一个单元。
systemctl reload <unit> 重新加载单元的配置(需要单元自身支持)。
systemctl enable <unit> 设置开机自启。只创建符号链接,不会启动当前未运行的单元。
systemctl disable <unit> 取消开机自启。只移除符号链接,不会停止当前正在运行的单元。
systemctl enable dropbear.socket 示例:设置 dropbear 服务为开机 Socket 激活模式。
systemctl start dropbear.socket 示例:立即开始监听 dropbear 的套接字(通常在 enable 后可选执行)。
systemctl edit <unit> 使用 drop-in 文件覆盖部分配置(最佳实践)。
systemctl edit --full <unit> 完整编辑单元文件,覆盖默认配置。
systemctl daemon-reload 当手动修改磁盘上的单元文件后,执行此命令让 systemd 重新加载所有配置。