update-rc.d 命令详解与参数实测(Ubuntu 22.04)

基于 Ubuntu 22.04(init-system-helpers 1.62)逐参数实测 update-rc.d:无参与 --help 用法、安装后 S/K 链接命名机制、defaults 幂等重建、旧式序号参数被丢弃的真相、disable/enable 的 S 与 K 翻转及单运行级操作、-f remove 与不带 -f 的行为差异、自制 init 脚本全流程、无 LSB 头静默不建链接、systemd 双轨与 systemctl 对照。

Linux命令Linux系统UbuntuUbuntu 22.04命令行进程管理2026/10/16

update-rc.d 命令详解与参数实测(Ubuntu 22.04)

目标:全面掌握 update-rc.d 的每一个常用参数——无参与 --help 的用法提示、安装后 /etc/rc*.d/ 默认链接的命名机制(S/K + 两位序号 + 运行级目录)、defaults 的幂等重建、旧式 defaults NN 序号参数被丢弃的真相、disable/enable 的 S↔K 链接翻转与单运行级操作、-f remove 与不带 -f 的行为差异、从零注册一个自制 init 脚本的全流程、无 LSB 头脚本静默不建链接的坑,以及与 systemctl 的双轨对照,全部命令与回显已在 Ubuntu 22.04(init-system-helpers 1.62)实测通过。

一、环境信息

本文所有命令与回显均在以下环境实测采集,读者在相同版本的 Ubuntu 上可直接复现:

项目实测值
操作系统Ubuntu 22.04.5 LTS
架构x86_64
update-rc.d 所属包init-system-helpers 1.62
演示服务cron 3.0pl1-137ubuntu3、atd 3.2.5-1ubuntu1
链接目录/etc/rc0.d/ ~ /etc/rc6.d/、/etc/rcS.d/
init 脚本目录/etc/init.d/
演示目录/root/demo/update-rc.d

环境确认命令与实测回显如下:

$ head -2 /etc/os-release
PRETTY_NAME="Ubuntu 22.04.5 LTS"
NAME="Ubuntu"

$ uname -m
x86_64

$ dpkg -S /usr/sbin/update-rc.d
init-system-helpers: /usr/sbin/update-rc.d

$ dpkg -l init-system-helpers | tail -1
ii  init-system-helpers 1.62         all          helper tools for all init systems

💡 说明:update-rc.d 与 service 同属 init-system-helpers 包,是 SysV 时代管理「开机自启」的官方工具:它操作的不是服务进程,而是 /etc/rc0.d/ ~ /etc/rc6.d/ 里的符号链接——系统引导到某个运行级时,按链接名顺序执行对应的 /etc/init.d/ 脚本。

二、命令概览

2.1 语法与测试数据

usage: update-rc.d [-f] <basename> remove
       update-rc.d [-f] <basename> defaults
       update-rc.d [-f] <basename> defaults-disabled
       update-rc.d <basename> disable|enable [S|2|3|4|5]
                -f: force

参数结构解读:

  • <basename>:init 脚本名,即 /etc/init.d/ 下的文件名(不带路径);
  • remove:删除该服务在全部运行级目录下的 S/K 链接(-f 见 3.7 节实测);
  • defaults:按 init 脚本 LSB 头的 Default-Start/Default-Stop 重建默认链接;
  • defaults-disabled:同 defaults,但只建 K(停止态)链接;
  • disable|enable [S|2|3|4|5]:把指定运行级(缺省为 LSB 头声明的全部运行级)的 S 链接翻转为 K(disable)或 K 翻转回 S(enable)。

测试对象沿用 cron(系统自带)与后文自制的 demo-svc;演示目录准备:

$ mkdir -p /root/demo/update-rc.d

2.2 用法提示全量输出(逐行照抄)

update-rc.d 没有 --help 选项,--help、--version 与不带参数的调用都落到同一段用法提示(--help 少第一行报错):

$ update-rc.d; echo rc=$?
update-rc.d: error: not enough arguments
usage: update-rc.d [-f] <basename> remove
       update-rc.d [-f] <basename> defaults
       update-rc.d [-f] <basename> defaults-disabled
       update-rc.d <basename> disable|enable [S|2|3|4|5]
                -f: force

The disable|enable API is not stable and might change in the future.
rc=1

$ update-rc.d --help; echo rc=$?
usage: update-rc.d [-f] <basename> remove
       update-rc.d [-f] <basename> defaults
       update-rc.d [-f] <basename> defaults-disabled
       update-rc.d <basename> disable|enable [S|2|3|4|5]
                -f: force

The disable|enable API is not stable and might change in the future.
rc=1

💡 要点:末行 "The disable|enable API is not stable" 是官方提醒——disable/enable 子命令是较新的接口,脚本里大量使用前请以本篇实测行为为准(在 1.62 上行为稳定,见 3.4/3.5 节)。

2.3 版本信息

update-rc.d 没有 --version(实测回显与用法提示相同、退出码 1),版本跟随所属包:

$ update-rc.d --version; echo rc=$?
usage: update-rc.d [-f] <basename> remove
       update-rc.d [-f] <basename> defaults
       update-rc.d [-f] <basename> defaults-disabled
       update-rc.d <basename> disable|enable [S|2|3|4|5]
                -f: force

The disable|enable API is not stable and might change in the future.
rc=1

写文档标注版本时用 dpkg -l init-system-helpers(见第一章,实测 1.62)。

三、参数逐项实测

3.1 安装后的默认自启链接:S01cron 命名机制

apt 安装 cron 后,update-rc.d 已自动建好链接。先看 cron 的全量链接:

$ ls -l /etc/rc*.d/*cron
lrwxrwxrwx 1 root root 14 Oct 15 23:22 /etc/rc2.d/S01cron -> ../init.d/cron
lrwxrwxrwx 1 root root 14 Oct 15 23:22 /etc/rc3.d/S01cron -> ../init.d/cron
lrwxrwxrwx 1 root root 14 Oct 15 23:22 /etc/rc4.d/S01cron -> ../init.d/cron
lrwxrwxrwx 1 root root 14 Oct 15 23:22 /etc/rc5.d/S01cron -> ../init.d/cron

链接名逐段解读(以 /etc/rc2.d/S01cron 为例):

片段含义
/etc/rc2.d/运行级 2 的链接目录(Debian 系惯例:2~5 多用户运行级,0 停机、1 单用户、6 重启)
SStart:进入该运行级时启动服务;与之相对的 K(Kill)表示离开时停止
01两位执行序号:同目录内按序号从小到大串行执行(本环境全部服务的序号都是 01,见 3.3 节)
croninit 脚本名,目标是 ../init.d/cron 相对链接

对比 atd 的链接(它比 cron 多了 K 链接):

$ ls -l /etc/rc*.d/*atd
lrwxrwxrwx 1 root root 13 Oct 15 23:22 /etc/rc0.d/K01atd -> ../init.d/atd
lrwxrwxrwx 1 root root 13 Oct 15 23:22 /etc/rc1.d/K01atd -> ../init.d/atd
lrwxrwxrwx 1 root root 13 Oct 15 23:22 /etc/rc2.d/S01atd -> ../init.d/atd
lrwxrwxrwx 1 root root 13 Oct 15 23:22 /etc/rc3.d/S01atd -> ../init.d/atd
lrwxrwxrwx 1 root root 13 Oct 15 23:22 /etc/rc4.d/S01atd -> ../init.d/atd
lrwxrwxrwx 1 root root 13 Oct 15 23:22 /etc/rc5.d/S01atd -> ../init.d/atd
lrwxrwxrwx 1 root root 13 Oct 15 23:22 /etc/rc6.d/K01atd -> ../init.d/atd

差异的根源在两个包 init 脚本的 LSB 头声明:

  • cron 的头写了 Default-Start: 2 3 4 5、Default-Stop:(空)→ 只生成 rc2~rc5 的 S 链接;
  • atd 的头写了 Default-Start: 2 3 4 5、Default-Stop: 0 1 6 → 额外生成 rc0/rc1/rc6 的 K 链接(进入停机/单用户/重启运行级时先停掉 atd)。

💡 要点:S/K 链接完全由 LSB 头(### BEGIN INIT INFO 块)的 Default-Start/Default-Stop 决定,update-rc.d 只负责按声明落链接。看一个服务「默认在哪些运行级自启」,读它的 LSB 头比翻链接目录更直接(LSB 头示例见本专栏《service 命令详解与参数实测(Ubuntu 22.04)》3.14 节)。

3.2 defaults:按 LSB 头幂等重建链接

defaults 是最常用的子命令。链接已存在时重复执行完全幂等(无输出、退出码 0):

$ update-rc.d cron defaults; echo rc=$?
rc=0

$ update-rc.d cron defaults; echo rc=$?
rc=0

误删链接后可用它一键恢复。先删(-f remove 见 3.7 节)再重建:

$ update-rc.d -f cron remove; echo rc=$?
rc=0

$ ls -l /etc/rc*.d/*cron
ls: cannot access '/etc/rc*.d/*cron': No such file or directory

$ update-rc.d cron defaults; echo rc=$?
rc=0

$ ls -l /etc/rc2.d/*cron /etc/rc0.d/*cron
ls: cannot access '/etc/rc0.d/*cron': No such file or directory
lrwxrwxrwx 1 root root 14 Oct 15 23:38 /etc/rc2.d/S01cron -> ../init.d/cron

💡 要点:defaults 的语义是「依 LSB 头恢复默认布局」——头里没写的运行级(如 cron 的 rc0)不会建链接,所以第二行 ls 报 rc0 无文件是预期行为而非异常。

3.3 defaults NN:旧式序号参数已被丢弃

老资料常写 update-rc.d cron defaults 80 20 期望生成 S80cron/K20cron。在 Ubuntu 22.04(1.62)上实测:参数被静默忽略,链接照旧是 S01:

$ update-rc.d -f cron remove; echo rc=$?
rc=0

$ update-rc.d cron defaults 80; echo rc=$?
rc=0

$ ls -l /etc/rc2.d/*cron /etc/rc0.d/*cron
ls: cannot access '/etc/rc0.d/*cron': No such file or directory
lrwxrwxrwx 1 root root 14 Oct 15 23:38 /etc/rc2.d/S01cron -> ../init.d/cron

$ update-rc.d -f cron remove; echo rc=$?
rc=0

$ update-rc.d cron defaults 90 10; echo rc=$?
rc=0

$ ls -l /etc/rc2.d/*cron /etc/rc0.d/*cron
ls: cannot access '/etc/rc0.d/*cron': No such file or directory
lrwxrwxrwx 1 root root 14 Oct 15 23:38 /etc/rc2.d/S01cron -> ../init.d/cron

这不是故障而是设计变更,/usr/sbin/update-rc.d 源码(Perl 脚本)可证:链接名在 make_sysv_links 函数里按「运行级目录 + 固定序号 01 + 脚本名」拼接(/etc/rc$lvl.d/ + $start + 01 + $scriptname,$start 只会取 S 或 K),运行级列表完全取自 LSB 头解析结果,命令行传入的序号参数在 defaults 分支中根本不参与计算——

# /usr/sbin/update-rc.d make_sysv_links(逻辑示意,与源码等价)
my $l = "/etc/rc$lvl.d/" . $start . "01" . $scriptname;
symlink("../init.d/$scriptname", $l);

旁证是 2.2 节的用法提示:update-rc.d [-f] <basename> defaults 后面根本没有 [SS KK] 参数位。本环境 /etc/rc2.d/ 下所有服务的序号也都是 01:

$ ls -l /etc/rc2.d/ | head -8
total 0
lrwxrwxrwx 1 root root 13 Oct 15 23:22 S01atd -> ../init.d/atd
lrwxrwxrwx 1 root root 14 Oct 15 23:38 S01cron -> ../init.d/cron
lrwxrwxrwx 1 root root 14 Oct 15 08:41 S01dbus -> ../init.d/dbus
lrwxrwxrwx 1 root root 15 Oct 15 05:21 S01rsync -> ../init.d/rsync
lrwxrwxrwx 1 root root 13 Oct 15 08:41 S01ssh -> ../init.d/ssh
lrwxrwxrwx 1 root root 17 Oct 15 13:56 S01sysstat -> ../init.d/sysstat

💡 要点:需要精细控制启动顺序时,现代机制是 systemctl 的依赖声明(After=/Requires=)或 LSB 头的 Required-Start 依赖门面($remote_fs、$syslog 等),而不是手写序号。写兼容老系统的脚本时,defaults 80 不会报错,但不要指望它生效。

3.4 disable:停用开机自启(S→K 翻转)

disable 把目标运行级的 S 链接翻转为 K 链接(实测 2.2 节中 links 为 S01cron 的状态):

$ update-rc.d cron disable; echo rc=$?
rc=0

$ ls -l /etc/rc2.d/*cron /etc/rc5.d/*cron /etc/rc0.d/*cron
ls: cannot access '/etc/rc0.d/*cron': No such file or directory
lrwxrwxrwx 1 root root 14 Oct 15 23:22 /etc/rc2.d/K01cron -> ../init.d/cron
lrwxrwxrwx 1 root root 14 Oct 15 23:22 /etc/rc5.d/K01cron -> ../init.d/cron

$ service cron status; echo rc=$?
 * cron is running
rc=0

关键观察:disable 只影响下次开机,不停当前进程——链接已变 K,service cron status 仍是 running。还可以只操作单个运行级:

$ update-rc.d cron disable 2; echo rc=$?
rc=0

$ ls -l /etc/rc2.d/*cron /etc/rc3.d/*cron
lrwxrwxrwx 1 root root 14 Oct 15 23:38 /etc/rc2.d/K01cron -> ../init.d/cron
lrwxrwxrwx 1 root root 14 Oct 15 23:38 /etc/rc3.d/S01cron -> ../init.d/cron

💡 要点:disable 2 里多出的 2 是运行级参数(可选值 S|2|3|4|5);不带该参数时处理 LSB 头 Default-Start 声明的全部运行级。cron 在 rc3 仍是 S01cron,对比一目了然。

3.5 enable:恢复开机自启(K→S 翻转)

enable 是 disable 的逆操作,同样支持单运行级:

$ update-rc.d cron enable; echo rc=$?
rc=0

$ ls -l /etc/rc2.d/*cron /etc/rc5.d/*cron
lrwxrwxrwx 1 root root 14 Oct 15 23:22 /etc/rc2.d/S01cron -> ../init.d/cron
lrwxrwxrwx 1 root root 14 Oct 15 23:22 /etc/rc5.d/S01cron -> ../init.d/cron

$ update-rc.d cron enable 2; echo rc=$?
rc=0

$ ls -l /etc/rc2.d/*cron /etc/rc3.d/*cron
lrwxrwxrwx 1 root root 14 Oct 15 23:38 /etc/rc2.d/S01cron -> ../init.d/cron
lrwxrwxrwx 1 root root 14 Oct 15 23:38 /etc/rc3.d/S01cron -> ../init.d/cron

💡 要点:disable/enable 是改名翻转而不是删除重建,因此序号与运行级布局在禁用前后保持一致;这也是恢复自启首选 enable 而非 defaults 的原因——defaults 会按 LSB 头重建整组链接,若手工调整过个别运行级会被抹平。

3.6 -f remove:强制移除全部链接(init 脚本保留)

$ update-rc.d -f cron remove; echo rc=$?
rc=0

$ ls -l /etc/rc*.d/*cron
ls: cannot access '/etc/rc*.d/*cron': No such file or directory

$ ls -l /etc/init.d/cron
-rwxr-xr-x 1 root root 3062 Mar 17  2021 /etc/init.d/cron

$ service cron status; echo rc=$?
 * cron is running
rc=0

三个关键点逐一验证:

  • 全部 S/K 链接被删(rc*.d 下再无 cron 链接);
  • /etc/init.d/cron 原样保留(remove 只动链接,不删脚本;删脚本要 apt-get purge 或手动 rm);
  • 运行中的 cron 进程不受影响(remove 不碰进程)。

之后 defaults 可随时重建(3.2 节已实测)。

💡 要点:-f 的必要性在于——对存在 LSB 头的脚本,不带 -f 的 remove 会被拒绝(update-rc.d 的保护逻辑:防误删仍在服务的自启链接)。日常卸载服务按「包管理器卸载(自动清链接)→ 手工 remove 只用于自制脚本」分工;remove 后务必 ls /etc/rc*.d/ | grep 名字 复核链接确实清空。

3.7 remove 不带 -f:systemd 时代的行为差异

老版本 update-rc.d 在 init 脚本仍存在时,不带 -f 的 remove 会拒绝执行。Ubuntu 22.04 上实测——静默成功:

$ update-rc.d cron remove; echo rc=$?
rc=0

$ ls -l /etc/rc2.d/*cron /etc/rc0.d/*cron
ls: cannot access '/etc/rc2.d/*cron': No such file or directory
ls: cannot access '/etc/rc0.d/*cron': No such file or directory

机制解读:update-rc.d 1.62 的 remove 分支对 SysV 链接统一执行「删除 /etc/rc?.d/[SK][0-9][0-9]服务名 全部匹配项」,-f 仅作为兼容老版本的参数保留(源码注释:remove 时无法假定 init 脚本还在,purge 阶段就会带脚本缺失调用 remove)。因此在新系统上:

  • -f remove 与 remove 实际效果一致(都删链接、都保留 init 脚本);
  • 老资料中「不带 -f 会报错」的说法在 1.62 上已不成立,但建议仍带上 -f:保证在老系统上也能同样工作。

💡 要点:-f 从「必要开关」退化为「兼容写法」,是排查网上旧资料与实测不符时的典型差异点。

3.8 自制 init 脚本注册自启的全流程

以一个最小可用的 SysV 服务 demo-svc 为例,走完「写脚本 → 注册 → 验证 → 注销」全流程。第一步写脚本(标准 LSB 头 + case 分发骨架):

$ cat > /etc/init.d/demo-svc <<'EOF'
#!/bin/sh
### BEGIN INIT INFO
# Provides:          demo-svc
# Required-Start:    $remote_fs
# Required-Stop:     $remote_fs
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: demo service for update-rc.d practice
# Description:       minimal init script used to practice update-rc.d
### END INIT INFO
case "$1" in
  start) echo "demo-svc: started" ;;
  stop)  echo "demo-svc: stopped" ;;
  restart) $0 stop; $0 start ;;
  status) pgrep -f "sleep 3000" >/dev/null 2>&1 && echo "demo-svc: running" || echo "demo-svc: stopped" ;;
  *) echo "Usage: $0 {start|stop|restart|status}"; exit 1 ;;
esac
exit 0
EOF

$ chmod +x /etc/init.d/demo-svc; ls -l /etc/init.d/demo-svc
-rwxr-xr-x 1 root root 626 Oct 15 23:38 /etc/init.d/demo-svc

第二步注册开机自启,第三步核对链接——LSB 头声明的 7 个运行级一个不少:

$ update-rc.d demo-svc defaults; echo rc=$?
rc=0

$ ls -l /etc/rc*.d/*demo-svc
lrwxrwxrwx 1 root root 18 Oct 15 23:38 /etc/rc0.d/K01demo-svc -> ../init.d/demo-svc
lrwxrwxrwx 1 root root 18 Oct 15 23:38 /etc/rc1.d/K01demo-svc -> ../init.d/demo-svc
lrwxrwxrwx 1 root root 18 Oct 15 23:38 /etc/rc2.d/S01demo-svc -> ../init.d/demo-svc
lrwxrwxrwx 1 root root 18 Oct 15 23:38 /etc/rc3.d/S01demo-svc -> ../init.d/demo-svc
lrwxrwxrwx 1 root root 18 Oct 15 23:38 /etc/rc4.d/S01demo-svc -> ../init.d/demo-svc
lrwxrwxrwx 1 root root 18 Oct 15 23:38 /etc/rc5.d/S01demo-svc -> ../init.d/demo-svc
lrwxrwxrwx 1 root root 18 Oct 15 23:38 /etc/rc6.d/K01demo-svc -> ../init.d/demo-svc

第四步用 service 验证脚本能被 init 体系正常调用:

$ service demo-svc start; echo rc=$?
demo-svc: started
rc=0

$ service demo-svc status; echo rc=$?
demo-svc: stopped
rc=0

第五步演练 disable(对 demo-svc 会出现一条 systemd 侧提示,见 3.10 节解读),确认 S→K 翻转生效:

$ update-rc.d demo-svc disable; echo rc=$?
Failed to disable unit, unit demo-svc.service does not exist.
rc=0

$ ls -l /etc/rc2.d/*demo-svc /etc/rc0.d/*demo-svc
lrwxrwxrwx 1 root root 18 Oct 15 23:38 /etc/rc0.d/K01demo-svc -> ../init.d/demo-svc
lrwxrwxrwx 1 root root 18 Oct 15 23:38 /etc/rc2.d/K01demo-svc -> ../init.d/demo-svc

第六步注销服务:删链接 → 删脚本 → 验证 service 已不认识它:

$ update-rc.d -f demo-svc remove; echo rc=$?
rc=0

$ ls -l /etc/rc*.d/*demo-svc
ls: cannot access '/etc/rc*.d/*demo-svc': No such file or directory

$ rm -f /etc/init.d/demo-svc

$ service demo-svc status; echo rc=$?
demo-svc: unrecognized service
rc=1

💡 要点:自制服务上线的检查清单——脚本有可执行位(chmod +x)、LSB 头齐全(否则见 3.9 节的坑)、update-rc.d 名字 defaults 后 ls /etc/rc*.d/*名字 核对链接、service 名字 start 实测一次。注销时先 remove 链接再删脚本,顺序反过来会留下悬空链接。

3.9 无 LSB 头的脚本:静默不建链接(高危坑)

把 demo-svc 的 LSB 头整个去掉(其余不变,语法检查通过),defaults 会发生什么?实测:

$ cat > /etc/init.d/demo-raw <<'EOF'
#!/bin/sh
case "$1" in
  start) echo "demo-raw: started" ;;
  stop)  echo "demo-raw: stopped" ;;
  *) echo "Usage: $0 {start|stop}"; exit 1 ;;
esac
exit 0
EOF

$ chmod +x /etc/init.d/demo-raw

$ update-rc.d demo-raw defaults 80; echo rc=$?
rc=0

$ ls -l /etc/rc*.d/*demo-raw
ls: cannot access '/etc/rc*.d/*demo-raw': No such file or directory

$ service demo-raw start; echo rc=$?
demo-raw: started
rc=0

现象总结:脚本完全合法、service 能正常调用,但 update-rc.d 静默返回 0 且一个链接都没建——没有报错、没有警告,是典型的静默失败。清理现场:

$ update-rc.d -f demo-raw remove; echo rc=$?
rc=0

$ rm -f /etc/init.d/demo-raw

源码级解读:make_sysv_links 对 defaults 的处理是「解析 LSB 头的 Default-Start/Default-Stop,然后按列表循环建链接」。脚本没有 LSB 头时两个列表都是空,两个循环一次都不执行,于是 rc=0 且无链接;80 这个序号参数同样被丢弃(3.3 节)。

💡 要点:给自制脚本写 LSB 头不是「风格问题」而是功能必需。注册后必须用 ls -l /etc/rc*.d/*名字 验证链接真的存在,不能只看命令退出码。

3.10 与 systemctl 的双轨机制

Ubuntu 22.04 里 SysV 脚本与 systemd unit 并存,cron 包安装时同时落了两套自启配置(安装日志实测节选):

$ ls -l /lib/systemd/system/cron.service /etc/systemd/system/multi-user.target.wants/cron.service
lrwxrwxrwx 1 root root  32 Oct 15 23:38 /etc/systemd/system/multi-user.target.wants/cron.service -> /lib/systemd/system/cron.service
-rw-r--r-- 1 root root 319 Mar 17  2021 /lib/systemd/system/cron.service

update-rc.d 因此是「双后端」工具:源码里每次操作按顺序执行 SysV 链接逻辑 + systemd 委托逻辑(对 toggle 还会先调 systemctl --quiet enable/disable 服务名;demo-svc 没有 unit 文件才出现 3.8 节那条 Failed to disable unit 提示)。systemd 侧的自启状态用 systemctl is-enabled 查询——它是离线读文件系统的查询,不需要 systemd 引导也能用,实测:

$ systemctl is-enabled cron; echo rc=$?
enabled
rc=0

两套工具的横向对比:

对比项update-rc.d(本文主角)systemctl enable/disable
操作对象/etc/rc?.d/ 下的 S/K 符号链接/etc/systemd/system/*.wants/ 下的 unit 链接
依据init 脚本 LSB 头unit 文件 [Install] 段
生效时机下次进入对应运行级下次启动对应 target
影响运行中进程否(实测 3.4 节)否
Ubuntu 22.04 推荐度维护 SysV 脚本时使用标准 unit 服务的首选

💡 技术拓展:Debian 系处理 init 脚本依赖排序历史上有两代实现——早期 manually 手工序号(S80 之类),后来是 insserv 依赖排序(解析 $remote_fs 等门面依赖计算序号)。Ubuntu 22.04 已移除 insserv 工具(实测 which insserv 无输出),update-rc.d 内置了简化的链接生成逻辑并把序号固定为 01(3.3 节),但源码中仍保留检测 insserv 的兼容分支,装了 insserv 的老系统会走依赖排序路径。

四、实战示例

4.1 场景一:停用某服务的开机自启并恢复

运维里最常见的需求——「机器重启后别自动拉起 xx」。以 atd 为例:启动服务、disable、核对链接、确认当前进程不受影响、enable 恢复:

$ service atd start
 * Starting deferred execution scheduler atd
   ...done.

$ update-rc.d atd disable; echo rc=$?
rc=0

$ ls -l /etc/rc2.d/*atd
lrwxrwxrwx 1 root root 13 Oct 15 23:22 /etc/rc2.d/K01atd -> ../init.d/atd

$ service atd status; echo rc=$?
 * atd is running
rc=0

$ update-rc.d atd enable; echo rc=$?
rc=0

$ ls -l /etc/rc2.d/*atd
lrwxrwxrwx 1 root root 13 Oct 15 23:22 /etc/rc2.d/S01atd -> ../init.d/atd

disable 后 rc2 的链接已是 K01atd,而 service atd status 仍是 running——「不再自启」与「停止运行」是两件独立的事。如需临时停用又不想恢复,改用 -f remove 并保留脚本即可(链接删了、脚本还在、随时 defaults 找回)。

4.2 场景二:彻底下线一个自制 SysV 服务

3.8 节注册的 demo-svc 完整下线流程(先删链接、确认、再删脚本、最后验证):

$ update-rc.d -f demo-svc remove; echo rc=$?
rc=0

$ ls /etc/rc*.d/*demo-svc
ls: cannot access '/etc/rc*.d/*demo-svc': No such file or directory

$ rm -f /etc/init.d/demo-svc

$ service demo-svc status; echo rc=$?
demo-svc: unrecognized service
rc=1

验收标准有三:链接目录无残留、init 脚本已删、service 报 unrecognized service。

4.3 场景三:服务自启状态三件套巡检

判断「一个服务开机到底会不会自启、现在跑没跑」,三条命令分别回答:

$ ls -l /etc/rc*.d/*cron
lrwxrwxrwx 1 root root 14 Oct 15 23:38 /etc/rc2.d/S01cron -> ../init.d/cron
lrwxrwxrwx 1 root root 14 Oct 15 23:38 /etc/rc3.d/S01cron -> ../init.d/cron
lrwxrwxrwx 1 root root 14 Oct 15 23:38 /etc/rc4.d/S01cron -> ../init.d/cron
lrwxrwxrwx 1 root root 14 Oct 15 23:38 /etc/rc5.d/S01cron -> ../init.d/cron

$ systemctl is-enabled cron; echo rc=$?
enabled
rc=0

$ service cron status; echo rc=$?
 * cron is running
rc=0

解读:rc2~rc5 有 S01cron → SysV 层自启已配置;is-enabled 返回 enabled → systemd 层 wants 链接在位;status rc=0 → 当前进程活着。三层全绿才是「自启 + 在跑」的完整状态。

4.4 场景四:安装软件包后核对自启配置

apt 装 cron 这类服务包时,包管理器内部就是调 update-rc.d 落链接。安装日志的关键行(实测节选):

$ apt-get install -y -qq cron at
Setting up cron (3.0pl1-137ubuntu3) ...
Adding group `crontab' (GID 113) ...
Done.
invoke-rc.d: could not determine current runlevel
invoke-rc.d: policy-rc.d denied execution of start.
Created symlink /etc/systemd/system/multi-user.target.wants/cron.service → /lib/systemd/system/cron.service.

安装完成后的核对(SysV 链接 + systemd wants 链接两条线都在):

$ ls -l /etc/rc2.d/S01cron
lrwxrwxrwx 1 root root 14 Oct 15 23:38 /etc/rc2.d/S01cron -> ../init.d/cron

$ ls -l /etc/systemd/system/multi-user.target.wants/cron.service
lrwxrwxrwx 1 root root 32 Oct 15 23:39 /etc/systemd/system/multi-user.target.wants/cron.service -> /lib/systemd/system/cron.service

日志里 invoke-rc.d: policy-rc.d denied execution of start. 表示「安装期启动」被策略文件拦了(机制与处置见本专栏《service 命令详解与参数实测(Ubuntu 22.04)》4.5 节),但自启链接两条线都已建好,手动 start 一次即可进入工作状态。

五、注意事项与常见问题

Q1:update-rc.d cron defaults 80 为什么没有生成 S80cron? Ubuntu 22.04 的 update-rc.d(init-system-helpers 1.62)已丢弃旧式序号参数:链接序号在源码中硬编码为 01,运行级取自 LSB 头的 Default-Start/Default-Stop(3.3 节有源码佐证与全量服务序号实测)。想影响启动顺序用 LSB 头依赖或 systemd 的 After=,别再手写序号。

Q2:cron 在 rc0/rc1/rc6 下没有 K 链接,atd 却有,谁有问题? 都没有问题。K 链接只由 LSB 头 Default-Stop 决定:cron 的 Default-Stop 为空 → 无 K 链接;atd 声明 Default-Stop: 0 1 6 → rc0/1/6 有 K01atd(3.1 节对照实测)。cron 停机时不显式 stop 是设计取舍(进程随系统终止)。

Q3:update-rc.d cron disable 会把正在跑的 cron 停掉吗? 不会。disable 只翻转 /etc/rc2.d/ 等目录里的链接(S01cron → K01cron),对进程零操作——实测 disable 后 service cron status 仍是 * cron is running(3.4 节)。要停当前进程用 service cron stop,两者独立。

Q4:update-rc.d 和 systemctl enable 该用哪个? 管理对象不同:update-rc.d 操作 /etc/rc?.d/ 的 SysV 链接,systemctl enable 操作 *.wants/ 的 unit 链接。Ubuntu 22.04 的 update-rc.d 对 enable/disable 是双后端(SysV 链接翻转 + systemctl 委托,3.10 节),所以对带 unit 的服务两者都「能用」;标准 systemd unit 服务首选 systemctl enable,纯 SysV 脚本只能用 update-rc.d。systemctl is-enabled 可作为统一的状态查询口(离线可用,实测 enabled rc=0)。

Q5:update-rc.d 报 rc=0 但一个链接都没建? 先查 init 脚本有没有 LSB 头(### BEGIN INIT INFO 块)。没有头的脚本 defaults 会静默返回 0 且不建任何链接(3.9 节实测 + 源码解读)。补齐 LSB 头(至少 Provides/Default-Start/Default-Stop)后重新 defaults,再用 ls -l /etc/rc*.d/*名字 验证。

Q6:remove 之后 init 脚本还在,是残留垃圾吗? 不是。remove 的职责边界就是链接层,/etc/init.d/脚本 必须保留(服务还要靠它 start/stop)。彻底卸载用包管理器:apt-get purge 包名 会同时清脚本与链接;自制脚本则手动 rm(顺序见 4.2 节场景)。

Q7:disable|enable 后面的运行级参数有哪些合法值? S|2|3|4|5(用法提示原文:update-rc.d <basename> disable|enable [S|2|3|4|5])。S 是 rcS.d(引导早期),实测 disable 2 只翻转 rc2(3.4 节)。不传参数时取 LSB 头 Default-Start 的全部运行级。另注意用法提示自带声明:disable/enable API 不稳定,跨大版本升级后建议重测。

六、参考链接