目录
IPC通信
概念
进程间通讯,创建的两个进程之间是彼此独立的,那两个进程之间如果想打交道的时候,通过内核,提供了一块儿缓冲区(buffer),实现了数据的一个交换,这种机制就叫做InterProcess Communication。
规范的说法为:Linux环境下,进程地址空间相互独立,每个进程各自有不同的用户地址空间。任何一个进程的全局变量在另一个进程中都看不到,所以进程和进程之间不能相互访问,要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信(IPC,InterProcess Communication)。
IPC通信的方式
pipe 管道——最简单 (只支持“有血缘关系”的进程)
fifo 有名管道——是对pipe管道类型的改进加强版(囊括了有血缘关系和没有血缘关系的所有进程间,都可以通信)
mmap 文件映射共享IO——速度最快(在内存中开一块缓冲区,然后,将文件映射到内存上,然后,直接操作内存就可以实现控制了)
本地socket——最稳定
信号——携带信息量最小的
共享内存——在内存中用特定API申请的一块儿内存区,这块区域相当于是共享的,当开辟这块内存的进程退了,这块内存区域还是可以保留下来,其他进程可以去访问这块儿区域。
消息队列
进程间通信的目的
通过进程之间的互相通信,共同完成一件事情。
常见的通信方式
有三种:单工(广播)、半双工(同一个时刻数据只能往一个方向流,比如:对讲机)、全双工(可以同时刻双向数据流通,比如:打电话)
pipe 管道(半双工通信方式)
运行原理
当需要通信的时候,通过内核创建一个伪文件(相当于就是刚才说的缓冲区,这个缓冲区留了两个端口,一端是读,一端是写,类似于一个管道),
过程就是,先pipe一下,创建管道,然后,一个进程负责往里面写,另一个进程负责往外面读,这样的话,就达到了数据交换的目的,在这个过程中,需要注意的是,对于管道的创建时间,是在子进程的创建之前啊,还是子进程的创建之后呢?很明显,应该是在子进程的创建之前进行,只有这样,子进程才能共享两个端口,整个过程应该是父进程先创建pipe管道,然后,建立读写端口之间的联系(或者说建立与伪文件之间的联系),然后,子进程在和这个两个端口建立联系,这样,就实现了父子进程之间的共享了!
本节所说的管道就是半双工通信的方式。
如何使用
管道函数:int pipe(int pipefd[2]);
- pipefd 读写文件描述符 ,0-代表读,1-代表写
- 返回值:失败返回-1,成功返回0
父子进程实现pipe通信,实现ps aux|grep bash 功能
#include <stdio.h>
#include <unistd.h>
int main()
{
int fd[2];
pipe(fd);
pid_t pid = fork();
if(pid == 0){
//son
//son -- > ps
//关闭 读端
close(fd[0]);
//1. 先重定向
dup2(fd[1],STDOUT_FILENO);//标准输出重定向到管道写端
//2. execlp
execlp("ps","ps","aux",NULL);
}else if(pid > 0){
//parent
//关闭写端
close(fd[1]);
//1. 先重定向,标准输入重定向到管道读端
dup2(fd[0],STDIN_FILENO);
//2. execlp
execlp("grep","grep","bash",NULL);
}
return 0;
}
其中,grep命令就是过滤内容的,如果grep后面接入内容,其效果是等待标准输入。
运行如下:
以这段代码为例,
管道的读写行为
读管道
- 写端全部关闭——read读到0,相当于读到文件末尾
- 写端没有全部关闭
- 有数据—read读到数据
- 没有数据—read阻塞 (fcntl函数可以更改非阻塞)
写管道
- 读端全部关闭——会产生一个信号SIGPIPE(程序异常中止,用这个信号报错)
- 读端未全部关闭
- 管道已满—write阻塞
- 管道未满—write正常写入
实现效果演示效果的代码:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
int fd[2];
pipe(fd);
pid_t pid = fork();
if(pid == 0){
//son
sleep(3);
close(fd[0]);//关闭读端
write(fd[1],"hello",5);
close(fd[1]);
while(1){
sleep(1);
}
}else if(pid > 0){
//parent
close(fd[1]);//关闭写端
close(fd[0]);
int status;
wait(&status);
if(WIFSIGNALED(status)){
printf("killed by %d\n",WTERMSIG(status));
}
//父进程只是关闭读写两端,但是不退出
while(1){
sleep(1);
}
char buf[12]={0};
while(1){
int ret = read(fd[0],buf,sizeof(buf));
if(ret == 0){
printf("read over!\n");
break;
}
if(ret > 0){
write(STDOUT_FILENO,buf,ret);
}
}
}
return 0;
}
运行效果正常。
管道的大小和优劣
管道的大小
可以使用ulimit –a 命令来查看当前系统中创建管道文件所对应的内核缓冲区大小。
也可以使用fpathconf函数,借助参数->选项来查看。使用该宏应引入头文件<unistd.h>
long fpathconf(int fd, int name);
成功:返回管道的大小 ;失败:-1
管道的优劣
优点
- 简单,相比信号,套接字实现进程间通信,简单很多。
缺点
- 1. 只能单向通信,双向通信需建立两个管道。(如果想父进程给进程发,子进程给父进程发,得创建两根管道,没法实现双向通信)
- 2. 只能用于父子、兄弟进程(有共同祖先)间通信。该问题后来使用fifo有名管道解决。