You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rust中如何让子进程在父进程接收SIGINT/SIGTERM后存活

解决Linux下Rust父进程被信号终止时子进程存活的问题

这是典型的进程组信号传递问题——默认情况下,你启动的子进程和父进程属于同一个进程组。当父进程收到SIGINT(Ctrl+C)或SIGTERM这类终端信号时,内核会把信号广播给整个进程组的所有进程,所以你的子进程会跟着父进程一起退出。这也是为什么你之前尝试的多线程方案都没用——线程仍属于同一个进程,进程组并未改变。

而你测试的spawn_and_exit能正常工作,是因为父进程主动调用process::exit(0)时,内核不会触发进程组信号广播,子进程只是变成孤儿进程被systemd/init收养,自然能继续运行。

Linux下的核心解决方案:让子进程脱离父进程组

我们可以通过两个系统调用实现这个目标,二选一即可:

  1. setpgid:将子进程的进程组ID设置为自身PID,脱离父进程的进程组
  2. setsid:创建一个新的会话,让子进程成为会话首进程,彻底脱离父进程的控制终端和进程组

下面是具体的Rust实现方式:

方法1:使用nix库(类型安全,推荐)

首先在Cargo.toml中添加依赖:

[dependencies]
nix = "0.27"

然后编写脱离进程组的spawn函数:

use nix::unistd::{setpgid, Pid};
use std::process::{Command, Stdio};
use std::thread;
use std::time::Duration;

const EXECUTABLE: &str = "/usr/bin/firefox";

fn wait() {
    thread::sleep(Duration::from_millis(250));
}

fn hang() {
    println!("You can now kill the process (e.g. Ctrl+C)");
    loop {
        wait();
    }
}

// 启动脱离父进程组的子进程
fn spawn_detached() -> std::io::Result<std::process::Child> {
    Command::new(EXECUTABLE)
        // 重定向标准IO到null,避免子进程依赖父进程的终端
        .stdin(Stdio::null())
        .stdout(Stdio::null())
        .stderr(Stdio::null())
        .before_exec(|| {
            // setpgid(0,0) 等价于将当前进程的组ID设为自身PID
            setpgid(Pid::from_raw(0), Pid::from_raw(0))?;
            Ok(())
        })
        .spawn()
}

// 测试:父进程挂起,按Ctrl+C后子进程继续存活
fn spawn_and_force_shutdown() {
    println!("Spawn and force shutdown (detached)");
    let _child = spawn_detached().unwrap();
    wait();
    hang();
}

fn main() {
    spawn_and_force_shutdown();
}

方法2:直接使用libc调用系统调用

如果不想引入额外库,可以用libc直接调用底层系统函数:

[dependencies]
libc = "0.2"

对应的代码:

use libc::{setpgid, setsid};
use std::os::unix::process::CommandExt;
use std::process::{Command, Stdio};
use std::thread;
use std::time::Duration;

const EXECUTABLE: &str = "/usr/bin/firefox";

fn wait() {
    thread::sleep(Duration::from_millis(250));
}

fn hang() {
    println!("You can now kill the process (e.g. Ctrl+C)");
    loop {
        wait();
    }
}

fn spawn_detached() -> std::io::Result<std::process::Child> {
    Command::new(EXECUTABLE)
        .stdin(Stdio::null())
        .stdout(Stdio::null())
        .stderr(Stdio::null())
        .pre_exec(|| {
            unsafe {
                // 选择setpgid或setsid其中一个即可
                if setpgid(0, 0) == -1 {
                    return Err(std::io::Error::last_os_error());
                }
                // 或者用setsid彻底脱离终端:
                // if setsid() == -1 {
                //     return Err(std::io::Error::last_os_error());
                // }
            }
            Ok(())
        })
        .spawn()
}

fn spawn_and_force_shutdown() {
    println!("Spawn and force shutdown (detached)");
    let _child = spawn_detached().unwrap();
    wait();
    hang();
}

fn main() {
    spawn_and_force_shutdown();
}

为什么这个方法有效?

当子进程通过setpgid或setsid脱离父进程组后:

  • 父进程收到SIGINT/SIGTERM时,信号只会发送给父进程所在的进程组
  • 子进程在独立的进程组(或会话)中,不会收到这个信号,因此会继续运行,即使父进程被终止

内容的提问来源于stack exchange,提问作者BafDyce

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 15:18:10