sv 命令详解与参数实测(Void Linux)
在 Void Linux(runit 2.3.1)实测逐参数讲解 runit 服务管理器前端 sv:status/up/down/once/start/stop/restart/reload/hup/pause/cont/term/kill/exit/check 全子命令与 -v/-w 选项的逐字回显,覆盖服务目录与 run 脚本编写、runsv 手动监管、runsvdir 与 /var/service 软链启用停用机制、多服务批量操作、supervise 管道结构,以及 run 不可执行、崩溃循环与 finish 脚本、端口占用重启风暴、短名解析失败等真实排障全程,并附 runit 与 sysvinit/OpenRC/systemd 对比。
sv 命令详解与参数实测(Void Linux)
目标:全面掌握 Void Linux 默认服务管理器 runit 的前端命令
sv——status、up、down、once、start、stop、restart、reload、hup、pause、cont、term、kill、exit、check全部子命令与-v/-w选项的逐字回显,以及服务目录与 run 脚本编写、runsv手动监管、runsvdir与/var/service软链启用停用机制、多服务批量操作、supervise 管道结构,和 run 脚本不可执行、服务反复重启、端口占用重启风暴、短名解析失败等真实排障全程,全部命令与回显已在 Void Linux(runit 2.3.1)实测通过。
一、环境信息
本文所有命令均在以下环境实测,回显逐字照抄:
- 操作系统:Void Linux(
cat /etc/os-release实测PRETTY_NAME="void"),x86_64 - init 方案:runit 工具链已安装并作为服务管理框架,包信息实测:
$ xbps-query runit | grep -E "pkgver|short_desc"
pkgver: runit-2.3.1_2
short_desc: UNIX init scheme with service supervision
$ xbps-query runit-void | grep -E "pkgver|short_desc"
pkgver: runit-void-20250212_4
short_desc: Void Linux runit scripts
- runit 全家桶二进制齐备:
$ ls /usr/sbin/ | grep -E "^(sv|runsv|runit|chpst|svlogd)"
chpst
runit
runit-init
runsv
runsvchdir
runsvdir
sv
svlogd
- 服务目录:系统自带
/etc/sv/下有 16 个服务定义(agetty 系列、sulogin、uuidd 等);/var/service是指向../run/runit/runsvdir/current的符号链接,初始状态下/run/runit尚不存在,链接是悬挂的:
$ ls -ld /var/service /run/runit 2>&1
ls: cannot access '/run/runit': No such file or directory
lrwxrwxrwx 1 root root 29 Oct 13 13:42 /var/service -> ../run/runit/runsvdir/current
- 演示服务依赖:python3 3.14.8(
xbps-install -y python3 curl实装,连带 17 包)、curl 8.22.0 - 执行身份:root
💡 说明:本机 runit 未作为 1 号进程接管启动流程,因此
/run/runit/runsvdir/current不存在。这不影响sv的任何功能演示——runsv/runsvdir是普通用户态程序,可对任意服务目录后台运行监管(这正是排障时"不重启系统就能测试服务定义"的标准手法),软链机制一节会把这个目录补齐。
二、命令概览
2.1 sv --help:极简到只有一行
$ sv --help
usage: sv [-v] [-w sec] command service ...
$ sv help
usage: sv [-v] [-w sec] command service ...
没有参数清单、退出码 100。sv 的模型是「一个动词 + 一个或多个服务」:sv <command> <service...>,动词就是子命令本身。-v 输出附加信息、-w sec 设置等待超时秒数,都是全局前置选项。
runsv 则连 usage 都没有——它的参数就是一个服务目录:
$ runsv --help
runsv --help: fatal: unable to change to directory: file does not exist
退出码 111。runsv <dir> 会长期驻留,把 <dir> 当服务定义监管:启动目录里的 run 脚本、响应 sv 通过 supervise/control 管道发来的指令、进程退出后自动重拉。
2.2 服务定义:目录 + run 脚本
runit 的"服务"就是一个含可执行 run 脚本的目录。搭建演示服务 webdemo(python 自带的 http.server,前台运行、行为可观察):
mkdir -p /etc/sv/webdemo /srv/webdemo
echo '<h1>webdemo OK</h1>' > /srv/webdemo/index.html
cat > /etc/sv/webdemo/run <<'EOF'
#!/bin/sh
cd /srv/webdemo
exec python3 -m http.server 8000 --bind 0.0.0.0
EOF
chmod +x /etc/sv/webdemo/run
$ ls -l /etc/sv/webdemo/
total 4
-rwxr-xr-x 1 root root 74 Oct 17 00:37 run
$ cat /etc/sv/webdemo/run
#!/bin/sh
cd /srv/webdemo
exec python3 -m http.server 8000 --bind 0.0.0.0
run 脚本三要素:可执行(chmod +x)、前台运行(用 exec 让 run 脚本本身被替换为服务进程,绝不能自行 fork 到后台,否则 runsv 会认为服务已退出而反复重拉)、自己处理工作目录与环境(cd、清缓存等脚本管,runsv 不管)。
2.3 后台运行 runsv 监控服务目录
runsv /etc/sv/webdemo &
runsv 启动后立刻拉起 run 脚本(除非目录里存在 down 文件),随后进入监管循环。2 秒后查看状态并验证服务真的在工作:
$ sv status /etc/sv/webdemo
run: /etc/sv/webdemo: (pid 5454) 2s
$ curl -sS --max-time 3 http://127.0.0.1:8000/
127.0.0.1 - - [17/Oct/2026 00:37:28] "GET / HTTP/1.1" 200 -
<h1>webdemo OK</h1>
$ curl -sS -o /dev/null -w '%{http_code}\n' --max-time 3 http://127.0.0.1:8000/
127.0.0.1 - - [17/Oct/2026 00:37:28] "GET / HTTP/1.1" 200 -
200
状态行格式:run: <服务目录>: (pid N) <运行时长>。此后按服务生命周期逐个实测子命令。
💡 传给 sv 的服务可以是完整目录路径(本文 3.1 节全部用这种形式,不依赖任何全局状态),也可以是短名——短名靠
$SVDIR(默认/var/service)下的软链解析,见 3.3 节。
三、参数逐项实测
以下如无特别说明,服务均为 2.2/2.3 搭建的 webdemo(python http.server,端口 8000)。
3.1 status:状态总览
$ sv status /etc/sv/webdemo
run: /etc/sv/webdemo: (pid 5454) 2s
状态行首词是核心:run 运行中、down 已停止。运行时长超过一分钟直接秒数递增(实测 210s):
$ sv status /etc/sv/webdemo
run: /etc/sv/webdemo: (pid 5626) 210s
状态行还会附加状态标志,全部形态在后续小节陆续出现:, paused(被暂停)、, want down(监管目标为停止但进程还活着)、, want up(目标是拉起但一直起不来)。服务处于 finish 脚本阶段时,首词变成 finish:(见 5.2 排障实录)。
3.2 up / down / start / stop:基本起停
up 拉起、down 停止(并记住"想要停止"的状态),都是静默完成:
$ sv up /etc/sv/webdemo
$ sv status /etc/sv/webdemo
run: /etc/sv/webdemo: (pid 5465) 0s
$ sv down /etc/sv/webdemo
$ sv status /etc/sv/webdemo
down: /etc/sv/webdemo: 0s, normally up
down: 行尾的 normally up 表示"正常状态是运行"(即没有 down 文件),停了只是暂时的。
stop 与 start 是它们的"会等待并回显"版本:stop 发 TERM 后等服务真正退出;start 等服务起来后打印状态行:
$ sv stop /etc/sv/webdemo
ok: down: /etc/sv/webdemo: 0s, normally up
$ sv start /etc/sv/webdemo
ok: run: /etc/sv/webdemo: (pid 5469) 1s
两者都幂等——对已停止的服务再 stop、对已运行的服务再 start,同样输出 ok 与状态行,不报错:
$ sv start /etc/sv/webdemo
ok: run: /etc/sv/webdemo: (pid 5626) 210s
$ sv stop /etc/sv/webdemo
ok: down: /etc/sv/webdemo: 1s, normally up
$ sv down /etc/sv/webdemo
停止后验证端口确实释放:
$ curl -sS --max-time 2 http://127.0.0.1:8000/
curl: (7) Failed to connect to 127.0.0.1:8000 after 0 ms: Could not connect to server
3.3 restart:重启
$ sv restart /etc/sv/webdemo
ok: run: /etc/sv/webdemo: (pid 5472) 0s
$ sv status /etc/sv/webdemo
run: /etc/sv/webdemo: (pid 5472) 0s
restart = TERM + 等退出 + 重新拉起,回显的是新 pid。对比 3.2 节的 pid 可见进程确实换了。
3.4 check:健康检查与 -w 超时
check 验证服务是否处于"应该的状态",服务正常时输出与 status 相同的状态行:
$ sv check /etc/sv/webdemo
ok: run: /etc/sv/webdemo: (pid 5472) 0s
$ sv -w 5 check /etc/sv/webdemo
ok: run: /etc/sv/webdemo: (pid 5472) 0s
-w 5 表示最多等 5 秒。服务起不来时 check 等满超时后以 timeout: 前缀报告(见 5.1 排障实录):
timeout: down: /etc/sv/webdemo: 0s, normally up, want up
check 的退出码可用于脚本判断(成功 0、失败 1)。
3.5 -v:附加信息
$ sv -v up /etc/sv/webdemo
ok: run: /etc/sv/webdemo: (pid 5472) 0s
up 本是静默的,加 -v 后回显执行结果状态行。适合在脚本里让 up/down 也留下痕迹。
3.6 reload 与 hup:重载信号
两者都向服务进程发送 SIGHUP,但回显行为不同。用 pid 对照看效果:
$ cat /etc/sv/webdemo/supervise/pid
6482
$ sv reload /etc/sv/webdemo
ok: run: /etc/sv/webdemo: (pid 6482) 1s
$ cat /etc/sv/webdemo/supervise/pid
6490
$ sv hup /etc/sv/webdemo
$ cat /etc/sv/webdemo/supervise/pid
6501
💡 两个实测结论:①
reload会回显状态行(还是发信号瞬间的旧 pid),hup完全静默,但两者都真实发出了 SIGHUP——python http.server 不处理 SIGHUP,进程被终止后 runsv 立刻重拉,pid 从 6482 → 6490 → 6501;对 nginx 这类把 SIGHUP 实现为"重读配置"的服务,同样操作就是真正的平滑 reload。② 需要重载语义的服务,请在 run 脚本里选型时确认其对 SIGHUP 的处理方式。
3.7 pause / cont:暂停与继续
pause 向服务进程发 SIGSTOP(进程冻结但不清场),cont 发 SIGCONT 解冻。状态行出现 , paused 标志:
$ sv pause /etc/sv/webdemo
$ sv status /etc/sv/webdemo
run: /etc/sv/webdemo: (pid 5480) 0s, paused
$ sv cont /etc/sv/webdemo
$ sv status /etc/sv/webdemo
run: /etc/sv/webdemo: (pid 5480) 0s
暂停期间 pid 不变、服务目录仍记为 run,但进程完全不响应(监听队列也不再消费)。适合"冻结现场"式排查。
3.8 once:只启动一次
once = "拉起,但进程退出后不再自动重启"。实测:先 down,再 once,状态行出现 want down 标志——监管目标仍是"停止",只是当前被临时拉起:
$ sv down /etc/sv/webdemo
$ sv once /etc/sv/webdemo
$ sv status /etc/sv/webdemo
run: /etc/sv/webdemo: (pid 6458) 0s, want down
$ sv status /etc/sv/webdemo
run: /etc/sv/webdemo: (pid 6458) 2s, want down
此时手动 kill 服务进程,runsv 不会重拉,服务落回 down:
$ kill $(cat /etc/sv/webdemo/supervise/pid)
(kill 6458)
$ sv status /etc/sv/webdemo
down: /etc/sv/webdemo: 2s, normally up
对照 3.9 的 term/kill(want up 状态下杀掉立即重拉),once 的差异一目了然。跑批处理型任务(跑完即退)建议用 once。
3.9 term / kill:直接发信号
term 发 SIGTERM、kill 发 SIGKILL,都是"只发信号不等结果"的静默命令。want up 状态下进程被杀后 runsv 秒级重拉,pid 变化可证:
$ sv term /etc/sv/webdemo
$ sv status /etc/sv/webdemo
run: /etc/sv/webdemo: (pid 5496) 1s
$ sv kill /etc/sv/webdemo
$ sv status /etc/sv/webdemo
run: /etc/sv/webdemo: (pid 5500) 1s
3.10 exit:让 runsv 退出监管
exit 指示 runsv 自身退出(若服务在运行会先 TERM 并等待),监管关系解除后再查就是另一种报错形态:
$ sv status /etc/sv/exitsvc
run: /etc/sv/exitsvc: (pid 5688) 2s
$ sv exit /etc/sv/exitsvc
$ sv status /etc/sv/exitsvc
fail: /etc/sv/exitsvc: runsv not running
fail: ... runsv not running 与"从未被监管"的 warning: ... unable to open supervise/ok(见 5.3)是两种不同状态,排障时先分清。
3.11 多服务批量操作
sv 的服务参数天然支持多个,全部执行完才返回;批量 stop 会逐个回显:
$ sv status /etc/sv/webdemo /etc/sv/sleepsvc
run: /etc/sv/webdemo: (pid 5500) 14s
run: /etc/sv/sleepsvc: (pid 5521) 5s
$ sv stop /etc/sv/webdemo /etc/sv/sleepsvc
ok: down: /etc/sv/webdemo: 0s, normally up
ok: down: /etc/sv/sleepsvc: 0s, normally up
$ sv status /etc/sv/webdemo /etc/sv/sleepsvc
down: /etc/sv/webdemo: 0s, normally up
down: /etc/sv/sleepsvc: 0s, normally up
$ sv up /etc/sv/webdemo /etc/sv/sleepsvc
$ sv status /etc/sv/webdemo /etc/sv/sleepsvc
run: /etc/sv/webdemo: (pid 5529) 0s
run: /etc/sv/sleepsvc: (pid 5528) 0s
shell glob 一次喂给 sv 全部服务目录,能瞬间盘点整台机器的监管状况(本机 /etc/sv/ 下 16 个系统自带目录从未被 runsv 接管,输出为 warning;两个演示服务为 run):
$ sv status /etc/sv/*
warning: /etc/sv/agetty-console: unable to open supervise/ok: file does not exist
warning: /etc/sv/agetty-generic: unable to open supervise/ok: file does not exist
warning: /etc/sv/agetty-hvc0: unable to open supervise/ok: file does not exist
warning: /etc/sv/agetty-hvsi0: unable to open supervise/ok: file does not exist
warning: /etc/sv/agetty-serial: unable to open supervise/ok: file does not exist
warning: /etc/sv/agetty-tty1: unable to open supervise/ok: file does not exist
warning: /etc/sv/agetty-tty2: unable to open supervise/ok: file does not exist
warning: /etc/sv/agetty-tty3: unable to open supervise/ok: file does not exist
warning: /etc/sv/agetty-tty4: unable to open supervise/ok: file does not exist
warning: /etc/sv/agetty-tty5: unable to open supervise/ok: file does not exist
warning: /etc/sv/agetty-tty6: unable to open supervise/ok: file does not exist
warning: /etc/sv/agetty-ttyAMA0: unable to open supervise/ok: file does not exist
warning: /etc/sv/agetty-ttyS0: unable to open supervise/ok: file does not exist
warning: /etc/sv/agetty-ttyUSB0: unable to open supervise/ok: file does not exist
run: /etc/sv/sleepsvc: (pid 5528) 0s
warning: /etc/sv/sulogin: unable to open supervise/ok: file does not exist
warning: /etc/sv/uuidd: unable to open supervise/ok: file does not exist
run: /etc/sv/webdemo: (pid 5529) 0s
💡 实测退出码规律:批量操作时 sv 以"失败条数"作为退出码——本次 16 个 warning 对应退出码 16;两服务批操作全部成功时为 0。脚本可以直接用退出码判断批结果。
3.12 /var/service 软链机制:启用与停用
Void 的服务启用方式是把 /etc/sv/<服务> 软链进 /var/service/(实际指向 /run/runit/runsvdir/current)。runsvdir 守护这个目录、每 5 秒扫描一次,发现新软链就拉起对应 runsv,发现软链被删就终止监管——这就是"开机自启"在 runit 世界里的全部实现。
先补齐目录并后台启动 runsvdir(正常开机时由 runit 阶段 2 自动完成,这里手动等价复现):
mkdir -p /run/runit/runsvdir/current
runsvdir /run/runit/runsvdir/current &
此时 /var/service/ 为空。把演示服务 webdemo2(同款 http.server,端口 8001)软链进去:
$ ln -s /etc/sv/webdemo2 /run/runit/runsvdir/current/webdemo2
(等待一个 5 秒扫描周期)
$ ls -l /var/service/
total 0
lrwxrwxrwx 1 root root 16 Oct 17 00:32 webdemo2 -> /etc/sv/webdemo2
$ sv status webdemo2
run: webdemo2: (pid 6554) 4s
$ curl -sS -o /dev/null -w '%{http_code}\n' --max-time 3 http://127.0.0.1:8001/
127.0.0.1 - - [17/Oct/2026 00:42:23] "GET / HTTP/1.1" 200 -
200
没有执行任何 start,服务已经起来了——软链即启用。短名解析也随 /var/service 恢复可用(此前悬挂时短名全部失败,见 5.4):
$ sv status webdemo2
run: webdemo2: (pid 6554) 4s
$ sv stop webdemo2
ok: down: webdemo2: 0s, normally up
$ sv status webdemo2
down: webdemo2: 0s, normally up
$ sv up webdemo2
$ sv status webdemo2
run: webdemo2: (pid 6562) 0s
删除软链即停用——runsvdir 会终止对应的 runsv,短名解析随之失效,全路径查询报 fail: ... runsv not running:
$ rm /run/runit/runsvdir/current/webdemo2
(等待一个 5 秒扫描周期)
$ sv status webdemo2
fail: webdemo2: unable to change to service directory: file does not exist
$ sv status /etc/sv/webdemo2
fail: /etc/sv/webdemo2: runsv not running
重新软链,服务又被自动拉起:
$ ln -s /etc/sv/webdemo2 /run/runit/runsvdir/current/webdemo2
(等待一个 5 秒扫描周期)
$ sv status webdemo2
run: webdemo2: (pid 6570) 3s
💡 运维速记:
ln -s /etc/sv/foo /var/service/= 开机自启 + 立即运行;rm /var/service/foo= 取消自启 + 停止。想"取消自启但不停止",在服务目录touch down后再删软链即可。
四、实战示例
4.1 三步上线一个自启服务
# 1) 定义服务
mkdir -p /etc/sv/myapp
cat > /etc/sv/myapp/run <<'EOF'
#!/bin/sh
exec /usr/bin/myapp -F --config /etc/myapp.conf
EOF
chmod +x /etc/sv/myapp/run
# 2) 启用(软链进 /var/service,runsvdir 5 秒内拉起)
ln -s /etc/sv/myapp /var/service/
# 3) 验证
sv status myapp
4.2 不动系统环境调试服务定义
runsv /etc/sv/webdemo & # 手动监管,绕过 runsvdir
sv status /etc/sv/webdemo # 全路径操作
sv restart /etc/sv/webdemo # 改完 run 脚本直接 restart 生效
sv exit /etc/sv/webdemo # 结束监管
改 run 脚本无需"重新加载"——runsv 每次拉起都重新读取并执行 run,一次 restart 即生效。
4.3 日常批量运维
sv status /etc/sv/* # 全机服务盘点(退出码=异常条数)
sv restart webdemo webdemo2 # 批量重启
sv -w 10 stop myapp # 给优雅退出留 10 秒
sv check /etc/sv/* # 健康检查,退出码判断异常
4.4 观察服务崩溃自愈
sv status myapp # 记下 pid
kill $(cat /etc/sv/myapp/supervise/pid) # 模拟崩溃
sv status myapp # 新 pid,服务无感恢复
五、注意事项与常见问题(排障实录)
5.1 run 脚本不可执行:down 且 want up,check 超时
把 run 的执行位去掉后 sv up,runsv 反复尝试 exec 都失败,服务永远起不来:
$ sv stop /etc/sv/webdemo
ok: down: /etc/sv/webdemo: 0s, normally up
$ chmod -x /etc/sv/webdemo/run
$ sv up /etc/sv/webdemo
$ sv status /etc/sv/webdemo
down: /etc/sv/webdemo: 1s, normally up, want up
$ sv status /etc/sv/webdemo
down: /etc/sv/webdemo: 0s, normally up, want up
$ sv status /etc/sv/webdemo
down: /etc/sv/webdemo: 0s, normally up, want up
$ sv check /etc/sv/webdemo
timeout: down: /etc/sv/webdemo: 0s, normally up, want up
$ chmod +x /etc/sv/webdemo/run
$ sv status /etc/sv/webdemo
run: /etc/sv/webdemo: (pid 5626) 1s
特征:down + want up 反复出现且 uptime 归零、sv check 报 timeout:。恢复执行位后 runsv 自动把服务带起来,无需任何干预。新写的服务定义起不来时,第一步永远是 ls -l /etc/sv/<svc>/run 检查 -rwxr-xr-x。
5.2 服务反复重启:finish 脚本与退出码
构造一个每 2 秒崩溃一次的服务(exit 3),并挂 finish 脚本记录收场参数:
cat > /etc/sv/crashsvc/run <<'EOF'
#!/bin/sh
echo "run started"
sleep 2
exit 3
EOF
cat > /etc/sv/crashsvc/finish <<'EOF'
#!/bin/sh
echo "finish: exit_code=$1 signal=$2" >> /tmp/finish-demo.log
sleep 1
exit 111
EOF
chmod +x /etc/sv/crashsvc/run /etc/sv/crashsvc/finish
runsv /etc/sv/crashsvc &
连续观察:pid 每次都变、run 输出的 run started 反复刷屏、第三轮抓到 finish 阶段专属状态行(首词 finish:):
$ sv status /etc/sv/crashsvc
run: /etc/sv/crashsvc: (pid 5643) 1s
run started
$ sv status /etc/sv/crashsvc
run: /etc/sv/crashsvc: (pid 5652) 2s
run started
$ sv status /etc/sv/crashsvc
finish: /etc/sv/crashsvc: (pid 5665) 3s
$ cat /tmp/finish-demo.log
finish: exit_code=3 signal=0
finish: exit_code=3 signal=0
finish: exit_code=3 signal=0
finish: exit_code=3 signal=0
$ sv check /etc/sv/crashsvc
run started
ok: run: /etc/sv/crashsvc: (pid 5671) 0s
实测要点:
- run 脚本退出(无论正常异常)→ runsv 调用
finish(若有)→ finish 参数$1=run 的退出码、$2=收到的信号号(正常退出为 0)→ 然后重拉 run,如此循环。 - run 的输出会透传到 runsv 所在终端(上面的
run started);生产服务建议在目录下建log/子服务配合svlogd落盘。 - 与 runit 手册描述不符的实测发现:runsv(8) 称 finish 以退出码 111 结束时不再重启 run,但 runit 2.3.1 实测中 finish 连续四次以 111 退出,run 依然被反复重拉。不要依赖 111 停止重启循环,治本方法是修 run 脚本,或用
sv down明确叫停。
5.3 三种"看不到服务"的报错,对应三种病因
| 回显 | 病因 | 处置 |
|---|---|---|
warning: <dir>: unable to open supervise/ok: file does not exist | 目录存在但从未被 runsv 接管 | runsv <dir> & 或软链进 /var/service/ |
fail: <dir>: runsv not running | 曾被接管,但 runsv 已退出(如执行过 sv exit、或 runsvdir 因删软链终止了它) | 重新启用监管 |
fail: <短名>: unable to change to service directory: file does not exist | 短名解析失败(/var/service/ 下没有这个软链或目录本身不可用) | 用全路径,或建立软链(见 5.4) |
从未接管的目录实测(up 报的是另一个管道文件名):
$ sv status /etc/sv/ghostsvc
warning: /etc/sv/ghostsvc: unable to open supervise/ok: file does not exist
$ sv check /etc/sv/ghostsvc
warning: /etc/sv/ghostsvc: unable to open supervise/ok: file does not exist
$ sv up /etc/sv/ghostsvc
warning: /etc/sv/ghostsvc: unable to open supervise/control: file does not exist
5.4 短名解析失败:/var/service 悬挂
runsvdir 尚未建立 /run/runit/runsvdir/current 时(runit 未接管启动的机器上常见),/var/service 是悬挂符号链接,一切短名操作全部失败:
$ sv status webdemo
fail: webdemo: unable to change to service directory: file does not exist
$ sv status webdemo sleepsvc
fail: webdemo: unable to change to service directory: file does not exist
fail: sleepsvc: unable to change to service directory: file does not exist
服务本身可能活得好好的(curl 200 正常),只是 sv 找不到入口。两条出路:改用全路径 /etc/sv/webdemo;或按 3.12 补齐 mkdir -p /run/runit/runsvdir/current 并启动 runsvdir,让短名恢复解析。
5.5 端口占用引发的重启风暴
服务依赖的端口被其他进程(常见:上一轮没退干净的孤儿进程)占住时,run 秒退、runsv 秒拉,形成重启风暴。特征回显:
OSError: [Errno 98] Address already in use
$ sv status webdemo2
down: webdemo2: 0s, normally up, want up
down ... want up + run 脚本报错刷屏。处置:先解决端口占用(ss -ltnp | grep 8001 找真凶,含可能的孤儿进程),服务会自动恢复;期间用 sv down 停掉止损也行。另有环境实测显示同一监管目录可能被两套 runsv 争抢(手动 runsv + runsvdir 各一),新 runsv 抢锁失败后反复尝试——同一服务目录只保持一条监管路径。
5.6 supervise 目录:sv 与 runsv 的通信枢纽
每个被监管的服务目录下会生成 supervise/ 子目录:
$ ls -l /etc/sv/webdemo/supervise/
total 12
prw------- 1 root root 0 Oct 17 00:37 control
-rw------- 1 root root 0 Oct 17 00:37 lock
prw------- 1 root root 0 Oct 17 00:37 ok
-rw-r--r-- 1 root root 5 Oct 17 00:38 pid
-rw-r--r-- 1 root root 4 Oct 17 00:38 stat
-rw-r--r-- 1 root root 20 Oct 17 00:38 status
$ cat /etc/sv/webdemo/supervise/stat
run
$ cat /etc/sv/webdemo/supervise/pid
5626
$ ls -l /etc/sv/webdemo/supervise/control/
ls: cannot access '/etc/sv/webdemo/supervise/control/': Not a directory
control 与 ok 是命名管道(FIFO)——ls 当目录打开会报 Not a directory,恰好证明了这一点:sv 把 up/down/term 等单字符指令写进 control 管道,runsv 读取执行;ok 供 sv 确认 runsv 活着;stat/status/pid 是状态快照。理解这个结构,5.3 的三种报错(缺 ok、runsv 不在、目录不可达)就都能对号入座。
5.7 run 脚本铁律:前台、可执行、别自己守护
- 必须
exec一个前台进程。run 自己 fork 后台再退出 = runsv 认为服务死了 → 无限重拉(现象同 5.2 的崩溃循环)。 - 必须可执行(5.1),且 shebang 正确。
- 服务自带 daemon 化参数(如
-d)时不要用——runit 的价值就在它帮你守护,把"守护"交还给 runsv。 - 服务需要预处理(清临时文件、导出环境变量)写在
exec之前的普通命令里;需要收尾(清 pid 文件、告警)写进finish。
5.8 技术拓展:runit 与其他 init/服务管理器
| 维度 | runit(本文实测 2.3.1) | sysvinit | OpenRC | systemd |
|---|---|---|---|---|
| 进程监督 | 每服务一个 runsv 常驻监管,崩溃自动重拉 | 无监督,rc 脚本启动后不管 | 无内置监督(可配 supervise-daemon) | PID 1 统一监管 + cgroup |
| 服务定义 | /etc/sv/<svc>/run 可执行脚本 | /etc/init.d/<svc> shell 脚本 | /etc/init.d/<svc> + rc.conf | /usr/lib/systemd/system/*.unit |
| 启用方式 | 软链进 /var/service/ | rc.update/chkconfig 类 | rc-update add | systemctl enable |
| 依赖管理 | 无依赖树,靠目录组织与阶段划分 | 依赖靠脚本内逻辑 | 显式 depend() 声明 | 强依赖图(After/Requires) |
| 日志方案 | 按服务可选 svlogd | syslog | syslog | journald 内建 |
| 定时任务角色 | 无(配合 cron) | 无 | 无 | timer unit 可替代 cron |
| 体积与复杂度 | 极小(C 实现,二进制组合) | 小(shell) | 中(shell + 可选监督) | 大(全家桶) |
| 代表系统 | Void Linux(本文环境)、antiX、Artix 可选 | 传统 Debian、Slackware | Gentoo、Alpine、Artix | 主流发行版默认 |
runit 的哲学是"一组各司其职的小二进制":runit 管 1/2/3 三个阶段,runsvdir 管服务目录扫描,runsv 管单个服务的生死,sv 是人机接口,svlogd 管日志,chpst 管降权。没有依赖解析、没有事务,换来的是极快启动与极强可预测性——本文全部现象(pid 变化、管道指令、5 秒扫描)都能在源码级解释。
六、参考链接
- runit 官方站点:http://smarden.org/runit/(runsv(8)、sv(8)、runsvdir(8)、svlogd(8) 手册)
- Void Linux 手册·服务管理:https://docs.voidlinux.org/config/services/index.html
- runit-void 源码(Void 的服务脚本与 sv 集成):https://github.com/void-linux/runit-void
- 本专栏姊妹篇:xbps 命令详解与参数实测(Void Linux)——本文演示服务的 python3/curl 即由 XBPS 安装