使用pipe()和execvp()杀死我的程序

时间:2016-09-27 02:45:00

标签: c shell

当我运行这段代码时,我的程序被杀了,但我似乎无法找出原因。

int pipefd[2];
pipe(pipefd);
pid = fork();

if(pid == 0){                      //child process, receives data from pipe for execution   
  close(0);
  dup(pipefd[0]);                       
  close(pipefd[1]);
  execvp(pipeCom2[0], pipeCom2);
}
else if (pid > 1){                //parent process writes data to pipe for execution 
  close(1);
  dup(pipefd[1]);                                      
  close(pipefd[0]);
  execvp(pipeCom1[0], pipeCom1);
  waitpid(pid, &status, WUNTRACED);
}

我一直在测试cat project1.c | grep include命令。它在我的程序中正确显示所有include语句。

所以pipeCom1包含{" cat"," project1.c"}

和pipeCom2包含{" grep"," include"}

在程序的早期,我使用此代码覆盖输出,并在程序结束时恢复正常。因此,当进程被终止时,输出就会搞砸。

newConfig.c_lflag &= ~(ICANON| ECHOE | ECHO);
tcsetattr(0, TCSANOW, &newConfig);

1 个答案:

答案 0 :(得分:1)

将评论转移到答案中。

关闭管道

您没有关闭足够的管道文件描述符。如果将描述符复制到标准输入或输出,则应关闭两个管道描述符。

  

我应该关闭哪些其他管道文件描述符?

你有:

int pipefd[2];
pipe(pipefd);
pid = fork();

if (pid == 0)                      //child process, receives data from pipe for execution
{   
    close(0);
    dup(pipefd[0]);                       
    close(pipefd[1]);
    execvp(pipeCom2[0], pipeCom2);
}
else if (pid > 1)                 //parent process writes data to pipe for execution
{
    close(1);
    dup(pipefd[1]);                                      
    close(pipefd[0]);
    execvp(pipeCom1[0], pipeCom1);
    waitpid(pid, &status, WUNTRACED);
}

在调用pipefd[0]后,您需要在两组代码中同时关闭pipefd[1]dup()。您也可以使用dup2()代替dup();这通常更简单。

在这个带有试用管道的程序中,如果没有添加缺少的close()操作,那么一切都会很好。但是,一般来说,关闭不需要的管道描述符是至关重要的,因为开放描述符意味着进程不会获得它期望的EOF,或者它应该不会得到SIGPIPE信号或写入错误,因为进程有错误的管道末端仍然打开。

家长调用execvp()

此外,父进程包含execvp()调用;仅当exec失败时才执行以下代码。当进程运行execvp()(或任何其他exec*()函数时,只有在执行操作失败时才会返回调用。

如果您需要终端重置,请在其中一个exec调用之前执行,或者创建两个子节点并让父节点关闭其所有管道描述符并等待子节点完成(die),然后重置终端。考虑一下信号处理。在执行其他程序之前重置终端更简单。

  

在程序的早期,我使用此代码覆盖输出,并在程序结束时恢复正常。因此,当进程被终止时,输出就会搞砸。

newConfig.c_lflag &= ~(ICANON| ECHOE | ECHO);
tcsetattr(0, TCSANOW, &newConfig);

假设您有一个oldConfig来自tcgetattr()的输出,您应该这样做:

  tcsetattr(0, TCSANOW, &oldConfig);
在父进程运行execvp()之前

Null终止命令参数列表

你说:

  

pipeCom1包含{“cat”,“project1.c”}

     

和pipeCom2包含{“grep”,“include”}

正确操作至关重要pipeCom1pipeCom2都有一个空(指针)终止的参数字符串列表。例如,您可以使用以下硬连线参数列表:

char *pipeCom1[] = { "cat", "project1.c", 0 };
char *pipeCom2[] = { "grep", "include", 0 };

如果您愿意,可以用NULL代替0。

报告错误并在execvp()失败时退出

您需要考虑execvp()失败后应该发生什么。正确的答案很少“继续,好像没有出错”(这是你的代码所做的)。

您应该考虑将错误报告给标准错误。您通常应该使用exit(EXIT_FAILURE);_exit(EXIT_FAILURE);或其中某些亲属退出并显示失败状态。

汇总所有这些变化

#include <stdio.h>
#include <termios.h>
#include <stdlib.h>
#include <unistd.h>

int main(void)
{
    struct termios oldConfig;
    struct termios newConfig;
    tcgetattr(0, &oldConfig);           // Error check omitted
    newConfig = oldConfig;
    newConfig.c_lflag &= ~(ICANON| ECHOE | ECHO);
    tcsetattr(0, TCSANOW, &newConfig);  // Error check omitted

    /* Do stuff without echoing enabled */

    /* Now do some execution */

    char *pipeCom1[] = { "cat", "project1.c", 0 };
    char *pipeCom2[] = { "grep", "include", 0 };

    int pipefd[2];                      // Error check omitted
    pipe(pipefd);
    pid_t pid = fork();

    if (pid == 0)                       //child process
    {   
        close(0);
        dup(pipefd[0]);                       
        close(pipefd[1]);
        close(pipefd[0]);
        execvp(pipeCom2[0], pipeCom2);
        perror(pipeCom2[0]);
        exit(EXIT_FAILURE);
    }
    else if (pid > 1)                   //parent process
    {
        close(1);
        dup(pipefd[1]);                                      
        close(pipefd[0]);
        close(pipefd[1]);
        tcsetattr(0, TCSANOW, &oldConfig);
        execvp(pipeCom1[0], pipeCom1);
        perror(pipeCom1[0]);
        exit(EXIT_FAILURE);
    }
}

样品运行

假设project1.c是上述源代码的副本,该命令的输出为:

#include <stdio.h>
#include <termios.h>
#include <stdlib.h>
#include <unistd.h>
    char *pipeCom2[] = { "grep", "include", 0 };

,过程结束后终端处于正确的模式。