C Unix管道示例

5
尝试实现一个shell,主要是管道。我编写了这个测试用例,期望将ls简单地导入到wc中...但它却没有按预期工作。它先将ls打印到终端,然后打印内存耗尽。 我很迷失在如何修复它并使它正常运行的过程中。find_path在我的所有测试中都有效。
编辑-我必须使用execv进行项目,这是一个课堂要求,但我已经尝试过execvp,效果完全相同。此外,这只是一个例子,一个测试,看看为什么它不起作用,我分别调用两个命令的fork和waitpid,因为我没有其他事情要做。
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
int find_path(char* execname, char** dst)
{        
        char *path = getenv("PATH");
        path = strdup(path);
        char *pos;
        path = strtok_r(path, ":", &pos);
        char *originalpath  = path;
        do
        {
                char* test = (char*)calloc(strlen(path) + strlen(execname) + 2, sizeof(char));
                test = strcpy(test, path);
                int testlen = strlen(test);
                (*(test+testlen)) = '/';
                strcpy(test + testlen + 1,execname);
                struct stat buf;
                int result = stat(test, &buf);
                if (result == 0)
                {
                        *dst = test;
                        free (originalpath);
                        return 1;
                }
                else
                {
                        free(test);
                }

        } while ((path = strtok_r(NULL, ":", &pos)) != NULL);
        free(originalpath);
        return 0;
}

int main()
{
    char *cmd1 = "ls";
    char *cmd2 = "wc";
    int filedes[2];
    pipe(filedes);
    char** argv = (char**)calloc(1, sizeof(char*)); 
    argv[0] = (char*)malloc(sizeof(char*));
    argv[0] = NULL;

    pid_t pid = fork();
    if (pid == 0)
    {
        char *path;
                find_path(cmd1, &path);
        dup2(filedes[1],stdout);

        execv(path,argv); 
    }
    pid = fork();
    if (pid == 0)
    {
        dup2(filedes[0], stdin);
        char *path;
        find_path(cmd2, &path);
        execv(path, argv);

    }
    else
        waitpid(pid);

}

你看过去年那些“如何使用... fork ... pipe ... exec ...”实现shell的问题吗?答案很可能在其中一个(更可能是许多个)里面。 - Jonathan Leffler
2个回答

9

通常情况下,当程序难以调试时,最好简化它,以消除错误的源头。这里是您的程序,已经简化,删除了find_path作为错误来源:

#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>

int main(void)
{
    int filedes[2];
    pipe(filedes);

    /* Run LS. */
    pid_t pid = fork();
    if (pid == 0) {
        /* Set stdout to the input side of the pipe, and run 'ls'. */
        dup2(filedes[1], 1);
        char *argv[] = {"ls", NULL};
        execv("/bin/ls", argv);
    } else {
        /* Close the input side of the pipe, to prevent it staying open. */
        close(filedes[1]);
    }

    /* Run WC. */
    pid = fork();
    if (pid == 0) {
        dup2(filedes[0], 0);
        char *argv[] = {"wc", NULL};
        execv("/usr/bin/wc", argv);
    }

    /* Wait for WC to finish. */
    waitpid(pid);
}

这应该能按照你的预期运行。

简化过程中出现了一些错误:

  • argv[] 没有被正确设置,特别是,argv[0] 被设置为 NULL
  • 程序没有关闭被给予 ls 的管道的输入端。当 ls 完成后,由于 wc 进程仍然保持它打开状态,管道没有被关闭,导致 wc 永远无法完成。
  • 程序混淆了类型为 FILE*stdoutstdin 值与文件描述符号 01(由 duppipe 等使用)。

太棒了,你发现了我所有愚蠢的错误。在测试中,我尝试了你建议的所有东西,只是完全忘记了第一个命令行参数传递的应该是可执行文件名!非常感谢。 - Ben

1

有很多方法可以改进这段代码(例如,将其分解为较小的函数是一个好的开始),但我怀疑你的内存问题来自于find_path()中的代码,你可以完全避免这个问题,使用execvp,它会使用标准PATH机制为您定位可执行文件。最好使用sigaction安装信号处理程序来处理SIGCHLD并从信号处理程序调用waitpid,而不是像您正在做的那样随意调用waitpid()。您似乎fork了比您想要的更多次,并且没有检查错误。希望这些建议能够帮助到您。


我编辑了我的帖子,解释了为什么我使用find_path,但execvp也可以做同样的事情,等待可能不是最好的方法,但我只是在这里创建一个问题的示例,所以这与错误无关。 - Ben

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