Проблема восстановления стандартного вывода после использования канала - PullRequest
0 голосов
/ 18 февраля 2020

Используя совет, найденный здесь: Восстановление стандартного вывода после использования dup Я попытался восстановить стандартный ввод и стандартный вывод. Однако при использовании printf для проверки восстановления стандартного вывода я не смог прочитать вывод. Код выглядит следующим образом. Восстановив стандартный вывод как 1, я попытался вывести «готово».

#include <stdio.h>
#include <unistd.h>

void main(int argv, char *argc) {
    int stdin_copy = dup(STDIN_FILENO);
    int stdout_copy = dup(STDOUT_FILENO);
    int testpipe[2];
    pipe(testpipe); 
    int PID = fork();
    if (PID == 0) { 
        dup2(testpipe[0], 0);
        close(testpipe[1]);
        execl("./multby", "multby", "3", NULL);// Just multiplies argument 3 with the number in testpipe.
    } else {
        dup2(testpipe[1], 1);
        close(testpipe[0]);
        printf("5");
        fclose(stdout);
        close(testpipe[1]);
        char initialval[100];
        read(testpipe[0], initialval, 100);
        fprintf(stderr, "initial value: %s\n", initialval);
        wait(NULL);
        dup2(stdin_copy, 0);
        dup2(stdout_copy, 1);
        printf("done");//was not displayed when I run code.
    }
}

Однако при запуске кода я не увидел «готово». (Там должно быть сделано после 15). Это мой вывод: начальное значение: exe c успешно a: 3 b: 5 multby успешно 15

Что я сделал не так при восстановлении стандартного вывода?

Ответы [ 2 ]

1 голос
/ 20 февраля 2020

Общие замечания

Повторение того, что я говорил ранее о SO в других ответах.

Вы недостаточно закрываете файловые дескрипторы в дочернем процессе.


Правило большого пальца : Если вы dup2() один конец канала для стандартного ввода или стандартного вывода, закройте оба исходные файловые дескрипторы возвращаются pipe() как можно скорее. В частности, вы должны закрыть их перед использованием любого из семейства функций exec*().

Правило также применяется, если вы дублируете дескрипторы с dup() или fcntl() с F_DUPFD или F_DUPFD_CLOEXEC.


Если родительский процесс не будет связываться ни с одним из своих дочерних элементов через канал, он должен убедиться, что он закрывает оба конца канала достаточно рано (например, перед ожиданием), чтобы его дочерние элементы могли получать показания EOF при чтении (или получать сигналы SIGPIPE или ошибки записи при записи), а не блокировать бесконечно. Даже если родительский канал использует канал без использования dup2(), он обычно должен закрывать по крайней мере один конец канала - программа очень редко может читать и писать на обоих концах одного канала.

Обратите внимание, что опция O_CLOEXEC для open() и опции FD_CLOEXEC и F_DUPFD_CLOEXEC для fcntl() также могут учитывать это обсуждение.

Если вы используете posix_spawn() и его обширное семейство функций поддержки (всего 21 функция), вам нужно будет рассмотреть, как закрыть файловые дескрипторы в порожденном процессе (posix_spawn_file_actions_addclose(), et c .).

Обратите внимание, что использование dup2(a, b) безопаснее, чем использование close(b); dup(a); по ряду причин. Во-первых, если вы хотите, чтобы дескриптор файла был больше обычного, dup2() - единственный разумный способ сделать это. Другое состоит в том, что если a совпадает с b (например, оба 0), то dup2() обрабатывает его правильно (он не закрывается b до дублирования a), тогда как отдельный close() и dup() ужасно терпит неудачу. Это маловероятное, но не невозможное обстоятельство.


Анализ кода

Вопрос содержит код:

#include <stdio.h>
#include <unistd.h>

void main(int argv, char *argc) {
    int stdin_copy = dup(STDIN_FILENO);
    int stdout_copy = dup(STDOUT_FILENO);
    int testpipe[2];
    pipe(testpipe); 
    int PID = fork();
    if (PID == 0) { 
        dup2(testpipe[0], 0);
        close(testpipe[1]);
        execl("./multby", "multby", "3", NULL);// Just multiplies argument 3 with the number in testpipe.
    } else {
        dup2(testpipe[1], 1);
        close(testpipe[0]);
        printf("5");
        fclose(stdout);
        close(testpipe[1]);
        char initialval[100];
        read(testpipe[0], initialval, 100);
        fprintf(stderr, "initial value: %s\n", initialval);
        wait(NULL);
        dup2(stdin_copy, 0);
        dup2(stdout_copy, 1);
        printf("done");//was not displayed when I run code.
    }
}

Строка void main(int argv, char *argc) { должна быть int main(void), так как вы не используете аргументы командной строки. У вас также есть имена argv и argc, обратные обычному соглашению - первый аргумент обычно называется argc (количество аргументов), а второй обычно называется argv (вектор аргумента). Кроме того, тип для второго аргумента должен быть char **argv (или char **argc, если вы хотите запутать всех ваших случайных читателей). См. Также Что должен main() вернуть в C и C ++?

Следующий блок кода, требующий обсуждения:

    if (PID == 0) { 
        dup2(testpipe[0], 0);
        close(testpipe[1]);
        execl("./multby", "multby", "3", NULL);// Just multiplies argument 3 with the number in testpipe.
    }

Это нарушает правило большого пальца Вы также должны поставить код обработки ошибок после execl().

if (PID == 0)
{
    dup2(testpipe[0], STDIN_FILENO);
    close(testpipe[0]);
    close(testpipe[1]);
    execl("./multby", "multby", "3", (char *)NULL);
    fprintf(stderr, "failed to execute ./multby\n");
    exit(EXIT_FAILURE);
}

Следующий блок кода для анализа:

dup2(testpipe[1], 1);
close(testpipe[0]);
printf("5");
fclose(stdout);
close(testpipe[1]);

Теоретически вы должны использовать STDOUT_FILENO вместо из 1, но у меня есть значительное сочувствие к использованию 1 (не в последнюю очередь потому, что, когда я впервые узнал C, не было такой константы c символов). Вы фактически закрываете оба конца канала, но я бы предпочел, чтобы оба закрылись сразу после вызова dup2(), в соответствии с практическим правилом. printf() без перевода строки отправляет что-либо по трубе; он хранит 5 в буфере ввода / вывода.

Поскольку Крис Додд , идентифицированный в их ответе , вызов fclose(stdout) вызывает много проблем , Вы, вероятно, должны просто заменить его на fflush(stdout).

Переходя:

char initialval[100];
read(testpipe[0], initialval, 100);
fprintf(stderr, "initial value: %s\n", initialval);
wait(NULL);
dup2(stdin_copy, 0);
dup2(stdout_copy, 1);
printf("done");

Вы не проверяли, сработал ли read(). Это не так; это не удалось с EBADF, потому что чуть выше этого вы используете close(testpipe[0]);. То, что вы печатаете на stderr есть неинициализированная строка - это не хорошо. На практике, если вы хотите надежно читать информацию от ребенка, вам нужны два канала: один для общения между родителями и другой для общения между детьми. В противном случае, нет гарантии, что родитель не прочитает то, что написал. Если вы дожидаетесь, пока ребенок дойдет до ie перед чтением, у вас будет достаточно шансов, что он сработает, но вы не всегда можете рассчитывать на то, что сможете это сделать.

Первый из два dup2() звонка бессмысленны; вы не изменили перенаправление для стандартного ввода (так что на самом деле stdin_copy не требуется). Второй изменяет назначение стандартного дескриптора выходного файла, но вы уже закрыли stdout, поэтому нет простого способа открыть его так, чтобы printf() работал. Сообщение должно заканчиваться символом новой строки - большинство строк формата printf() должны заканчиваться символом новой строки, если только оно не используется преднамеренно для создания отдельной строки выходных данных. Однако, если вы обратили внимание на возвращаемое значение, вы обнаружите, что оно не удалось (-1) и велика вероятность, что вы снова найдете errno == EBADF.

Исправление кода - fr agile решение

Учитывая этот код для multby.c:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv)
{
    if (argc != 2)
    {
        fprintf(stderr, "Usage; %s number", argv[0]);
        return 1;
    }
    int multiplier = atoi(argv[1]);
    int number;
    if (scanf("%d", &number) == 1)
        printf("%d\n", number * multiplier);
    else
        fprintf(stderr, "%s: failed to read a number from standard input\n", argv[0]);
    return 0;
}

и этот код (как pipe23.c, скомпилированный в pipe23):

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(void)
{
    int stdout_copy = dup(STDOUT_FILENO);
    int testpipe[2];
    pipe(testpipe);
    int PID = fork();
    if (PID == 0)
    {
        dup2(testpipe[0], 0);
        dup2(testpipe[1], 1);
        close(testpipe[1]);
        close(testpipe[0]);
        execl("./multby", "multby", "3", NULL);// Just multiplies argument 3 with the number in testpipe.
        fprintf(stderr, "failed to execute ./multby (%d: %s)\n", errno, strerror(errno));
        exit(EXIT_FAILURE);
    }
    else
    {
        dup2(testpipe[1], 1);
        close(testpipe[1]);
        printf("5\n");
        fflush(stdout);
        close(1);
        wait(NULL);
        char initialval[100];
        read(testpipe[0], initialval, 100);
        close(testpipe[0]);
        fprintf(stderr, "initial value: [%s]\n", initialval);
        dup2(stdout_copy, 1);
        printf("done\n");
    }
}

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

Вывод:

initial value: [15
]
done

Исправление кода - надежное решение

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

Вот модифицированный multby.c, который зацикливается на чтении:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv)
{
    if (argc != 2)
    {
        fprintf(stderr, "Usage; %s number", argv[0]);
        return 1;
    }
    int multiplier = atoi(argv[1]);
    int number;
    while (scanf("%d", &number) == 1)
        printf("%d\n", number * multiplier);
    return 0;
}

, а вот модифицированный pipe23.c, который использует две трубы и записывает 3 числа ребенку и возвращает три результата. Обратите внимание, что не нужно ставить новую строку после третьего номера с этой организацией (хотя не будет никакого вреда, если он будет включать новую строку). Кроме того, если вы обманчивы, второй пробел в списке чисел тоже не нужен; - не является частью второго числа, поэтому сканирование останавливается после 0.

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(void)
{
    int stdout_copy = dup(STDOUT_FILENO);
    int p_to_c[2];
    int c_to_p[2];
    if (pipe(p_to_c) != 0 || pipe(c_to_p) != 0)
    {
        fprintf(stderr, "failed to open a pipe (%d: %s)\n", errno, strerror(errno));
        exit(EXIT_FAILURE);
    }
    int PID = fork();
    if (PID == 0)
    {
        dup2(p_to_c[0], 0);
        dup2(c_to_p[1], 1);
        close(c_to_p[0]);
        close(c_to_p[1]);
        close(p_to_c[0]);
        close(p_to_c[1]);
        execl("./multby", "multby", "3", NULL);// Just multiplies argument 3 with the number in testpipe.
        fprintf(stderr, "failed to execute ./multby (%d: %s)\n", errno, strerror(errno));
        exit(EXIT_FAILURE);
    }
    else
    {
        dup2(p_to_c[1], 1);
        close(p_to_c[1]);
        close(p_to_c[0]);
        close(c_to_p[1]);
        printf("5 10 -15");
        fflush(stdout);
        close(1);
        char initialval[100];
        int n = read(c_to_p[0], initialval, 100);
        if (n < 0)
        {
            fprintf(stderr, "failed to read from the child (%d: %s)\n", errno, strerror(errno));
            exit(EXIT_FAILURE);
        }
        close(c_to_p[0]);
        wait(NULL);
        fprintf(stderr, "initial value: [%.*s]\n", n, initialval);
        dup2(stdout_copy, 1);
        printf("done\n");
    }
}

Обратите внимание, что там много вызовов на close() - по два для каждого из 4 дескриптора, участвующих в обработке двух каналов. Это нормально. Не заботясь о том, чтобы закрыть файловые дескрипторы, можно легко привести к зависанию системы.

Вывод при запуске этого pipe23 - это то, что я и хотел:

initial value: [15
30
-45
]
done
1 голос
/ 18 февраля 2020

Вы вызываете fclose(stdout);, который закрывает объект stdout FILE, а также STDOUT_FILELO - дескриптор файла stdout (это две разные вещи, связанные вместе). Поэтому при последующем вызове dup2(stdout_copy, 1); это восстанавливает дескриптор файла, но объект FILE остается закрытым, поэтому вы не можете использовать его для вывода чего-либо. Невозможно повторно открыть объект FILE, чтобы обратиться к дескриптору файла 1 , поэтому лучше всего просто удалить строку fclose. dup2 закроет дескриптор файла, который вы заменяете (поэтому вам не нужно отдельное закрытие), и вы сможете увидеть вывод


1 Возможно, вы можете использовать freopen с / dev / fd в некоторых системах, но / dev / fd непереносим

...