C中多管道的实现
我正在尝试在 C 语言的 shell 中实现多个管道。我找到了一个关于此的教程 网站,我做的功能就是基于这个例子。这是该函数
void executePipes(cmdLine* command, char* userInput) {
int numPipes = 2 * countPipes(userInput);
int status;
int i = 0, j = 0;
int pipefds[numPipes];
for(i = 0; i < (numPipes); i += 2)
pipe(pipefds + i);
while(command != NULL) {
if(fork() == 0){
if(j != 0){
dup2(pipefds[j - 2], 0);
}
if(command->next != NULL){
dup2(pipefds[j + 1], 1);
}
for(i = 0; i < (numPipes); i++){
close(pipefds[i]);
}
if( execvp(*command->arguments, command->arguments) < 0 ){
perror(*command->arguments);
exit(EXIT_FAILURE);
}
}
else{
if(command != NULL)
command = command->next;
j += 2;
for(i = 0; i < (numPipes ); i++){
close(pipefds[i]);
}
while(waitpid(0,0,0) < 0);
}
}
}
执行后并输入命令,例如 ls | grep bin
,shell 只是挂在那里并且不输出任何结果。我确保关闭了所有管道。但它只是挂在那里。我认为问题出在 waitpid
上。我删除了 waitpid
,执行后没有得到任何结果。我做错了什么?谢谢。
添加的代码:
void runPipedCommands(cmdLine* command, char* userInput) {
int numPipes = countPipes(userInput);
int status;
int i = 0, j = 0;
pid_t pid;
int pipefds[2*numPipes];
for(i = 0; i < 2*(numPipes); i++){
if(pipe(pipefds + i*2) < 0) {
perror("pipe");
exit(EXIT_FAILURE);
}
}
while(command) {
pid = fork();
if(pid == 0) {
//if not first command
if(j != 0){
if(dup2(pipefds[(j-1) * 2], 0) < 0){
perror(" dup2");///j-2 0 j+1 1
exit(EXIT_FAILURE);
//printf("j != 0 dup(pipefd[%d], 0])\n", j-2);
}
//if not last command
if(command->next){
if(dup2(pipefds[j * 2 + 1], 1) < 0){
perror("dup2");
exit(EXIT_FAILURE);
}
}
for(i = 0; i < 2*numPipes; i++){
close(pipefds[i]);
}
if( execvp(*command->arguments, command->arguments) < 0 ){
perror(*command->arguments);
exit(EXIT_FAILURE);
}
} else if(pid < 0){
perror("error");
exit(EXIT_FAILURE);
}
command = command->next;
j++;
}
for(i = 0; i < 2 * numPipes; i++){
close(pipefds[i]);
puts("closed pipe in parent");
}
while(waitpid(0,0,0) <= 0);
}
}
I'm trying to implement multiple pipes in my shell in C. I found a tutorial on this website and the function I made is based on this example. Here's the function
void executePipes(cmdLine* command, char* userInput) {
int numPipes = 2 * countPipes(userInput);
int status;
int i = 0, j = 0;
int pipefds[numPipes];
for(i = 0; i < (numPipes); i += 2)
pipe(pipefds + i);
while(command != NULL) {
if(fork() == 0){
if(j != 0){
dup2(pipefds[j - 2], 0);
}
if(command->next != NULL){
dup2(pipefds[j + 1], 1);
}
for(i = 0; i < (numPipes); i++){
close(pipefds[i]);
}
if( execvp(*command->arguments, command->arguments) < 0 ){
perror(*command->arguments);
exit(EXIT_FAILURE);
}
}
else{
if(command != NULL)
command = command->next;
j += 2;
for(i = 0; i < (numPipes ); i++){
close(pipefds[i]);
}
while(waitpid(0,0,0) < 0);
}
}
}
After executing it and typing a command like for example ls | grep bin
, the shell just hangs there and doesn't output any result. I made sure I closed all pipes. But it just hangs there. I thought that it was the waitpid
that's was the problem. I removed the waitpid
and after executing I get no results. What did I do wrong? Thanks.
Added code:
void runPipedCommands(cmdLine* command, char* userInput) {
int numPipes = countPipes(userInput);
int status;
int i = 0, j = 0;
pid_t pid;
int pipefds[2*numPipes];
for(i = 0; i < 2*(numPipes); i++){
if(pipe(pipefds + i*2) < 0) {
perror("pipe");
exit(EXIT_FAILURE);
}
}
while(command) {
pid = fork();
if(pid == 0) {
//if not first command
if(j != 0){
if(dup2(pipefds[(j-1) * 2], 0) < 0){
perror(" dup2");///j-2 0 j+1 1
exit(EXIT_FAILURE);
//printf("j != 0 dup(pipefd[%d], 0])\n", j-2);
}
//if not last command
if(command->next){
if(dup2(pipefds[j * 2 + 1], 1) < 0){
perror("dup2");
exit(EXIT_FAILURE);
}
}
for(i = 0; i < 2*numPipes; i++){
close(pipefds[i]);
}
if( execvp(*command->arguments, command->arguments) < 0 ){
perror(*command->arguments);
exit(EXIT_FAILURE);
}
} else if(pid < 0){
perror("error");
exit(EXIT_FAILURE);
}
command = command->next;
j++;
}
for(i = 0; i < 2 * numPipes; i++){
close(pipefds[i]);
puts("closed pipe in parent");
}
while(waitpid(0,0,0) <= 0);
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
我认为这里的问题是您的等待和关闭在创建子项的同一个循环内。在第一次迭代中,子进程将执行(这将销毁子程序,用您的第一个命令覆盖它),然后父进程关闭其所有文件描述符并等待子进程完成,然后再迭代创建下一个子进程。此时,由于父级已关闭其所有管道,因此任何其他子级将无法写入或读取任何内容。由于您没有检查 dup2 调用是否成功,因此不会注意到这一点。
如果您想保持相同的循环结构,则需要确保父级仅关闭已使用的文件描述符,而保留那些未使用的文件描述符。然后,在创建所有子项之后,您的父项就可以等待了。
编辑:我在答案中混淆了父/子,但推理仍然成立:再次分叉的进程会关闭其所有管道副本,因此第一个分叉之后的任何进程将没有有效的文件描述符可供读取/写入。
伪代码,使用预先创建的管道数组:
在此代码中,原始父进程为每个命令创建一个子进程,因此在整个考验中幸存下来。孩子们检查是否应该从上一个命令获取输入以及是否应该将输出发送到下一个命令。然后他们关闭管道文件描述符的所有副本,然后执行。父级除了 fork 之外什么都不做,直到它为每个命令创建了一个子级。然后它关闭所有描述符副本并可以继续等待。
首先创建所需的所有管道,然后在循环中管理它们,这是很棘手的并且需要一些数组算术。不过,目标看起来像这样:
认识到在任何给定时间,您只需要两组管道(上一个命令的管道和下一个命令的管道)将简化您的代码并使其更加健壮。 Ehemient 在此处提供了伪代码。他的代码更干净,因为父级和子级不必执行不必要的循环来关闭不需要的文件描述符,并且父级可以在分叉后立即轻松关闭其文件描述符的副本。
附带说明:您应该始终检查 pipeline、dup2、fork 和 exec 的返回值。
编辑2:伪代码中的拼写错误。 OP:num-pipes 是管道的数量。例如,“ls | grep foo | sort -r”将有 2 个管道。
I believe the issue here is that your waiting and closing inside the same loop that's creating children. On the first iteration, the child will exec (which will destroy the child program, overwriting it with your first command) and then the parent closes all of its file descriptors and waits for the child to finish before it iterates on to creating the next child. At that point, since the parent has closed all of its pipes, any further children will have nothing to write to or read from. Since you are not checking for the success of your dup2 calls, this is going un-noticed.
If you want to keep the same loop structure, you'll need to make sure the parent only closes the file descriptors that have already been used, but leaves those that haven't alone. Then, after all children have been created, your parent can wait.
EDIT: I mixed up the parent/child in my answer, but the reasoning still holds: the process that goes on to fork again closes all of its copies of the pipes, so any process after the first fork will not have valid file descriptors to read to/write from.
pseudo code, using an array of pipes created up-front:
In this code, the original parent process creates a child for each command and therefore survives the entire ordeal. The children check to see if they should get their input from the previous command and if they should send their output to the next command. Then they close all of their copies of the pipe file descriptors and then exec. The parent doesn't do anything but fork until it's created a child for each command. It then closes all of its copies of the descriptors and can go on to wait.
Creating all of the pipes you need first, and then managing them in the loop, is tricky and requires some array arithmetic. The goal, though, looks like this:
Realizing that, at any given time, you only need two sets of pipes (the pipe to the previous command and the pipe to the next command) will simplify your code and make it a little more robust. Ephemient gives pseudo-code for this here. His code is cleaner, because the parent and child do not have to do unnecessary looping to close un-needed file descriptors and because the parent can easily close its copies of the file descriptors immediately after the fork.
As a side note: you should always check the return values of pipe, dup2, fork, and exec.
EDIT 2: typo in pseudo code. OP: num-pipes would be the number of pipes. E.g., "ls | grep foo | sort -r" would have 2 pipes.
这是正确的功能代码
Here's the correct functioning code
(缩短的)相关代码是:
这意味着父(控制)进程执行以下操作:
但它应该是这样的:
The (shortened) relevant code is:
Which means the parent (controlling) process does this:
But it should be something like this:
您只需要两个管道交替,如下所示:
示例用法:
我将留下一个 链接 到完整的工作代码给有需要的人。
You only need two pipes alternating like below:
Example usage:
I'll leave a link to a full working code for someone who needs it.
基于 Christopher Neylan 提到的在给定时间最多使用两个管道的想法,我编写了 n 管道的伪代码。 args 是大小为“args_size”的字符指针数组,它是一个全局变量。
Building upon the idea of using a maximum of two pipes at a given time mentioned by Christopher Neylan, I put together pseudocode for n-pipes. args is an array of character pointers of size 'args_size' which is a global variable.
基本上,您想要做的是一个递归函数,其中子级执行第一个命令,如果没有其他命令,则父级执行第二个命令或再次调用该函数。
Basically what you wanna do is a recursive function where the child executes the first command and the parent executes the second one if no other commands are left or calls the function again.