C处理管道时悬挂外壳 [英] C Shell hanging when dealing with piping

查看:87
本文介绍了C处理管道时悬挂外壳的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm working on a C shell and am having trouble with getting an arbitrary amount of pipes to work. When I run the shell, it hangs on any piping. For some reason, when I do ls -la | sort, it hangs on the sort until I enter stuff and hit Ctrl+D. I know it has something to do with a pipe not closing, but the print statements show that pipes 3,4,5 all get closed in both the parent and child. I've been at this for a few hours and don't know why this doesn't work. Any help would be much appreciated.

原始代码:

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;

推荐答案

您没有在子级(或本例中的父级)中关闭足够的文件描述符.

You aren't closing enough file descriptors in the children (or, in this case, in the parent).

经验法则:如果您 dup2() 管道的一端到标准输入或标准输出,同时关闭两个 由返回的原始文件描述符 pipe() 尽快地. 特别是,在使用任何 exec*() 系列功能.

Rule of thumb: If you dup2() one end of a pipe to standard input or standard output, close both of the original file descriptors returned by pipe() as soon as possible. In particular, you should close them before using any of the exec*() family of functions.

如果您将描述符与任何一个重复,则该规则也适用 dup() 或者 fcntl() F_DUPFD

The rule also applies if you duplicate the descriptors with either dup() or fcntl() with F_DUPFD

在您的代码中,创建所有管道之前,您需要派生任何子级;因此,每个子级在复制要用于输入或输出的一个或两个后,都需要关闭所有管道文件描述符.

In your code, you create all the pipes before you fork any children; therefore, each child needs to close all the pipe file descriptors after duplicating the one or two that it is going to use for input or output.

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

The parent process must also close all the pipe descriptors.

此外,父母不应该等待孩子完成,直到发射所有孩子.通常,如果让子进程按顺序运行,则子进程将使用全管道缓冲区进行阻塞.您还会打败并行性的好处.但是请注意,在启动所有子级之前,父级必须保持管道打开-在启动每个子级后,父级必须不关闭它们.

Also, the parent should not wait for children to complete until after launching all the children. In general, children will block with full pipe buffers if you make them run sequentially. You also defeat the benefits of parallelism. Note, however, that the parent must keep the pipes open until it has launched all the children — it must not close them after it launches each child.

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

For your code, the outline operation should be:

  • 创建N个管道
  • 对于N个(或N + 1个)孩子中的每一个:
  • Create N pipes
  • For each of N (or N+1) children:
  1. 叉子.
  2. 子级复制标准输入和输出管道
  3. 孩子关闭所有管道文件描述符
  4. 孩子执行流程(并报告错误,如果失败则退出)
  5. 父母记录孩子的PID.
  6. 父级继续进行下一个迭代;没有等待就没有结束.
  1. Fork.
  2. Child duplicates standard input and output pipes
  3. Child closes all of the pipe file descriptors
  4. Child executes process (and reports error and exits if it fails)
  5. Parent records child PID.
  6. Parent goes on to next iteration; no waiting, no closing.

  • 父级现在关闭了N条管道.
  • 父母现在正在等待适当的孩子死亡.
  • 还有其他组织方式,或多或少都比较复杂.这些替代方法通常避免将所有管道提前打开,从而减少了要关闭的管道的数量.

    There are other ways of organizing this, of greater or lesser complexity. The alternatives typically avoid opening all the pipes up front, which reduces the number of pipes to be closed.

    适当的子代"意味着有多种方法可以确定何时完成"管道(通过管道连接的命令序列).

    'Appropriate children' means there are various ways of deciding when a pipeline (sequence of commands connected by pipes) is 'done'.

    • 一个选择是等待序列中的最后一个命令退出.这具有优势-是传统的实现方式.另一个优点是父进程可以启动最后一个子进程.孩子可以在管道中启动其前任,回到管道中的第一个过程.在这种情况下,父级永远不会创建管道,因此不必关闭任何管道.它也只有一个孩子等待.管道中的其他进程是一个孩子的后代.
    • 另一种选择是等待所有进程终止(1).这或多或少是Bash所做的.这使Bash可以知道管道中每个元素的退出状态.替代方案不允许这样做-与set -o pipefailPIPEFAIL数组有关.
    • One option is to wait for the last command in the sequence to exit. This has advantages — and is the traditional way to do it. Another advantage is that the parent process can launch the last child; the child can launch its predecessor in the pipeline, back to the first process in the pipeline. In this scenario, the parent never creates a pipe, so it doesn't have to close any pipes. It also only has one child to wait for; the other processes in the pipeline are descendents of the one child.
    • Another option is to wait for all the processes to die(1). This is more or less what Bash does. This allows Bash to know the exit status of each element of the pipeline; the alternative does not permit that — which is relevant to set -o pipefail and the PIPEFAIL array.

    您能帮我理解为什么中间管道的dup2语句是dup2(fd[(2*j)+1], fileno(stdout))dup2(fd[2*(j-1)], fileno(stdin))吗?我从Google那里得到了它,但它可以工作,但是我不确定为什么.

    Can you help me understand why the dup2 statement for the middle pipes is dup2(fd[(2*j)+1], fileno(stdout)) and dup2(fd[2*(j-1)], fileno(stdin))? I got it off Google and it works, but I'm unsure why.

    • fileno(stdout)1.
    • fileno(stdin)0.
    • 管道的读取端是文件描述符0(类似于标准输入).
    • 管道的写端是文件描述符1(类似于标准输出).
    • 您有一个数组int fd[2*N];,其值的N> 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.
        • fileno(stdout) is 1.
        • fileno(stdin) is 0.
        • The read end of a pipe is file descriptor 0 (analogous to standard input).
        • The write end of a pipe is file descriptor 1 (analogous to standard output).
        • You have an array int fd[2*N]; for some value of N > 1, and you get a pair of file descriptors for each pipe.
        • For an integer k, fd[k*2+0] is the read descriptor of a pipe, and fd[k*2+1] is the read descriptor.
        • When j is neither 0 nor (N-1), you want it to read from the previous pipe and to write to its pipe:
          • fd[(2*j)+1] is the write descriptor of pipe j — which gets connected to stdout.
          • fd[2*(j-1)] is the read descriptor of pipe j-1 — which gets connected to stdin.
          • (1) 在某些模糊的情况下,这会使父级无限期挂起.我强调晦涩;它需要类似进程的东西,而该进程可以作为守护进程挂起而无需派生.

            (1) There can be obscure scenarios where this leaves the parent hung indefinitely. I emphasize obscure; it requires something like a process that hangs around as a daemon without forking.

            这篇关于C处理管道时悬挂外壳的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆