【问题标题】:Disabling stdout buffering of a forked process禁用分叉进程的标准输出缓冲
【发布时间】:2010-10-30 09:05:43
【问题描述】:

我用 C/C++ 编写了一个代码,它派生出一个子进程,将标准输入/标准输出复制到管道末端并调用 execvp。

一切正常(即 stdin/err/out 的输出被父进程捕获)

问题是子标准输出被缓冲了。

所以如果子代码如下所示:

printf("Enter any key and hit ENTER:\n");
fgets(line);
printf("read: %s\n", line);
exit(0);

在父进程中,我看不到“输入任何键:”行 - 只有在程序调用 exit(自动刷新标准输出缓冲区)或显式调用“flush(stdout )' 被添加

我做了一些研究并尝试通过添加对以下内容的调用来添加禁用标准输出缓冲的调用:

setvbuf(stdout, NULL, _IONBF, 0); 就在父进程中调用 execvp(...) 之前

所以相关代码现在看起来像这样:

int rc = fork();
if ( rc == 0 ) {
    // Child process
    if(workingDirectory.IsEmpty() == false) {
        wxSetWorkingDirectory( workingDirectory );
    }
    int stdin_file  = fileno( stdin  );
    int stdout_file = fileno( stdout );
    int stderr_file = fileno( stderr );

    // Replace stdin/out with our pipe ends
    dup2 ( stdin_pipe_read,  stdin_file );
    close( stdin_pipe_write );

    dup2 ( stdout_pipe_write, stdout_file);
    dup2 ( stdout_pipe_write, stderr_file);
    close( stdout_pipe_read );

    setvbuf(stdout, NULL, _IONBF, 0);

    // execute the process
    execvp(argv[0], argv);
    exit(0);

}

没有运气。

有什么想法吗?

编辑:

这里是父代码的示例,唯一需要更改的是子可执行文件的路径:

#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/select.h>
#include <errno.h>
#include <sys/wait.h>
#include <string>
#include <string.h>
#include <cstdio>

static int   read_handle(-1);
static pid_t pid;

bool read_from_child(std::string& buff) {
    fd_set  rs;
    timeval timeout;

    memset(&rs, 0, sizeof(rs));
    FD_SET(read_handle, &rs);
    timeout.tv_sec  = 1; // 1 second
    timeout.tv_usec = 0;

    int rc = select(read_handle+1, &rs, NULL, NULL, &timeout);
    if ( rc == 0 ) {
        // timeout
        return true;

    } else if ( rc > 0 ) {
        // there is something to read
        char buffer[1024*64]; // our read buffer
        memset(buffer, 0, sizeof(buffer));
        if(read(read_handle, buffer, sizeof(buffer)) > 0) {
            buff.clear();
            buff.append( buffer );
            return true;
        }

        return false;
    } else { /* == 0 */
        if ( rc == EINTR || rc == EAGAIN ) {
            return true;
        }

        // Process terminated
        int status(0);
        waitpid(pid, &status, 0);
        return false;
    }
}

void execute() {
    char *argv[] = {"/home/eran/devl/TestMain/Debug/TestMain", NULL};
    int    argc = 1;

    int filedes[2];
    int filedes2[2];

    // create a pipe
    int d;
    d = pipe(filedes);
    d = pipe(filedes2);

    int stdin_pipe_write = filedes[1];
    int stdin_pipe_read  = filedes[0];

    int stdout_pipe_write = filedes2[1];
    int stdout_pipe_read  = filedes2[0];

    int rc = fork();
    if ( rc == 0 ) {

        // Child process
        int stdin_file  = fileno( stdin  );
        int stdout_file = fileno( stdout );
        int stderr_file = fileno( stderr );

        // Replace stdin/out with our pipe ends
        dup2 ( stdin_pipe_read,  stdin_file );
        close( stdin_pipe_write );

        dup2 ( stdout_pipe_write, stdout_file);
        dup2 ( stdout_pipe_write, stderr_file);
        close( stdout_pipe_read );

        setvbuf(stdout, NULL, _IONBF, 0);

        // execute the process
        execvp(argv[0], argv);

    } else if ( rc < 0 ) {
        perror("fork");
        return;

    } else {
        // Parent
        std::string buf;
        read_handle = stdout_pipe_read;
        while(read_from_child(buf)) {
            if(buf.empty() == false) {
                printf("Received: %s\n", buf.c_str());
            }
            buf.clear();
        }
    }
}

int main(int argc, char **argv) {
    execute();
    return 0;
}

【问题讨论】:

  • 我认为你需要在分别调用 dup2 之后关闭(stdout_pipe_write) 和 stdin_piperead

标签: c++ c posix


【解决方案1】:

实际上,经过一番挣扎后,似乎解决这个问题的唯一方法是使用操作系统伪终端 API 调用将“父”进程伪装成终端。

应该在 fork() 之前调用 'openpty()',在子代码中,他应该调用 'login_tty(slave)',slave 将成为标准输入/输出和标准错误。

通过伪装成终端,stdout 的缓冲会自动设置为“行模式”(即在遇到 \n 时发生刷新)。父进程应该使用“主”描述符来读取/写入子进程。

修改后的父代码(以防万一有人需要):

#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/select.h>
#include <errno.h>
#include <sys/wait.h>
#include <string>
#include <string.h>
#include <cstdio>
#include <pty.h>
#include <utmp.h>
static int   read_handle(-1);
static pid_t pid;

bool read_from_child(std::string& buff) {
    fd_set  rs;
    timeval timeout;

    memset(&rs, 0, sizeof(rs));
    FD_SET(read_handle, &rs);
    timeout.tv_sec  = 1; // 1 second
    timeout.tv_usec = 0;

    int rc = select(read_handle+1, &rs, NULL, NULL, &timeout);
    if ( rc == 0 ) {
        // timeout
        return true;

    } else if ( rc > 0 ) {
        // there is something to read
        char buffer[1024*64]; // our read buffer
        memset(buffer, 0, sizeof(buffer));
        if(read(read_handle, buffer, sizeof(buffer)) > 0) {
            buff.clear();
            buff.append( buffer );
            return true;
        }

        return false;
    } else { /* == 0 */
        if ( rc == EINTR || rc == EAGAIN ) {
            return true;
        }

        // Process terminated
        int status(0);
        waitpid(pid, &status, 0);
        return false;
    }
}

void execute() {
    char *argv[] = {"/home/eran/devl/TestMain/Debug/TestMain", NULL};
    int    argc = 1;

    int master, slave;
    openpty(&master, &slave, NULL, NULL, NULL);

    int rc = fork();
    if ( rc == 0 ) {
        login_tty(slave);
        close(master);

        // execute the process
        if(execvp(argv[0], argv) != 0)
            perror("execvp");

    } else if ( rc < 0 ) {
        perror("fork");
        return;

    } else {
        // Parent
        std::string buf;
        close(slave);

        read_handle = master;
        while(read_from_child(buf)) {
            if(buf.empty() == false) {
                printf("Received: %s", buf.c_str());
            }
            buf.clear();
        }
    }
}

int main(int argc, char **argv) {
    execute();
    return 0;
}

【讨论】:

【解决方案2】:

在 printf 之后插入对fflush(stdout) 的调用不够吗?

否则 setvbuf 应该可以解决问题:

setvbuf(stdout,NULL,_IOLBF,0);

【讨论】:

  • fflush(NULL) 将刷新所有打开的FILEs,在forking 之前可能是个好主意。
  • 我可以将 fflush(stdout) 添加到示例代码中,但是,我经常“分叉”我无法控制的第三方代码。我添加了对 setvbuf(..) 的调用,其中包含我能想到的所有可能选项,但没有运气......
  • 你能显示父母的代码吗?理想情况下,您可以创建显示问题的最小可能示例。使用说明问题的起点在我的系统上进行调试/测试会更容易。
  • @awoodland:我修改了原始帖子,它现在包含父代码的最小化版本,它执行 + 启动循环以读取子级的输出。使用我提供的客户端示例,您可以看到问题(至少在我的 Ubuntu 10.04 / 64 位下可见)
  • 谢谢,我稍后再试试看
【解决方案3】:

【讨论】:

    【解决方案4】:

    如果您需要对子进程的缓冲进行更多控制,我制作了一个模块来公开 stdbuf 预加载技巧的功能,请参阅我的这个自我回答的问题:

    C Control buffering of Child process

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-11-28
      • 2013-10-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-12-07
      相关资源
      最近更新 更多