systemd 核心功能三层模型

一句话总结:本文以"进程怎么创建、怎么通信、创建了什么"三层模型重新梳理了 systemd 的全部源码组件,核心结论是 systemd 的本质 = PID 1 进程生命周期管理引擎 + sd-bus / D-Bus 通信基础设施 + 一系列让裸内核变得可用的守护进程。

核心认知模型

一个裸 Linux 内核启动后什么也做不了——没有网络、没有日志、设备插入无响应。systemd 做的事就三件:

┌─────────────────────────────────────────────┐
│  第 3 层:创建了什么?                         │
│  让系统变得可用的守护进程                       │
│  journald / udevd / networkd / logind ...    │
├─────────────────────────────────────────────┤
│  第 2 层:怎么通信?                           │
│  进程间 IPC 基础设施                           │
│  sd-bus 库 + D-Bus 总线                      │
├─────────────────────────────────────────────┤
│  第 1 层:进程怎么创建?                        │
│  生命周期管理引擎                               │
│  PID 1 (unit / job / transaction 引擎)       │
├─────────────────────────────────────────────┤
│  支撑层:基础库                                │
│  fundamental → basic → shared → libsystemd   │
└─────────────────────────────────────────────┘

三层之上还有命令行工具(systemctl / journalctl / busctl),是用户与三层交互的操作界面。


总览流程图

操作界面

第3层:创建了什么

第2层:怎么通信

第1层:进程怎么创建

支撑层:基础库

按需启动

按需启动

按需启动

按需启动

生成临时 unit

基于

IPC 通信

IPC 通信

IPC 通信

D-Bus 控制

D-Bus 查询

D-Bus 调试

src/fundamental/

src/basic/

src/shared/

src/libsystemd/ sd-bus/sd-event/sd-journal

src/core/ PID 1 unit/job/transaction 引擎

Generator 启动时动态生成 unit

sd-bus 库 消息序列化/反序列化/路由匹配

D-Bus 总线 dbus-daemon/dbus-broker

journald 日志

udevd 设备管理

networkd 网络

logind 会话管理

resolved/timesyncd/coredumpd...

systemctl

journalctl

busctl

第 1 层:进程怎么创建(PID 1 进程生命周期管理引擎)

内核已经能做 fork/exec,systemd 加了什么

内核的 fork() + exec() 只提供了机制——“创建一个进程”。systemd 的 PID 1 提供的是策略——什么时候创建、依赖谁、失败了怎么办、用多少资源、stdout 收到哪、挂了自己重启。简单说:内核造进程,systemd 管进程的一生。

核心源码:src/core/

核心文件 职责
unit.c/h 所有可管理事物的基类:统一状态机(active / inactive / failed / reloading)
service.c/h 最复杂的 unit 类型:fork + exec 普通守护进程,跟踪主 PID,检测崩溃自动重启
socket.c/h socket 激活:先创建监听 socket,有请求才 wake up 服务进程
mount.c/h 挂载点 unit:/etc/fstab 的动态版本
device.c/h 设备 unit:与 udevd 联动,插 U 盘自动触发挂载
timer.c/h 定时器 unit:替代 crond
scope.c/h 外部创建的进程组(如 Docker 容器、用户登录 session)
slice.c/h cgroup 分层节点:/system.slice/user.slice
target.c/h 逻辑分组:multi-user.targetgraphical.target(替代 SysV runlevel)
job.c/h job 调度引擎:按依赖图决定启动/停止的执行顺序
transaction.c/h 事务引擎:验证依赖是否满足,形成可执行的 job 队列
manager.c/h 全局管理者:持有所有 unit、job、cgroup 的引用,D-Bus 请求入口
cgroup.c/h cgroup 操作:创建/删除 cgroup,设置 CPU/内存限制
dbus-*.c/h PID 1 的 D-Bus 接口层(dbus-manager.c, dbus-unit.c, dbus-service.c…)
load-fragment.c/h 解析 unit 配置文件(*.service*.socket*.mount 等)

创建流程:一个 service 从声明到运行

unit 配置文件 (/etc/systemd/system/bmcweb.service)
  → load-fragment.c 解析
  → unit.c 创建 unit 对象,状态 = inactive
  → transaction.c 检查依赖(需要 network-online.target + dbus.service)
  → job.c 生成 job 队列(先启依赖,再启本身)
  → service.c fork + exec,状态 = active
  → cgroup.c 放入 /system.slice/bmcweb.service
  → 持续监控:进程退出 → 自动重启或标记 failed

Generator:动态生成 unit 的小工具

有些 unit 不能预先写死(比如机器有几块硬盘、几个 tty),所以 PID 1 在加载 unit 之前先跑 Generator:

启动顺序:
  PID 1 启动
    → 第 1 步:跑所有 Generator(一次性小程序)
        fstab-generator:  /etc/fstab → mount unit
        getty-generator:  检测 tty → getty@ttyS0.service
        sysv-generator:   兼容 /etc/init.d/ 脚本 → unit
        输出:写到 /run/systemd/generator/ 临时目录
    → 第 2 步:PID 1 加载所有 unit(包括 generator 生成的)
    → 第 3 步:计算依赖图,启动服务
    → Generator 早已退出

Generator 不通过 D-Bus 通信(此时总线还没启动),直接写文件到特定目录。


第 2 层:怎么通信(sd-bus + D-Bus IPC 基础设施)

D-Bus 协议不是 systemd 发明的,sd-bus 是

D-Bus 协议和 dbus-daemon 在 2003 年就有了。systemd 做的是:

  • 重写了客户端库 sd-bussrc/libsystemd/sd-bus/bus-message.c 就是你最初打开的文件)——消息序列化/反序列化更高效
  • 把 D-Bus 变成了 systemd 的控制面板——systemd 所有组件之间的通信全部走到 D-Bus 总线上

通信全景

┌─────────────────── D-Bus 总线 (dbus-daemon / dbus-broker) ───────────────┐
│                                                                          │
│   PID 1            journald         udevd           networkd            │
│   暴露接口:         暴露接口:        暴露接口:         暴露接口:           │
│   .Manager         .Journal         .Device          .Network           │
│   .Unit            (日志读写)        (设备枚举)        (网络配置)         │
│   .Service                                                              │
│                                                                          │
│   所有组件通过 sd-bus 库在总线上互相调用、读取属性、订阅信号                │
└──────────────────────────────────────────────────────────────────────────┘
        ↑                ↑              ↑               ↑
        │                │              │               │
   systemctl        journalctl      udevadm        networkctl
   (启停服务)         (查日志)        (查设备)        (查网络)

   命令行工具 = 用户操作界面,背后全是通过 D-Bus 与对应守护进程通信

核心源码

目录 内容
src/libsystemd/sd-bus/ sd-bus 核心实现:bus-message.c(序列化)、bus-control.c(连接管理)、bus-match.c(规则匹配)、bus-objects.c(对象树)、bus-signature.c(类型签名)
src/busctl/ busctl 命令行工具:D-Bus 调试(类似 dbus-send / dbus-monitor
src/systemd/sd-bus.h sd-bus 公开头文件

通信流程示例

systemctl start bmcweb.service
  → sd_bus_call_method("StartUnit", "bmcweb.service", "replace")
  → 序列化为 D-Bus 消息 (bus-message.c)
  → 发送到 dbus-daemon
  → dbus-daemon 路由到 PID 1
  → PID 1 (core/dbus-manager.c) 反序列化 → manager_start_unit()
  → 执行完毕后 PID 1 通过 D-Bus 回复 systemctl

第 3 层:创建了什么(让系统变得可用的守护进程)

裸内核只有一个 shell。systemd 自带了一套守护进程,让系统"开箱即用":

守护进程 源码目录 做了什么 没有它的话
journald src/journal/ 收集所有进程的 stdout/stderr,二进制存储,支持索引查询 每个服务各自写文本日志,到处找 log 文件
udevd src/udev/ 监听内核设备事件,自动创建设备节点、加载驱动、触发挂载 插 U 盘完全没反应
networkd src/network/ 网络接口配置:DHCP 获取 IP、静态 IP、VLAN、桥接 网卡没有 IP,手动 ifconfig
resolved src/resolve/ DNS 缓存与解析,管理 /etc/resolv.conf 每次 DNS 查询都直接打上游,没有缓存
logind src/login/ 用户会话管理:多 seat、多 session、切换 tty、电源按钮处理 用户登录后的 session 不受管理
timesyncd src/timesync/ SNTP 时间同步(轻量版 ntpd) 系统时钟慢慢漂移
hostnamed src/hostname/ 主机名管理 hostname 命令改了但重启丢失
timedated src/timedate/ 时间/时区管理 手动 date + tzselect
localed src/locale/ 系统 locale 管理 手工编辑 /etc/locale.conf
coredumpd src/coredump/ 崩溃时收集 core dump,存到 journal 进程崩了就崩了,不知道崩在哪
oomd src/oom/ 内存压力下智能杀进程 内核 OOM killer 随机杀,可能杀掉关键服务
nspawn src/nspawn/ 轻量级容器(不需要 Docker) 没有容器环境
homed src/home/ 可迁移加密主目录 传统 /etc/passwd + /home

加上 units/ 目录下的预置配置(basic.targetmulti-user.targetgetty@.service 等几百个文件),systemd 把一个裸内核变成了"通电就能用"的完整系统。


支撑层:基础库的分层架构

以上三层都依赖底层库,库的分层规则是每层只能向下依赖,不能向上

src/fundamental/    最基础的类型和宏,连 basic 都依赖它
    ↑
src/basic/          底层工具(alloc-util, hashmap, string-util, fileio, cgroup-util...)
    ↑
src/shared/         系统级共享模块(比 basic 更高层,可调 mount/umount 等系统调用)
    ↑
src/libsystemd/     公开 API(sd-bus / sd-event / sd-journal / sd-daemon / sd-login)
    ↑
┌──────────────────┬──────────────────┬────────────────────┐
src/core/          src/journal/        src/systemctl/
(PID 1)            (journald)          (命令行工具)

关键规则

  • basic/ 里的代码不能 include shared/core/ 的内部头文件(底层不知道上层存在)
  • core/ 可以 include 下面所有层
  • 外部程序(如 bmcweb)只应链接 libsystemd,不能 include core/ 的内部头文件


汇总:一条命令穿过所有三层

你敲下: systemctl start bmcweb.service
  │
  │  [第 2 层:通信]
  │  1. systemctl 通过 sd-bus 将 "StartUnit" 序列化为 D-Bus 消息
  │  2. dbus-daemon 路由消息到 PID 1
  │
  │  [第 1 层:进程创建]
  │  3. PID 1 (dbus-manager.c) 反序列化 → manager_start_unit()
  │  4. transaction.c 检查依赖:network-online.target + dbus.service
  │  5. job.c 按依赖顺序排队:先启依赖,再启 bmcweb
  │  6. service.c fork() + exec("/usr/bin/bmcweb")
  │  7. cgroup.c 放入 /system.slice/bmcweb.service,设置资源限制
  │
  │  [第 3 层:配套设施]
  │  8. journald 自动收集 bmcweb 的 stdout/stderr 到日志
  │  9. networkd 在此之前已配好网络(bmcweb 需要监听端口)
  │
  ▼  bmcweb 运行中,systemd 持续监控:崩了自动重启,D-Bus 接口可查询状态

学习路径

顺序 目标 读什么
1 理解进程怎么创建 src/core/unit.hunit.cjob.ctransaction.cservice.c
2 理解怎么通信 src/libsystemd/sd-bus/bus-message.c(序列化)→ bus-control.c(连接)→ bus-match.c(路由)
3 理解创建了什么 units/*.servicesrc/journal/src/network/(挑一个感兴趣的 daemon)
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐