【问题标题】:exit() or _exit() after forking?分叉后退出()或_exit()?
【发布时间】:2013-04-28 14:59:50
【问题描述】:

我正在编写一个程序,它需要同时与外部程序进行双向通信,即同时读取和写入外部程序。

我创建了两个管道,一个用于向外部进程发送数据,一个用于从外部进程接收数据。在 fork 成为外部程序的子进程之后,父进程再次 fork。新的子进程现在将数据写入到外部程序的传出管道中,而父进程现在从外部程序的传入管道中读取数据以进行进一步处理。

我听说使用 exit(3) 可能会导致缓冲区被刷新两次,但是我也担心使用 _exit(2) 可能会使缓冲区未刷新。在我的程序中,分叉前后都有输出。在这种情况下,我应该使用哪个 exit(3) 或 _exit(2)?

以下是我的主要功能。为简单起见,省略了#includes 和辅助函数。

int main() {
    srand(time(NULL));
    ssize_t n;
    cin >> n;
    for (double p = 0.0; p <= 1.0; p += 0.1) {
        string s = generate(n, p);
        int out_fd[2];
        int in_fd[2];
        pipe(out_fd);
        pipe(in_fd);
        pid_t child = fork();
        if (child) {
            // parent
            close(out_fd[0]);
            close(in_fd[1]);
            if (fork()) {
                close(out_fd[1]);
                ssize_t size = 0;
                const ssize_t block_size = 1048576;
                char buf[block_size];
                ssize_t n_read;
                while ((n_read = read(in_fd[0], buf, block_size)) != 0) {
                    size += n_read;
                }
                size += n_read;
                close(in_fd[0]);
                cout << "p = " << p << "; compress ratio = " << double(size) / double(n) << '\n'; // data written before forking (the loop continues to fork)
            } else {
                write(out_fd[1], s.data(), s.size()); // data written after forking
                exit(EXIT_SUCCESS); // exit(3) or _exit(2) ?
            }
        } else {
            // child
            close(in_fd[0]);
            close(out_fd[1]);
            dup2(out_fd[0], STDIN_FILENO);
            dup2(in_fd[1], STDOUT_FILENO);
            close(STDERR_FILENO);
            execlp("xz", "xz", "-9", "--format=raw", reinterpret_cast<char *>(NULL));
        }
    }
}

【问题讨论】:

  • @hvd:不,我不这么认为。链接的副本是关于处理错误的。这个案例好像不是关于错误的(虽然子进程中的错误处理好像有点欠缺……
  • @MatsPetersson 我不明白其他问题是如何处理错误的,你能详细说明一下吗?无论如何,我认为在接受的答案中提出的观点在这里适用。
  • 好的,这是关于“当某种形式的 exec 返回时你会做什么”,这是一个错误条件,因为“exec”将替换现有进程。这个更复杂,因为子进程中的输出仍在作为分叉进程执行。在这种情况下,你也不能只使用_exit()
  • @MatsPetersson 另一个问题在子进程中也有输出:它调用perror。是的,输出发生在那个问题的错误条件下,而不是这个问题的错误条件下,但我看不出这有什么区别。实现所知道的只是输出已写入,它不知道也无法知道它是否是错误条件。 (实际上,根据具体的exec,程序员可能也无从得知是否是错误情况!)

标签: c++ fork exit


【解决方案1】:

在 fork() 的子分支中,使用 exit() 通常是不正确的,因为这会导致 stdio 缓冲区被刷新两次,并且临时文件被意外删除。在 C++ 代码中情况更糟,因为静态对象的析构函数可能运行不正确。 (有一些不寻常的情况,比如守护进程,父进程应该调用 _exit() 而不是子进程;适用于绝大多数情况的基本规则是每次进入 main 时都应该只调用一次 exit() .)

【讨论】:

    【解决方案2】:

    您需要小心处理这些事情。 exit()_exit() 做不同的事情,又与_Exit() 不同,正如duplicate 所建议的答案所解释的那样,_Exit(与_exit 不同,注意大写E)不会调用atexit() 处理程序,或刷新任何输出缓冲区、删除临时文件等[实际上可能是atexit() 处理,但也可以直接调用,具体取决于C 库代码的编写方式] .

    您的大部分输出是通过write 完成的,从应用程序的角度来看,它应该是无缓冲的。但是你也打电话给cout &lt;&lt; ...。您需要确保在退出之前已刷新。现在,您使用'\n' 作为行尾标记,它可能会或可能不会刷新输出。如果将其更改为 endl,它将刷新文件。现在您可以从输出的角度安全地使用_Exit() - 例如,如果您的代码要设置自己的atexit() 处理程序,打开临时文件或一堆其他类似的东西,这将是有问题的。如果你想在分叉的过程中做更复杂的事情,应该由另一个exec来完成。

    在你的程序中,没有任何待刷新的输出,所以无论如何它“工作”,但如果你在代码开头添加cout &lt;&lt; ... &lt;&lt; '\n';(或没有换行符)类型语句,你会看到它出错了。如果您添加cout.flush();,它将“修复”问题(基于您当前的代码)。

    您还应该检查您的execlp() 调用的返回值,并在这种情况下调用_Exit()(并在主进程中处理它,以免在失败时继续循环?)

    【讨论】:

    • 我的代码仅供内部使用(它不是应用程序代码),所以为了简单起见,我省略了错误检查。但是,我的代码必须准确输出我打算这样做的内容,因为它会被外部程序解析。
    猜你喜欢
    • 2015-12-12
    • 1970-01-01
    • 2011-01-20
    • 1970-01-01
    • 1970-01-01
    • 2017-12-18
    • 1970-01-01
    • 2013-08-01
    • 1970-01-01
    相关资源
    最近更新 更多