叉形过程中禁用标准输出缓冲 [英] Disabling stdout buffering of a forked process

查看:94
本文介绍了叉形过程中禁用标准输出缓冲的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的C / C ++语言派生一个子进程,复制的标准输入/输出入管端并调用execvp。写了一个code

一切工作正常(即从标准输入/ ERR /输出输出由父进程捕获)

的问题是,子标准输出进行缓冲。

所以,如果孩子code是这样的:

 的printf(请输入任意键,按回车:\\ n);
与fgets(线);
的printf(读:%S \\ n,行);
出口(0);

在父进程,我不看行中输入任何键: - 这将是刷新程序调用后才能出口(其中自动刷新标准输出缓冲),或刷新(标准输出的显式调用)添加

我做了一些研究,并尝试添加一个呼叫通过添加一个调用禁用标准输出缓冲:

setvbuf用来(标准输出,NULL,_IONBF,0);
只是调用父进程execvp(...)之前

因此​​相关code现在看起来是这样的:

  INT RC = fork()的;
如果(RC == 0){
    //子进程
    如果(workingDirectory.IsEmpty()==假){
        wxSetWorkingDirectory(工作目录);
    }
    INT stdin_file =的fileno(标准输入);
    INT stdout_file =的fileno(标准输出);
    INT stderr_file =的fileno(标准错误);    //替换标准输入/输出我们的管端
    dup2(stdin_pipe_read,stdin_file);
    关闭(stdin_pipe_write);    dup2(stdout_pipe_write,stdout_file);
    dup2(stdout_pipe_write,stderr_file);
    关闭(stdout_pipe_read);    setvbuf用来(标准输出,NULL,_IONBF,0);    //执行过程
    execvp(的argv [0],argv的);
    出口(0);}

没有运气。

任何想法?

编辑:

下面是父code的样品,唯一需要改变的是路径子可执行文件:

 的#include<&unistd.h中GT;
#包括LT&;&signal.h中GT;
#包括LT&; SYS / types.h中>
#包括LT&; SYS / select.h>
#包括LT&;&errno.h中GT;
#包括LT&; SYS / wait.h>
#包括LT&;串GT;
#包括LT&;&string.h中GT;
#包括LT&;&cstdio GT;静态INT read_handle(-1);
静态将为pid_t PID;布尔read_from_child(标准::字符串&安培; BUFF){
    FD_SET RS;
    的timeval超时;    memset的(安培; RS,0,sizeof的(RS));
    FD_SET(read_handle,&安培; RS);
    timeout.tv_sec = 1; // 1秒
    timeout.tv_usec = 0;    INT RC =选择(read_handle + 1,&安培; RS,NULL,NULL,&安培;超时);
    如果(RC == 0){
        // 暂停
        返回true;    }否则如果(RC大于0){
        //有东西可以读
        字符缓冲区[1024 * 64]; //我们的读取缓冲区
        memset的(缓冲液,0,sizeof的(缓冲液));
        如果(读(read_handle,缓冲器,的sizeof(缓冲液))大于0){
            buff.clear();
            buff.append(缓冲液);
            返回true;
        }        返回false;
    }其他{/ * == 0 * /
        如果(RC == EINTR || RC == EAGAIN){
            返回true;
        }        //处理终止
        INT状态(0);
        waitpid函数(PID,和放大器;状态,0);
        返回false;
    }
}无效的execute(){
    的char * argv的[] = {/家/埃兰/ DEVL / TestMain /调试/ TestMain,NULL};
    INT ARGC = 1;    INT与filedes [2];
    INT filedes2 [2];    //创建一个管道
    INT D组;
    D =管(与filedes);
    D =管道(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()的;
    如果(RC == 0){        //子进程
        INT stdin_file =的fileno(标准输入);
        INT stdout_file =的fileno(标准输出);
        INT stderr_file =的fileno(标准错误);        //替换标准输入/输出我们的管端
        dup2(stdin_pipe_read,stdin_file);
        关闭(stdin_pipe_write);        dup2(stdout_pipe_write,stdout_file);
        dup2(stdout_pipe_write,stderr_file);
        关闭(stdout_pipe_read);        setvbuf用来(标准输出,NULL,_IONBF,0);        //执行过程
        execvp(的argv [0],argv的);    }否则如果(RC℃,){
        PERROR(叉);
        返回;    }其他{
        //父
        标准::字符串BUF;
        read_handle = stdout_pipe_read;
        而(read_from_child(BUF)){
            如果(buf.empty()==假){
                的printf(收到:%S \\ n,buf.c_str());
            }
            buf.clear();
        }
    }
}INT主(INT ARGC,字符** argv的){
    执行();
    返回0;
}


解决方案

其实,它挣扎了一下之后,好像对这个问题唯一的办法就是通过使'父'的过程pretending是一个终端使用的操作系统伪终端API调用。

每个人都应该称之为openpty()'叉()之前,孩子code里面,他应该叫'login_tty(奴隶),那么从正在成为标准输入/输出和标准错误。

通过pretending到终端,标准输出的缓冲自动设置为行模式(遇到的\\ n发生时即刷新)。家长应使用主描述符看书/与子进程写作。

修改父code(万一有人永远都需要这一点):

 的#include<&unistd.h中GT;
#包括LT&;&signal.h中GT;
#包括LT&; SYS / types.h中>
#包括LT&; SYS / select.h>
#包括LT&;&errno.h中GT;
#包括LT&; SYS / wait.h>
#包括LT&;串GT;
#包括LT&;&string.h中GT;
#包括LT&;&cstdio GT;
#包括LT&;&pty.h GT;
#包括LT&;&utmp.h GT;
静态INT read_handle(-1);
静态将为pid_t PID;布尔read_from_child(标准::字符串&安培; BUFF){
    FD_SET RS;
    的timeval超时;    memset的(安培; RS,0,sizeof的(RS));
    FD_SET(read_handle,&安培; RS);
    timeout.tv_sec = 1; // 1秒
    timeout.tv_usec = 0;    INT RC =选择(read_handle + 1,&安培; RS,NULL,NULL,&安培;超时);
    如果(RC == 0){
        // 暂停
        返回true;    }否则如果(RC大于0){
        //有东西可以读
        字符缓冲区[1024 * 64]; //我们的读取缓冲区
        memset的(缓冲液,0,sizeof的(缓冲液));
        如果(读(read_handle,缓冲器,的sizeof(缓冲液))大于0){
            buff.clear();
            buff.append(缓冲液);
            返回true;
        }        返回false;
    }其他{/ * == 0 * /
        如果(RC == EINTR || RC == EAGAIN){
            返回true;
        }        //处理终止
        INT状态(0);
        waitpid函数(PID,和放大器;状态,0);
        返回false;
    }
}无效的execute(){
    的char * argv的[] = {/家/埃兰/ DEVL / TestMain /调试/ TestMain,NULL};
    INT ARGC = 1;    INT主,从;
    openpty(安培;主,和放大器;奴隶,NULL,NULL,NULL);    INT RC = fork()的;
    如果(RC == 0){
        login_tty(奴隶);
        接近(主);        //执行过程
        如果(execvp(argv的[0],argv的)!= 0)
            PERROR(execvp);    }否则如果(RC℃,){
        PERROR(叉);
        返回;    }其他{
        //父
        标准::字符串BUF;
        接近(从);        read_handle =主;
        而(read_from_child(BUF)){
            如果(buf.empty()==假){
                的printf(收到:%S,buf.c_str());
            }
            buf.clear();
        }
    }
}INT主(INT ARGC,字符** argv的){
    执行();
    返回0;
}

I wrote a code in C/C++ which forks a child process, duplicates the stdin/stdout into a pipe ends and calls execvp.

Everything is working fine (i.e. the output from stdin/err/out is captured by the parent process)

The problem is that the child stdout is buffered.

so if the child code looks like this:

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

In the parent process I don't see the line 'Enter any key:' - it will be "flushed" only after the program calls exit (which auto flushes the stdout buffer) or an explicit call to 'flush(stdout)' is added

I did some research and tried adding a call to disable the stdout buffering by adding a call to:

setvbuf(stdout, NULL, _IONBF, 0); just before calling execvp(...) in the parent process

so the relevant code looks now like this:

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);

}

With no luck.

Any ideas?

EDIT:

here is a sample of the parent code, the only thing needs changing is the path to the child executable:

#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;
}

解决方案

Actually, after struggling with it a bit, it seems like the only solution to this problem is by making the 'parent' process pretending to be a terminal using the OS pseudo terminal API calls.

One should call 'openpty()' before the fork(), and inside the child code, he should call 'login_tty(slave)' the slave is then becoming the stdin/out and stderr.

By pretending to a terminal, the buffering of stdout is automatically set to 'line mode' (i.e. flush occurs when \n is encountered). The parent should use the 'master' descriptor for readin/writing with the child process.

The modified parent code (in case anyone will ever need this):

#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;
}

这篇关于叉形过程中禁用标准输出缓冲的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆