管道,一种古老的进程间通信形式。一个管道由一个进程创建,然后该进程调用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.
Linux编程 — IPC之管道
时间: 2024-12-28 19:00:29