我使用以下代码为 IPC 编写了以下代码pipe()
:
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
int main(void) {
char message_buffer[15] = "Hello World \n";
char read_buffer[15];
int fd[2];
int return_value = pipe(fd);
if (return_value < 0) {
printf("Error creating the pipe");
}
int rc = fork();
if (rc < 0) {
printf("Error forking a child");
}
if (rc > 0) {
close(fd[0]);
write(fd[1], message_buffer, 15);
close(fd[1]);
wait(NULL);
} else {
close(fd[1]);
read(fd[0], read_buffer, 15);
close(fd[0]);
printf("The Message: %s", read_buffer);
}
return 0;
}
我是管道新手,我有以下问题:
- 我不明白为什么父母需要在写入之前关闭读取端,而需要在写入后关闭写入端?
- 孩子也一样,为什么在读之前要关闭写端,为什么读后要关闭读端呢?
- 由于父母和孩子同时运行,如果孩子在父母写消息的同时阅读会发生什么?
- 由于父母和孩子同时运行,如果孩子读取并且父母还没有在管道中写入任何东西会发生什么?
我的问题似乎很愚蠢,但请帮助回答它们,因为我正在为我的课程考试学习普通管道。
问题 1 和 2的答案在
pipe
手册页(“示例”部分)中:由于管道是单向的,它有指定的两端——读端和写端。如果父级将使用此管道向子级写入数据,则父级没有必要保持读取端打开。相反,如果孩子要从管道中读取数据,则不需要打开写端。
编辑:
您还问为什么父母需要在写完后关闭写端,为什么孩子需要在读完后关闭读端。
他们不必这样做。如果两个程序要继续运行并使用管道交换数据,它们必须保持管道打开。在一个简短的示例程序中,它仅演示了管道的使用并在传输一条消息后终止,父子程序关闭管道文件描述符可能是为了在程序终止之前正确清理资源。
问题 #3 和 #4 的答案在
pipe(7)
手册页中。你的问题#3:
子进程将能够读取管道中已由父进程写入的任何可用数据。根据手册页:
你的问题#4:
手册页说:
对评论中的问题的回答:
它不应该阻止管道工作,但它会占用程序使用的资源。通过关闭管道的不需要的末端,这些资源就不会被保留。
手册页说:
这意味着管道不关心您传输的数据。它不知道“消息”是什么意思,也不知道父级是否已经完成写入,或者它是否想要写入更多数据。
您将需要实施自己的技术来确定什么是“完整消息”。例如,父母可以通过发送一个特殊字符来向孩子表明已经写入了完整的消息,例如,
\0
或者实际上在使用管道的特定上下文中有意义的任何其他内容。请参阅
pipe(7)
手册页。在“管道和 FIFO 上的 I/O”下,它说:
让孩子关闭其写入端的副本(它不会使用),使得孩子可以检测到父母何时这样做。如果孩子保持写端打开,它永远不会在管道上看到 EOF,因为它本质上是在等待自己。(2)
同样,让父母关闭其读取端的副本也可以让父母检测孩子是否离开。(1)
并不是说您那里的代码曾经检查过
read()
和write()
或尝试读取/写入可变数量的数据的返回值,因此除了父级获得 SIGPIPE 信号之外,这基本上没有实际意义。在写入后关闭父级中的写入端并在子级中读取后关闭读取端只是常见的家务。如果进程无论如何都立即退出,则显式关闭不会产生任何影响。
我不确定你的问题 3 和 4 是否相同,但是如果阅读器在没有什么可阅读的情况下阅读,系统调用将阻塞:
如果写入器在读取器执行其他操作时写入,则数据将被复制到操作系统中的缓冲区,至少在有足够空间的情况下是这样。如果没有,那么作者将阻止:
“下面”是“管道容量”部分。
如果他们同时这样做,操作系统只会复制数据。
其他人提供了很好的答案。这是 ilkkachu 对其上述答案的评论的扩展,旨在通过实验帮助您。考虑以下程序,它是您最初发布的内容的略微修改版本:
这个版本不发送消息,它发送 1 到 10 之间的随机消息数。这样,孩子不会提前知道要阅读多少条消息——当它从管道中读取所有内容时它会停止并且没有其他内容可以写入管道(即,当所有写入端都关闭并
read
返回负值时)。这是几个示例运行:请注意,我在
close()
与管道关联的每个调用之前都添加了注释。如果仅注释掉第 (1) 行,则程序的行为没有明显变化:如果只注释掉第 (2) 行,则程序在运行时会死锁,例如:
为什么?如果父进程没有关闭管道的写端,那么子进程将永远阻塞在调用中以
read
等待更多数据。(read
如果有任何打开的文件描述符与管道的写入端相关联,则调用将阻塞。)然后父级将在调用wait
. 父母和孩子都永远等待对方。如果仅注释掉第 (3) 行,则程序在运行时会死锁,例如:
为什么?
read
同样,只要存在与管道的写端关联的任何打开的文件描述符,并且子进程有一个,调用就会阻塞。结果,子进程再次阻塞调用 toread
而父进程阻塞调用 towait
并且没有任何进一步的进展。最后,如果您只注释掉第 (4) 行,那么程序的行为没有明显变化:
通过确保所有进程关闭它们不需要的文件描述符,您可以确保没有进程最终被阻塞等待从管道中读取永远不会到来的数据。