Systemd 启动初探
作为linux系统启动重要的组成部分,对systemd 启动流程进行初步探索一下
systemd 是用户空间的 PID 1,它管理系统的服务、cgroup、日志、依赖关系和并行启动。启动流程可以从内核 rest_init()
调用 /sbin/init
开始追踪。这个systemd的binary到底是怎么搞出来的,要干哪些事情,今天研究一下
用户空间入口:PID 1 启动
-
内核启动完成后,通过
rest_init()
创建init
进程。
注意init相关的参数解析
init=
和 rdinit=
的区别主要体现在 启动源 和 优先级 上:
init=
-
指定内核启动后的 用户态第一个程序(PID 1)。
-
可以是磁盘上的常规程序,例如:
-
init=/sbin/init
init=/bin/systemd
-
内核解析命令行后,会在
rest_init()
里调用user_mode_thread()
启动该程序。 -
如果没有指定,内核会尝试默认路径
/sbin/init
、/etc/init
、/bin/init
。
rdinit=
-
指定 initramfs 内的初始程序。
-
只在 内核挂载了 initramfs 并且希望从 ramdisk 启动时有效。
-
例如:
-
rdinit=/init
-
这个
/init
是 initramfs 文件系统里的/init
文件,不是磁盘上的。 -
如果 initramfs 存在,
rdinit
优先于init=
被使用:-
内核会先执行
rdinit
。 -
rdinit
结束后,可能再启动init=
指定的程序。
-
-
PID 1 的默认 init 可由内核命令行参数指定:
-
init=/lib/systemd/systemd
-
如果未指定,内核按
/sbin/init
、/etc/init
等路径查找。 -
systemd 作为 PID 1,负责初始化整个用户空间。
systemd 的 main()
是进程启动后最先运行的函数,位于 systemd/src/core/main.c。当它作为 PID 1 启动时,负责从 内核切换到用户空间的第一步管理工作。源码较长,但逻辑可以分为以下几个阶段:
-
时间戳与运行环境初始化
dual_timestamp_from_monotonic(&kernel_timestamp, 0);
dual_timestamp_now(&userspace_timestamp);
skip_setup = early_skip_setup_check(argc, argv);
-
记录启动时间戳:保存内核到用户空间的过渡时刻。
-
判断是否跳过 setup:用于区分首次启动还是重新执行(reexec)。
-
设置进程名为 systemd,避免因
/sbin/init
链接调用而混淆。
-
环境准备与日志初始化
save_argc_argv(argc, argv);
save_env();
log_set_upgrade_syslog_to_journal(true);
-
保存启动参数和环境变量,便于后续可能的 reexec。
-
设置日志系统:优先写入 journal;若 PID=1,先临时使用
/dev/kmsg
,保证早期日志不会丢失。 -
容器与宿主机判断:
detect_container()
,决定日志输出到 console 还是 kmsg。
-
PID 1 特有的早期初始化
如果 getpid() == 1
,说明这是 系统模式(system mode):
主要工作包括:
-
挂载早期文件系统(/proc、/sys、/dev),确保能读取
/proc/cmdline
。 -
解析内核命令行参数,调整日志或调试选项。
-
初始化安全模块(SELinux、AppArmor 等)。
-
加载内核模块(kmod_setup)。
-
挂载标准 API 文件系统,为后续单元加载做好环境。
挂载文件系统有挂载表的指导:
用户模式下:
-
配置解析与参数检查
-
读取 systemd 配置文件(/etc/systemd/system.conf 等)。
-
解析命令行参数(比如
systemd.unit=
,systemd.debug_shell
)。 -
安全检查:确保 cgroup v2 存在,否则报错退出。
-
Runtime 初始化
r = initialize_runtime(...);
r = manager_new(..., &m);
r = manager_startup(m, arg_serialization, fds, NULL);
-
初始化运行环境(随机数种子、保护系统目录、core dump 设置等)。
-
创建 Manager 对象:这是 systemd 的“大脑”,维护所有单元(unit)的状态。
-
manager_startup():加载所有 unit 文件,建立依赖关系,准备启动事务。
-
默认任务队列与事件循环
-
创建默认启动任务:一般是
default.target
。 -
进入主事件循环:监听 D-Bus、管理进程、执行单元依赖,正式开始调度。
-
退出与关机路径
-
根据返回状态执行相应的退出逻辑:
-
reexec
→ 重新执行 systemd -
reboot/poweroff
→ 调用关机程序 -
exit
→ 直接退出(主要用于 user mode systemd)
-
unit加载: