C вилка / exec с неблокирующей трубой IO - PullRequest
7 голосов
/ 25 июля 2010

Это, кажется, довольно распространенная вещь, и мне удалось научить себя всему, что мне нужно, чтобы она работала, за исключением того, что у меня теперь есть одна проблема, которая не поддается устранению.

int nonBlockingPOpen(char *const argv[]){
    int inpipe;
    pid_t pid;
    /* open both ends of pipe nonblockingly */
    pid = fork();

    switch(pid){
        case 0:         /*child*/
            sleep(1); /*child should open after parent has open for reading*/

            /*redirect stdout to opened pipe*/
            int outpipe = open("./fifo", O_WRONLY);
            /*SHOULD BLOCK UNTIL MAIN PROCESS OPENS FOR WRITING*/
            dup2(outpipe, 1);
            fcntl(1, F_SETFL, fcntl(1, F_GETFL) | O_NONBLOCK);

            printf("HELLO WORLD I AM A CHILD PROCESS\n");
            /*This seems to be written to the pipe immediately, blocking or not.*/
            execvp(*argv, argv);
            /*All output from this program, which outputs "one" sleeps for 1 second
             *outputs "two" sleeps for a second, etc, is captured only after the
             *exec'd program exits!
             */
            break;

        default:        /*parent*/
            inpipe = open("./fifo", O_RDONLY | O_NONBLOCK);
            sleep(2);
            /*no need to do anything special here*/
            break;
    }

    return inpipe;
}

Почему дочерний процесс не записывает свой стандартный вывод в канал каждый раз, когда генерируется строка? Что-то мне не хватает в работе execvp или dup2? Я осознаю, что мой подход ко всему этому немного странный, но я не могу найти другой способ программной записи выходных данных двоичных файлов с закрытыми исходными кодами.

Ответы [ 4 ]

3 голосов
/ 25 июля 2010

Когда процесс запускается (через execvp () в вашем примере), поведение стандартного вывода зависит от того, является ли устройство вывода терминалом или нет.Если это не так (и FIFO не является терминалом), то выходные данные будут полностью буферизованы, а не буферизованы строкой.Вы ничего не можете с этим поделать;(Стандартная) библиотека C делает это.

Если вы действительно хотите, чтобы рабочая строка была буферизована, вам придется предоставить программе псевдотерминал в качестве стандартного вывода.Это попадает в интересные сферы - псевдо-терминалы или ptys не так просты в обращении.О функциях POSIX см .:

  • grantpt() - предоставить доступ к подчиненному псевдо-терминальному устройству
  • posix_openpt()- открыть псевдо-терминальное устройство
  • ptsname() - получить имя подчиненного псевдо-терминального устройства
  • unlockpt() - разблокироватьпсевдо-терминал главная / подчиненная пара
3 голосов
/ 25 июля 2010

Полагаю, вы получите вывод программы exec'd только после ее выхода, поскольку она не flush после каждого сообщения.Если это так, то вы ничего не можете сделать извне.

Я не совсем уверен, как это должно быть связано с выбором между блокирующим и неблокирующим вводом / выводом в вашем вопросе.Неблокирующая запись может завершиться неудачей полностью или частично: вместо того, чтобы блокировать программу до тех пор, пока в канале не освободится место, вызов немедленно возвращается и сообщает, что он не может написать все, что должен иметь.Неблокирующий ввод-вывод не увеличивает размер буфера и не приводит к сбрасыванию вывода, и это может плохо поддерживаться некоторыми программами.

Вы не можете принудительно принудительно сбросить исполняемую только двоичную программу,Если вы подумали, что неблокирующий ввод-вывод является решением этой проблемы, извините, но я боюсь, что это довольно ортогонально.

РЕДАКТИРОВАТЬ: Хорошо, если программа exec'd использует только предоставленную буферизациюс помощью libc (не реализует свою собственную) и динамически связан, вы можете принудительно сбросить его, связав его с измененным libc, который сбрасывает каждую запись.Это было бы отчаянной мерой.пытаться, только если все остальное не удалось.

1 голос
/ 25 июля 2010

Почему дочерний процесс не записывает свой стандартный вывод в канал каждый раз, когда генерируется строка?

Откуда ты это знаешь? Вы даже не пытаетесь прочитать вывод из fifo.

N.B. по имени файла я предполагаю, что вы используете fifo . Или это простой файл?

И незначительная ошибка у ребенка: после dup2() необходимо close(outpipe).

fcntl (1, F_SETFL, fcntl (1, F_GETFL) | O_NONBLOCK);

В зависимости от того, какую программу вы выполняете (), вы можете либо потерять какой-то вывод, либо вызвать сбой программы, так как теперь запись в стандартный вывод может завершиться с EWOULDBLOCK.

IIRC fifos имеет тот же размер буфера, что и каналы. В POSIX минимум 512 байт, обычно 4K или 8K.

Вы, вероятно, хотите объяснить, зачем вам это вообще нужно. Неблокирующая IO имеет другую семантику по сравнению с блокирующей IO, и если ваш дочерний процесс не ожидает, что вы столкнетесь с различными проблемами.

printf ("ПРИВЕТ, МИР, Я ДЕТСКИЙ ПРОЦЕСС \ n");

stdout буферизован, я бы после этого fflush(stdout). (Не удается найти документацию, будет ли exec () самостоятельно очищать стандартный вывод или нет.)

Что-то мне не хватает в работе execvp или dup2? Я осознаю, что мой подход ко всему этому немного странный, но я не могу найти другой способ программной записи выходных данных двоичных файлов с закрытыми исходными кодами.

Я бы не стал играть с неблокирующим вводом-выводом - и оставил бы все как есть в режиме блокировки.

И я бы использовал pipe () вместо fifo. В Linux man pipe есть удобный пример с fork ().

В остальном это обычная практика.

0 голосов
/ 25 июля 2010

sleep() s делают , а не гарантируют, что родитель откроет канал первым - как говорит Dummy00001 , вы должны использовать pipe() канал, а не именованный канал , Вам также следует проверить, не произошли ли ошибки execvp() и fork(), и не следует настраивать дочернюю сторону на неблокирующую - это решение для дочернего процесса.

int nonBlockingPOpen(char *const argv[])
{
    int childpipe[2];
    pid_t pid;

    pipe(childpipe);
    pid = fork();

    if (pid == 0)
    {
        /*child*/

        /*redirect stdout to opened pipe*/
        dup2(childpipe[1], 1);

        /* close leftover pipe file descriptors */
        close(childpipe[0]);
        close(childpipe[1]);

        execvp(*argv, argv);

        /* Only reached if execvp fails */
        perror("execvp");
        exit(1);
    }

    /*parent*/

    /* Close leftover pipe file descriptor */
    close(childpipe[1]);

    /* Check for fork() failing */
    if (pid < 0)
    {
         close(childpipe[0]);
         return -1;
    }

    /* Set file descriptor non-blocking */
    fcntl(childpipe[0], F_SETFL, fcntl(childpipe[0], F_GETFL) | O_NONBLOCK);

    return childpipe[0];
}
...