在了解管道之前,我们先来了解下进程之间为什么需要通信。
1、进程间通信的目的
- 数据传输: 一个进程需要将它的数据发送给另一个进程
- 资源共享:多个进程之间共享同样的资源
- 通知事件: 一个进程需要向另一个或一组进程发送消息,通知它发什么了某种事件
- 进程控制:有些进程希望完全控制另一个进程的执行,此时控制进程希望能够拦截另一个所有陷入和异常,并能够及时知道它的状态。
那么为什么进程不直接通信呢?
进程具有独立性,它们时互相独立的,每个进程都有自己的PCB,在fork()时,父子进程都采用的是写时拷贝,因此,在每个进程看来,自己享有所有的内存资源,所以两个进程不能直接通信。我们只有让两个或多个进程访问到同一个资源,才能达到通信的目的。
2、管道
什么是管道
- 管道是unix中最古老的进程间通信的形式。
- 我们把一个进程连接到另一个进程的一个数据流称为一个“管道”。
- 管道的本质是内核的一块缓存。
管道的内核缓存大小是65536字节,往管道中写入数据时,一次写入大小不要超过PIPE_BUF(4096)
管道分为两种:匿名管道和命名管道。
1.匿名管道
#include <unistd.h>
功能:创建一个无名管道
原型:
int pipe(int fd[2]);
参数:
fd:文件描述符 fd[0]为表示读端,fd[1]表示写端
返回值:成功返回0,失败返回错误代码
下面我们来看一个例子:
//父进程向管道中写数据,子进程读数据
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main( void )
{
int fds[2];
pipe(fds); // 创建管道
if ( fork() == 0 ) { // 子进程读取管道
close(fds[1]); // 管道的读端是fds[0], 所以关闭fds[1]
char buf[100] = {};
read(fds[0], buf, 100); // 读取管道中的内容
printf("child,buf=[%s]\n", buf);
close(fds[0]); // 关闭管道的读端
exit(0);
} else { // 父进程向管道中写入数据
close(fds[0]); // 父进程要往管道中写数据,所以关闭fds[0]
char *msg = "this is maomaochong";
sleep(2);
write(fds[1], msg, strlen(msg)); // 两秒之后向管道中写入数据
close(fds[1]);
}
}
fork之后
fork之后关掉不用的描述符
从文件描述符的角度来看:
1.父进程创建管道
2.父进程fork出子进程
3.父进程关闭fd[0],子进程关闭fd[1]
匿名管道的特点
- 只能用于具有共同祖先的进程(具有亲缘关系的,兄弟进程也可以)之间通信。
- 一般而言,进程退出,管道释放。所以管道生命周期随进程。
- 一般而言,内核会对管道进行同步与互斥。
- 管道提供面向字节流服务,数据只能向一个方向传输,双方传输时,需要建起两个管道。
管道的读写规则
- 写方一只写,读方不读:write调用阻塞,直到有进程读走数据。
- 写方不写,读方一直读:read调用阻塞,直到有数据来为止。
- 写方将写端关闭,读方一直读:读方将读完然后返回0;
- 写方一直写,读方将读端关闭:会产生SIGPIPE信号,终止write进程。
2.命名管道
- 匿名管道的一个限制就是必须在具有共同祖先的进程间通信。
- 如果想在毫无相关的进程间交换数据,就要使用命名管道。
- 命名管道是一种特殊类型的文件
创建一个命名管道有两种方式:
在命令行创建:
$ mkfifo filename
命名管道可以从程序里创建,相关函数是;
int mkfifo(const char8 filename,mode_t mode);
成功返回0,失败返回-1;
下面我们来看一个例子:
writepipe.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
int main( void )
{
int fd = open("my.p", O_WRONLY);
char buf[10] = {};
for (int i=0; ; i++ ) {
sprintf(buf, "%c", 'A'+i%26);
write(fd, buf, 1);
printf("write %d ok\n", i);
sleep(1);
}
}
readpipe.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
int main( void )
{
int fd = open("my.p", O_RDONLY);
char buf[1024];
while ( 1 ) {
memset(buf, 0x00, sizeof(buf));
read(fd, buf, 1024);
printf("buf=[%s]\n", buf);
}
}
匿名管道与命名管道的区别
- 匿名管道有函数pipe创建并打开
- 命名函数有mkfifo创建,打开用open
- 命名管道与匿名管道之间唯一的区别在他们创建于打开的方式不同,一旦这些工作做好之后,他们具有相同的的语义。
- 最大的区别是:命名管道时文件在硬盘上有对应的文件,而匿名管道是在内核中实现的。
打开规则
- 为读打开时:
无写方:阻塞直到有相应进程来打开该FIFO
有写方:返回成功; - 为写打开:
无读方:阻塞直到有相应进程为读而打开该FIFO
有读方:返回失败;