管道是UNIX系统IPC的最古老的形式,并且所有UNIX系统都提供此种通信机制。但是管道存在如下特点:
- 管道是半双工的。
- 管道只能用在具有公共祖先的进程之间。
管道的创建
管道是通过调用pipe函数创建的:
#include <unistd.h>
int pipe(int filedes[2]);
参数filedes[2]是两个文件描述符,filedes[0]为读打开,filedes[1]为写打开。filedes[1]的输出是filedes[0]的输入。
管道的实现机制
管道是由内核管理的一个缓冲区,相当于我们放入内存中的一个纸条。管道的一端连接一个进程的输出。
这个进程会向管道中放入信息。管道的另一端连接一个进程的输入,这个进程取出被放入管道的信息。
一个缓冲区不需要很大,它被设计成为环形的数据结构,以便管道可以被循环利用。
当管道中没有信息的话,从管道中读取的进程会等待,直到另一端的进程放入信息。
当管道被放满信息的时候,尝试放入信息的进程会等待,直到另一端的进程取出信息。当两个进程都终结的时候,管道也自动消失。
这个进程会向管道中放入信息。管道的另一端连接一个进程的输入,这个进程取出被放入管道的信息。
一个缓冲区不需要很大,它被设计成为环形的数据结构,以便管道可以被循环利用。
当管道中没有信息的话,从管道中读取的进程会等待,直到另一端的进程放入信息。
当管道被放满信息的时候,尝试放入信息的进程会等待,直到另一端的进程取出信息。当两个进程都终结的时候,管道也自动消失。
最开始的时候,上面的两个箭头都连接在同一个进程Process 1上(连接在Process 1上的两个箭头)。当fork复制进程的时候,会将这两个连接也复制到新的进程(Process 2)。
随后,每个进程关闭自己不需要的一个连接 (两个黑色的箭头被关闭; Process 1关闭从PIPE来的输入连接,Process 2关闭输出到PIPE的连接).
这样,剩下的红色连接就构成了如上图的PIPE。
管道的读写
- 对于写管道:
对于写操作,如果一次write调用写的数据量小于管道容量,则写必须一次完成,
即如果管道所剩余的容量不够,write被阻塞直到管道的剩余容量可以一次写完为止。如果write调用写的数据量大于管道容量,则写操作分多次完成。
如果用fcntl设置管道写端口为非阻塞方式,则管道满不会阻塞写,而只是对写返回0。
- 对于读管道:
如果管道为空,且管道的写端口是打开状态,则读操作被阻塞直到有数据写入为止。
一次read调用,如果管道中的数据量不够read指定的数量,则按实际的数量读取,并对read返回实际数量值。
如果读端口使用fcntl设置了非阻塞方式,则当管道为空时,read调用返回0。
- 对于管道的关闭:
关闭写端口是给读端口一个文件结束符的唯一方法。对于写端口关闭后,在该管道上的read调用将返回0。
管道应用实例
- 实例一:用于shell
比如,当在某个shell程序键入who│wc -l后,相应shell程序将创建who以及wc两个进程和这两个进程间的管道。
- 实例二:用于具有亲缘关系的进程间通信
下面的程序创建了一个从父进程到子进程的管道。并且父进程经由管道向子进程传送数据。
1 #include <stdlib.h>
2 #include <stdio.h>
3 #include <unistd.h>
4 #include <signal.h>
5 #include <string.h>
6
7 #define MAXLEN 128
8
9 int main()
10 {
11 int ret = 0;
12 int filedes[2] = {-1, -1};
13 char buf[MAXLEN];
14
15 ret = pipe(filedes);
16 if (ret == -1) {
17 perror("create pipe error");
18 return -1;
19 }
20
21 ret = fork();
22 if (ret < 0) { // fork error
23 perror("fork error");
24 return -1;
25 } else if (ret > 0) { // parent process
26 close(filedes[0]);
27
28 write(filedes[1], "hello world\n", 12);
29
30 } else { // child process
31 close(filedes[1]);
32
33 ret = read(filedes[0], buf, MAXLEN);
34 if (ret < 0) {
35 perror("read pipe error");
36 return -1;
37 }
38 write(STDOUT_FILENO, buf, ret);
39 }
40
41 return 0;
42 }
程序执行结果如下:
[shenlx@CentOS pipe]$ ./a.out
hello world
管道的局限性
管道的主要局限性正体现在它的特点上:
- 只支持单向数据流;
- 只能用于具有亲缘关系的进程之间;
- 没有名字;
- 管道的缓冲区是有限的(管道制存在于内存中,在管道创建时,为缓冲区分配一个页面大小);
- 管道所传送的是无格式字节流,这就要求管道的读出方和写入方必须事先约定好数据的格式,比如多少字节算作一个消息(或命令、或记录)等等。