Отключение буферизации стандартного вывода разветвленного процесса - PullRequest
7 голосов
/ 30 октября 2010

Я написал код на C / C ++, который разветвляет дочерний процесс, дублирует stdin / stdout в конец канала и вызывает execvp.

Все работает нормально (т.е. вывод из stdin / err / out перехватывается родительским процессом)

Проблема в том, что дочерний стандартный буфер буферизован.

так что, если дочерний код выглядит так:

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

В родительском процессе я не вижу строки «Enter any key:» - она ​​будет «очищена» только после того, как программа вызовет exit (который автоматически очищает буфер stdout) или явный вызов flush (stdout ) 'добавлено

Я провел некоторое исследование и попытался добавить вызов, чтобы отключить буферизацию стандартного вывода, добавив вызов:

setvbuf (стандартный вывод, 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);

}

Без удачи.

Есть идеи?

EDIT:

Вот пример родительского кода, единственное, что нужно изменить, - это путь к дочернему исполняемому файлу:

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

Ответы [ 3 ]

10 голосов
/ 31 октября 2010

На самом деле, после некоторой борьбы с этим, кажется, что единственное решение этой проблемы - сделать «родительский» процесс притворяющимся терминалом, использующим вызовы API псевдотерминала ОС.

Нужновызовите openpty () перед fork () и внутри дочернего кода он должен вызвать login_tty (slave), после чего ведомый становится stdin / out и stderr.

Притворяясь терминаломбуферизация стандартного вывода автоматически устанавливается в «линейный режим» (т. е. сбрасывание происходит при обнаружении \ n).Родитель должен использовать дескриптор 'master' для чтения / записи с дочерним процессом.

Модифицированный родительский код (на случай, если это кому-нибудь понадобится):

#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 голосов
/ 30 октября 2010

Будет ли вставка вызова на fflush(stdout) после printf недостаточной?

В противном случае setvbuf должен добиться цели:

setvbuf(stdout,NULL,_IOLBF,0);
0 голосов
/ 13 августа 2018

http://lists.gnu.org/archive/html/bug-coreutils/2008-11/msg00164.html

вы можете использовать LD_PRELOAD для установки vbuf

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...