我一直在尝试在应用程序中使用管道来实现类似shell的功能,我正在关注this example。如果原件被删除,我将在此处复制代码以供将来参考:
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
/**
* Executes the command "cat scores | grep Villanova | cut -b 1-10".
* This quick-and-dirty version does no error checking.
*
* @author Jim Glenn
* @version 0.1 10/4/2004
*/
int main(int argc, char **argv)
{
int status;
int i;
// arguments for commands; your parser would be responsible for
// setting up arrays like these
char *cat_args[] = {"cat", "scores", NULL};
char *grep_args[] = {"grep", "Villanova", NULL};
char *cut_args[] = {"cut", "-b", "1-10", NULL};
// make 2 pipes (cat to grep and grep to cut); each has 2 fds
int pipes[4];
pipe(pipes); // sets up 1st pipe
pipe(pipes + 2); // sets up 2nd pipe
// we now have 4 fds:
// pipes[0] = read end of cat->grep pipe (read by grep)
// pipes[1] = write end of cat->grep pipe (written by cat)
// pipes[2] = read end of grep->cut pipe (read by cut)
// pipes[3] = write end of grep->cut pipe (written by grep)
// Note that the code in each if is basically identical, so you
// could set up a loop to handle it. The differences are in the
// indicies into pipes used for the dup2 system call
// and that the 1st and last only deal with the end of one pipe.
// fork the first child (to execute cat)
if (fork() == 0)
{
// replace cat's stdout with write part of 1st pipe
dup2(pipes[1], 1);
// close all pipes (very important!); end we're using was safely copied
close(pipes[0]);
close(pipes[1]);
close(pipes[2]);
close(pipes[3]);
execvp(*cat_args, cat_args);
}
else
{
// fork second child (to execute grep)
if (fork() == 0)
{
// replace grep's stdin with read end of 1st pipe
dup2(pipes[0], 0);
// replace grep's stdout with write end of 2nd pipe
dup2(pipes[3], 1);
// close all ends of pipes
close(pipes[0]);
close(pipes[1]);
close(pipes[2]);
close(pipes[3]);
execvp(*grep_args, grep_args);
}
else
{
// fork third child (to execute cut)
if (fork() == 0)
{
// replace cut's stdin with input read of 2nd pipe
dup2(pipes[2], 0);
// close all ends of pipes
close(pipes[0]);
close(pipes[1]);
close(pipes[2]);
close(pipes[3]);
execvp(*cut_args, cut_args);
}
}
}
// only the parent gets here and waits for 3 children to finish
close(pipes[0]);
close(pipes[1]);
close(pipes[2]);
close(pipes[3]);
for (i = 0; i < 3; i++)
wait(&status);
}
我很难理解为什么仅在调用execvp
并读取或写入任何数据之前关闭了管道。我相信这与将EOF标志传递给进程有关,以便它们可以停止读取写入,但是在将任何实际数据发送到管道之前,我看不出有什么帮助。我希望能得到一个清晰的解释。谢谢。
答案 0 :(得分:3)
我很难理解为什么在调用execvp并读取或写入任何数据之前就关闭了管道。
管道没有关闭。而是,一些与管道末端关联的文件描述符正在关闭。每个子进程都将管道端文件描述符复制到其一个或两个标准流上,然后关闭实际上不使用的所有管道端文件描述符,这就是所有存储在pipes
中的子描述符数组。只要每个端部在至少一个进程中处于打开状态,每个管道本身便保持打开状态和可用状态,并且每个子进程都将一个管道的至少一端保持打开状态。当子进程终止时(或至少在子进程的控制下,发布execvp()
),这些关闭。
执行此类关闭的原因之一是整洁和资源管理。一个进程一次可以打开多少个文件描述符是有限制的,因此,最好避免将不需要的文件描述符保持打开状态。
但是,从功能上讲,从一个管道读取的进程在任何进程中,直到与管道的写入端关联的所有所有打开文件描述符关闭,才会检测到文件结尾。这就是管道上的EOF表示的 ,这是有道理的,因为只要写端在任何地方都打开,就有可能向其中写入更多数据。