读取系统调用阻止共享管道
Read system call blocked sharing a pipe
我是 Unix 系统编程的新手,我正在努力理解文件描述符和管道。让我们考虑这个简单的代码:
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
int main() {
int fd[2], p;
char *m = "123456789\n", c;
pipe(fd);
p = fork();
if (p == 0) {
// child
while(read(fd[0], &c, 1) > 0) write(1, &c, 1);
}
else {
// parent
write(fd[1], m, strlen(m));
close(fd[1]);
wait(NULL);
}
exit (0);
}
当我编译和 运行 代码时,它输出 123456789,但除非我发出 ^C,否则该过程永远不会结束。实际上,这两个进程在 htop 中都显示为已停止。
如果 child 在 read() 之前关闭 fd[1] 那么它似乎工作正常,但我不明白为什么。 fd 在两个进程之间共享,parent 在写入后关闭 fd[1]。为什么 child 在读取时没有得到 EOF?
提前致谢!
If the child closes fd[1] prior to read() then it seems to work OK but I don't understand why.
这就是您需要做的。仅此而已。从管道的读取端读取不会 return 0(发出 EOF 信号),直到内核确定不会再向该管道的写入端写入任何内容,并且只要它仍然在任何地方打开,包括读取的过程,不能确定。
嗯,首先你的 parent 进程正在等待 child 在 wait(2)
系统调用中终止,为什么你的 child 在管道中被阻塞到 read(2)
另一个字符。这两个进程都被阻止了......所以你需要从外部采取行动来取消它们。问题是 child 进程没有关闭它的管道写描述符(而且 parent 没有关闭它的管道读描述符,但这不影响这里)管道阻塞任何 reader 而至少一个这样的写描述符仍然打开。只有当所有写描述符都关闭时,读returns 0
到reader.
当你执行 fork(2)
时,两个管道描述符(fd[0]
和 fd[1]
)都在 child 进程中被 dup()
ed,所以你有一个管道有两个打开的文件描述符(一个在 parent 中,一个在 child 中)用于写入,还有两个打开的描述符(同样,一个在 parent 中,一个在 child) 进行读取,因此当一个写入器保持管道打开以进行写入时(在本例中为 child 进程),child 进行的读取仍然会阻塞。内核无法将此检测为异常,因为如果另一个线程(或信号处理程序)需要,child 仍然可以在管道上写入。
顺便说一句,我要评论一下你在代码中做的不好的事情:
- 首先是parent和child只考虑来自
fork()
的两种情况,但是如果分叉失败,它将return-1
并且你将有一个 parent 进程在没有读取进程的管道上写入,所以它可能应该阻塞(正如我所说,这不是你的情况,但这也是一个错误)你有总是检查系统调用的错误,不要假设你的 fork()
调用永远不会失败(认为 -1
被认为是 != 0
所以它落在 parent 的代码)。只有一个系统调用可以在不检查错误的情况下执行,它是close(2)
(尽管对此有很多争议)
read()
和 write()
也是如此。解决问题的更好方法是使用更大的缓冲区(不仅仅是一个字符,以减少程序进行的系统调用次数,从而加快速度)并使用 return 值 read()
作为 write()
调用的参数。
您的程序应该(确实在我的系统上确实如此)只需插入以下行即可工作:
close(fd[1]);
就在 child 代码中的 while
循环之前,如下所示:
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
int main() {
int fd[2], p;
char *m = "123456789\n", c;
pipe(fd);
p = fork();
if (p == 0) {
// child
close(fd[1]); // <--- this close is fundamental for the pipe to work properly.
while(read(fd[0], &c, 1) > 0) write(1, &c, 1);
}
else if (p > 0) {
// parent
// another close(fd[0]); should be included here
write(fd[1], m, strlen(m));
close(fd[1]);
wait(NULL);
} else {
// include error processing for fork() here
}
exit (0);
}
我是 Unix 系统编程的新手,我正在努力理解文件描述符和管道。让我们考虑这个简单的代码:
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
int main() {
int fd[2], p;
char *m = "123456789\n", c;
pipe(fd);
p = fork();
if (p == 0) {
// child
while(read(fd[0], &c, 1) > 0) write(1, &c, 1);
}
else {
// parent
write(fd[1], m, strlen(m));
close(fd[1]);
wait(NULL);
}
exit (0);
}
当我编译和 运行 代码时,它输出 123456789,但除非我发出 ^C,否则该过程永远不会结束。实际上,这两个进程在 htop 中都显示为已停止。
如果 child 在 read() 之前关闭 fd[1] 那么它似乎工作正常,但我不明白为什么。 fd 在两个进程之间共享,parent 在写入后关闭 fd[1]。为什么 child 在读取时没有得到 EOF?
提前致谢!
If the child closes fd[1] prior to read() then it seems to work OK but I don't understand why.
这就是您需要做的。仅此而已。从管道的读取端读取不会 return 0(发出 EOF 信号),直到内核确定不会再向该管道的写入端写入任何内容,并且只要它仍然在任何地方打开,包括读取的过程,不能确定。
嗯,首先你的 parent 进程正在等待 child 在 wait(2)
系统调用中终止,为什么你的 child 在管道中被阻塞到 read(2)
另一个字符。这两个进程都被阻止了......所以你需要从外部采取行动来取消它们。问题是 child 进程没有关闭它的管道写描述符(而且 parent 没有关闭它的管道读描述符,但这不影响这里)管道阻塞任何 reader 而至少一个这样的写描述符仍然打开。只有当所有写描述符都关闭时,读returns 0
到reader.
当你执行 fork(2)
时,两个管道描述符(fd[0]
和 fd[1]
)都在 child 进程中被 dup()
ed,所以你有一个管道有两个打开的文件描述符(一个在 parent 中,一个在 child 中)用于写入,还有两个打开的描述符(同样,一个在 parent 中,一个在 child) 进行读取,因此当一个写入器保持管道打开以进行写入时(在本例中为 child 进程),child 进行的读取仍然会阻塞。内核无法将此检测为异常,因为如果另一个线程(或信号处理程序)需要,child 仍然可以在管道上写入。
顺便说一句,我要评论一下你在代码中做的不好的事情:
- 首先是parent和child只考虑来自
fork()
的两种情况,但是如果分叉失败,它将return-1
并且你将有一个 parent 进程在没有读取进程的管道上写入,所以它可能应该阻塞(正如我所说,这不是你的情况,但这也是一个错误)你有总是检查系统调用的错误,不要假设你的fork()
调用永远不会失败(认为-1
被认为是!= 0
所以它落在 parent 的代码)。只有一个系统调用可以在不检查错误的情况下执行,它是close(2)
(尽管对此有很多争议) read()
和write()
也是如此。解决问题的更好方法是使用更大的缓冲区(不仅仅是一个字符,以减少程序进行的系统调用次数,从而加快速度)并使用 return 值read()
作为write()
调用的参数。
您的程序应该(确实在我的系统上确实如此)只需插入以下行即可工作:
close(fd[1]);
就在 child 代码中的 while
循环之前,如下所示:
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
int main() {
int fd[2], p;
char *m = "123456789\n", c;
pipe(fd);
p = fork();
if (p == 0) {
// child
close(fd[1]); // <--- this close is fundamental for the pipe to work properly.
while(read(fd[0], &c, 1) > 0) write(1, &c, 1);
}
else if (p > 0) {
// parent
// another close(fd[0]); should be included here
write(fd[1], m, strlen(m));
close(fd[1]);
wait(NULL);
} else {
// include error processing for fork() here
}
exit (0);
}