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 对照。
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 重启) |
S | Start:进入该运行级时启动服务;与之相对的 K(Kill)表示离开时停止 |
01 | 两位执行序号:同目录内按序号从小到大串行执行(本环境全部服务的序号都是 01,见 3.3 节) |
cron | init 脚本名,目标是 ../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 不稳定,跨大版本升级后建议重测。
六、参考链接
- update-rc.d(8) 手册页(Ubuntu Jammy):https://manpages.ubuntu.com/manpages/jammy/en/man8/update-rc.d.8.html
- init-system-helpers 源码(Debian Salsa):https://salsa.debian.org/debian/init-system-helpers
- invoke-rc.d(8) 手册页:https://manpages.ubuntu.com/manpages/jammy/en/man8/invoke-rc.d.8.html
- LSB Core 规范之 Init Script Actions:https://refspecs.linuxbase.org/LSB_5.0.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html
- systemctl(1) 手册页:https://manpages.ubuntu.com/manpages/jammy/en/man1/systemctl.1.html
- 关联阅读:本专栏《service 命令详解与参数实测(Ubuntu 22.04)》《crontab 命令详解与参数实测(Ubuntu 22.04)》