Основные пометки
Повторяю то, что уже говорил о SO в других ответах.
Вы не закрываете достаточно файловых дескрипторов в дочернем процессе.
Практическое правило: если вы dup2()
один конец канала к стандартному вводу или стандартному выводу, закройте оба исходных файловых дескриптора, возвращенных pipe()
как можно скорее. В частности, вы должны закрыть их перед использованием любого из семейства exec*()
. функций.
Правило также применяется, если вы дублируете дескрипторы с помощью dup()
или < a href="http://pubs.opengroup.org/onlinepubs/9699919799/functions/fcntl.html" rel="nofollow noreferrer">fcntl()
с F_DUPFD
или F_DUPFD_CLOEXEC
.
Если родительский процесс не будет взаимодействовать ни с одним из своих дочерних процессов через канал, он должен убедиться, что он закрывает оба конца канала достаточно рано (например, перед ожиданием), чтобы его дочерние процессы могли получать индикацию EOF при чтении (или получать SIGPIPE). сигналы или ошибки записи при записи), а не блокировать на неопределенный срок. Даже если родитель использует канал без использования dup2()
, в норме он должен закрывать по крайней мере один конец канала — чрезвычайно редко программа может читать и писать на обоих концах одного канала.
Обратите внимание, что параметр O_CLOEXEC
соответствует open()
, а параметры FD_CLOEXEC
и F_DUPFD_CLOEXEC
to fcntl()
также может участвовать в этом обсуждении.
Если вы используете posix_spawn()
и его обширное семейство вспомогательных функций (21 функция в целом), вам нужно будет просмотреть, как закрывать файловые дескрипторы в порожденном процессе (posix_spawn_file_actions_addclose()
и т. д.).
Обратите внимание, что использование 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, такой символической константы не было). На самом деле вы закрываете оба конца канала, но я бы предпочел, чтобы оба закрывались сразу после вызова 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
, есть неинициализированная строка — это нехорошо. На практике, если вы хотите надежно считывать информацию от дочернего элемента, вам нужны два канала: один для связи родитель-потомок, а другой — для связи потомок-родитель. В противном случае нет никакой гарантии, что родитель не прочитает написанное. Если бы вы ждали, пока ребенок умрет, прежде чем читать, у вас были бы неплохие шансы на то, что это сработает, но вы не всегда можете полагаться на то, что сможете это сделать.
Первый из двух вызовов dup2()
бессмыслен; вы не изменили перенаправление для стандартного ввода (поэтому на самом деле stdin_copy
не нужен). Второй изменяет назначение дескриптора стандартного выходного файла, но вы уже закрыли stdout
, поэтому нет простого способа открыть его заново, чтобы printf()
заработал. Сообщение должно заканчиваться новой строкой — большинство строк формата printf()
должны заканчиваться новой строкой, если только она не используется намеренно для построения одной строки вывода по частям. Однако, если вы обратите внимание на возвращаемое значение, вы обнаружите, что оно не удалось (-1
), и велики шансы, что вы снова найдете errno == EBADF
.
Исправление кода — хрупкое решение
Учитывая этот код для 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
person
Jonathan Leffler
schedule
20.02.2020