systemd 是一个庞大而复杂的项目,是现代 Linux 发行版的核心组件。直接通读所有源码是不现实的,但我们可以从高层次的架构、核心概念和关键目录结构入手,为你提供一个清晰的导览。

这篇讲解会分为以下几个部分:

  1. 首先,systemd 是什么? (高层次概述)

  2. 源码结构导览 (最重要的目录和文件)

  3. 核心概念与设计哲学 (理解 systemd 的“思想”)

  4. 代码风格与编程语言

  5. 如何开始阅读和贡献? (实践指南)


1. 首先,systemd 是什么?

在看源码之前,必须理解它的定位。systemd 不仅仅是一个 init 程序(PID 1),它是一个系统与服务管理器,一个为构建操作系统提供的基础组件集合

它的主要职责包括:

  • 启动系统:作为内核启动后的第一个用户态进程(PID 1),它负责并行地、按需地启动所有系统服务。

  • 服务管理:管理系统服务的生命周期(启动、停止、重启、监控)。

  • 系统状态管理:管理系统的主机名、时区、语言、挂载点、登录会话等。

  • 提供核心守护进程:包括日志(journald)、网络配置(networkd)、DNS 解析(resolved)、时间同步(timesyncd)等。

理解这一点至关重要:你看到的源码不仅仅是 PID 1 的逻辑,而是整个 systemd "全家桶" 的所有组件。


2. 源码结构导览

我们来浏览一下 GitHub 仓库的顶层目录,这是理解代码组织方式的关键。

Generated code

.
├── units/              # 默认的 Unit 配置文件
├── src/                # 核心 C 源代码
├── man/                # man 手册页面
├── test/               # 测试套件
├── po/                 # 国际化/翻译文件
├── shell-completion/   # Shell 自动补全脚本
└── meson.build         # 项目构建系统配置文件 (Meson)

Use code with caution.

meson.build 文件

这是 systemd 的构建系统定义文件。systemd 使用 Meson 和 Ninja 进行构建。如果你想编译 systemd,你需要从这个文件开始。它定义了所有的编译选项、依赖关系和要构建的目标文件。

units/ 目录

这个目录非常重要,它包含了 systemd 自带的默认单元文件 (Unit files)。这些 .service, .socket, .target, .timer 文件是 systemd 管理资源的声明式配置文件。阅读这些文件是理解 systemd 如何组织和启动系统服务的最佳方式之一。例如,你可以在这里找到 sshd.service 或 network.target 的模板。

man/ 目录

包含了所有 systemd 命令和配置文件的 man 手册页源码。当你对某个命令(如 systemctl)或配置文件(如 systemd.service(5))有疑问时,这里的源码是最终的权威文档。

src/ 目录 (最重要的部分)

这是所有 C 语言源码所在的地方。它的结构是模块化的,每个主要组件都有自己的子目录。

Generated code

src/
├── core/             # 🔥 PID 1 的核心实现 (最复杂的部分)
├── login/            # systemd-logind 的实现,管理用户会话
├── journal/          # systemd-journald 的实现,日志系统
├── network/          # systemd-networkd 的实现,网络管理
├── resolve/          # systemd-resolved 的实现,DNS 解析器
├── timesync/         # systemd-timesyncd 的实现,时间同步
|
├── libsystemd/       # 公共库 libsystemd 的源码,供外部程序链接
├── shared/           # 🔥 内部共享代码库 (非常关键)
|
├── systemctl/        # systemctl 命令的实现
├── journalctl/       # journalctl 命令的实现
├── boot/             # systemd-boot (UEFI 启动管理器) 的实现
├── nspawn/           # systemd-nspawn (轻量级容器) 的实现
... (还有很多其他工具和守护进程)

Use code with caution.

  • src/core/: 这是 systemd 的心脏,即 PID 1 的实现。它包含了事件循环(Event Loop)、单元(Unit)对象的管理、依赖关系解析、事务处理等。这里的代码最为复杂,不建议初学者直接阅读。

  • src/shared/: 这是理解 systemd 代码库的钥匙。它包含了一个庞大的内部共享库,提供了各种工具函数和抽象,比如:

    • 字符串处理 (string-util.c)

    • 文件 I/O (file-util.c)

    • D-Bus 封装 (sd-bus)

    • 事件循环封装 (sd-event)

    • 设备管理封装 (sd-device)

    • ...等等。
      systemd 的所有组件都大量使用 src/shared/ 中的代码。如果你想了解 systemd 的编程范式,可以从这里开始。

  • src/libsystemd/: 这是编译成 libsystemd.so 的代码。它提供了一组稳定的、公共的 API,供第三方应用程序调用,以便与 systemd 的各个子系统(如 journal, logind, sd-bus)进行交互。它与 src/shared/ 的区别在于,libsystemd 是对外暴露的公共接口,而 shared 是内部使用的。

  • src/systemctl/src/journalctl/ 等:这些是用户与之交互的命令行工具的源码。它们通常是很好的阅读起点,因为它们逻辑相对简单:解析命令行参数,然后通过 D-Bus 与相应的守护进程(如 systemd 主进程或 journald)通信。


3. 核心概念与设计哲学

要读懂源码,必须理解其背后的设计思想。

  • 声明式 (Declarative) vs. 命令式 (Imperative)

    • 传统的 SysVinit 脚本是命令式的:你写一个 shell 脚本,告诉系统如何一步步启动服务。

    • systemd 的单元文件是声明式的:你写一个配置文件,告诉系统你想要什么(比如“运行这个程序”、“在网络就绪后启动”),systemd 自己去计算如何最高效地达成这个状态。

  • 单元 (Unit)
    Unit 是 systemd 管理的基本对象。源码中,你会看到大量的 Unit 结构体和相关函数。常见的 Unit 类型有:

    • .service: 一个服务进程。

    • .socket: 一个套接字,可以用于按需启动服务(socket activation)。

    • .target: 一组 Unit 的集合,类似于 SysVinit 的运行级别(runlevel),但更灵活。

    • .timer: 一个定时器,用于定时触发其他 Unit。

    • .mount, .automount: 挂载点。

  • 依赖关系 (Dependencies)
    systemd 通过单元文件中的 Wants=, Requires=, After=, Before= 等指令来构建一个依赖关系图。PID 1 的核心任务之一就是解析这个图,并尽可能地并行启动没有依赖关系的服务,从而大大加快了启动速度。

  • D-Bus
    D-Bus 是 systemd 生态系统的中央总线。几乎所有的通信都是通过 D-Bus 完成的。

    • systemctl 命令向 systemd (PID 1) 发送 D-Bus 消息来启动/停止服务。

    • loginctl 命令向 logind 守护进程发送 D-Bus 消息。

    • systemd 内部的各个组件也可能通过 D-Bus 通信。
      因此,你会看到源码中到处都是 sd-bus 相关的函数调用。

  • Cgroups (Control Groups)
    这是 systemd 能够可靠追踪和管理进程的基石。systemd 为每个服务创建一个 cgroup,并将该服务及其所有子进程都放入其中。这带来了几个好处:

    • 可靠的进程追踪:即使进程 fork 并创建了守护进程,systemd 也能追踪到所有子孙进程。

    • 资源控制:可以对整个服务(包括所有子进程)进行资源限制(CPU, 内存, I/O)。

    • 干净的清理:当停止一个服务时,systemd 可以杀死 cgroup 中的所有进程,确保没有进程残留。


4. 代码风格与编程语言

  • 语言: 主要是 C (大部分遵循 C99/C11 标准)。

  • 风格: 类似 K&R 风格,但有自己的特点。缩进是 8 个空格,这在现代项目中很少见。

  • : 大量使用宏来简化代码,特别是错误处理和日志记录。

  • 错误处理: 函数通常返回负的 errno 值来表示错误,这是 Linux 内核中常见的模式。例如 return -EINVAL;。

  • 命名: 函数和变量名通常是小写,并用下划线分隔(snake_case)。

  • 实用工具库: 强烈倾向于使用 src/shared/ 中的自定义函数,而不是标准库中的某些函数,因为这些自定义函数通常提供了更强的错误检查或特定的功能。


5. 如何开始阅读和贡献?

直接跳进 src/core/manager.c 就像第一天学游泳就跳进深水区——你会很快迷失方向。建议采用以下策略:

  1. 从简单工具开始:

    • 选择一个简单的命令行工具,比如 hostnamectl (src/hostname/hostnamectl.c) 或 timedatectl (src/timedate/timedatectl.c)。

    • 阅读它的 main 函数,看看它如何解析参数,然后如何使用 sd-bus 连接到 systemd 总线并调用相应守护进程上的方法。

    • 这会让你熟悉 systemd 的 IPC 模式和 shared 库的用法。

  2. 阅读单元文件:

    • 花时间阅读 units/ 目录下的文件。它们是人类可读的,并且能让你深刻理解 systemd 的功能和设计意图。

  3. 探索 src/shared/:

    • 选择一个你感兴趣的模块,比如 string-util.c 或 path-util.c。这些文件相对独立,可以帮助你适应 systemd 的代码风格和常见的编程模式。

  4. 跟踪一个简单的流程:

    • 比如,研究 systemctl start some.service 的背后发生了什么。

    • systemctl: 解析命令,通过 D-Bus 向 PID 1 发送一个 "StartUnit" 请求。

    • PID 1 (src/core/): D-Bus 模块接收到请求,将其转化为一个内部的 "job",放入事务中。

    • PID 1: 事务处理器检查依赖关系,将需要启动的单元(units)加入队列,然后执行启动操作(如 fork 和 execve)。

    • 当然,这个过程非常复杂,但你可以分段理解,先看 systemctl 的部分,再看 D-Bus 接口定义,最后再尝试深入 core。

  5. 构建和测试:

    • 在虚拟机中尝试从源码编译 systemd。运行测试套件 (ninja test)。这能帮助你建立开发环境,并理解其构建过程。

总结

systemd 的源码是一个体现了现代 Linux 系统底层架构的优秀范例。它虽然庞大,但组织良好、高度模块化。

  • 不要害怕: 从小处着手,比如一个命令行工具或一个共享库函数。

  • 概念先行: 先理解 Unit, Dependency, D-Bus, Cgroups 等核心概念。

  • 由外到内: 从用户命令 (systemctl) 和配置文件 (units/) 开始,逐步深入到守护进程 (src/journal/),最后才挑战核心 (src/core/)。

希望这份导览能为你探索 systemd 的世界提供一张清晰的地图。祝你阅读愉快!

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐