管道:进程间双向通信的渠道。最早的 IPC 机制之一。
设计考虑:
- 支持双向通信还是单向?POSIX 普通管道一般是单向的
- 如果支持双向,是半双工(即同时只能有一方在写)还是全双工(双方能同时写)?POSIX 管道是半双工
- 进程间是否需要有一层关系(比如父子关系)?POSIX 普通管道一般是父子关系(否则没有意义);命名管道则不需要
- 管道能否通过网络来通讯,还是需要通讯双方进程在同一机器?POSIX 管道需要在同一机器
POSIX 普通管道
POSIX 普通管道(也叫匿名管道)是两种机制的结合:
- 管道本身,即是两个文件描述符
fds[]
,使得write(fd[1], ...)
时,内容可以通过read(fd[0], ...)
得到 - 由于
fork()
出来的子进程会继承文件描述符,因此可以实现父进程write()
而子进程read()
,或者反过来子进程写父进程读
虽然理论上普通管理可以是双向的,但双向会导致混乱(比如进程读到自己写的内容),所以实践上都是父子双方约定好谁写谁读,然后相应的在各自进程中将不需要使用的 fd 关掉。
代码实例
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#define BUFFER_SIZE 25
#define READ_END 0
#define WRITE_END 1
int main(void) {
char write_msg[BUFFER_SIZE] = "Greetings";
char read_msg[BUFFER_SIZE];
int fd[2];
pid_t pid;
/* create the pipe */
if (pipe(fd) == -1) {
fprintf(stderr, "Pipe failed");
return 1;
}
/* fork a child process */
pid = fork();
if (pid < 0) { /* error occurred */
fprintf(stderr, "Fork Failed");
return 1;
}
if (pid > 0) { /* parent process */
/* close the unused end of the pipe */
close(fd[READ_END]);
/* write to the pipe */
write(fd[WRITE_END], write_msg, strlen(write_msg) + 1);
/* close the write end of the pipe */
close(fd[WRITE_END]);
} else { /* child process */
/* close the unused end of the pipe */
close(fd[WRITE_END]);
/* read from the pipe */
read(fd[READ_END], read_msg, BUFFER_SIZE);
printf("read %s", read_msg);
/* close the read end of the pipe */
close(fd[READ_END]);
}
return 0;
}
原子操作:POSIX 规定 512 字节内的读写是原子的;Linux 把这个限制扩大到 4096。
POSIX 命名管道
POSIX 称它的命名管道为 FIFO(First In First Out)。与普通管道不同之处在于:
- FIFO 是个磁盘上的实际的文件(通过
mkfifo()
创建) - 进程间不需要是父子关系也可以互相通信
- 即使使用它的进程退出了,FIFO 文件仍然存在并且可以被访问
FIFO 虽然可以是双向的(但仍是半双工),但实际用的时候还是建两个 FIFO 用于双向读写(不然就比较混乱,读到自己写的内容)。一个 FIFO 可以被多个进程写以及读。
参考
- The Linux Programmer's Guide: 6.2 Half-duplex UNIX Pipes, 6.3 Named Pipes
- Operating System Concepts