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 对比。

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

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)sysvinitOpenRCsystemd
进程监督每服务一个 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 addsystemctl enable
依赖管理无依赖树,靠目录组织与阶段划分依赖靠脚本内逻辑显式 depend() 声明强依赖图(After/Requires)
日志方案按服务可选 svlogdsyslogsyslogjournald 内建
定时任务角色无(配合 cron)无无timer unit 可替代 cron
体积与复杂度极小(C 实现,二进制组合)小(shell)中(shell + 可选监督)大(全家桶)
代表系统Void Linux(本文环境)、antiX、Artix 可选传统 Debian、SlackwareGentoo、Alpine、Artix主流发行版默认

runit 的哲学是"一组各司其职的小二进制":runit 管 1/2/3 三个阶段,runsvdir 管服务目录扫描,runsv 管单个服务的生死,sv 是人机接口,svlogd 管日志,chpst 管降权。没有依赖解析、没有事务,换来的是极快启动与极强可预测性——本文全部现象(pid 变化、管道指令、5 秒扫描)都能在源码级解释。

六、参考链接