我正在使用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;
答案 0 :(得分:2)
您没有在子项中关闭足够的文件描述符(或者,在本例中,在父项中)。
经验法则:如果你
dup2()
管道的一端到标准输入或标准输出,关闭两者
返回的原始文件描述符
pipe()
尽快地。
特别是,你应该在使用任何之前关闭它们
exec*()
功能系列。
如果您使用其中任何一个复制描述符,该规则也适用
dup()
要么
fcntl()
与F_DUPFD
在代码中,在分叉任何子代之前创建所有管道;因此,每个子节点在复制它将用于输入或输出的一个或两个之后需要关闭所有管道文件描述符。
父进程还必须关闭所有管道描述符。
此外,在启动所有孩子之后,父母不应等待孩子完成。通常,如果按顺序运行,子项将使用完整管道缓冲区进行阻塞。你也打败了并行的好处。但请注意,父母必须保持管道打开,直到它启动了所有孩子 - 它不能在启动每个孩子后关闭它们。
对于您的代码,大纲操作应为:
还有其他组织方式,或多或少的复杂性。替代方案通常避免在前面打开所有管道,这减少了要关闭的管道数量。
'适当的孩子'意味着有多种方法可以决定管道(通过管道连接的命令序列)何时“完成”。
set -o pipefail
和PIPEFAIL
数组相关。您能否帮我理解为什么中间管道的
dup2
声明为dup2(fd[(2*j)+1], fileno(stdout))
和dup2(fd[2*(j-1)], fileno(stdin))
?我从谷歌那里得到它并且它有效,但我不确定为什么。
fileno(stdout)
是1
。fileno(stdin)
是0
。int fd[2*N];
,其中某个值为N&gt; 1,你得到每个管道的一对文件描述符。k
,fd[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) 可能存在一些模糊不清的情况,这会使父母无限期地挂起。我强调晦涩;它需要一个像守护进程一样挂起的进程,而不需要分叉。