2. SysV init脚本基础:运行级别概念、init脚本结构、脚本头部规范(LSB header)

好,咱们正式开始啃 SysV init 这块硬骨头。很多刚接触 Linux 系统启动的朋友,一上来就被各种脚本和运行级别搞晕了。别急,我当年也一样。记得我第一次接手一个生产环境的服务器优化任务,愣是被 /etc/rc.d 下的那一堆脚本搞得头皮发麻。后来摸清了门道,才发现这东西其实挺有章法的。

这一节,咱们就把 SysV init 的底裤扒干净。你搞懂了运行级别、脚本结构和 LSB 头部,后面写自己的定制脚本就跟玩似的。

2.1 运行级别:系统的“档位”

运行级别(Runlevel),说白了就是 Linux 系统当前处于什么“档位”。它定义了系统启动后应该运行哪些服务。你可以把它想象成汽车的 P、R、N、D 档——不同档位干不同的事。

SysV init 定义了 7 个运行级别(0-6),每个级别对应一组要启动或停止的服务。我习惯用下面这张表来记忆:

运行级别 标识 用途 典型状态
0 关机 停止所有服务,安全关闭系统 halt
1 单用户模式 维护模式,root 登录,无网络 single
2 多用户模式(无网络) 多用户,但 NFS 等网络服务不启动 multiuser
3 完整多用户模式 标准服务器模式,有网络,命令行 multiuser
4 未使用/自定义 留给管理员自定义 user-definable
5 图形界面模式 多用户 + 图形化桌面 graphical
6 重启 停止所有服务,重启系统 reboot
我的经验: 生产服务器通常跑在运行级别 3。为什么?因为少跑一个 X Window 就少一个攻击面,还能省下几百兆内存。我曾经帮客户优化一台 2G 内存的旧服务器,从级别 5 切到级别 3,直接省出 400M 空闲内存,数据库查询响应快了 30%。

系统启动时,init 进程(PID 1)会读取 /etc/inittab 文件,找到默认运行级别。然后它就去执行对应目录下的脚本。这些目录长这样:

/etc/rc.d/rc0.d/   → 关机
/etc/rc.d/rc1.d/   → 单用户
/etc/rc.d/rc2.d/   → 多用户(无网络)
/etc/rc.d/rc3.d/   → 完整多用户
/etc/rc.d/rc4.d/   → 自定义
/etc/rc.d/rc5.d/   → 图形界面
/etc/rc.d/rc6.d/   → 重启

有些发行版(比如 Debian/Ubuntu)把 rc.d 直接放在 /etc/ 下,路径是 /etc/rc0.d/ 这样。本质一样,别纠结。

2.2 init 脚本结构:骨架与血肉

一个标准的 SysV init 脚本,本质上就是一个 Shell 脚本。但它有固定的骨架。我写脚本的习惯是:先搭架子,再填肉。

一个完整的 init 脚本通常包含这几部分:

  1. LSB 头部注释 —— 脚本的“身份证”
  2. Shell 解释器声明 —— 通常是 #!/bin/bash
  3. 函数定义 —— start、stop、status、restart 等
  4. 主逻辑 —— 根据传入参数调用对应函数

来看一个我实际项目中用过的简化版 Nginx 启动脚本:

#!/bin/bash
#
# chkconfig: 345 85 15
# description: Nginx Web Server
# processname: nginx
# config: /etc/nginx/nginx.conf

# Source function library
. /etc/init.d/functions

# 定义变量
NAME=nginx
DAEMON=/usr/sbin/$NAME
CONFIG=/etc/nginx/nginx.conf
PIDFILE=/var/run/$NAME.pid

start() {
    echo -n "Starting $NAME: "
    daemon $DAEMON
    RETVAL=$?
    echo
    [ $RETVAL -eq 0 ] && touch /var/lock/subsys/$NAME
    return $RETVAL
}

stop() {
    echo -n "Stopping $NAME: "
    killproc $DAEMON
    RETVAL=$?
    echo
    [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/$NAME
    return $RETVAL
}

status() {
    status $DAEMON
    RETVAL=$?
    return $RETVAL
}

case "$1" in
    start)
        start
        ;;
    stop)
        stop
        ;;
    status)
        status
        ;;
    restart)
        stop
        start
        ;;
    *)
        echo "Usage: $0 {start|stop|status|restart}"
        exit 1
        ;;
esac

exit $RETVAL
关键点: 注意 /etc/init.d/functions 这个文件。它提供了 daemon、killproc、status 等辅助函数。不同发行版这个文件的位置和内容略有差异,但作用一样——帮你省掉很多重复的进程管理代码。

2.3 LSB 头部规范:脚本的“身份证”

LSB(Linux Standard Base)头部,就是脚本开头那几行注释。别小看这几行,它们决定了脚本如何被系统管理工具识别和控制。

我曾经踩过一个坑:写了一个自定义服务脚本,忘了加 LSB 头部,结果 chkconfig --add 死活不认。折腾了半天才发现是头部格式不对。

标准的 LSB 头部包含以下字段:

字段 格式 说明
chkconfig # chkconfig: 运行级别 启动优先级 关闭优先级 指定脚本在哪些运行级别启动,以及启动/关闭的顺序
description # description: 服务描述 简短描述这个服务的作用
processname # processname: 进程名 指定守护进程的名称,用于 status 和 killproc
config # config: 配置文件路径 (可选)指定配置文件位置
pidfile # pidfile: PID文件路径 (可选)指定 PID 文件位置

拿上面 Nginx 脚本的头部举例:

# chkconfig: 345 85 15
# description: Nginx Web Server
# processname: nginx
# config: /etc/nginx/nginx.conf

这里 345 85 15 的意思是:

  • 在运行级别 3、4、5 下启动这个服务
  • 启动优先级是 85(数字越小越早启动)
  • 关闭优先级是 15(数字越小越早关闭)
注意: 优先级数字不是随便写的。系统服务有约定俗成的范围:早期服务(如网络、磁盘)用 10-30,中间服务(如数据库)用 50-70,后期服务(如应用)用 80-99。我曾经见过有人把 MySQL 的启动优先级设成 10,结果网络还没起来 MySQL 就开始连——那肯定连不上啊。

2.4 知识体系总览

说了这么多,咱们用一张图把这一节的核心逻辑串起来。你看着这张图,就能明白运行级别、脚本目录、LSB 头部和脚本结构之间的关系:

SysV init 脚本知识体系 运行级别 (0-6) /etc/rc.d/rcN.d/ 目录 init 脚本 (Shell) LSB 头部 chkconfig / description 函数定义 start / stop / status 主逻辑 case 分支处理 核心流程:运行级别 → 对应目录 → 执行脚本 → LSB头部 + 函数 + 主逻辑 优先级数字决定启动/关闭顺序,S/K 前缀区分启动和停止

2.5 避坑指南

最后,分享几个我实际工作中踩过的坑,你遇到了能少走弯路:

  • LSB 头部格式必须严格 —— # chkconfig: 后面必须跟一个空格,冒号是英文的。我曾经手滑打成中文冒号,结果 chkconfig --list 死活看不到这个服务。
  • 优先级冲突 —— 两个脚本如果优先级相同,启动顺序取决于文件名排序。所以别把两个依赖关系强的脚本设成同一个优先级。
  • 函数库路径 —— Red Hat 系用 /etc/init.d/functions,Debian 系可能没有这个文件。跨发行版写脚本时,最好自己实现核心函数,或者做兼容判断。
  • PID 文件清理 —— 服务异常退出后,PID 文件可能残留。下次启动时如果检测到 PID 文件存在但进程已死,脚本会误判服务正在运行。我习惯在 start() 函数里先检查并清理残留 PID 文件。
我的习惯: 写新脚本前,先找个同发行版现成的脚本(比如 /etc/init.d/sshd)当模板。把它的 LSB 头部和函数框架复制过来,改改变量名和启动命令就行。这样既保证格式正确,又省时间。

嗯,运行级别、脚本结构、LSB 头部,这三块就是 SysV init 的基石。你把这章吃透了,后面讲定制脚本、优化启动速度的时候,咱们就有共同语言了。