C壳管在处理时悬挂

时间:2018-04-20 03:53:10

标签: c pipe fork exec dup

我正在使用C shell,但是在使用任意数量的管道时遇到了麻烦。当我运行外壳时,它挂在任何管道上。出于某种原因,当我执行ls -la | sort时,它会挂起,直到我输入内容并点击 Ctrl + D 。我知道它与管道没有关闭有关,但是打印语句显示管道3,4,5都在父和子中都关闭了。我已经在这几个小时了,不知道为什么这不起作用。任何帮助将不胜感激。

原始代码:

char *current_command;
current_command = strtok_r(cmdline_copy, "|", &cmdline_copy);
char *commands[100][MAX_ARGS]; //Max 100 piped commands with each having MAX_ARGS arguments
int i = 0;
while (current_command != NULL) { //Go through each command and add it to the array
    char *copy = malloc(strlen(current_command)*sizeof(char)); //Copy of curretn command
    strcpy(copy, current_command);
    char *args_t[MAX_ARGS];
    int nargs_t = get_args(copy, args_t);
    memcpy(commands[i], args_t, sizeof(args_t)*nargs_t); //Copy the command and it's arguments to the 2d array
    i++;
    current_command = strtok_r(NULL, "|\n", &cmdline_copy); //Use reentrant version of strtok to prevent fighting with get_args function
}
int fd[2*(i-1)]; //Set up the pipes i.e fd[0,1] is first pipe, fd[1,2] second pipe, etc.
for (int j = 0; j < i*2; j+=2) {
    pipe(fd+j);
}
//Here is where we do the commands
for (int j = 0; j < i; j++) {
    pid = fork(); //Fork
    if (pid == 0) { //Child process
        if (j == 0) { //First process
            printf("Child Closed %d\n", fd[0]);
            close(fd[0]);
            dup2(fd[1], fileno(stdout));
        }
        else if (j == i -1) { //Last process
            dup2(fd[j], fileno(stdin));
            printf("Child closed %d\n", fd[j]);
            printf("Child closed %d\n", fd[j+1]);
            close(fd[j+1]);
            close(fd[j]);
        }
        else { //Middle processes
            dup2(fd[j], fileno(stdin));
            dup2(fd[j+1], fileno(stdout));
            printf("Child closed %d\n", fd[j]);
            close(fd[j]);
        }
        execvp(commands[j][0], commands[j]);
    }
    else if (pid > 0) { //Parent
        printf("Parent closed %d\n", fd[j]);
        close(fd[j]);
        printf("Parent closed %d\n", fd[j+1]);
        close(fd[j+1]);
        waitpid(pid, NULL, 0); //Wait for the process
    }
    else {
        perror("Error with fork");
        exit(1);
    }
}

最终守则:

char *current_command;
current_command = strtok_r(cmdline_copy, "|", &cmdline_copy);
char *commands[100][MAX_ARGS]; //Max 100 piped commands with each having MAX_ARGS arguments
int command_count = 0;
while (current_command != NULL) { //Go through each command and add it to the array
   char *copy = malloc(strlen(current_command)*sizeof(char)); //Copy of curretn command because get_args uses strtok
   strcpy(copy, current_command);
   char *args_t[MAX_ARGS];
   int nargs_t = get_args(copy, args_t);
   memcpy(commands[command_count], args_t, sizeof(args_t)*nargs_t); //Copy the command and it's arguments to the 2d array
   command_count++;
   current_command = strtok_r(NULL, "|\n", &cmdline_copy); //Use reentrant version of strtok to prevent fighting with get_args function
}
int fd[command_count*2-1];
pid_t pids[command_count];
for (int j = 0; j < command_count*2; j+=2) { //Open up a pair of pipes for every command
   pipe(fd+j);
}
for (int j = 0; j < command_count; j++) {
   pids[j] = fork();
   if (pids[j] == 0) { //Child process
      if (j == 0) { //Duplicate only stdout pipe for first pipe
         dup2(fd[1], fileno(stdout));
      }
      else if (j == (command_count-1)) { //Duplicate only stdin for last pipe
         up2(fd[2*(command_count-1)-2], fileno(stdin));
      }
      else { //Duplicate both stdin and stdout
         dup2(fd[2*(j-1)], fileno(stdin));
         dup2(fd[2*j+1], fileno(stdout));
      }
      for (int k = 0; k < j*2; k++) { //Close all fds
         close(fd[k]);
      }
      execvp(commands[j][0], commands[j]); //Exec the command
   }
   else if (pids[j] < 0) {
      perror("Error forking");
   }
}
for (int k = 0; k < command_count*2; k++) { //Parent closes all fds
   close(fd[k]);
}
waitpid(pids[command_count-1], NULL, 0); //Wait for only the last process;

1 个答案:

答案 0 :(得分:2)

您没有在子项中关闭足够的文件描述符(或者,在本例中,在父项中)。

经验法则:如果你 dup2() 管道的一端到标准输入或标准输出,关闭两者 返回的原始文件描述符 pipe() 尽快地。 特别是,你应该在使用任何之前关闭它们 exec*() 功能系列。

如果您使用其中任何一个复制描述符,该规则也适用 dup() 要么 fcntl()F_DUPFD

在代码中,在分叉任何子代之前创建所有管道;因此,每个子节点在复制它将用于输入或输出的一个或两个之后需要关闭所有管道文件描述符。

父进程还必须关闭所有管道描述符。

此外,在启动所有孩子之后,父母不应等待孩子完成。通常,如果按顺序运行,子项将使用完整管道缓冲区进行阻塞。你也打败了并行的好处。但请注意,父母必须保持管道打开,直到它启动了所有孩子 - 它不能在启动每个孩子后关闭它们。

对于您的代码,大纲操作应为:

  • 创建N个管道
  • 对于N(或N + 1)名儿童中的每一名:
    1. 叉。
    2. 儿童复制标准输入和输出管道
    3. 子项关闭管道文件描述符的所有
    4. 子进程执行(并报告错误并在失败时退出)
    5. 父记录子PID。
    6. 父进行下一次迭代;没有等待,没有关闭。
  • 父亲现在关闭N个管道。
  • 父母现在等待适当的孩子死去。

还有其他组织方式,或多或少的复杂性。替代方案通常避免在前面打开所有管道,这减少了要关闭的管道数量。

'适当的孩子'意味着有多种方法可以决定管道(通过管道连接的命令序列)何时“完成”。

  • 一个选项是等待序列中的最后一个命令退出。这具有优势 - 并且是传统的方法。另一个优点是父进程可以启动最后一个子进程;孩子可以在管道中启动其前身,回到管道中的第一个过程。在这种情况下,父节点永远不会创建管道,因此它不必关闭任何管道。它也只有一个孩子可以等待;管道中的其他流程是一个孩子的后代。
  • 另一种选择是等待所有进程死于(1)。这或多或少是Bash所做的。这允许Bash知道管道的每个元素的退出状态;替代方案不允许 - 与set -o pipefailPIPEFAIL数组相关。
  

您能否帮我理解为什么中间管道的dup2声明为dup2(fd[(2*j)+1], fileno(stdout))dup2(fd[2*(j-1)], fileno(stdin))?我从谷歌那里得到它并且它有效,但我不确定为什么。

  • fileno(stdout)1
  • fileno(stdin)0
  • 管道的读取结束是文件描述符0(类似于标准输入)。
  • 管道的写入端是文件描述符1(类似于标准输出)。
  • 您有一个数组int fd[2*N];,其中某个值为N&gt; 1,你得到每个管道的一对文件描述符。
  • 对于整数kfd[k*2+0]是管道的读取描述符,fd[k*2+1]是读取描述符。
  • j既不是0也不是(N-1)时,您希望它从前一个管道中读取并写入其管道:
    • fd[(2*j)+1]是管道j的写入描述符 - 它与stdout连接。
    • fd[2*(j-1)]是管道j-1的读取描述符 - 它与stdin连接。
  • 因此,两个dup2()调用将正确的管道文件描述符连接到管道中进程j的标准输入和标准输出。

(1) 可能存在一些模糊不清的情况,这会使父母无限期地挂起。我强调晦涩;它需要一个像守护进程一样挂起的进程,而不需要分叉。