【问题标题】:Closing the write-end of a pipe doesn't send an EOF to the other process关闭管道的写端不会向其他进程发送 EOF
【发布时间】:2021-08-23 14:41:45
【问题描述】:

我正在尝试实现一个必须能够通过管道传输命令的 minishell。

最初,我是按顺序执行进程,也就是说,我在等待nth 进程终止,然后再启动n + 1th 进程(这是执行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();
}

为什么会这样?

【问题讨论】:

  • 您不能“发送 EOF”。 EOF 不是出现在管道上的东西。一旦管道的所有写入端都关闭,任何尝试从管道读取的读取器都将无法获取数据。 read 调用将返回一个在某些(许多)语言中称为“EOF”的值。但是 EOF 不是出现(或“发送到”)管道的东西。
  • 毫无疑问,您将打开管道的一些写入端。仔细检查您的代码并确保所有末端都在正确的位置关闭。
  • 我建议您尝试简化您的代码,或者可能使用空的main 函数从头开始。然后你一个简单的一点点,测试它并确保它有效。一旦它在没有警告的情况下构建(启用了详细的额外警告)并通过了所有测试,那么您将继续下一个小而简单的部分。并且不要使用循环或过于通用的函数。这本身会大大简化,也可以更容易地单独测试每个位。
  • exec wc 之前,您永远不会关闭管道的写入端,因此wc 正在等待该管道关闭。它自己被阻止了。
  • dup2 的标准样板包括一个关闭。它应该几乎总是dup2(a, b); close(a)(您可以使用close(b); dup2(a,b); close(a);,但不需要初始关闭)

标签: c process pipe fork exec


【解决方案1】:

感谢富有洞察力的 cmets。

如果我在dup2()s 之后立即添加close(pipefd[0])close(pipefd[1]),问题就解决了。

我认为只从主 shell 进程关闭管道就足够了,但结果发现所有出现的 fd 都必须关闭,包括继承副本的子进程内的 fd。

我感到困惑的原因是第一段代码有效。现在我意识到,我正在等待第一个(ls)进程退出,所以当它退出时,它的写端副本被破坏,我在主进程中关闭它的最后一个副本。那么wc 不会继承任何东西。

所以现在在我的 shell 中,在一个子进程中执行期间,在 dup2()ing 描述符之后,我关闭了由 pipe() 创建的每个描述符,它就可以工作了。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-10-31
    • 2016-03-21
    • 2016-08-09
    • 1970-01-01
    • 2023-03-22
    • 2012-07-20
    相关资源
    最近更新 更多