关闭管道的写入端不会将 EOF 发送到其他进程
Closing the write-end of a pipe doesn't send an EOF to the other process
我正在尝试实现一个必须能够通过管道传输命令的 minishell。
最初,我按顺序执行进程,也就是说,我在等待第 n
个进程终止,然后再启动第 n + 1
个进程(这是一个最小的可重现示例,它执行ls | wc -l
):
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>
pid_t family[2];
int pipefd[2];
extern char **environ;
void ft_exec(int i)
{
char **ls = malloc(sizeof(char *) * 2);
ls[0] = "/bin/ls";
ls[1] = NULL;
char **wc = malloc(sizeof(char *) * 3);
wc[0] = "/usr/bin/wc";
wc[1] = "-l";
wc[2] = NULL;
printf("ENTERED BY %d\n", getpid());
if (i == 0)
{
dup2(pipefd[1], 1);
execve(ls[0], ls, environ);
}
if (i == 1)
{
dup2(pipefd[0], 0);
execve(wc[0], wc, environ);
}
}
void ft_interpret(void)
{
int i;
i = 0;
while (i < 2)
{
family[i] = fork();
printf("CREATED %d INSIDE %d\n", family[i], getpid());
if (family[i] == 0)
ft_exec(i);
waitpid(family[i], NULL, 0);
printf("TERMINATED %d IN %d\n", family[i], getpid());
if (i == 0)
{
printf("CLOSED W%d\n", pipefd[1]);
close(pipefd[1]);
}
else if (i == 1)
{
printf("CLOSED R%d\n", pipefd[0]);
close(pipefd[0]);
}
i++;
}
}
int main(int argc, char **argv)
{
(void)argc;
(void)argv;
pipe(pipefd);
printf("PARENT ID IS %d\n", getpid());
printf("CREATED PIPE WITH FDS R%d AND W%d\n", pipefd[0], pipefd[1]);
ft_interpret();
}
然而,我意识到这不是 shell 的工作方式,如果给出 cat /dev/urandom | head -c 100
这样的命令,程序将陷入死锁。
所以我决定先 fork 进程,然后无论哪个进程退出,我都关闭相关的管道写端。
由于某种原因,读取命令(在本例中为 wc
;如果我们将参数更改为非读取命令,例如 printenv
,它将正确执行)在读取时卡住,即使主进程已经关闭了管道的写端并且必须向 wc
.
发送了一个 EOF
修改后的代码如下:
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>
pid_t family[2];
int pipefd[2];
extern char **environ;
void ft_exec(int i)
{
char **ls = malloc(sizeof(char *) * 2);
ls[0] = "/bin/ls";
ls[1] = NULL;
char **wc = malloc(sizeof(char *) * 3);
wc[0] = "/usr/bin/wc";
wc[1] = "-l";
wc[2] = NULL;
printf("ENTERED BY %d\n", getpid());
if (i == 0)
{
dup2(pipefd[1], 1);
execve(ls[0], ls, environ);
}
if (i == 1)
{
dup2(pipefd[0], 0);
execve(wc[0], wc, environ);
}
}
void ft_block_main_process(void)
{
int i;
pid_t terminated;
i = 0;
while (i < 2)
{
terminated = waitpid(-1, NULL, 0);
printf("TERMINATED %d IN %d\n", terminated, getpid());
if (terminated == family[0])
{
printf("CLOSED W%d\n", pipefd[1]);
close(pipefd[1]);
}
else if (terminated == family[1])
{
printf("CLOSED R%d\n", pipefd[0]);
close(pipefd[0]);
}
i++;
}
}
void ft_interpret(void)
{
int i;
i = 0;
while (i < 2)
{
family[i] = fork();
printf("CREATED %d INSIDE %d\n", family[i], getpid());
if (family[i] == 0)
ft_exec(i);
i++;
}
ft_block_main_process();
}
int main(int argc, char **argv)
{
(void)argc;
(void)argv;
pipe(pipefd);
printf("PARENT ID IS %d\n", getpid());
printf("CREATED PIPE WITH FDS R%d AND W%d\n", pipefd[0], pipefd[1]);
ft_interpret();
}
为什么会这样?
感谢您富有洞察力的评论。
如果我在 dup2()
之后立即添加 close(pipefd[0])
和 close(pipefd[1])
,问题就解决了。
我认为只从主 shell 进程关闭管道就足够了,但结果发现所有出现的 fds 都必须关闭,包括 child 中的那些任谁继承一份。
我感到困惑的原因是第一段代码有效。现在我意识到,我正在等待第一个 (ls
) 进程退出,所以当它退出时,它的 write-end 副本被销毁,我在主要过程。那么wc
不继承任何。
所以现在在我的 shell 中,在 child 中执行期间,在 dup2()
ing 描述符之后,我关闭了由 pipe()
创建的每个描述符,并且它有效。
我正在尝试实现一个必须能够通过管道传输命令的 minishell。
最初,我按顺序执行进程,也就是说,我在等待第 n
个进程终止,然后再启动第 n + 1
个进程(这是一个最小的可重现示例,它执行ls | wc -l
):
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>
pid_t family[2];
int pipefd[2];
extern char **environ;
void ft_exec(int i)
{
char **ls = malloc(sizeof(char *) * 2);
ls[0] = "/bin/ls";
ls[1] = NULL;
char **wc = malloc(sizeof(char *) * 3);
wc[0] = "/usr/bin/wc";
wc[1] = "-l";
wc[2] = NULL;
printf("ENTERED BY %d\n", getpid());
if (i == 0)
{
dup2(pipefd[1], 1);
execve(ls[0], ls, environ);
}
if (i == 1)
{
dup2(pipefd[0], 0);
execve(wc[0], wc, environ);
}
}
void ft_interpret(void)
{
int i;
i = 0;
while (i < 2)
{
family[i] = fork();
printf("CREATED %d INSIDE %d\n", family[i], getpid());
if (family[i] == 0)
ft_exec(i);
waitpid(family[i], NULL, 0);
printf("TERMINATED %d IN %d\n", family[i], getpid());
if (i == 0)
{
printf("CLOSED W%d\n", pipefd[1]);
close(pipefd[1]);
}
else if (i == 1)
{
printf("CLOSED R%d\n", pipefd[0]);
close(pipefd[0]);
}
i++;
}
}
int main(int argc, char **argv)
{
(void)argc;
(void)argv;
pipe(pipefd);
printf("PARENT ID IS %d\n", getpid());
printf("CREATED PIPE WITH FDS R%d AND W%d\n", pipefd[0], pipefd[1]);
ft_interpret();
}
然而,我意识到这不是 shell 的工作方式,如果给出 cat /dev/urandom | head -c 100
这样的命令,程序将陷入死锁。
所以我决定先 fork 进程,然后无论哪个进程退出,我都关闭相关的管道写端。
由于某种原因,读取命令(在本例中为 wc
;如果我们将参数更改为非读取命令,例如 printenv
,它将正确执行)在读取时卡住,即使主进程已经关闭了管道的写端并且必须向 wc
.
修改后的代码如下:
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>
pid_t family[2];
int pipefd[2];
extern char **environ;
void ft_exec(int i)
{
char **ls = malloc(sizeof(char *) * 2);
ls[0] = "/bin/ls";
ls[1] = NULL;
char **wc = malloc(sizeof(char *) * 3);
wc[0] = "/usr/bin/wc";
wc[1] = "-l";
wc[2] = NULL;
printf("ENTERED BY %d\n", getpid());
if (i == 0)
{
dup2(pipefd[1], 1);
execve(ls[0], ls, environ);
}
if (i == 1)
{
dup2(pipefd[0], 0);
execve(wc[0], wc, environ);
}
}
void ft_block_main_process(void)
{
int i;
pid_t terminated;
i = 0;
while (i < 2)
{
terminated = waitpid(-1, NULL, 0);
printf("TERMINATED %d IN %d\n", terminated, getpid());
if (terminated == family[0])
{
printf("CLOSED W%d\n", pipefd[1]);
close(pipefd[1]);
}
else if (terminated == family[1])
{
printf("CLOSED R%d\n", pipefd[0]);
close(pipefd[0]);
}
i++;
}
}
void ft_interpret(void)
{
int i;
i = 0;
while (i < 2)
{
family[i] = fork();
printf("CREATED %d INSIDE %d\n", family[i], getpid());
if (family[i] == 0)
ft_exec(i);
i++;
}
ft_block_main_process();
}
int main(int argc, char **argv)
{
(void)argc;
(void)argv;
pipe(pipefd);
printf("PARENT ID IS %d\n", getpid());
printf("CREATED PIPE WITH FDS R%d AND W%d\n", pipefd[0], pipefd[1]);
ft_interpret();
}
为什么会这样?
感谢您富有洞察力的评论。
如果我在 dup2()
之后立即添加 close(pipefd[0])
和 close(pipefd[1])
,问题就解决了。
我认为只从主 shell 进程关闭管道就足够了,但结果发现所有出现的 fds 都必须关闭,包括 child 中的那些任谁继承一份。
我感到困惑的原因是第一段代码有效。现在我意识到,我正在等待第一个 (ls
) 进程退出,所以当它退出时,它的 write-end 副本被销毁,我在主要过程。那么wc
不继承任何。
所以现在在我的 shell 中,在 child 中执行期间,在 dup2()
ing 描述符之后,我关闭了由 pipe()
创建的每个描述符,并且它有效。