【问题标题】:Start another program then quit启动另一个程序然后退出
【发布时间】:2018-11-26 09:13:50
【问题描述】:

从一个用 rust 编写的程序 A,我想启动一个程序 B,让 A 结束,让 B 正常运行,就像它在 A 终止后从同一个 shell 手动启动一样。

我目前的计划:

use std::process::Command;

pub fn execute(exe: &str, args: &[&str]) {
    Command::new(exe)
        .args(args)
        .spawn()
        .expect("failed to start external executable");
}

fn main() {
    execute("/usr/bin/nvim", &["/home/dys/todo.txt"]);
}

这失败了。 nvim 是作为子程序启动的,一旦调用程序停止,它就无法工作。

如何编写execute 以便调用程序立即停止并让nvim(或其他程序)正常运行(即使没有任何窗口系统)?

【问题讨论】:

  • 父进程死后Windows真的会杀死所有子进程吗?自从我在 Windows 上进行软件开发以来已经过去了 20 年,但我不记得这种行为了。
  • @SvenMarnach 我不知道 Windows,因为我现在没有任何可用的盒子。我的要求是让它在 windows、linux 和 macos 上运行。我将删除操作系统标签(可能有一段时间)以明确这不是 Windows 特定的问题。
  • 我错过了还有一个 Linux 标签。当父进程退出时,Linux 和 Windows 都不会杀死子进程。
  • @SvenMarnach 因为process::spawn() 正在父子之间设置一些管道。有一个while true; echo foo; sleep 1; done 作为孩子,你应该看到失败。
  • 这解释了为什么 os.setsid() 在开始时该过程有效,以及为什么它不能用 strace 重现(stdin 不是 pty)

标签: process rust


【解决方案1】:

经过进一步讨论,我们确定了实际问题:您正在启动的程序应该停留在前台,因此它可以从终端读取(在 Unix 上后台进程不能这样做)。

有两种方法可以实现这一点。第一个也是最简单的方法是在父进程退出之前等待子进程:

use std::process::{Command, ExitStatus};
use std::io::Result;

pub fn execute(exe: &str, args: &[&str]) -> Result<ExitStatus> {
    Command::new(exe).args(args).spawn()?.wait()
}

这确保进程(父进程和子进程)保持在前台,因为 shell 正在等待父进程,因此子进程可以从终端读取。

如果由于某种原因您无法在子进程运行时让父进程逗留,那么您需要依赖于平台的代码。在 Unix 上,您可以使用来自 exec() 家族的一些系统调用将父进程的图像替换为子进程的图像:

use std::process::Command;
use std::os::unix::process::CommandExt;
use std::io::Error;

pub fn execute(exe: &str, args: &[&str]) -> Error {
    Command::new(exe).args(args).exec()
}

该函数仅在出现错误时返回。否则,过程映像将被新映像替换。从shell的角度来看,还是同一个进程,所以shell会等待你启动的命令完成。

第二种方法的优势似乎微乎其微。它不适用于 Windows,因为 Windows 不支持exec() 和朋友。运行该命令时您将少一个进程,但实际上该进程的资源使用量应该很小——它不使用任何 CPU,并且可以根据需要换出内存页面。

原答案

从一个用 rust 编写的程序 A,我想启动一个程序 B,让 A 结束,让 B 正常运行,就像它在 A 终止后从同一个 shell 手动启动一样。

这或多或少是您的代码已经在做的事情。不过,在 Unix 系统上直接从 shell 启动的进程有一些不同之处:

  • 新进程不会包含在shell的作业列表中,所以不能使用bgfg等shell的作业控制命令。
  • 新进程将在后台运行,Rust 程序退出后,shell 会立即显示提示。

这失败了,因为 nvim 是作为一个子进程启动的,并在调用程序停止时立即被杀死。

UnixWindows 都不是这样。

如何编写 execute 以便调用程序立即停止并让 nvim(或其他程序)正常运行(即使没有任何窗口系统)?

这应该正是您的 Rust 代码正在做的事情(以及它在我的 Linux 机器上运行时所做的事情)。另一方面,您答案中的代码做了其他事情:它使用execv() 将Rust 进程替换为nvim。实际上,该进程不会立即停止,并且 shell 一直处于阻塞状态,直到 nvim 退出。

【讨论】:

  • "以及它在我的 Linux 机器上运行时的作用" 您是否尝试过使用像 vim 这样的真正终端程序而不仅仅是 sleep ?你的解释很有趣,但我不明白你是如何解决问题的。
  • @DenysSéguret 我没有安装“nvim”,所以我没有尝试,我尝试了bash -c 'while true; do echo a; sleep 2; done',它仍然在后台运行。
  • @DenysSéguret 我还建议了一个简单的解决方案,该解决方案的行为类似于您在 cmets 中对您的回答的回答 - 具体来说,只需等待子进程而不是立即退出。我没有在这个答案中包含这个,因为它与问题中的要求直接冲突(要求调用程序立即退出)。
  • 感谢您的回答。接受与否只有在更多的研究之后才会出现。
  • @DenysSéguret 我调整了答案以解释 cmets 中的发现。
【解决方案2】:

这是一个在 linux 上工作的解决方案,使用 execv 函数的包装:


use nix::unistd;
use std::ffi::CString;

pub fn executev(args: &[&str]) {
    let mut args: Vec<CString> = args.iter()
        .map(|t| CString::new(*t).expect("not a proper CString"))
        .collect();
    unistd::execv(
        &args[0],
        &args,
    ).expect("failed");
}

fn main() {
    executev(&["/usr/bin/nvim", "/home/dys/todo.txt"]);
}

注意:确实启动另一个程序并退出,但要小心替换当前进程意味着您正确关闭了打开的资源。如果您可以接受让您的程序保持活动状态,您可能希望按照 Sven Marnach 的建议发送电子邮件至 wait

【讨论】:

  • 如果确认这是正确的解决方案,并且确认它已经在 Windows 上运行,那么我将清理这个快速编写的解决方案。
  • 这会有所不同:execv() 函数在成功时不会返回,当前进程被新进程替换。请注意,Command::spawn() 在后台也使用execv(),但仅在fork() 子进程之后使用。我建议查看 into this answer 以了解您所看到行为的可能根本原因。
  • @SvenMarnach 我的理解是避免fork 是目标
  • 那么简单地等待子进程也可以工作,并且避免特定于平台的代码。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-06-02
  • 2016-05-22
  • 2018-03-04
  • 1970-01-01
  • 1970-01-01
  • 2014-01-26
  • 2019-07-13
相关资源
最近更新 更多