bigywater.tech
Rust · 145

子进程与 Command

Command 看起来很简单,实际有不少陷阱:参数会不会被 shell 解释?退出码与信号怎么区分?管道读法不对为什么死锁?子进程什么时候变僵尸?这篇每个问题都跑一个真实的子进程来回答。

0 次注入
含 ; 与 $HOME 的参数被 shell 解释的次数(参数不经过 shell)
None
子进程被信号终止时 ExitStatus::code() 的值
Z
没有 wait 的已退出子进程在 ps 里的状态(僵尸)

output 与参数

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    echo 输出 "hello a; echo INJECTED $HOME\n"  状态 Some(0)  成功 true
    (参数里的 ; 和 $HOME 原样当作字符,没有被 shell 解释,也就没有 INJECTED 这个独立命令)
    sh -c:stdout "out\n" stderr "err\n" 退出码 Some(3) 成功 false

信号的细节见信号处理;标准输入输出重定向见标准输入输出重定向。

错误与退出状态

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    不存在的命令:kind = NotFound,os_error = Some(2)
    没有执行权限的文件:kind = PermissionDenied,os_error = Some(13)
    子进程被 SIGTERM 终止:code() = None,signal() = Some(15),success() = false
    Display:signal: 15 (SIGTERM)

管道

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    sort -r 的输出:"cherry\nbanana\napple\n"
    管道 printf | wc -l:"3"

管道死锁

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    500 ms 后:读 stdout 的线程完成了吗? false(子进程卡在写 stderr,没有机会写 stdout 或退出)
    现在读 stderr,读到 200000 字节(用时 2 ms),死锁解除
    stdout 线程结果:"done"
    对照:output() 内部同时读两个管道,不会死锁:stdout "done",stderr 200000 字节

死锁是 macOS 上的实测现象;管道缓冲区的具体大小没有测量。

环境与工作目录

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    继承环境 + env + current_dir:MY=from-rust HOME_SET=yes PWD=tmp
    env_clear 之后只剩手动设置的:MY=only HOME_SET=
    env_remove("HOME"):HOME_SET=

环境变量读取见环境变量读取。

僵尸进程

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    子进程 true 已经退出,父进程没有 wait,ps 状态:"Z"(Z = 僵尸)
    drop(Child) 之后状态:"Z"(drop 不会 wait)
    调用 wait() 之后:退出状态 Some(0),ps 状态 ""(已被回收,不再存在)

分步演示

子进程与 Command
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动。sort、wc、printf、sh 为系统自带
    • 没有验证:Windows 上的 Command(参数拼接成命令行字符串的转义规则)、Stdio::inherit 的行为、process_group 与 setsid、kill 一个进程树、tokio::process、exec 替换当前进程(CommandExt::exec)
    京ICP备2026067502号-1京公网安备11010502063487号