C fork/exec с неблокирующим каналом ввода-вывода

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

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? Я знаю, что мой подход ко всему этому немного странный, но я не могу найти другого способа программно захватить вывод двоичных файлов с закрытым исходным кодом.


person conartist6    schedule 24.07.2010    source источник
comment
Я только что нашел homepage.ntlworld.com/jonathan.deboynepollard/FGA/ эту страницу, что указывает на то, что я не должен пытаться передать дочернему процессу неблокирующий дескриптор, но ни одна из перечисленных там причин похоже< /i> для применения к тому, что я делаю. Я понимаю, что программа, которую я запускаю, вероятно, рухнет, если процесс прослушивания отключится от канала (сгенерировав EAGAIN в дочернем элементе), но это меня не беспокоит.   -  person conartist6    schedule 25.07.2010


Ответы (4)


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

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

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

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

person Pascal Cuoq    schedule 24.07.2010
comment
Я принимаю ваше решение и дополняю его своим. Я, видимо, не был осведомлен о полном количестве и типе буферов, которые работают в этой ситуации. pixelbeat.org/programming/stdio_buffering отлично мне помог. Вы были правы, говоря, что причиной сбоя было то, что линейный буфер не очищался до завершения программы, однако на связанной странице подробно описано множество возможных решений для изменения формы буферизации, используемой stdio. Метод, который я сейчас использую, просто вызывает unbuffer перед моей командой. Я не уверен, насколько это эффективно, но это работает! - person conartist6; 25.07.2010

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

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

  • grantpt() — предоставить доступ к подчиненному псевдотерминальному устройству
  • posix_openpt() — открыть псевдотерминальное устройство
  • ptsname() — получить имя подчиненного псевдотерминального устройства
  • unlockpt() — разблокировать пару псевдотерминал ведущий/подчиненный
person Jonathan Leffler    schedule 25.07.2010

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

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

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

И мелкий баг в дочке: после dup2() нужно close(outpipe).

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

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

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

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

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

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

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

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

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

В остальном это вполне нормальная практика.

person Dummy00001    schedule 24.07.2010
comment
Извините - я не включил сюда всю программу, только функцию, которая открывает канал для чтения. Файл действительно является именованным fifo. Проблема заключалась не в том, что находится в этом коде, а в том, что блок exec запускает код (сервер), который будет работать неопределенно, производя только случайный вывод (недостаточный для заполнения буфера, если все идет хорошо). Кроме того, вы правы, кажется, что после всего это совершенно нормально, если дочерний блок ввода-вывода, и в этом отношении, возможно, даже родительский. Спасибо. - person conartist6; 25.07.2010
comment
@conartist6: неблокирующий флаг ввода-вывода имеет совершенно другой эффект для файловых дескрипторов. но в равной степени программно-логические деструктивные. или вообще никакого эффекта. в зависимости от программы. linux.die.net/man/2/fcntl — и найдите O_NONBLOCK. просто удалите его. - person Dummy00001; 25.07.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];
}
person caf    schedule 25.07.2010