systemd 复杂,往往不是因为某一条命令难记,而是因为它把「开机之后用户态该发生的一切」收进了同一套模型。挂盘、起网络、跑守护进程、定时任务、登录会话、甚至不少容器编排——表面上五花八门,落到 systemd 里,多半都是某种 unit。
这篇文章只做一件事:把 systemd 负责哪些事、有哪些 unit 类型、文件后缀怎么对应,以及你听到的 .container、.volume、.network 和原生 unit 是什么关系,从两个维度说清楚。运行级别(runlevel)的历史与今天的 target,留给下一篇。
读前可先备着的入口
- 手册总览:systemd(1)
- unit 通项:systemd.unit(5)
- 文末有完整「参考资料」与本机可对照的
man列表
systemd 是什么
- 内核自己起来,完成硬件探测、挂上初始根、找到 init 程序。
- 内核再拉起 第一个用户态进程,传统上叫 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 |
再补一句边界:
- NetworkManager 很常见,尤其是桌面/笔记本;它和 systemd-networkd 二选一为主的情况很多,不是「systemd 一定亲自配网」。
- 容器:原生 systemd 管的是「怎么把某个进程当 service 跑」;Podman 的 Quadlet(
.container等)是另一层「生成器」,最后仍落成原生 unit。
二、维度一:服务类型(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 时会反复见到):
After=/Before=:顺序(不隐含必须已启动)Requires=/Wants=:硬依赖 / 软依赖WantedBy=:装进某个 target(enable时创建符号链接)PartOf=/BindsTo=:生命周期绑得更紧
不必一次啃完;先建立「target 是篮子,service 是篮子里的苹果,timer/socket/path 是谁在什么条件下把苹果拿出来」这张图就够用。
四、容易混进来的后缀:它们不全是「原生 unit」
你提到的 .container、.volume、.network,日常确实「systemd 生态里都要管」,但要拆开说,否则会和原生类型对不上号。
1. Podman Quadlet:.container / .volume / .network / …
在较新的 Podman 上,推荐用 Quadlet:你在
/etc/containers/systemd/(系统)~/.config/containers/systemd/(用户)
里写 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 是更现代的维护方式。)
2. systemd-networkd:.network / .netdev / .link
若启用 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。
六、用两个维度收束
按「类型 / 职责」记
- 要跑程序 → service(可加 socket / timer / path 触发)
- 要挂盘 / swap → mount / automount / swap
- 要表达「系统处于某种模式」→ target
- 要限资源 / 收编进程组 → slice / scope
按「磁盘上的后缀」记
- 在
systemd/system或systemd/user下、后缀是上表那些 → 原生 unit - 在
containers/systemd下、后缀是.container等 → Quadlet,生成后再变成 unit - 在
systemd/network下、后缀是.network等 → networkd 配置,不是 unit 类型
七、和下一篇的衔接
既然 systemd 把服务、挂载、定时器都织进同一张依赖图,传统 SysV 的 运行级别(runlevel 0–6) 在理论上也完全可以用「某一组 unit 的集合」来表达——这正是 .target 干的事。
下一篇:从 runlevel 到 target:运行级别的历史与今天 —— runlevel 从哪来、和 target 怎么映射、今天还有没有必要惦记数字级别。
参考资料
官方手册(freedesktop,建议优先)
- systemd(1) — 总览与 unit 类型列表
- systemd.unit(5) — 所有 unit 共用的
[Unit]/[Install]、加载路径 - systemd.service(5) / systemd.socket(5) / systemd.timer(5)
- systemd.mount(5) / systemd.path(5) / systemd.target(5)
- systemd.network(5) — networkd 配置(不是 Quadlet)
- systemd.special(7) — 特殊 unit / target 名称
本机等价查阅:man systemd.unit、man systemd.service 等(与线上 latest 可能差小版本)。
发行版与生态
- Fedora:Managing systemd — 面向日常管理的速览
- Red Hat:Managing systemd — RHEL 系系统设置文档中的 systemd 章节
- Arch Wiki:systemd — 概念密、排障例子多(注意与 Fedora 路径习惯对照)
- podman-systemd.unit(5) — Quadlet 各后缀说明