管道,一种古老的进程间通信形式。一个管道由一个进程创建,然后该进程调用fork,此后父、子进程就可以用管道通信了。
函数原型:
#include <unistd.h>
int pipe(int filedes[2]); // 成功返回0,出错返回-1
参数filedes返回两个文件描述符。filedes[0]用来输入,filedes[1]用来输出。注意,经过实验,这里的两个描述符并不对应标准输入和标准输出。下面是一个简单的测试例程:
#include <stdio.h>
#include <unistd.h>
#define MAXLINE 1024
int main(void)
{
int n;
int fd[2];
pid_t pid;
char buf[MAXLINE];
if (pipe(fd) < 0)
return -1;
if ((pid = fork()) < 0)
return -1;
else if (pid > 0)
{
// 父进程
printf("parent ID = %d\n", getpid());
close(fd[0]);
write(fd[1], "Hello world\n", 12);
}
else
{
// 子进程
printf("child ID = %d\n", getpid());
close(fd[1]);
n = read(fd[0], buf, MAXLINE);
write(STDOUT_FILENO, buf, n);
}
return 0;
}
运行结果:
上述程序利用管道,实现了数据从父进程传递到子进程。数据的传递方向决定了各进程应该关闭的描述符。
现在来看看如何在shell命令中使用管道:
上图使用了管道命令"|"。前一个命令的输出作为了后一个命令的输入。注意,这里的输出只能是标准输出,输入也只能是标准输入,也就是说前一个命令必须要有输出到标准输出的能力,后一个命令必须要有接受标准输入的能力。下图来自鸟哥的网站。
下面用一个程序来模拟上述shell命令的行为。一种常见的操作时创建一个管道连接到另一个进程,然后读其输出或向其输入发送数据。这是可以使用下列两个库函数:
#include <stdio.h>
FILE *popen(const char *cmdstring, const char *type); // 成功返回文件指针,出错返回NULL
int pclose(FILE *fp); // 获得cmdstring的终止状态,出错返回-1
popen等价于fork、exec("cmdstring")启动一个子进程。type的含义如下:
- "r"表示文件指针连接到cmdstring的标准输出,父进程读cmdstring的数据。
- "w"表示文件指针连接到cmdstring的标准输入,父进程向cmdstring写数据。
有几个问题需要注意:
- popen返回的文件指针同样不对应标准输入和标准输出,这可以通过fileno函数把文件指针转换成文件描述符进行查看。
- 函数pclose不仅关闭标准I/O流,还要获得子进程终止状态。如果不使用这个函数,当父进程还在运行而子进程运行完毕后会产生僵死进程,等下会通过实验来说明。
测试例程如下:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#define MAXLINE 1024
int main(void)
{
char buf[MAXLINE];
FILE *fpin;
int n;
if ((fpin = popen("date", "r")) == NULL) // 启动data命令
return -1;
fgets(buf, MAXLINE, fpin); // 获得data命令的输出
write(STDOUT_FILENO, buf, strlen(buf)); // 将数据打印到终端
pclose(fpin);
return 0;
}
运行结果:
父进程test正确地接收了data命令的输出,管道传输成功。如果去掉pclose(fpin);这条语句,那么结果如下:
啊哦,出现一个僵死进程。这就是因为父进程没有获得子进程的终止状态而引起的。
参考:
《unix环境高级编程》 P397-P404.