几种常见进程间通信(IPC)方式之管道pipe
几种常见进程间通信(IPC)方式-管道pipe
前言
进程间通信是指在不同进程之间传播或交换信息,在Linux环境下,进程地址空间相互独立,每个进程各自有不同的用户地址空间,进程之间不能相互访问。必须通过内核才能进行数据交换。如图:
常见的通信方式有以下几种:
- 管道pipe
- 有名管道FIFO
- 消息队列MessageQueue
- 共享存储
- 信号量Semaphore
- 信号Signal
- 套接字Socket
接下来我们将详细介绍管道
。
管道pipe
管道作用于有血缘关系的进程之间,调用pipe
系统函数可以创建管道。
- 原理:管道实为内核使用环形队列机制(以便管道可以重复利用),借助内核缓冲区(4k)实现
- 本质:是一个伪文件(实为内核缓冲区),由两个文件描述符引用,一个表示读端,一个表示写端。数据从管道的写端流入,从读端流出。
管道具有一定的局限性
- 一个进程只能控制读端或写端,不能同时自己写自己读。
- 数据不可反复读取,一旦被读走,在管道中就不复存在了。
- 管道采用 半双工通信方式,数据只能在一个方向上流动。
- 只能作用于有亲缘关系的进程间通信
创建管道 pipe()
//成功返回0,失败返回-1
int pipe(int pipefd[2]);
函数调用成功会返回r/w
两个文件描述符,规定pipefd[0]
对应r
,规定pipefd[1]
对应w
(需要手动close)。
父子间管道通信步骤如下:
- 父进程调用
pipe
函数创建管道,得到两个文件描述符,分别指向管道的读端和写端。 - 父进程调用
fork
创建子进程,那么子进程也会得到这两个文件描述符并且指向同一个管道。 - 父进程关闭管道读端(或写端),子进程关闭管道写端(或读端)。
这样一来,父进程可以向(从)管道中写(读)数据,子进程可以读(写)数据。
例子:
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/wait.h>
void sys_err(const char *str)
{
perror(str);
exit(1);
}
int main(void)
{
pid_t pid;
char buf[1024];
int fd[2];
char *p = "this is a test for pipe\n";
//父进程创建管道
if (pipe(fd) == -1)
sys_err("pipe");
//父进程创建子进程
pid = fork();
if (pid < 0) {
sys_err("fork err");
//子进程进入
} else if (pid == 0) {
//子进程关闭写端
close(fd[1]);
int len = read(fd[0], buf, sizeof(buf));
//输出到终端
write(STDOUT_FILENO, buf, len);
//子进程关闭写端
close(fd[0]);
//父进程进入
} else {
//父进程关闭读端
close(fd[0]);
write(fd[1], p, strlen(p));
wait(NULL);
//父进程关闭读端
close(fd[1]);
}
return 0;
}
管道的读写行为
- 读管道
1.管道中有数据,read
返回实际读到的字节数。
2.管道中无数据:
(1)管道写端被全部关闭,read
返回0
(2)管道写端没有全部被关闭,read
阻塞等待(等待管道中有数据可读) - 写管道
1.管道读端全部被关闭,进程异常终止。
2.管道读端没有全部关闭:
(1)管道已满,write
阻塞。
(2)管道未满,write
将数据写入,并返回实际写入的字节数。
管道缓冲区大小
//命令查看当前系统中创建管道文件所对应的内核缓冲区的大小
ulimit -a
pipe size
//成功:返回管道的大小,失败:返回-1
long fpathconf(int fd, int name);
总结
管道最大的优点就是简单,也有很多缺点,一个管道只能进行单向通信,想要实现双向通信必须建立两个管道。
并且只适用于有亲缘关系的进程间相互通信。接下来将要介绍有名管道FIFO
,它解决了这个问题。