中文
TL;DR
守护进程应该参考 nvidia-persistenced 去写
我有一个命令行应用,想要实现成守护进程,是这么写的
rust
use nix::unistd::{ForkResult, fork};
fn main() {
match unsafe { fork() } {
Ok(ForkResult::Parent { child, .. }) => {
// 进程A,没有去调用 wait 等待子进程 B;
// A 进程等待到 B 启动后退出
}
Ok(ForkResult::Child) => {
// A fork 出的进程 B,未被 wait,成为孤儿进程;
// 启动一个 server,死循环不退出
}
}
}
另外有一个 rust 编写的进程 C 去运行这个应用,它的实现和对应的现象是: 在进程 C 中 systemd 启动上述应用的服务,一切正常; 但是在没有 systemd 的环境下,进程 C 中会直接调用 std::process::Command
上述应用,调用了 std::process::Command::output()
等待应用退出。但是此时整个 C 进程就卡在了这里。此时通过 ps 命令可以查到,A 变成了一个僵尸进程。
换了一下进程 C 中的实现,发现调用 std::process::Child::wait()
就没问题了,std::process::Child::wait_with_output()
则会出现上面的现象。
阅读一下该方法的实现,可以发现它会先尝试读取 stdio 的 stdout 和 stderr,再进行 wait。于是这里 C 进程卡住的原因就是 A 进程的子进程 B 继承了 A 的 stdio fd,
rust
pub fn wait_with_output(
mut process: Process,
mut pipes: StdioPipes,
) -> io::Result<(ExitStatus, Vec<u8>, Vec<u8>)> {
drop(pipes.stdin.take());
let (mut stdout, mut stderr) = (Vec::new(), Vec::new());
match (pipes.stdout.take(), pipes.stderr.take()) {
(None, None) => {}
(Some(out), None) => {
let res = out.read_to_end(&mut stdout);
res.unwrap();
}
(None, Some(err)) => {
let res = err.read_to_end(&mut stderr);
res.unwrap();
}
(Some(out), Some(err)) => {
let res = read2(out, &mut stdout, err, &mut stderr);
res.unwrap();
}
}
let status = process.wait()?;
Ok((status, stdout, stderr))
}
所以需要 close 关闭或 dup 重定向之前的 fd。
rust
use nix::unistd::{ForkResult, fork};
fn dup_std_fds() {
let dev_null = std::fs::File::open("/dev/null").expect("Failed to open /dev/null");
let dev_null_fd = dev_null.as_raw_fd();
for &fd in &[libc::STDIN_FILENO, libc::STDOUT_FILENO, libc::STDERR_FILENO] {
let _ = unsafe { libc::dup2(dev_null_fd, fd) };
}
}
fn main() {
match unsafe { fork() } {
Ok(ForkResult::Parent { child, .. }) => {
// 进程A,没有去 wait 子进程 B
}
Ok(ForkResult::Child) => {
// A fork 出的进程 B,让它成为孤儿进程
}
}
}