C处理管道时悬挂外壳 [英] C Shell hanging when dealing with piping
问题描述
我在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:
- 叉子.
- 子级复制标准输入和输出管道
- 孩子关闭所有管道文件描述符
- 孩子执行流程(并报告错误,如果失败则退出)
- 父母记录孩子的PID.
- 父级继续进行下一个迭代;没有等待就没有结束.
- Fork.
- Child duplicates standard input and output pipes
- Child closes all of the pipe file descriptors
- Child executes process (and reports error and exits if it fails)
- Parent records child PID.
- Parent goes on to next iteration; no waiting, no closing.
还有其他组织方式,或多或少都比较复杂.这些替代方法通常避免将所有管道提前打开,从而减少了要关闭的管道的数量.
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 pipefail
和PIPEFAIL
数组有关.
- 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 thePIPEFAIL
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 isdup2(fd[(2*j)+1], fileno(stdout))
anddup2(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,并且每个管道都有一对文件描述符. - 对于整数
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
. fileno(stdout)
is1
.fileno(stdin)
is0
.- 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, andfd[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 pipej
— which gets connected tostdout
.fd[2*(j-1)]
is the read descriptor of pipej-1
— which gets connected tostdin
.
(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屋!
-