execvp() 永远不会在管道上完成

Posted

技术标签:

【中文标题】execvp() 永远不会在管道上完成【英文标题】:execvp() never finishes on pipe 【发布时间】:2015-01-30 03:26:54 【问题描述】:

我正在学习操作系统课程并编写一个 shell。我遇到了关于 execvp() 和管道的问题。以下代码是发生问题的代码的简化版本。

static pid_t
command_exec(command_t *cmd, int *pass_pipefd) //the function that executes commands. this function will be called multiple times if the input line contains multiple commands. Ex. "echo abc | cat"

    pid_t pid = 0; //child pid
    int pipefd[2]; //array for pipe
    if(cmd->controlop == CMD_PIPE)
        //if the control operation of the command is pipe(to the left of a '|'), create a pipe
        pipe(pipefd);
    
    pid = fork();
if(pid==0) //child branch
    dup2(*pass_pipefd,0);//redirect stdin to the pipe from last command
    if(strcmp(cmd->argv[0],"cd")) //if the command is not cd
        if(cmd->controlop == CMD_PIPE)
            dup2(pipefd[1],1); 
            //if command's control operation is pipe(to the left of a '|'), redirect stdout to pipefd[1]
        
        if(execvp(cmd->argv[0],cmd->argv)<0)//execute the command,use stdin as input and stdout as output(but they may be redirected)
            printf("%s fails\n",arg[0]);
    
    exit(0);
else//if parent
    wait(NULL); //wait for the child
    if(cmd->controlop == CMD_PIPE)
        *pass_pipefd = pipefd[0];//if command's control operation is pipe(to the left of a '|'), set *pass_pipefd to the output of the pipe array.
        return pid;
    

如果输入是“echo a”,那么输出是没有问题的。 execvp() 将完成并且父级中的 wait(NULL) 不会永远等待。但是,如果输入是“echo abc | cat”,那么“abc”将输出到终端,但程序会卡住。原因是执行“cat”的 execvp() 永远不会完成,因此父级中的 wait(NULL) 会永远等待。我知道 execvp() 不会返回,但它最终应该会完成。我想我可能会弄乱标准输入和标准输出重定向的东西,但我找不到错误。

【问题讨论】:

简单地说,您没有关闭足够多的文件描述符。特别是,父母必须关闭管道的两端。您也没有让父母同步等待(尽管这不是您的问题的一部分)。您必须让管道中的所有进程同时运行,因为如果您有 A | B 并且在启动 B 之前等待 A 完成,但 A 产生的数据多于管道缓冲区(4 KiB大约 64 KiB,IIRC,取决于平台),然后 A 永远不会退出,所以 B 永远不会启动,所以系统死锁。 [...继续...] [...continuation...] 由于您的代码不可执行 - 它不是 MCVE (Minimal, Complete, Verifiable Example) - 我不倾向于尝试修复它;我无法充分说明它是如何被调用的以及输入数据是什么样的。但是cat 在其标准输入返回 EOF 之前不会终止,并且当有一个管道的写入端打开的进程时,其标准输入不会返回 EOF。但是你的 shell 代码相当清楚地仍然打开了管道,所以你又死锁了。 @JonathanLeffler 谢谢乔纳森。我试试看 @JonathanLeffler 这将是一个很好的答案。 【参考方案1】:

简单地说,您没有关闭足够多的文件描述符。特别是,父级必须关闭管道的两端。

此外,在 shell 中,您不能让父级在运行下一个子级之前同步等待每个子级完成(尽管这不是您的问题的一部分)。您必须让管道中的所有进程同时运行,因为如果您有 A | B 并且在启动 B 之前等待 A 完成,但 A 产生的数据多于管道缓冲区(4 KiB到大约 64 KiB,IIRC,取决于平台),然后 A 永远不会退出,所以 B 永远不会启动,所以系统死锁。

由于您的代码不可执行——它不是 MCVE (Minimal, Complete, Verifiable Example)——我不打算尝试修复它;我无法充分说明它是如何被调用的以及输入数据是什么样的。但是cat 在其标准输入返回 EOF 之前不会终止,并且当有一个管道的写入端打开的进程时,其标准输入不会返回 EOF。但是你的 shell 代码相当清楚地仍然打开了管道,所以你又死锁了。

【讨论】:

以上是关于execvp() 永远不会在管道上完成的主要内容,如果未能解决你的问题,请参考以下文章

制作:execvp:g ++:权限被拒绝

execvp() 调用后主窗口关闭

pthread 的 malloc 内存,然后 fork + execvp

std::string -> execvp 等

允许使用execvp执行程序

为啥我会收到错误:无法在 macOS Catalina 下运行 PySide2 程序的“uic”:“execvp:没有这样的文件或目录”?