理解进程间通信
目录
- 1.理解层面
- 1.1为什么要进程间通信??
- 1.2什么是通信??
- 1.3怎么通信??
- 1.4 具体通信的方式??
- 2.管道
- 3.匿名管道
- 3.1 用fork来共享管道
- 3.2 特性
- 3.3 通信情况
- 4.命名管道
- 4.1 创建一个命名管道
- 4.2 匿名管道和命名管道的区别
- 5.System V 共享内存
- 5.1 共享内存函数
1.理解层面
1.1为什么要进程间通信??
- 数据传输:一个进程需要将它的数据发送给另一个进程
- 资源共享:多个进程之间共享同样的资源
- 通知事件:一个进程需要向另一个或一组进程发送消息,通知它发生了某种事情(如进程终止时要通知父进程)
- 进程控制:有些进程希望完全控制另一个进程的执行(Debug进程)
1.2什么是通信??
进程间通信是指在计算机系统中,运行在不同进程之间的数据交换和通信机制。由于每个进程都有自己的独立内存空间,因此进程间通信需要通过特定的机制来实现数据的共享和传递
1.3怎么通信??
进程间通信的本质:是先让不同的进程,先看到同一份资源[“内存”]
1.4 具体通信的方式??
- 基于文件的,管道通信
- 匿名管道pipe
- 命名管道
- System V ,本机通信
- System V 消息队列
- System V 共享内存
- System V 信号量
2.管道
什么是管道??
- 管道是Unix中最古老的进程间通信的形式
- 我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”
3.匿名管道
#include<unistd.h>
int pipe(int pipefd[2]);
参数:
pipefd:文件描述符数组,其中pipefd[0]表示读端,pipefd[1]表示写端
返回值:成功返回0,失败返回-1
//列子:查看pipefd对应的文件描述符
//testPipe.cc
#include <iostream>
#include <unistd.h>
int main()
{
//1.创建管道
int pipefd[2]={0};//pipefd[0] 读端 pipefd[1] 写端
int n=pipe(pipefd);
if(n<0)
{
perror("pipe fail");
exit(1);
}
std::cout<<"pipefd[0]: "<<pipefd[0]<<std::endl;
std::cout<<"pipefd[1]: "<<pipefd[1]<<std::endl;
}
//运行结果:
$ ./testPipe
pipefd[0]: 3
pipefd[1]: 4
3.1 用fork来共享管道
//测试样例:测试管道的读写
//让父进程读,子进程写
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>
#include <cstring>
void ChildWrite(int wfd)
{
char buff[1024];
int cnt=0;
while(true)
{
snprintf(buff,sizeof(buff),"I am child,my pid:%d,cnt: %d\n",getpid(),cnt++);
write(wfd,buff,strlen(buff));
sleep(1);
}
}
void FatherRead(int rfd)
{
char buff[1024];
while(true)
{
buff[0]=0;
ssize_t n=read(rfd,buff,sizeof(buff)-1);
if(n>0)
{
buff[n]=0;
std::cout<<"father say: "<<buff<<std::endl;
}
sleep(1);
}
}
int main()
{
// 1.创建管道
int pipefd[2] = {0}; // pipefd[0] 读端 pipefd[1] 写端
int n = pipe(pipefd);
if (n < 0)
{
perror("pipe fail");
exit(1);
}
std::cout << "pipefd[0]: " << pipefd[0] << std::endl;
std::cout << "pipefd[1]: " << pipefd[1] << std::endl;
// 2.创建子进程
pid_t id =fork();
if(id<0)
{
perror("fork fail");
exit(1);
}
else if(id==0)
{
//child
//3.关闭不需要的读端,形成通信
close(pipefd[0]);
ChildWrite(pipefd[1]);
close(pipefd[1]);
exit(0);
}
else
{
//father
//3.关闭不需要的写端,形成通信
close(pipefd[1]);
FatherRead(pipefd[0]);
close(pipefd[0]);
}
}
//查看运行结果:
$ ./testPipe
pipefd[0]: 3
pipefd[1]: 4
father say: I am child,my pid:1670938,cnt: 0
father say: I am child,my pid:1670938,cnt: 1
father say: I am child,my pid:1670938,cnt: 2
father say: I am child,my pid:1670938,cnt: 3
father say: I am child,my pid:1670938,cnt: 4
father say: I am child,my pid:1670938,cnt: 5
...
3.2 特性
- 匿名管道,只能用来进行具有血缘关系的进程进行进程间通信(常用于父子)
- 管道文件,自带同步机制
- 管道是单向通信的
- (管道)文件的生命周期,是随进程的
- 管道是面向字节流的,意味着数据以字节为单位字管道中传输
3.3 通信情况
-
写慢,读快 —读端就要阻塞(进程)
-
写快,读慢 —满了的时候,写就要阻塞等待
-
写关,继续读 —read就会读到返回值为0,表示文件结尾
-
读关闭,写继续 —写端在写入没有任何意义,OS会杀掉写端进程,发送异常信号
4.命名管道
- 让2个不相关的进程之间交换数据,可以使用FIFO文件来做这个工作,它叫做命名管道
- 命名管道是一种特殊类型的文件
4.1 创建一个命名管道
# 从命令行上创建
$ mkfifo filename
#从程序里创建
int mkfifo(const char *pathname, mode_t mode);
4.2 匿名管道和命名管道的区别
- 匿名管道由pipe函数创建并打开
- 命名管道由mkfifo函数创建,open打开
- 匿名管道只能在具有血缘关系的进程里进行进程间通信
- 命名管道可以用来进行不相关的进程的进程间通信
5.System V 共享内存
- 共享内存是进程间通信中,速度最快的方式(优点):
- 映射之后,读写,直接被对方看到
- 不需要进行系统调用获取或者写入
- 共享内存没有保护机制(缺点):
- 通信双方,没有所谓的“同步机制”
- 数据不一致
5.1 共享内存函数
- shmget函数
功能:用来创建共享内存
原型:
int shmget(key_t key, size_t size, int shmflg);
参数:
key:这个共享内存段名字
size:共享内存大小
shmflg:权限标志
取值IPC_CREAT:共享内存不存在,创建并返回;共享内存已存在,获取并返回
取值IPC_CREAT|IPC_EXCL:共享内存不存在,创建并返回;共享内存已存在,出错返回
返回值:
成功返回一个非负整数,即共享内存标识码;失败返回-1
- shmat函数
功能:将共享内存段连接到进程地址空间
原型:
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数:
shmid:共享内存标识
shmaddr:虚拟地址,固定地址进行挂接
shmflg:它的2个可能取值是SHM_RND和SHM_RDONLY
返回值:
成功返回起始的虚拟地址;失败返回-1
- shmdt函数
功能:将共享内存段与当前进程脱离
原型:
int shmdt(const void *shmaddr);
参数:
shmaddr:由shmat所返回的指针
返回值:
成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段
- shmctl函数
功能:用于控制共享内存
原型:
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数:
shmid:由shmget返回的共享内存标识码
cmd:要采用的行动
buf:指向一个保存着共享内存模式状态和访问权限的数据结构
返回值:
成功返回0;失败返回-1
命令(cmd) | 说明 |
---|---|
IPC_STAT | 把shmid_ds结构中的数据设置为共享内存的当前关联值 |
IPC_SET | 在进程有足够权限的前提下,把共享内存的当前关联值设置成shmid_ds数据结构中给出的值 |
IPC_RMID | 删除共享内存段 |