Rust中如何让子进程在父进程接收SIGINT/SIGTERM后存活
解决Linux下Rust父进程被信号终止时子进程存活的问题
这是典型的进程组信号传递问题——默认情况下,你启动的子进程和父进程属于同一个进程组。当父进程收到SIGINT(Ctrl+C)或SIGTERM这类终端信号时,内核会把信号广播给整个进程组的所有进程,所以你的子进程会跟着父进程一起退出。这也是为什么你之前尝试的多线程方案都没用——线程仍属于同一个进程,进程组并未改变。
而你测试的spawn_and_exit能正常工作,是因为父进程主动调用process::exit(0)时,内核不会触发进程组信号广播,子进程只是变成孤儿进程被systemd/init收养,自然能继续运行。
Linux下的核心解决方案:让子进程脱离父进程组
我们可以通过两个系统调用实现这个目标,二选一即可:
setpgid:将子进程的进程组ID设置为自身PID,脱离父进程的进程组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
相关产品推荐
相关产品推荐

