在 C 中正确使用 close
The proper use of close in C
我对如何在 C 中正确使用 close 到 close 管道感到困惑。我对 C 还很陌生,所以如果这太基础了我深表歉意,但我在其他地方找不到任何解释。
#include <stdio.h>
int main()
{
int fd[2];
pipe(fd);
if(fork() == 0) {
close(0);
dup(fd[0]);
close(fd[0]);
close(fd[1]);
} else {
close(fd[0]);
write(fd[1], "hi", 2);
close(fd[1]);
}
wait((int *) 0);
exit(0);
}
我的第一个问题是:在上面的代码中,child进程会关闭fd
的写端。如果我们先到达close(fd[1])
,然后parent进程到达write(fd[1], "hi", 2)
,那fd[1]
不就已经关闭了吗?
int main()
{
char *receive;
int[] fd;
pipe(fd);
if(fork() == 0) {
while(read(fd[0], receive, 2) != 0){
printf("got u!\n");
}
} else {
for(int i = 0; i < 2; i++){
write(fd[1], 'hi', 2);
}
close(fd[1]);
}
wait((int *) 0);
exit(0);
}
第二个问题是:在上面的代码中,我们是否可以在child进程接收完所有内容之前,在parent进程中到达close(fd[1])
?如果是,那么 parent 和 child 之间的正确通信方式是什么?这里我的理解是,如果我们在parent中不关闭fd[1]
,那么read会一直阻塞,程序也不会退出。
what is the correct way to communicate between parent and child[?]
parent 在分叉之前创建管道。在 fork 之后,parent 和 child 各自关闭它们未使用的管道端(管道应被视为单向;如果需要双向通信,请创建两个)。每个进程都有自己的每个 pipe-end 文件描述符副本,因此这些关闭不会影响其他进程使用管道的能力。然后,每个进程都使用它保持开放的一端来实现其方向性——写入写入端或从读取端读取。
当编写器完成将它打算写入管道的所有内容写入管道时,它就会关闭它的末端。这很重要,有时甚至是必不可少的,因为只要任何进程打开了写入端,reader 就不会在管道的读取端感知到 end-of-file。这也是为什么每个进程关闭它未使用的一端很重要的原因之一,因为如果 reader 也打开了写入端,那么它可以无限期地阻止尝试从管道读取,无论是什么任何其他进程都可以。
当然,reader 也应该在读取结束时关闭它(或终止,让系统处理)。不这样做就构成了过度的资源消耗,但这是否是一个严重的问题要视情况而定。
首先请注意,在 fork()
之后,文件描述符 fd
也会被复制到 child 进程。所以基本上,pipe
就像一个文件,每个进程都有自己对 read 和 write 末尾的引用 pipe
。本质上有 2 个读取和 2 个写入文件描述符,每个进程一个。
My first question is: In the above code, the child process will close
the write side of fd. If we first reach close(fd[1]), then the parent
process reach write(fd[1], "hi", 2), wouldn't fd[1] already been
closed?
答:不是。parent进程中的fd[1]
是parent的写端。 child 通过关闭 its fd[1]
放弃了在管道上写入的权利,这不会阻止 parent 写入管道。
在回答第二个问题之前,我将您的代码修改为 运行 并产生了一些结果。
int main()
{
char receive[10];
int fd[2];
pipe(fd);
if(fork() == 0) {
close(fd[1]); <-- Close UNUSED write end
while(read(fd[0], receive, 2) != 0){
printf("got u!\n");
receive[2] = '[=10=]';
printf("%s\n", receive);
}
close(fd[0]); <-- Close read end after reading
} else {
close(fd[0]); <-- Close UNUSED read end
for(int i = 0; i < 2; i++){
write(fd[1], "hi", 2);
}
close(fd[1]); <-- Close write end after writing
wait((int *) 0);
}
exit(0);
}
结果:
got u!
hi
got u!
hi
注意: 我们(貌似)丢失了一个 hi
因为我们正在将它读入同一个数组 receive
,这实际上覆盖了第一个 hi
.您可以使用二维字符数组来保留这两条消息。
The second question is: In the above code, would it be possible for us
to reach close(fd[1]) in the parent process before the child process
finish receiving all the contents?
答:是的。写入 pipe()
是 non-blocking(除非另有说明),直到管道缓冲区已满。
If yes, then what is the correct
way to communicate between parent and child. My understanding here is
that if we do not close fd[1] in the parent, then read will keep being
blocked, and the program won't exit either.
如果我们在parent中关闭fd[1]
,会提示parent已经关闭了它的写端。然而,如果 child 没有提前关闭它的 fd[1]
,它会在 read()
上阻塞,因为管道不会发送 EOF
,直到所有写端都关闭。因此 child 将等待自己写入 pipe
,同时从中读取!
现在,如果 parent 不关闭其未使用的读取端会发生什么?如果文件只有一个读取描述符(比如带有 child 的那个),那么一旦 child 关闭它,parent 将在尝试进一步写入时收到一些信号或错误pipe
因为没有读者。
但是在这种情况下,parent 也有一个打开的读描述符,它将能够写入缓冲区直到它被填满,这可能会导致下一个 write
调用出现问题,如果有的话。
现在这可能没有多大意义,但是如果您编写的程序需要一次又一次地通过管道传递值,那么不关闭未使用的末端会经常让您遇到令人沮丧的错误。
我对如何在 C 中正确使用 close 到 close 管道感到困惑。我对 C 还很陌生,所以如果这太基础了我深表歉意,但我在其他地方找不到任何解释。
#include <stdio.h>
int main()
{
int fd[2];
pipe(fd);
if(fork() == 0) {
close(0);
dup(fd[0]);
close(fd[0]);
close(fd[1]);
} else {
close(fd[0]);
write(fd[1], "hi", 2);
close(fd[1]);
}
wait((int *) 0);
exit(0);
}
我的第一个问题是:在上面的代码中,child进程会关闭fd
的写端。如果我们先到达close(fd[1])
,然后parent进程到达write(fd[1], "hi", 2)
,那fd[1]
不就已经关闭了吗?
int main()
{
char *receive;
int[] fd;
pipe(fd);
if(fork() == 0) {
while(read(fd[0], receive, 2) != 0){
printf("got u!\n");
}
} else {
for(int i = 0; i < 2; i++){
write(fd[1], 'hi', 2);
}
close(fd[1]);
}
wait((int *) 0);
exit(0);
}
第二个问题是:在上面的代码中,我们是否可以在child进程接收完所有内容之前,在parent进程中到达close(fd[1])
?如果是,那么 parent 和 child 之间的正确通信方式是什么?这里我的理解是,如果我们在parent中不关闭fd[1]
,那么read会一直阻塞,程序也不会退出。
what is the correct way to communicate between parent and child[?]
parent 在分叉之前创建管道。在 fork 之后,parent 和 child 各自关闭它们未使用的管道端(管道应被视为单向;如果需要双向通信,请创建两个)。每个进程都有自己的每个 pipe-end 文件描述符副本,因此这些关闭不会影响其他进程使用管道的能力。然后,每个进程都使用它保持开放的一端来实现其方向性——写入写入端或从读取端读取。
当编写器完成将它打算写入管道的所有内容写入管道时,它就会关闭它的末端。这很重要,有时甚至是必不可少的,因为只要任何进程打开了写入端,reader 就不会在管道的读取端感知到 end-of-file。这也是为什么每个进程关闭它未使用的一端很重要的原因之一,因为如果 reader 也打开了写入端,那么它可以无限期地阻止尝试从管道读取,无论是什么任何其他进程都可以。
当然,reader 也应该在读取结束时关闭它(或终止,让系统处理)。不这样做就构成了过度的资源消耗,但这是否是一个严重的问题要视情况而定。
首先请注意,在 fork()
之后,文件描述符 fd
也会被复制到 child 进程。所以基本上,pipe
就像一个文件,每个进程都有自己对 read 和 write 末尾的引用 pipe
。本质上有 2 个读取和 2 个写入文件描述符,每个进程一个。
My first question is: In the above code, the child process will close the write side of fd. If we first reach close(fd[1]), then the parent process reach write(fd[1], "hi", 2), wouldn't fd[1] already been closed?
答:不是。parent进程中的fd[1]
是parent的写端。 child 通过关闭 its fd[1]
放弃了在管道上写入的权利,这不会阻止 parent 写入管道。
在回答第二个问题之前,我将您的代码修改为 运行 并产生了一些结果。
int main()
{
char receive[10];
int fd[2];
pipe(fd);
if(fork() == 0) {
close(fd[1]); <-- Close UNUSED write end
while(read(fd[0], receive, 2) != 0){
printf("got u!\n");
receive[2] = '[=10=]';
printf("%s\n", receive);
}
close(fd[0]); <-- Close read end after reading
} else {
close(fd[0]); <-- Close UNUSED read end
for(int i = 0; i < 2; i++){
write(fd[1], "hi", 2);
}
close(fd[1]); <-- Close write end after writing
wait((int *) 0);
}
exit(0);
}
结果:
got u!
hi
got u!
hi
注意: 我们(貌似)丢失了一个 hi
因为我们正在将它读入同一个数组 receive
,这实际上覆盖了第一个 hi
.您可以使用二维字符数组来保留这两条消息。
The second question is: In the above code, would it be possible for us to reach close(fd[1]) in the parent process before the child process finish receiving all the contents?
答:是的。写入 pipe()
是 non-blocking(除非另有说明),直到管道缓冲区已满。
If yes, then what is the correct way to communicate between parent and child. My understanding here is that if we do not close fd[1] in the parent, then read will keep being blocked, and the program won't exit either.
如果我们在parent中关闭fd[1]
,会提示parent已经关闭了它的写端。然而,如果 child 没有提前关闭它的 fd[1]
,它会在 read()
上阻塞,因为管道不会发送 EOF
,直到所有写端都关闭。因此 child 将等待自己写入 pipe
,同时从中读取!
现在,如果 parent 不关闭其未使用的读取端会发生什么?如果文件只有一个读取描述符(比如带有 child 的那个),那么一旦 child 关闭它,parent 将在尝试进一步写入时收到一些信号或错误pipe
因为没有读者。
但是在这种情况下,parent 也有一个打开的读描述符,它将能够写入缓冲区直到它被填满,这可能会导致下一个 write
调用出现问题,如果有的话。
现在这可能没有多大意义,但是如果您编写的程序需要一次又一次地通过管道传递值,那么不关闭未使用的末端会经常让您遇到令人沮丧的错误。