一般说明
在其他答案中重申我之前在 SO 上所说的话。
您没有在子进程中关闭足够多的文件描述符。
经验法则:如果您
dup2()
管道的一端到标准输入或标准输出,关闭两者
返回的原始文件描述符
pipe()
尽快地。
特别是,您应该在使用任何
exec*()
函数族。
如果您使用以下任一方式复制描述符,该规则也适用
dup()
或者
fcntl()
F_DUPFD 或 F_DUPFD_CLOEXEC。
如果父进程不会通过以下方式与其任何子进程通信
管,它必须确保它及早关闭管道的两端
足够(例如,在等待之前),以便它的孩子可以收到
读取时的 EOF 指示(或获取 SIGPIPE 信号或写入错误)
write),而不是无限期地阻塞。
即使父母使用管道而不使用dup2(),它也应该
通常至少关闭管道的一端——这是非常罕见的
在单个管道的两端进行读写的程序。
请注意,O_CLOEXEC 选项
open(),
和FD_CLOEXEC 和F_DUPFD_CLOEXEC 选项到fcntl() 也可以考虑
进入这个讨论。
如果你使用
posix_spawn()
及其广泛的支持功能系列(总共 21 个功能),
您将需要查看如何在生成的进程中关闭文件描述符
(posix_spawn_file_actions_addclose(),
等等)。
请注意,使用dup2(a, b) 比使用close(b); dup(a); 更安全
出于各种原因。
一种是如果你想强制文件描述符大于
通常的号码,dup2() 是唯一明智的做法。
另一个是如果a 与b 相同(例如两者都是0),那么dup2()
正确处理它(在复制 a 之前它不会关闭 b)
而单独的 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,如果你想迷惑所有的普通读者)。另见What should main() return in C and 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 隐藏在 I/O 缓冲区中。
正如Chris Dodd 在他们的answer 中标识的那样,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