rc-update 命令详解与参数实测(Alpine Linux)

在 Alpine Linux 3.20(OpenRC 0.54)上对 rc-update 的 add/del/show 子命令与 -a/-s/-u/-q/-C 等选项逐项实测,讲清 boot/default/sysinit/shutdown 四层 runlevel 体系与开机自启的软链原理。

Linux命令Linux系统命令行进程管理2026/10/17

rc-update 命令详解与参数实测(Alpine Linux)

目标:讲清 OpenRC 开机自启管理器 rc-update 的全部子命令与选项,从软链机制到 boot/default/sysinit/shutdown 四层 runlevel 体系,再到栈式 runlevel、依赖树刷新等进阶用法,全部命令与回显已在 Alpine Linux 3.20.10(OpenRC 0.54)实测通过。

一、环境信息

项目值
操作系统Alpine Linux 3.20.10(PRETTY_NAME="Alpine Linux v3.20")
内核6.8.0-138-generic
架构x86_64
OpenRC0.54-r1(openrc --version → openrc (OpenRC) 0.54)
busybox1.36.1-r31(提供 busybox-openrc 内置服务的 init 脚本)
测试服务nginx 1.26.3-r0、busybox crond/syslog
测试日期2026-10-17

Alpine Linux 以 OpenRC 为默认 init 体系。OpenRC 管理服务分两个独立维度:当前状态由 rc-service 负责(start/stop/status,见姊妹篇《rc-service 命令详解与参数实测(Alpine Linux)》),开机自启则由本文主角 rc-update 负责。

二、命令概览

2.1 开机自启原理:runlevel 与软链

OpenRC 的「开机自启」没有任何魔法,本质就是把 /etc/init.d/ 下的服务脚本软链到 /etc/runlevels/<runlevel>/ 目录。OpenRC 引导时按 sysinit → boot → default 的顺序遍历这些目录,把其中的服务做依赖排序后依次拉起;关机时反向执行 shutdown 层。rc-update 的全部工作,就是替你增删查这些软链。

先看目录骨架(/sbin/rc-update 是命令本体):

$ which rc-service rc-update openrc
/sbin/rc-service
/sbin/rc-update
/sbin/openrc
$ ls -l /etc/runlevels/
total 20
drwxr-xr-x    2 root     root          4096 Oct 17 00:32 boot
drwxr-xr-x    2 root     root          4096 Oct 17 00:32 default
drwxr-xr-x    2 root     root          4096 Oct 17 00:32 nonetwork
drwxr-xr-x    2 root     root          4096 Oct 17 00:32 shutdown
drwxr-xr-x    2 root     root          4096 Oct 17 00:32 sysinit

五个 runlevel 的职责分工:

runlevel引导阶段典型成员说明
sysinit内核刚接管系统devfs、sysfs、cgroups、mdev/udev挂载内核伪文件系统、设备管理,最先执行
boot系统本地初始化root、fsck、localmount、hostname、hwclock、sysctl、modules在网络之前完成本地环境准备
default常规运行态networking、sshd、crond、nginx 及各应用服务「开机自启」通常指的目标层,多用户服务都加这里
shutdown关机流程killprocs、mount-ro、savecache系统关停时反向执行
nonetwork单机维护无预置成员default 的无网变体,供救援/单机场景叠加

💡 技术拓展:Linux 的四种主流 init 体系对比。init 是 PID 1,决定「服务怎么定义、怎么启动、怎么管自启」:

init 体系服务定义启动模型自启管理命令
sysvinit/etc/init.d/ LSB 脚本编号串行,无依赖图chkconfig / update-rc.d
OpenRC(本文)/etc/init.d/ 脚本 + depend() 依赖声明依赖排序后并行,runlevel 分层rc-update(Alpine、Gentoo、Devuan)
systemdunit 文件 + cgroup 跟踪socket/D-Bus 激活,依赖图systemctl enable/disable
runit/service/ 监督脚本目录即服务,进程监督重启软链 /var/service(Void Linux)

与 systemd 把自启记录写进 unit 文件不同,OpenRC 的 runlevel 就是纯目录+软链,可以直接 ls -l /etc/runlevels/default/ 看真相——这也是它简单、可审计、易救援的原因。

2.2 语法与 --help 全量回显

rc-update 只有三个子命令(add/del/show),配四个全局选项:

$ rc-update --help
Usage: rc-update [options] add <service> [<runlevel>...]
   or: rc-update [options] del <service> [<runlevel>...]
   or: rc-update [options] [show [<runlevel>...]]

Options: [ asuChqVv ]
  -a, --all                         Process all runlevels
  -s, --stack                       Stack a runlevel instead of a service
  -u, --update                      Force an update of the dependency tree
  -h, --help                        Display this help output
  -C, --nocolor                     Disable color output
  -V, --version                     Display software version
  -v, --verbose                     Run verbosely
  -q, --quiet                       Run quietly (repeat to suppress errors)

2.3 测试数据准备

与姊妹篇同一套环境(安装命令与 init 脚本原文见《rc-service 命令详解与参数实测(Alpine Linux)》2.3 节):

$ apk add openrc busybox-openrc nginx

三个测试服务的角色:crond(add/del 主角)、nginx(裸 add 的 runlevel 回落演示)、syslog(boot 层演示)。初始状态 runlevel 全空,可以从零观测每条命令的效果。

⚠️ 前置要求:rc-update 的 -u(依赖树刷新)依赖 OpenRC 引导建立的 /run/openrc/ 运行态目录。本文实测在 OpenRC 为 init 的真实 Alpine 服务器语境下进行;救援盘/chroot 等未引导环境的初始化方法见姊妹篇 5.1 节。

三、参数逐项实测

3.1 rc-update show:查看自启注册表(不带参数等价于 show)

$ rc-update
$ echo $?
0
$ rc-update show
$ echo $?
0

全新系统的 runlevel 全空,两条命令都无输出、退出码 0。加了服务后再看输出格式(见 3.3 节)。

3.2 show -v:把未注册的服务也列出来

$ rc-update show -v
                acpid |                                        
               agetty |                                        
               binfmt |                                        
             bootmisc |                                        
              cgroups |                                        
          consolefont |                                        
                crond |                                        
                devfs |                                        
                dmesg |                                        
            firstboot |                                        
                 fsck |                                        
             hostname |                                        
              hwclock |                                        
            hwdrivers |                                        
            killprocs |                                        
                klogd |                                        
             loadkmap |                                        
                local |                                        
           localmount |                                        
             loopback |                                        
           machine-id |                                        
              modloop |                                        
              modules |                                        
             mount-ro |                                        
                 mtab |                                        
           net-online |                                        
             netmount |                                        
           networking |                                        
                nginx |                                        
                 ntpd |                                        
              numlock |                                        
              osclock |                                        
               procfs |                                        
                rdate |                                        
               rfkill |                                        
                 root |                                        
             runsvdir |                                        
            s6-svscan |                                        
         save-keymaps |                                        
    save-termencoding |                                        
            savecache |                                        
              seedrng |                                        
          staticroute |                                        
                 swap |                                        
              swclock |                                        
               sysctl |                                        
                sysfs |                                        
            sysfsconf |                                        
               syslog |                                        
         termencoding |                                        
             watchdog |                                        

不带 -v 时只列出已注册服务;-v 把 /etc/init.d/ 里全部 51 个服务都铺出来,右列留出 runlevel 标注位——这是盘点「装了哪些服务、哪些还没配自启」的常用手段(服务清单与《rc-service》3.11 节的 -l 同源)。

3.3 add <service> <runlevel>:注册开机自启

$ rc-update add crond default
 * service crond added to runlevel default
$ echo $?
0
$ ls -l /etc/runlevels/default/
total 0
lrwxrwxrwx    1 root     root            17 Oct 17 00:32 crond -> /etc/init.d/crond

输出确认 + 软链实锤:/etc/runlevels/default/crond -> /etc/init.d/crond,这就是「开机自启」的全部实现。重复添加是幂等的:

$ rc-update add crond default
 * rc-update: crond already installed in runlevel `default'; skipping
$ echo $?
0

3.4 add 省略 runlevel:回落到「当前运行级」,无运行级时落 sysinit

任务书之外的重要发现——add 的 runlevel 参数可以省略:

$ rc-update add nginx
 * service nginx added to runlevel sysinit
$ echo $?
0
$ ls -l /etc/runlevels/sysinit/ /etc/runlevels/default/
/etc/runlevels/default/:
total 0
lrwxrwxrwx    1 root     root            17 Oct 17 00:32 crond -> /etc/init.d/crond

/etc/runlevels/sysinit/:
total 0
lrwxrwxrwx    1 root     root            17 Oct 17 00:32 nginx -> /etc/init.d/nginx

nginx 被加进了 sysinit!规则是:省略 runlevel 时取「当前运行级」(/run/openrc/softlevel 记录的值),正常引导的 default 运行级服务器上等价于 add nginx default;本环境无当前运行级记录,回落到兜底值 sysinit。交互敲 rc-update add nginx 少打一个 default 是常见手滑,用 show 能立即发现。清理:

$ rc-update del nginx sysinit
 * service nginx deleted from runlevel sysinit
$ echo $?
0

⚠️ 反直觉提醒:sysinit 层加应用服务没有意义(该层在伪文件系统就绪后即执行完退出),虽然 rc-update 不做拦截,但这个服务永远不会在 default 阶段被拉起。

3.5 show [<runlevel>...]:按列解读注册表

添加后先看全貌(列为全部 runlevel,| 后按列标注归属):

$ rc-update show
                crond |      default                           
                nginx |                                 sysinit
$ echo $?
0

只看某一层、或多层过滤:

$ rc-update show default
                crond |      default                           
$ echo $?
0
$ rc-update show sysinit boot
                nginx |                                 sysinit
$ echo $?
0

加入 boot 层的 syslog 后三列对照更直观(先注册 syslog):

$ rc-update add syslog boot
 * service syslog added to runlevel boot
$ echo $?
0
$ ls -l /etc/runlevels/boot/
total 0
lrwxrwxrwx    1 root     root            18 Oct 17 00:32 syslog -> /etc/init.d/syslog
$ rc-update show
                crond |      default                           
                nginx |                                 sysinit
               syslog | boot                                   

同一服务可以同时注册多层(下一层未完成前不会被重复执行,OpenRC 引擎按依赖图去重):

$ rc-update add crond boot
 * service crond added to runlevel boot
$ echo $?
0
$ rc-update show
                crond | boot default                           

3.6 del <service> [<runlevel>...]:注销自启

$ rc-update del syslog boot
 * service syslog deleted from runlevel boot
$ echo $?
0
$ rc-update del crond default boot
 * service crond deleted from runlevel default
 * service crond deleted from runlevel boot
$ echo $?
0

del 支持一次指定多个 runlevel,逐一删除并逐行报告。删不存在的注册会报错:

$ rc-update del nosuch default
 * rc-update: service `nosuch' is not in the runlevel `default'
$ echo $?
1

3.7 -a/--all:对全部 runlevel 批量操作

-a 让 add/del 作用于所有 runlevel。del 场景最实用——不记得服务注册在哪层时一键摘除(先重建双 runlevel 场景):

$ rc-update add crond default
 * service crond added to runlevel default
$ rc-update add crond boot
 * service crond added to runlevel boot
$ rc-update -a del crond
 * rc-update: service `crond' is not in the runlevel `nonetwork'
 * rc-update: service `crond' is not in the runlevel `shutdown'
 * service crond deleted from runlevel default
 * rc-update: service `crond' is not in the runlevel `sysinit'
 * service crond deleted from runlevel boot
$ echo $?
1

-a del 遍历全部五个 runlevel:不含目标服务的层逐条告警(nonetwork/shutdown/sysinit),含的层执行删除(default/boot);只要有一条告警,整体退出码为 1。注意遍历顺序是目录序(实测两次输出顺序不同),脚本里不要依赖行序。验证删净:

$ rc-update show
$ echo $?
0

-a 与 add 组合会被拒绝(语义不明,官方不允许):

$ rc-update -a add crond default
 * rc-update: the -a option is invalid with add
$ echo $?
1

3.8 -s/--stack:栈式 runlevel(把一个 runlevel 叠进另一个)

-s 切换 add/del 的操作对象:不再是「服务 → runlevel」,而是「runlevel → runlevel」,把一个 runlevel 整体叠加为另一个的成员:

$ rc-update -s add nonetwork default
 * runlevel nonetwork added to runlevel default
$ echo $?
0
$ ls -la /etc/runlevels/default/
total 8
drwxr-xr-x    2 root     root          4096 Oct 17 00:32 .
drwxr-xr-x    7 root     root          4096 Oct 17 00:32 ..
lrwxrwxrwx    1 root     root            12 Oct 17 00:32 nonetwork -> ../nonetwork

栈的存储也是软链:default/nonetwork -> ../nonetwork。引导 default 层时,OpenRC 会递归展开 nonetwork 里的全部成员。移除:

$ rc-update -s del nonetwork default
 * runlevel nonetwork deleted from runlevel default
$ echo $?
0
$ ls -la /etc/runlevels/default/
total 8
drwxr-xr-x    2 root     root          4096 Oct 17 00:32 .
drwxr-xr-x    7 root     root          4096 Oct 17 00:32 ..

两条防护性限制(实测回显):

$ rc-update -s add default default
 * rc-update: cannot stack `default' onto itself
$ echo $?
1
$ rc-update -s add nonetwork boot
 * rc-update: cannot stack the sysinit runlevel
$ echo $?
1

自身叠自身直接拒绝;往 boot 层叠栈被拦(提示文案笼统地写作 "sysinit",实际 sysinit 与 boot 都不允许叠栈——引导早期层必须保持扁平可控)。

3.9 -u/--update:强制刷新依赖树,且会短路后续子命令

$ rc-update -u
Service `acpid' needs non existent service `dev'
Service `hwdrivers' needs non existent service `dev'
Service `machine-id' needs non existent service `dev'
Service `watchdog' needs non existent service `dev'
 * Caching service dependencies ... [ ok ]
$ echo $?
0

-u 立即重算服务依赖图并写入 /run/openrc/deptree 缓存。那 4 行 "needs non existent service dev'" 是最小环境特有的提示:acpid/hwdrivers 等脚本 need dev,而本环境未装 mdev/udev 设备管理服务;真实服务器装齐设备服务后不会有这些行。依赖缓存刷新后,rc-status --servicelist、服务启动排序等才反映最新注册状态——**add/del 之后建议跟一条 rc-update -u`**。

一个实测陷阱:-u 处理完直接返回,同一命令行里跟在后面的子命令不会执行(官方设计为 update 即动作):

$ rc-update -u show
Service `acpid' needs non existent service `dev'
Service `hwdrivers' needs non existent service `dev'
Service `machine-id' needs non existent service `dev'
Service `watchdog' needs non existent service `dev'
 * Caching service dependencies ... [ ok ]
$ echo $?
0

想「刷新后再看列表」要分两条命令执行。

3.10 show 与悬空软链:注册表只认服务脚本

手工制造一个指向不存在脚本的软链(模拟服务被 apk delete 但自启残留的典型事故):

$ ln -s /etc/init.d/nosuchsvc /etc/runlevels/default/nosuchsvc
$ ls -l /etc/runlevels/default/
total 0
lrwxrwxrwx    1 root     root            21 Oct 17 00:32 nosuchsvc -> /etc/init.d/nosuchsvc
$ rc-update show
$ echo $?
0

show 不显示悬空软链(无法解析成服务就不进列表),但它在引导时会被引擎警告。用 del 清理即可:

$ rc-update del nosuchsvc default
 * service nosuchsvc deleted from runlevel default
$ echo $?
0
$ ls -l /etc/runlevels/default/
total 0

💡 Gentoo 老版本 rc-update 曾有 fix 子命令专扫悬空链,OpenRC 0.54 已移除(实测见 3.12 节),现在清理残留就是 rc-update del <svc> <runlevel> 一条条做,或直接 rm 软链。

3.11 -q/--quiet:静默执行

$ rc-update -q add syslog default
$ echo $?
0
$ rc-update show
               syslog |      default                           

成功的 add 被完全静默,只留退出码 0;注册表确认 syslog 已进入 default。适合批量脚本。未加 -q 的对照(同一服务的普通删除):

$ rc-update del syslog default
 * service syslog deleted from runlevel default
$ echo $?
0

3.12 -C/--nocolor、无效子命令与 -h/-V

rc-update 的输出( * service ... added ...)在终端下同样着色,-C 强制纯文本(重定向/管道时本就无色,效果一致):

$ rc-update -C add syslog boot
 * service syslog added to runlevel boot
$ echo $?
0
$ rc-update del syslog boot
 * service syslog deleted from runlevel boot
$ echo $?
0

0.54 只认 add/del/show 三个子命令,网上老文档提到的其他写法都会被拒(均为实测):

$ rc-update show-default
 * rc-update: invalid command `show-default'
$ echo $?
1
$ rc-update show-sysinit
 * rc-update: invalid command `show-sysinit'
$ echo $?
1
$ rc-update fix
 * rc-update: invalid command `fix'
$ echo $?
1
$ rc-update --version
rc-update (OpenRC) 0.54
$ rc-update -V
rc-update (OpenRC) 0.54

3.13 -v/--verbose 与 show 的列宽

-v 配合 show 即 3.2 节的全量清单;配 add/del 时输出与默认一致(add/del 本就单行报告)。列宽规则:服务名列左对齐、宽度随最长服务名自动扩展(save-termencoding 把列撑到 18 字符),runlevel 标注从 | 右侧按固定列位书写,多层注册按 sysinit → boot → default → shutdown 顺序排列。

四、实战示例

4.1 配置一组服务的开机自启

装完 nginx 与 crond 后的最小自启配置(注意:add 不等于 start,本节命令执行完服务并不会立即运行,要立即生效需另行 rc-service start):

$ rc-update add networking default
 * service networking added to runlevel default
$ rc-update add nginx default
 * service nginx added to runlevel default
$ rc-update add crond default
 * service crond added to runlevel default
$ rc-update -u
Service `acpid' needs non existent service `dev'
Service `hwdrivers' needs non existent service `dev'
Service `machine-id' needs non existent service `dev'
Service `watchdog' needs non existent service `dev'
 * Caching service dependencies ... [ ok ]
$ rc-update show
                crond |      default                           
           networking |      default                           
                nginx |      default                           

(-u 输出里 4 条 dev 提示的含义见 3.9 节:本环境未装设备管理服务所致,真实服务器装齐后没有这些行。)

为什么 networking 也要加?nginx 的 init 脚本 need net,而 net 由 networking 提供——开机时如果 networking 不在 default 层,nginx 会因依赖无法满足而起不来(这条教训来自实测:networking 启动失败时 nginx 的 start 直接报 "cannot start nginx as networking would not start")。

4.2 更换自启实现:del + add 组合

把 crond 从「default 层自启」调整为「boot 层尽早自启」(示例演示操作链,实际取舍见 5.2 节;另两行是 4.1 注册的 networking/nginx,正好对照):

$ rc-update del crond default
 * service crond deleted from runlevel default
$ rc-update add crond boot
 * service crond added to runlevel boot
$ rc-update show
                crond | boot                                   
           networking |      default                           
                nginx |      default                           

4.3 批量审查自启面

接手陌生机器时的一组只读命令(沿用 4.2 完成后的注册表状态):

$ rc-update show
                crond | boot                                   
           networking |      default                           
                nginx |      default                           
$ rc-update show | wc -l
3
$ rc-update show -v | grep -c '|'
51

show 看全貌与分层归属,行数统计回答「配了自启的服务有几个」,show -v 的全量清单(51 个服务全在列)对比即可发现「装了却没配自启」的服务,再结合 rc-service -l 交叉核对。

五、注意事项与常见问题

5.1 add 不等于 start,del 不等于 stop

rc-update 只改「下次开机」的注册表(软链),对正在运行的服务没有任何即时影响:rc-update add crond default 执行完 crond 并不会跑起来(要用 rc-service crond start);rc-update del 也不会停掉在跑的服务(要用 rc-service crond stop)。两篇命令的分工:rc-update 管「要不要自启」,rc-service 管「现在跑不跑」。

5.2 服务该加 default 还是 boot?

经验法则:在根文件系统挂载完成前必须就绪的系统级服务进 boot(如早期日志、时钟同步基础),需要网络或依赖较复杂的常驻服务进 default。放错层的典型症状:服务在 boot 层因依赖(如 net)未就绪而启动失败。多数发行版包自带的服务脚本已由打包者把 boot 层成员(bootmisc、syslog 等基础件)预设好,应用服务一律 default 即可。

5.3 省略 runlevel 的裸 add 会落到哪?

见 3.4 节实测:省略 runlevel 时取当前运行级;引导完成的服务器上是 default(softlevel 记录为 default),救援环境/chroot 中可能回落 sysinit。生产操作永远显式写 runlevel,并在执行后用 rc-update show 复核。

5.4 注册表与软链可以互相直接操作吗?

可以。rc-update 本质就是软链管理器,ln -s /etc/init.d/crond /etc/runlevels/default/crond 与 rc-update add crond default 结果等价(3.3 节的软链实拍就是证据)。但手工操作有三个坑:悬空链不会被告警(3.10 节)、重复添加不会去重提示、依赖树不自动刷新。常规运维走 rc-update,救援时才手工 ln/rm。

5.5 add 之后为什么还要 rc-update -u?

add/del 只动软链;OpenRC 引导与服务启动时读取的依赖图缓存 /run/openrc/deptree 要靠 -u 重算。不加的话,新注册服务的依赖排序可能沿用旧缓存(正常引导流程会在 runlevel 切换时自动刷新,手工操作后手动刷一次最稳妥)。注意 -u 会短路同一命令行里的后续子命令(3.9 节实测)。

5.6 runlevel 里那些奇怪的层(nonetwork/shutdown)怎么用?

nonetwork 是 default 的无网变体:rc-update -s add nonetwork default 把它叠进 default 后,其成员会随 default 一起启动,专门放置「单机维护模式才需要」的服务。shutdown 层的成员(killprocs、mount-ro、savecache)由 OpenRC 在关机时按依赖反向执行,一般不动它。sysinit/boot 两层不允许叠栈(3.8 节实测报错)。

5.7 老教程里的 show-<runlevel>、fix 子命令为什么不存在?

Gentoo 时代 rc-update 曾支持 show-default、fix 等扩展,OpenRC 0.5x 已精简为 add/del/show 三子命令(3.12 节实测 invalid command 回显)。查特定层的注册直接用位置参数:rc-update show default、rc-update show boot。

六、参考链接