关闭管道的写入端不会将 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() 创建的每个描述符,并且它有效。