分叉后退出()或_exit()?

时间:2013-05-04 07:48:55

标签: c++ fork exit

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

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

我听说使用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));
        }
    }
}

2 个答案:

答案 0 :(得分:0)

你需要小心这些事情。 exit()_exit()做了不同的事情,但又与_Exit()做了不同的事情,并且正如duplicate所解释的答案所解释的那样_Exit(与_exit不同{1}},注意大写字母E)不会调用atexit()个处理程序,或者刷新任何输出缓冲区,删除临时文件等[实际上可能是atexit()处理,但它也可以完成作为直接调用,取决于C库代码的编写方式]。

您的大部分输出都是通过write完成的,应该从应用程序的角度来看它是无缓冲的。但你也在呼叫cout << ...。您需要确保在退出之前刷新它。现在,您使用'\n'作为行结束标记,可能会也可能不会刷新输出。如果您将其更改为endl,则会刷新文件。现在,您可以从输出角度安全地使用_Exit() - 如果您的代码要设置自己的atexit()处理程序,例如打开临时文件或其他一些此类内容,则会出现问题。如果您想在分叉流程中执行更复杂的操作,则应由另一个exec完成。

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

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

答案 1 :(得分:0)

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