【问题标题】:grep hangs when executed using execvp function in my C program在我的 C 程序中使用 execvp 函数执行 grep 时挂起
【发布时间】:2020-05-08 23:59:40
【问题描述】:

我已经用 C 编写了一个类似于 Linux shell 的 bash。

(链接到我的存储库:https://github.com/oneiro-naut/wish .文件src/execute.c里面有execute_pipeline函数)

我在执行产生一些输出的内置命令时遇到问题。不产生任何输出的内置函数,如 cd 和 exit 工作得很好。外部命令运行流畅。 Shell 也支持管道结构和 I/O 重定向。问题是当我尝试运行内部命令时(它只是打印一条消息)。当我使用管道将内置命令的输出重定向到 grep 命令时,grep 子进程挂起,我认为这是因为 grep 在从管道读取时从未遇到流(或 EOF)的结尾。

注意:当管道中只有外部命令时不会发生这种情况。(我注意在下一个命令尚未启动之前没有(外部)命令完成,因此它不会挂在那里)

这是我的问题的简化版本 `

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>

int main()
{

int pipe_fd[2] = {-1,-1};
int EXIT_STAT = 0;

pipe(pipe_fd);//creating pipe


char* argv[]={"grep","Radiohead",NULL};

int std_out_dup = dup(1);
dup2(pipe_fd[1],1); // redirecting stdout to parent pipe's read end
//close(pipe_fd[0]);//closing parent pipe's write end 

printf("Alice in Chains\n");
printf("Radiohead\n");
printf("Dinosaur Jr.\n");
printf("Pixies\n");
printf("Soundgarden\n");
printf("Porcupine Tree\n");
printf("Pain of Salvation\n");


pid_t pid = fork();
if(pid == 0)//child process
{
dup2(pipe_fd[0],0); //redirecting grep's stdin to parent pipe's write end
close(pipe_fd[1]);
execvp(argv[0],argv);//executing grep command
perror("\n");
exit(-1);
}


else if(pid > 0)//parent process
{

    int i;//closing pipes for parent
    for(i=0;i<2;i++)
        close(pipe_fd[i]);

    do{ // waiting for grep to exit
        waitpid(pid,&EXIT_STAT,WUNTRACED);
        //write(,"\x4",1);
    }while(!WIFEXITED(EXIT_STAT)&&!WIFSIGNALED(EXIT_STAT));
}
else ;//fork error

dup2(std_out_dup,1); // restore stdout fd for parent process



return 0;

}

`
如果我尝试在不打印任何内容的情况下运行它,程序就会挂起。 但是按 ^D 会终止产生输出的程序:

Alice in Chains Radiohead Dinosaur Jr. Pixies Soundgarden Porcupine Tree Pain of Salvation

所以 grep 似乎无法无限期地读取,因为它没有收到任何 EOF 信号。 请指出我的错误我真的很困惑。

编辑:现在即使按 ^D 也不会终止,所以我只需要 ^C 程序。

改正后的程序:

int main()
{

    int pipe_fd[2] = {-1,-1};
    int EXIT_STAT = 0;

    pipe(pipe_fd);//creating pipe


    char* argv[]={"grep","Radiohead",NULL};

    int std_out_dup = dup(1);
    //printf("%d \n",std_out_dup);
    dup2(pipe_fd[1],1); // redirecting stdout to parent pipe's read end
    //close(pipe_fd[0]);//closing parent pipe's write end 

    printf("Alice in Chains\n");
    printf("Radiohead\n");
    printf("Dinosaur Jr.\n");
    printf("Pixies\n");
    printf("Soundgarden\n");
    printf("Porcupine Tree\n");
    printf("Pain of Salvation\n");
    fflush(stdout);

    pid_t pid = fork();
    if(pid == 0)//child process
    {
    close(pipe_fd[1]);
    dup2(pipe_fd[0],0); //redirecting grep's stdin to parent pipe's write end
    dup2(std_out_dup,1); // restore stdout fd for parent process
    execvp(argv[0],argv);//executing grep command
    perror("\n");
    exit(-1);
    }


    else if(pid > 0)//parent process
    {

        int i;//closing pipes for parent

        for(i=0;i<2;i++)
           close(pipe_fd[i]);

        dup2(std_out_dup,1); // restore stdout fd for parent process

        do{ // waiting for grep to exit
            waitpid(pid,&EXIT_STAT,WUNTRACED);
        }while(!WIFEXITED(EXIT_STAT)&&!WIFSIGNALED(EXIT_STAT));
      }
          else ;//fork error



       return 0;
    }

【问题讨论】:

  • 默认情况下,libc 将不会发送到 TTY 的输出配置为进行大量缓冲,以提高吞吐量而不是延迟。如果标准输入永远挂起,并且没有足够的数据被打印以强制刷新,则内容可以无限期地位于该缓冲区中。
  • 您在fork() 之前关闭pipe_fd[0],然后在fork() 之后的子项中关闭dup2。 dup2 调用将失败,因为旧的 fd 不是打开的文件描述符。
  • ...无论如何,如果没有看到 EOF,请检查其他进程(f/e,来自其他实例使用fork() 生成相同的进程。
  • @Ian Abbott 哦,我没有注意到,感谢您的指出。我将删除 close(fd) 调用并检查。我还没有关闭原始文件中的 fd,即使它一直挂起。
  • @Charles Duffy 我会再次检查,谢谢。

标签: c shell grep pipe io-redirection


【解决方案1】:

你应该

1. 恢复孩子的标准输出;否则grep 子进程将写入管道的写入端,即它的标准输入。

在if(pid == 0)//child process ... { 之后添加dup2(std_out_dup,1)

2. 在所有.... printf("Pain of Salvation\n"); printfs 之后添加fflush(stdout);否则流将被刷新两次。

3.关闭父子管道的所有写端;在父级中的wait 循环之前添加dup2(std_out_dup,1) 或close(1)。

【讨论】:

  • 非常感谢!没有注意到在分叉之前我已经重定向了标准输出,所以在孩子中标准输出默认会被重定向(我猜对了吗?@mosvy)你能告诉我为什么它会被刷新两次吗? (我会尝试你所说的)......再次感谢你!
  • 通过exit()(不是_exit())终止或从main返回将导致stdio buffers被刷新:当你调用printf时,这些东西当没有被换行符终止并且标准输出不是终端(在你的情况下它是管道,而不是终端)时,默认情况下不会立即刷新/写入。看起来您从父级中删除了 dup2(std_out_dup,1),因此您应该将其放回原处,或者 close(1) 以便通过关闭管道的写入端来导致 EOF。
  • 我发现在恢复 stdout 后我也没有关闭 stdout_dup ......这导致它挂在我的 shell 代码中
猜你喜欢
  • 1970-01-01
  • 2013-02-10
  • 2023-01-12
  • 2021-05-12
  • 1970-01-01
  • 2021-08-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多