1. init系统概述:Linux启动流程、init进程的作用、SysV init vs Systemd vs Upstart对比
大家好,我是老李。做Linux系统优化这么多年,我见过太多启动慢得让人抓狂的系统。有的服务器重启一次要等三分钟,有的嵌入式设备开机像在熬粥——慢得让人心焦。其实,很多问题都出在init系统上。
今天咱们就来聊聊init系统。这是整个启动流程的“总指挥”。你搞懂了它,优化启动速度就有了方向。
1.1 Linux启动流程全景
先看一张图,把整个启动流程串起来。我习惯把它分成五个阶段:
核心观点:从按下电源键到出现登录提示符,Linux内核只负责硬件初始化和加载根文件系统,剩下的“用户空间初始化”全部由init进程接管。所以,init系统的效率直接决定了启动快慢。
嗯,这张图很直观。前三个阶段(BIOS → Bootloader → Kernel)基本是固定的,我们能优化的空间不大。但从init进程接管开始,到用户空间初始化完成——这块儿才是我们做定制init脚本的核心战场。
1.2 init进程的作用
init进程是Linux内核启动后创建的第一个用户空间进程,PID永远是1。它的职责说白了就三件事:
- 启动服务——按照配置顺序拉起所有系统服务
- 监控服务——当某个服务挂了,init负责重启它
- 回收孤儿进程——子进程的父进程退出后,init成为它们的“养父”
一个小技巧:你可以用 ps -p 1 -o pid,comm,cmd 看看当前系统的init是什么。我见过有人误以为PID 1是内核线程,其实不是——内核线程的PID通常从2开始。
我在项目中遇到过一件事:有个嵌入式设备频繁死机,查了三天才发现是某个子进程变成了孤儿,init来不及回收导致资源泄漏。后来我们优化了init脚本,把孤儿进程的回收策略改成了主动轮询——问题就解决了。
1.3 SysV init vs Systemd vs Upstart 对比
这三兄弟,我挨个说说我的使用感受。
SysV init——老派但可靠
SysV init是Unix System V时代传下来的老将。它的核心思想是“运行级别”(runlevel)。每个运行级别对应一组服务脚本,放在 /etc/rc.d/rc?.d/ 目录下。
它的启动方式是串行的——一个服务启动完,下一个才能开始。你想想看,如果某个服务卡住了,后面的都得等着。我早期做服务器运维时,最怕的就是某个服务脚本里有个 sleep 30,那启动时间直接多出半分钟。
# SysV init 的服务脚本示例(/etc/init.d/sshd)
#!/bin/sh
# chkconfig: 2345 55 25
# description: OpenSSH server daemon
case "$1" in
start)
echo "Starting sshd..."
/usr/sbin/sshd
;;
stop)
echo "Stopping sshd..."
killproc /usr/sbin/sshd
;;
restart)
$0 stop
$0 start
;;
status)
status /usr/sbin/sshd
;;
esac
注意:SysV init的脚本必须自己处理PID文件、锁文件、依赖关系。我曾经见过一个脚本忘记创建PID文件,导致 service xxx status 永远返回“stopped”——坑了不少人。
Upstart——过渡期的产物
Upstart是Ubuntu搞出来的,想解决SysV init的串行启动问题。它引入了“事件驱动”机制——服务可以基于事件(比如某个硬件就绪、某个服务启动完成)来触发启动。
说实话,Upstart比SysV init灵活多了,但它的配置语法有点怪。我记得第一次看Upstart的配置文件,愣是没搞懂 start on 和 stop on 的触发条件怎么写。后来Ubuntu自己也放弃了它,转向了Systemd。
# Upstart 配置示例(/etc/init/sshd.conf)
description "OpenSSH server"
start on filesystem or runlevel [2345]
stop on runlevel [!2345]
respawn
respawn limit 10 5
exec /usr/sbin/sshd -D
Systemd——现代Linux的标配
Systemd现在是主流发行版的默认init系统。它的核心是“单元”(unit),每个服务、挂载点、套接字都是一个单元。启动方式是并行的——没有依赖关系的服务可以同时启动。
我个人最喜欢Systemd的一点是它的依赖管理。你只需要在单元文件里写清楚 After= 和 Requires=,Systemd会自动帮你排序。而且它的 systemd-analyze blame 命令可以直接告诉你每个服务花了多少时间启动——这对优化启动速度太有用了。
# Systemd 单元文件示例(/etc/systemd/system/sshd.service)
[Unit]
Description=OpenSSH server daemon
Documentation=man:sshd(8) man:sshd_config(5)
After=network.target
[Service]
Type=notify
ExecStart=/usr/sbin/sshd -D
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=on-failure
RestartSec=42s
[Install]
WantedBy=multi-user.target
1.4 三种init系统的对比表格
我把它们的核心差异整理成了一张表,方便你对照:
| 特性 | SysV init | Upstart | Systemd |
|---|---|---|---|
| 启动方式 | 串行 | 事件驱动,部分并行 | 完全并行 |
| 配置格式 | Shell脚本 | 自定义配置文件 | 单元文件(INI风格) |
| 依赖管理 | 手动(脚本内处理) | 基于事件 | 自动(After/Requires) |
| 服务监控 | 无(需外部工具) | 内置respawn | 内置Restart策略 |
| 日志管理 | syslog | syslog | journald(二进制日志) |
| 启动速度 | 慢(串行瓶颈) | 中等 | 快(并行+按需启动) |
| 主流发行版 | 老旧系统、BusyBox | Ubuntu 9.04-14.10 | Fedora、Debian、Ubuntu、RHEL |
我的建议:如果你在做新项目,直接用Systemd。它功能强大,生态成熟。但如果你在做嵌入式系统或者老旧设备维护,SysV init依然有它的用武之地——轻量、简单、没有依赖。Upstart嘛……除非你维护的是古董系统,否则不建议碰了。
1.5 为什么我们要定制init脚本?
你可能会问:既然Systemd这么好,为什么还要自己写init脚本?
原因很简单:不是所有场景都适合Systemd。
- 嵌入式系统——资源受限,Systemd太臃肿,一个简单的Shell脚本就能搞定
- 容器环境——Docker容器里通常不需要完整的init系统,一个轻量级的init脚本就够了
- 特殊硬件——有些硬件初始化必须在特定顺序下完成,Systemd的并行启动反而会出问题
- 极致优化——Systemd虽然快,但它的启动流程是通用的。如果你对启动时间有极致要求(比如车载系统要求2秒内启动),手写init脚本才能做到“指哪打哪”
我记得有一次给一个工业控制器做系统优化。那个设备要求上电后1.5秒内必须开始执行控制逻辑。Systemd启动完基本服务就要2秒多,根本来不及。后来我们写了一个只有20行的init脚本,直接跳过所有不必要的服务,0.8秒就进入了控制程序——这就是定制init脚本的价值。
避坑指南:我曾经在定制init脚本时犯过一个低级错误——忘了处理 SIGTERM 信号。结果系统关机时,init进程直接被杀,所有子进程变成了孤儿。从那以后,我写的每个init脚本都会加上信号处理函数。