在C语言中实现管道功能

3

我刚刚完成了我的shell解释器,但是我认为我的管道实现有问题。

它能够工作,像ls | cat -e这样的基本操作可以正常运行,但是如果文件描述符超过60ko,我担心会出现分段错误的可能性。 当我对一个长于60ko的文件进行cat操作时,我发现还有一个无限循环。例如,如果我执行foo | cat -e,其中foo是一个长文件,就会发生无限循环。

还有其他例子,比如当我执行cat /dev/urandom | cat -e时,它不会显示任何内容,因此它首先执行cat /dev/urandom,然后执行cat -e

这是我的代码:

int son(int *fd_in, int p[2], t_list *cmd, char **env)
{
    (void)env;
    dup2(*fd_in, 0);
    if (cmd->act != ENDACT && cmd->act != LEFT && cmd->act != DLEFT)
        dup2(p[1], 1);
    close(p[0]);
    execve(cmd->av[0], cmd->av, NULL);
    return (-1);
}

t_list *execute_pipe(t_list *cmd, int *fd_in)
{
    int           p[2];
    pid_t         pid;

    *fd_in = 0;
    while (cmd->act != -1)
    {
        pipe(p);
        if ((pid = fork()) == -1)
            return (NULL);
        else if (pid == 0)
            son(fd_in, p, cmd, NULL);
        else
        {
            wait(NULL);
            close(p[1]);
            *fd_in = p[0];
            if (cmd->act != PIPE)
                return (cmd);
            cmd = cmd->next;
        }
    }
    return (cmd);
}

3
你有问题吗? - lcd047
是的,如何正确地使用管道。因为我现在的做法是错误的。 - Dimitri Danilov
1
这可能会有帮助:http://web.cse.ohio-state.edu/~mamrak/CIS762/pipes_lab_notes.html - Gil Hamilton
@trigger,这个问题与批处理文件无关,只是标签错误。 - John Bollinger
我的观点是,Unix的人们必须停止将事物标记为Windows。 - user4883543
@提醒,人们“必须”停止将C++问题标记为C,也要停止将Java问题标记为Javascript等等。通常,错误标签是由于发布者不理解标签含义而发生的,因此“这永远不会停止”。如果你能放下这些,你的生活会更充实。 - John Bollinger
1个回答

5
管道的一部分想法是所涉及的进程并发运行(或可能如此)。您提供的代码通过在启动下一个进程之前等待每个子进程来积极防止这种情况发生。除其他事项外,这会在准备好将其排出之前填充(OS级别)管道的缓冲区。这将导致死锁,或者如果你很幸运,会生成一个错误。
在高层次上,该过程应该像这样:
1. [shell] 让C最初成为管道第一段的命令,并将fd0设置为STDIN_FILENO 2. [shell] 准备输出文件描述符: a. 如果有任何后续命令,则创建一个pipe(),并将fd1设置为该管道的写端; b. 否则,将fd1设置为STDOUT_FILENO 3. [shell] fork()一个子进程来运行命令C。在其中: a. [child] 如果fd0与STDIN_FILENO不同,则将fd0复制到STDIN_FILENO上,并关闭fd0 b. [child] 如果fd1与STDOUT_FILENO不同,则将fd1复制到STDOUT_FILENO上,并关闭fd1 c. [child] exec命令C 4. [shell] 如果fd0与STDIN_FILENO不同,则关闭fd0 5. [shell] 如果fd1与STDOUT_FILENO不同,则关闭fd1 6. [shell] 如果管道中还有更多的命令,则: a. 设置C为下一个命令 b. 将fd0设置为步骤(2)中的管道的读端 c. 转到步骤2(准备输出文件描述符) 7. [shell] (此时,管道中的所有进程都已启动。)等待所有子进程的wait()或waitpid()
请注意,这同样适用于包含任何正数命令的管道,包括1个命令。

谢谢你的回答!所以我理解的是,你是告诉我应该停止等待子进程?那样会引起很多问题,对吗?比如一个进程可能在另一个进程之前结束,或者类似的情况。 - Dimitri Danilov
1
你必须等待每个子进程,但只有在它们全部启动后才能等待(我的答案中的第7步)。为每个管道段提供适当的命令是用户的问题。在这种情况下使用的典型命令将不会退出,直到它们的标准输入关闭。如果一个进程在管道中之前的进程消耗完所有数据之前退出,则会导致破损的管道。这在标准 shells 中有时会发生。您的 shell 可以通过更或少的优雅方式处理它。 - John Bollinger
该程序看起来合理,但上面的解释不符合(也没有意义)。在启动下一个进程之前,您不需要“等待每个子进程”(实际上,您上面的注释指出只有在所有进程启动后才会等待)。此外,除非出现一些特殊情况(并且是故意的),否则完整的管道不能导致死锁或错误。正如pipe(7)中所指出的那样,一个完整的管道只会阻塞写入者(除非设置了O_NONBLOCK,这是程序想要处理的特殊情况)。 - Gil Hamilton
@GilHamilton,顶部的注释描述了OP提供的代码存在的问题,而不是如何完成任务的建议。我已经更新了我的回答以澄清这一点。至于死锁,OP的代码可能会出现一个子进程被阻塞在写入管道时,另一个子进程的创建被阻塞在第一个子进程完成时。两者都无法在另一个进程之前进行-- 这就是死锁。或者至少,这就是我所指的术语。您可以选择其他不同的术语。 - John Bollinger
啊,好的。现在我明白了。 - Gil Hamilton

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接