systemd 的 unit

已修订更新于

首发于

systemd 复杂,往往不是因为某一条命令难记,而是因为它把「开机之后用户态该发生的一切」收进了同一套模型。挂盘、起网络、跑守护进程、定时任务、登录会话、甚至不少容器编排——表面上五花八门,落到 systemd 里,多半都是某种 unit。

这篇文章只做一件事:把 systemd 负责哪些事、有哪些 unit 类型、文件后缀怎么对应,以及你听到的 .container、.volume、.network 和原生 unit 是什么关系,从两个维度说清楚。运行级别(runlevel)的历史与今天的 target,留给下一篇。

读前可先备着的入口


systemd 是什么

  1. 内核自己起来,完成硬件探测、挂上初始根、找到 init 程序。
  2. 内核再拉起 第一个用户态进程,传统上叫 init,在多数现代发行版上就是 systemd,进程号是 PID 1。

所以:systemd 是 用户态的「总管家」,不是内核的一部分;内核把接力棒交给它之后,后续用户态世界基本由它编排。

可以粗记成:

硬件复位
  → 内核(不是 PID,不是用户态进程)
    → systemd(PID 1,用户态)
      → 挂载 / 网络 / 服务 / 定时器 / 登录 ……

一、systemd 实际在管什么

不必背「systemd 功能清单」。按开机后真实发生的事来想,大致是这些职责:

职责 典型手段 你会碰到的例子
文件系统就绪 .mount / .automount / .swap 挂 /home、激活 swap、按需挂载
守护进程与一次性任务 .service sshd、nginx、开机脚本
按需启动(socket 激活) .socket + .service 有人连上来才起服务
定时 .timer + .service 替代不少 cron 场景
路径/设备变化触发 .path / .device 目录出现文件再跑任务;设备出现再挂载
「系统到了哪种整体状态」 .target multi-user、graphical;对应旧 runlevel
资源分组与临时进程组 .slice / .scope cgroup 树;systemd-run 拉起的临时组
本机网络配置(可选组件) systemd-networkd 的配置文件 .network / .netdev / .link(注意:多数不是 unit)
主机名、时间、日志等 配套守护进程 systemd-hostnamed、systemd-timesyncd、journald
用户登录与会话 logind 等 seat、linger、用户级 systemd

再补一句边界:


二、维度一:服务类型(unit type)

systemd 的核心抽象叫 unit。每种 unit 有一种类型;类型决定「它描述的是什么对象、能对它做什么」。

官方原生类型(常见发行版都能见到)如下。

1. 进程与任务

类型 后缀 一句话
service .service 跑一个(组)进程:长期守护或一次性 ExecStart
socket .socket 先听端口/套接字;有流量再拉起对应 service(socket 激活)
timer .timer 到点(或周期)激活另一个 unit,通常是 service
path .path 监视路径变化,再激活另一个 unit
scope .scope 把「已经在跑、由外部启动」的进程收进 cgroup 管理
slice .slice cgroup 树上的节点,用来挂资源限额(CPU/内存等)

日常 systemctl start foo,十有八九在动 .service。
定时任务看 .timer;「有人连 22 端口才起 sshd」这类故事看 .socket。

2. 存储与设备

类型 后缀 一句话
mount .mount 等价于一条 fstab 挂载:什么设备挂到哪、用什么选项
automount .automount 访问挂载点时才真正挂上
swap .swap 启用一块 swap
device .device 由 udev 暴露的设备 unit;多半自动生成,少手写

fstab 里的条目,启动时常常会被 生成器 翻译成 .mount / .swap unit——所以「systemd 管挂盘」并不神秘,只是把挂载也纳入同一张依赖图。

3. 系统状态:target

类型 后缀 一句话
target .target 不跑程序,只当「同步点 / 运行模式」:把一堆 unit 收成一组目标

例如 multi-user.target、graphical.target。
它承接了旧 SysV runlevel 的角色——细节见下一篇。

4. 一张总表(原生 unit)

进程侧:  .service  .socket  .timer  .path  .scope  .slice
存储侧:  .mount  .automount  .swap  .device
状态侧:  .target

记法建议:先问「我描述的对象是什么」,再选类型;不要先纠结命令。


三、维度二:文件后缀(你在磁盘上看到的名字)

类型和后缀在原生 unit 上几乎一一对应:sshd.service 就是类型为 service 的 unit,名字叫 sshd。

1. unit 文件通常在哪

位置 含义
/usr/lib/systemd/system/ 发行版/软件包装来的系统 unit(厂商默认)
/etc/systemd/system/ 本机覆盖与自定义(管理员)
/run/systemd/system/ 运行时临时
~/.config/systemd/user/ 用户级 unit
/usr/lib/systemd/user/ 用户级厂商默认

优先级直觉和目录规范那套类似:厂商默认 → /etc 覆盖 → 用户目录再覆盖(用户实例另有一套)。

改完常要:

systemctl daemon-reload
systemctl enable --now something.service

2. 后缀即类型(原生)

后缀 unit 类型 典型关系
.service service 可被 socket / timer / path / target 拉起
.socket socket 通常 WantedBy= 某 target;Also= 或约定配对同名 service
.timer timer Unit= 指向要跑的 service(默认同名)
.path path 同样激活另一个 unit
.mount mount 名字由挂载点编码,如 home.mount → /home
.automount automount 常与同名 .mount 配对
.swap swap 对应 swap 设备/文件
.target target 被一堆 unit WantedBy= / RequiredBy=
.slice slice 被 service/scope 的 Slice= 引用
.scope scope 多由运行时创建,少见静态文件
.device device 多由 udev 生成

3. 它们怎么串起来(关系,不是清单)

graphical.target
  └─ wants/requires → 许多 .service / .socket / .mount …
       ↑
timer.service ← 被 foo.timer 到点激活
bar.service  ← 被 bar.socket 有连接时激活
baz.service  ← 被 baz.path 监视到变化时激活

依赖常用指令(写 unit 时会反复见到):

不必一次啃完;先建立「target 是篮子,service 是篮子里的苹果,timer/socket/path 是谁在什么条件下把苹果拿出来」这张图就够用。


四、容易混进来的后缀:它们不全是「原生 unit」

你提到的 .container、.volume、.network,日常确实「systemd 生态里都要管」,但要拆开说,否则会和原生类型对不上号。

1. Podman Quadlet:.container / .volume / .network / …

在较新的 Podman 上,推荐用 Quadlet:你在

里写 foo.container、bar.volume、baz.network 等。
开机或 daemon-reload 时,生成器把它们翻译成真正的 systemd unit(主要是 .service,以及配套的网络/卷相关 unit)。

所以:

你写的文件 本质 最终多半变成
app.container Quadlet 源文件 app.service(跑 podman)
data.volume Quadlet 源文件 管理卷的 unit
lan.network Quadlet 源文件 容器网络相关 unit

这和「systemd 原生就有 container 这种 unit 类型」不是一回事——是 Podman 借用 systemd 的生命周期,用生成器接入。

(旧文里常见的 podman generate systemd 也是同一思路:生成 .service;Quadlet 是更现代的维护方式。)

若启用 systemd-networkd,配置往往在 /etc/systemd/network/:

后缀 作用
.network 匹配网卡后如何配地址、路由、DNS 等
.netdev 创建虚拟设备(bridge、bond、vlan…)
.link 链路层策略(改名、MTU、唤醒等;也可由 udev 侧使用)

这些是 networkd 的配置文件,不是 systemctl start xxx.network 那种 unit。
真正作为 service 跑着的是 systemd-networkd.service。

同名都叫 .network,Quadlet 与 networkd 目录不同、语义不同——看路径就能分开。

3. 对照小结

原生 unit 后缀          → 直接被 PID 1 理解
Quadlet 后缀            → 生成器 → 原生 unit
networkd 的 .network 等 → 被 networkd 守护进程读取

把这三层分开后,「systemd 怎么连容器和网络一起管」就不神秘了:要么自己是 unit,要么生成 unit,要么是某个 unit 所跑程序的配置。


五、文章能用在哪:几类真实场景

概念表背完仍容易飘。下面按「你大概在干什么」对一下该看哪种 unit / 后缀。

1. 管一个长期跑的守护进程 → .service

典型:Web、数据库、自写的小 daemon、开机要起的脚本包装。

systemctl status sshd.service
systemctl cat sshd.service          # 看实际加载了哪份文件
systemctl edit sshd.service         # 在 /etc 下写 drop-in 覆盖,而不是改 /usr

自己写最小骨架时,核心往往是 [Service] 里的 ExecStart=,以及 [Install] 里的 WantedBy=multi-user.target(装进「多用户」那个篮子)。

2. 替代 cron 的周期性任务 → .timer + .service

典型:每日备份、证书续期、清理缓存。习惯是同名一对:backup.timer 激活 backup.service。

systemctl list-timers --all
systemctl status logrotate.timer    # 发行版自带例子,名字因发行版而异

OnCalendar= / OnBootSec= 等语法以 systemd.timer(5) 与 systemd.time(7) 为准。

3. 挂盘、swap、按需挂载 → .mount / .automount / .swap

典型:数据盘、NFS、加密卷。很多条目仍写在 /etc/fstab,启动时由生成器变成 mount/swap unit——排障时可:

systemctl list-units --type=mount
systemctl status home.mount
findmnt /

若要「访问目录才挂」,才需要显式碰 automount;日常改 fstab 往往就够。

4. 容器要跟开机/登录生命周期绑在一起 → Quadlet(.container 等)

典型:家用 Gitea、rootless 服务、笔记本上常驻的开发依赖容器。你写的是 ~/.config/containers/systemd/*.container,最终跑的是生成出来的 .service。

本站相关实践可对照:用 Podman + systemd 跑 Gitea、podman generate systemd / Quadlet 笔记。更现代的默认路径是 Quadlet,而不是长期手维 generate systemd 的输出。

5. 本机用 networkd 配静态网 / 桥 → /etc/systemd/network/*.network

典型:无 NetworkManager 的服务器、需要 bridge/bond 的虚机宿主机。确认跑的是 systemd-networkd.service,再改配置目录——不要和 Quadlet 的 .network 目录搞混。

桌面/笔记本更常见的是 NetworkManager;那时「配网」入口不在这些后缀上。

6. 先摸清本机已经有什么(建议每台机器做一次)

systemctl list-unit-files --type=service | head
systemctl list-units --type=target
systemctl list-timers
ls /usr/lib/systemd/system | head
systemctl show --property=FragmentPath sshd.service

目的不是背输出,而是建立肌肉记忆:后缀 → 类型 → 文件落在哪 → 被谁 WantedBy。


六、用两个维度收束

按「类型 / 职责」记

按「磁盘上的后缀」记


七、和下一篇的衔接

既然 systemd 把服务、挂载、定时器都织进同一张依赖图,传统 SysV 的 运行级别(runlevel 0–6) 在理论上也完全可以用「某一组 unit 的集合」来表达——这正是 .target 干的事。

下一篇:从 runlevel 到 target:运行级别的历史与今天 —— runlevel 从哪来、和 target 怎么映射、今天还有没有必要惦记数字级别。


参考资料

官方手册(freedesktop,建议优先)

本机等价查阅:man systemd.unit、man systemd.service 等(与线上 latest 可能差小版本)。

发行版与生态

站内相关

评论

可对整篇留言,或划选正文中的句子后评论该段。

登录后即可发表评论。

  • 还没有公开评论。
↑