1. 项目概述:理解RedHat系列Linux的启动脉络

作为一名在Linux运维和嵌入式开发领域摸爬滚打多年的工程师,我处理过无数次服务器重启后服务“罢工”的紧急情况。很多时候,问题根源不在于服务本身,而在于我们对系统启动流程的理解不够透彻,导致自启动脚本配置不当。RedHat系列(包括CentOS、Fedora、RHEL等)作为企业级和嵌入式领域的主力军,其System V init启动机制虽然正逐渐被systemd取代,但在大量存量系统和特定场景下,依然是必须掌握的核心知识。今天,我就结合自己的实战经验,为你彻底拆解RedHat系列Linux的开机启动脚本顺序,让你不仅知道怎么配,更明白为什么要这样配,从而真正掌控你的系统启动过程。

简单来说,这个过程就像一场精心编排的舞台剧:内核是舞台搭建者,init进程是总导演, /etc/rc.d/rc.sysinit 是开场前的全局准备工作, /etc/rc.d/rc 是根据剧本(运行级别)指挥演员(服务脚本)按顺序登场, /etc/rc.d/rc.local 则是所有既定节目演完后,留给管理员的自由发挥时间,最后 mingetty 打开观众席(终端)等待登录。理解了这个流程,你就能精准地安排你的服务在正确的时间点启动,避免因依赖关系(比如数据库没启动,Web服务就先跑了)导致的启动失败。无论你是运维工程师确保服务器服务高可用,还是嵌入式开发者定制系统启动项,这篇文章都将为你提供清晰的路径和避坑指南。

2. 启动流程深度解析:从内核到登录提示符

要配置好启动脚本,绝不能停留在“依葫芦画瓢”的层面,必须深入理解每个环节的职责和原理。RedHat传统的System V init启动流程是一个清晰的分阶段过程,环环相扣。

2.1 内核加载与init进程的诞生

当服务器通电或执行重启命令后,BIOS/UEFI完成自检,引导加载程序(如GRUB)会将Linux内核镜像加载到内存中并解压运行。内核初始化是启动过程中最底层、最核心的一步,它负责检测硬件、加载必要的驱动、挂载根文件系统。这一切完成后,内核空间的工作就基本结束了,它需要找一个“接班人”来管理接下来的用户空间事务——这个接班人就是 /sbin/init 进程。

为什么是init? 在Linux设计中,内核启动的第一个用户态进程的进程号(PID)固定为1。init进程由此成为所有用户进程的祖先,它负责启动和管理系统的所有其他进程和服务。如果内核找不到 /sbin/init ,它会尝试 /bin/sh 作为后备,如果连shell都找不到,系统启动就会宣告失败。因此,确保 /sbin/init 文件存在且可执行是系统能正常启动的底线。

2.2 初始化脚本:rc.sysinit的统一战线

内核将指挥棒交给init后,init做的第一件大事就是执行 /etc/rc.d/rc.sysinit 脚本。这个脚本的角色是“系统初始化总管”,它的任务是完成 所有运行级别都需要的、最基础的全局环境设置 。把它想象成舞台剧开场前,灯光、音响、布景、消防检查这些无论演什么剧目都必须做好的准备工作。

根据我的经验, rc.sysinit 脚本通常包含以下关键操作,理解它们有助于排查一些早期启动故障:

  1. 设置主机名与NIS域名 :系统身份标识的建立。
  2. 挂载 /proc /sys 文件系统 :这两个是内核与用户空间通信的虚拟文件系统,许多工具(如 ps , top )依赖它们。
  3. 激活交换分区(swapping) :即使物理内存充足,这一步也是必要的,它为内存管理奠定基础。
  4. 检查并挂载根文件系统为读写模式 :启动时根文件系统通常以只读模式挂载,防止检查时被修改。 rc.sysinit 会运行 fsck 检查磁盘,若无问题则重新挂载为读写模式,这是系统能进行写操作的前提。
  5. 加载键盘映射(keymap)和系统字体 :确保控制台输入输出正常。
  6. 启用磁盘配额(quota) :如果文件系统配置了配额。
  7. 加载内核模块 :根据 /etc/modprobe.conf /etc/modules-load.d/ 下的配置,加载必要的声卡、特定硬件驱动等模块。
  8. 设置系统时钟 :从硬件时钟读取时间并设置系统时间。

注意 rc.sysinit 的执行非常早期,此时网络通常还未初始化,因此 不要 在此脚本中尝试启动依赖网络的服务,这几乎注定会失败。它的职责是“修好舞台”,而不是“请演员上台”。

2.3 运行级别与rc脚本:导演的剧本分镜

完成全局初始化后,init会根据 /etc/inittab 文件中 id:x:initdefault: 这一行,确定系统要进入的 默认运行级别(Runlevel) 。运行级别定义了系统不同的操作状态。

运行级别详解:

  • 0:停机 - 关闭系统。 绝对不要 将默认运行级别设为0。
  • 1或s:单用户模式 - 最小化系统状态,通常用于系统修复(如忘记root密码)。只有root能登录,不启动网络、图形界面及大多数服务。
  • 2:多用户模式(无NFS) - 支持多用户登录,但不提供网络文件系统共享。
  • 3:完全多用户模式(文本模式) - 服务器最常用的运行级别 。提供完整的网络功能,但为命令行界面。
  • 4:用户自定义 - 默认未定义,可供管理员自由定制。
  • 5:图形界面模式 - 在级别3的基础上,启动图形登录管理器(如GDM/KDM)。
  • 6:重启 - 重启系统。 绝对不要 将默认运行级别设为6。

确定运行级别(假设为3)后,init会执行 /etc/rc.d/rc 3 。这个 rc 脚本是 运行级别切换的实际执行者 。它的工作逻辑非常经典:

  1. 它进入对应的运行级别目录: /etc/rc.d/rc3.d/
  2. 扫描该目录下所有以 S (Start)或 K (Kill)开头的符号链接文件。
  3. 首先, 按数字顺序执行所有以K开头的脚本 ,并传入 stop 参数。这是为了 停止 当前运行级别不需要的、但从上一个运行级别遗留下来的服务。例如,从图形界面(级别5)切换到文本模式(级别3),就需要停止图形服务。
  4. 然后, 按数字顺序执行所有以S开头的脚本 ,并传入 start 参数。这就是 启动 该运行级别需要的服务。

数字(xx)的意义 S10network S55sshd S90crond 中的数字 10 55 90 决定了启动顺序。数字越小,执行越早。这是解决服务间依赖关系的核心机制。例如,网络服务( S10network )必须早于SSH服务( S55sshd )启动,因为SSH依赖网络栈。

2.4 本地定制与用户登录:最后的收尾

/etc/rc.d/rc 脚本按照剧本(运行级别目录下的链接)指挥完所有“演员”(服务)就位后,系统的主体服务环境就准备好了。接下来,是最后一个官方提供的定制入口: /etc/rc.d/rc.local

rc.local 的定位与最佳实践 : 这是一个在所有指定运行级别的服务脚本都执行完毕后, 在用户登录之前 执行的Bash脚本。它最初的设计目的是让管理员可以方便地添加一些简单的启动命令,而无需编写完整的init脚本。

  • 典型用途 :设置简单的环境变量、启动那些没有提供init脚本的第三方二进制程序、在启动时输出一些自定义信息、挂载额外的网络存储等。
  • 重要限制 :由于它执行时,系统服务已经启动,所以 不适合 用于启动具有复杂依赖关系或需要严格顺序控制的核心服务。这类服务应该被做成标准的init脚本,放到 /etc/rc.d/init.d/ 并链接到相应的 rcN.d 目录。

最后,init进程会根据 /etc/inittab 中的配置,在指定的虚拟终端(tty1-tty6)上启动 /sbin/mingetty agetty 程序,它们会显示 login: 提示符,等待用户登录。至此,整个系统启动流程圆满完成。

3. 核心工具实战:chkconfig与手动配置

理解了流程,我们来看看如何实际管理这些启动项。主要有两种方式:使用 chkconfig 工具进行标准化管理,以及手动创建符号链接。前者更规范,后者更直接,两者都需要掌握。

3.1 使用chkconfig进行服务管理

chkconfig 是RedHat系列中管理System V init服务的核心命令行工具。它通过维护 /etc/rc.d/rcN.d/ 目录下的符号链接,来统一管理服务的启动和关闭。

1. 查看服务状态

chkconfig --list

这条命令会列出所有被 chkconfig 管理的服务,以及它们在运行级别0-6下的状态(on或off)。

chkconfig --list sshd

可以查看特定服务(如sshd)的状态。

2. 添加/删除服务管理 要让 chkconfig 能管理一个自定义脚本,该脚本必须位于 /etc/rc.d/init.d/ (或 /etc/init.d/ ,两者通常是链接关系)目录下,并且脚本的 第二行 必须包含特定的LSB头部注释。

# 假设你已经将自定义的myapp脚本放到了/etc/rc.d/init.d/下
chkconfig --add myapp  # 将myapp添加到chkconfig管理
chkconfig --del myapp  # 从chkconfig管理中移除myapp(不会删除脚本文件)

--add 操作会根据脚本中的LSB头信息,自动在相应的 rcN.d 目录创建 S K 链接。

3. 设置服务的启动级别 这是最常用的操作。

chkconfig sshd on       # 默认在级别2,3,4,5上设置为启动
chkconfig sshd off      # 默认在级别2,3,4,5上设置为关闭
chkconfig --level 35 sshd on  # 仅在级别3和5上设置为启动,其他级别关闭
chkconfig --level 345 nfs off # 在级别3,4,5上关闭nfs服务

LSB头部注释详解 一个标准的、能被 chkconfig 识别的init脚本,开头需要这样的注释:

#!/bin/bash
#
# myapp   This shell script starts and stops the myapp daemon
#
# chkconfig:   2345 90 10
# description: MyApp is a fantastic service that does amazing things. \
#              It is used to demonstrate LSB headers.
  • # chkconfig: 2345 90 10
    • 2345 :表示在运行级别2、3、4、5下默认 启用 该服务。
    • 90 启动优先级(Start order number) 。当进入这些运行级别时,会创建名为 S90myapp 的链接。数字越小,启动越早。
    • 10 停止优先级(Kill order number) 。当离开这些运行级别(如切换到级别1)时,会创建名为 K10myapp 的链接。数字越小,停止越早。
  • # description :对服务的描述,支持换行(用反斜杠 \ )。 chkconfig --list 时会显示此描述。

3.2 手动配置启动脚本:以Apache为例

虽然 chkconfig 是推荐方式,但手动操作能让你更深刻地理解其背后的机制。我们以在运行级别3下自动启动一个手动编译安装的Apache(假设安装路径为 /server/apache )为例。

步骤一:创建Init脚本 首先,在 /etc/rc.d/init.d/ 目录下创建服务控制脚本。

touch /etc/rc.d/init.d/apache
chown root:root /etc/rc.d/init.d/apache
chmod 755 /etc/rc.d/init.d/apache  # 755权限,root可写,其他用户可读可执行

然后编辑 /etc/rc.d/init.d/apache 文件,内容至少包括:

#!/bin/bash
#
# apache    Startup script for the Apache HTTP Server
#
# chkconfig: 3 85 15
# description: Apache is a world wide web server.
# processname: httpd
# config: /server/apache/conf/httpd.conf

# Source function library. (可选,但引入可以方便使用系统函数)
. /etc/rc.d/init.d/functions

# 定义apachectl路径
HTTPD=/server/apache/bin/apachectl

start() {
    echo -n "Starting Apache: "
    $HTTPD start
    # 通常更规范的写法是:daemon $HTTPD start
    RETVAL=$?
    echo
    return $RETVAL
}

stop() {
    echo -n "Stopping Apache: "
    $HTTPD stop
    RETVAL=$?
    echo
    return $RETVAL
}

# 根据传入的参数调用函数
case "$1" in
    start)
        start
        ;;
    stop)
        stop
        ;;
    restart)
        stop
        sleep 2
        start
        ;;
    *)
        echo "Usage: $0 {start|stop|restart}"
        exit 1
esac
exit $RETVAL

这个脚本比简单的一行命令更健壮,它提供了 start stop restart 参数,并利用了系统函数库( /etc/rc.d/init.d/functions ),使输出更规范。

步骤二:创建符号链接 我们需要在目标运行级别目录(这里是 rc3.d )下创建启动链接。

ln -s /etc/rc.d/init.d/apache /etc/rc.d/rc3.d/S85apache
  • S85 S 表示启动, 85 是启动顺序号。我们根据LSB头里写的 85 来设置。你需要确保这个数字能满足依赖关系(例如,如果Apache依赖MySQL,而MySQL的启动号是 S80mysql ,那么Apache的 85 就是合理的)。

步骤三:验证与测试

  1. 现在你可以直接运行 /etc/rc.d/init.d/apache start 来测试脚本是否能正常工作。
  2. 重启系统,进入运行级别3,检查Apache是否自动启动: ps aux | grep httpd
  3. 你也可以使用 service apache start 命令( service 命令会去 /etc/init.d/ 目录查找对应脚本)来管理它。

实操心得 :手动创建链接虽然直观,但在管理多运行级别时容易出错。例如,如果你只在 rc3.d 下创建了 S85apache ,那么在级别5下Apache就不会自动启动。而使用 chkconfig --add ,它会根据脚本中的 # chkconfig: 行自动为所有指定级别(如 2345 )创建链接,管理起来更全面、更不容易遗漏。

4. 高级技巧与深度避坑指南

掌握了基础流程和配置方法后,我们来看看在实际生产环境和嵌入式定制中会遇到哪些棘手问题,以及如何系统地解决它们。

4.1 服务依赖与启动顺序的精细控制

服务启动顺序是配置自启动时最常见的“坑”。例如,你的Web应用( S90myapp )依赖数据库( S80mysqld )和消息队列( S85rabbitmq )。如果顺序不对,应用启动时连接失败,可能导致启动报错或功能异常。

解决方案:

  1. 利用LSB头中的优先级数字 :这是最主要的手段。仔细规划每个服务的启动(S)和停止(K)序号。依赖的服务序号要小(先启动),被依赖的服务序号要大(后启动)。例如: S80mysqld , S85rabbitmq , S90myapp
  2. 在Init脚本中增加依赖检查 :对于关键依赖,可以在 start() 函数开始时加入检查逻辑。
    start() {
        # 检查MySQL端口是否就绪
        if ! nc -z localhost 3306 &>/dev/null; then
            echo "MySQL is not ready. Waiting..."
            sleep 5
            # 可以加入重试逻辑
        fi
        # ... 后续启动逻辑
    }
    
  3. 使用 /etc/rc.d/init.d/functions 中的 daemon 函数 :这个函数已经包含了一些健壮性处理。更高级的用法是查看其他标准服务(如 network iptables )的脚本,学习它们如何处理复杂依赖。

4.2 调试启动故障:当服务没有按预期启动时

服务器重启后,发现关键服务没起来,如何快速定位?

排查步骤:

  1. 检查运行级别 runlevel 命令可以查看之前的运行级别(N表示无)和当前运行级别。确认系统确实进入了你配置的那个级别(比如3)。
  2. 查看启动日志 /var/log/boot.log 文件记录了本次启动过程中 rc.sysinit rc 脚本执行的详细输出,是查找启动错误的第一现场。
  3. 检查服务脚本的权限和执行能力
    ls -l /etc/rc.d/init.d/myapp  # 确认有执行权限(x)
    sh -n /etc/rc.d/init.d/myapp   # 检查脚本语法错误
    /etc/rc.d/init.d/myapp start   # 手动执行,看具体报错
    
  4. 检查符号链接 :确认在正确的 rcN.d 目录下存在正确的 Sxx 链接,并且没有同名的 Kxx 链接(在非目标级别下, Kxx 链接是正常的)。
    ls -l /etc/rc.d/rc3.d/ | grep myapp
    
  5. 查看系统日志 /var/log/messages journalctl -b (如果系统同时使用了systemd的journal)可能包含服务进程启动失败的具体原因,比如配置文件错误、端口冲突、权限不足等。

4.3 传统init与systemd的共存与迁移

现代较新版本的RedHat/CentOS(7及以上)默认使用 systemd ,但它为了兼容性,通常仍会提供 /etc/rc.d/rc.local 的支持(需要 chmod +x ),并且 chkconfig 命令在背后可能是在操作systemd的单元模拟。了解这种共存状态很重要。

如何判断你的系统使用哪种?

# 查看PID 1的进程
ps -p 1 -o comm=
# 如果输出是 `systemd`,则你的系统主要受systemd管理。
# 如果输出是 `init`,则是传统的SysV init。

在混合环境下的建议:

  1. 优先使用systemd :如果你的系统是RHEL/CentOS 7+,对于新配置的服务,强烈建议学习并创建 systemd 的service unit文件( .service ),其功能更强大,依赖管理更精确。
  2. 兼容模式 systemd 会读取 /etc/rc.d/rc.local (如果它可执行),并在启动后期运行它。这为传统的启动命令提供了迁移窗口。
  3. chkconfig 的转变 :在systemd系统上, chkconfig 命令可能是一个兼容层脚本,它实际调用的是 systemctl 命令。例如, chkconfig sshd on 可能等价于 systemctl enable sshd.service

一个常见的坑 :在已切换到systemd的系统上,你手动往 /etc/rc.d/rc3.d/ 添加了符号链接,但发现服务并没有自启动。这是因为systemd可能根本不读取这些目录。此时,你需要使用 systemctl enable 来启用服务。

4.4 嵌入式场景下的特殊考量

在资源受限的嵌入式Linux环境中,System V init因其简单、可控而依然常见。在这里,优化启动速度是关键。

优化技巧:

  1. 精简 rc.sysinit :分析 /etc/rc.d/rc.sysinit 脚本,注释掉嵌入式设备不需要的步骤,比如不存在的硬件检查、不必要的模块加载。
  2. 并行启动 :标准的init是串行执行 rcN.d/ 下的脚本。可以通过修改 /etc/rc.d/rc 脚本,或者使用 start-parallel 之类的机制,让没有依赖关系的服务并行启动,缩短启动时间。 但必须谨慎评估服务依赖!
  3. 使用BusyBox init :许多嵌入式系统使用BusyBox提供的简化版init。它的配置更简单,通常只有一个 /etc/inittab 文件,启动脚本直接在其中指定,或者通过 /etc/init.d/ 目录下的脚本按字母顺序执行。需要查阅BusyBox的文档。
  4. 静态编译与最小依赖 :为嵌入式环境编译服务程序时,尽量静态链接,减少动态库依赖,避免因库文件找不到导致启动失败。同时,确保 /etc/rc.d/init.d/ 下的脚本不依赖 bash 的高级特性,最好用 /bin/sh (通常是 dash bash 的POSIX模式)来保证兼容性。

5. 常见问题排查与解决方案速查表

下面我将一些典型问题、现象及排查思路整理成表格,方便你快速对照解决。

问题现象 可能原因 排查命令与解决步骤
服务在特定运行级别不启动 1. 服务未在该运行级别启用。
2. 该级别存在 Kxx (停止)链接,且优先级高于 Sxx 链接(极罕见)。
3. initdefault 设置错误。
1. chkconfig --list servicename 检查状态。
2. ls -l /etc/rc.d/rc[级别].d/ | grep servicename 查看链接。
3. cat /etc/inittab | grep initdefault 确认默认级别。
服务启动顺序不符合预期 S 后面的数字设置不合理,未能体现依赖关系。 1. 检查相关服务的LSB头或链接名中的数字。
2. 使用 chkconfig --level 345 servicename --set 85 (假设)调整优先级,或手动修改链接文件名。
手动执行脚本成功,但开机不启动 1. 脚本没有执行权限( x )。
2. 脚本本身有语法错误或逻辑错误(如绝对路径错误)。
3. 脚本依赖的环境变量在启动时不存在。
1. chmod +x /etc/rc.d/init.d/myscript
2. bash -n /etc/rc.d/init.d/myscript 检查语法。手动用 /etc/rc.d/init.d/myscript start 测试,观察输出。
3. 在脚本中通过 source /etc/profile 或直接设置全路径来明确环境。
系统启动卡在某个服务处 该服务的启动脚本陷入死循环、等待超时或需要前台交互。 1. 重启进入单用户模式(在GRUB菜单内核行尾加 single 1 )。
2. 检查卡住服务脚本的 start() 函数逻辑。
3. 检查是否有服务在等待网络、磁盘挂载等未就绪的资源。考虑在脚本中加入超时或条件判断。
/etc/rc.d/rc.local 中的命令未执行 1. rc.local 文件没有执行权限。
2. 在RHEL7+ systemd系统上, rc-local 服务未启用。
3. 命令本身执行报错,但输出被丢弃。
1. chmod +x /etc/rc.d/rc.local
2. systemctl status rc-local 查看状态, systemctl enable rc-local 启用。
3. 在 rc.local 的命令中重定向输出到日志文件,例如: /my/command >> /var/log/rc.local.log 2>&1
使用 chkconfig --add 失败 1. 脚本不在 /etc/rc.d/init.d/ 目录下。
2. 脚本中缺少或格式错误的LSB头部注释( # chkconfig: )。
3. 脚本已存在同名的管理项。
1. 将脚本移动到正确目录。
2. 严格按照格式添加注释行。
3. 先使用 chkconfig --del 删除旧项,再添加。

掌握RedHat系列Linux的传统启动机制,是深入理解Linux系统运维和嵌入式定制的基石。即使在systemd成为主流的今天,这些知识在维护老系统、进行深度定制或解决一些棘手的兼容性问题时,依然不可或缺。核心在于理解那个“从内核到登录”的清晰链条,以及 chkconfig 和符号链接如何在这个链条上编排服务。当你下次再面对服务启动问题时,希望你能像翻阅地图一样,沿着这条启动链路,冷静地找到问题所在。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐