- 进程间通信基本背景:
- 进程之间具有独立性,进程和进程相互沟通配合就不太方便
- 要想完成进程间通信就需要有一个公共的媒介让多个进程都能访问到
进程间通信分类:
因为操作系统提供的进程间通信的使用场景有所不同,因此提供的进程间通信方式也有多种,各自有各自的特点。
(面) 进程间通信主要有以下几种方式:管道、共享内存、消息队列、信号量
(面) 命名管道与匿名管道的区别
(面) 进程间通信最快的方式
- 进程间通信有两套标准:
1.system V标准- 共享内存
- 消息队列
- 信号量
2.posix标准(库函数)
1.管道 (匿名管道/命名管道)
是操作系统内核中的一块缓冲区(内存),使用时当作函数,用于在进程间传输数据资源,内核是所有进程共享的,用户态不能操作。
操作系统为了接口统一,对于管道这块缓冲区的操作,与IO操作使用同一套接口。
匿名管道
创建匿名管道 :int pipe(int pipefd[2])
其参数是输出型参数,用于获取管道的操作描述符。pipefd[0]用于从管道中读取数据,pipefd[1]用于向管道中写入数据 ;返回值<0表示管道创建失败,返回值=0,表示管道创建成功。
- 匿名管道特点:
- 匿名管道创建的缓冲区没有标识,因此只能用于具有共同祖先(有血缘关系)的进程之间的通信,创建管道,返回文件描述符,当创建一个子进程,子进程复制父进程,因此子进程也有相同的描述符指向内核中相同的缓冲区,这时候就能够通信了。
- 管道是单向读取数据的(半双工的)
- 进程退出,管道释放,所以管道周期随进程
- 管道的读写特性:如果管道中没有数据,则read会阻塞,直到读取到数据;如果管道中数据满了,则write会阻塞,直到有数据被读取出去;如果管道的所有写端都被关闭,那么读端读完管道中的数据之后,会返回0;如果管道的所有读端都被关闭,那么写端写入数据的时候会触发异常,退出进程。
- 管道提供流式服务即面向字节流,读写灵活,不需完全匹配,可以接收字符串的一半,也可以全部接收,但是有可能会造成数据粘连(数据没有边界)
6)同步与互斥保护操作:互斥:对临界资源在同一时间的唯一访问性。同步:对临界资源操作的时序可控性。当对管道的读写数据大小不大于PIPE_BUF的时候,将保证数据读写的原子性(数据不被打断)
eg;
int fd[2] ={0};
int ret=pipe(fd);// 调用成功就会返回一对文件描述符
//其中fd[0]用来读 fd[1]用来写
//加上linux中一切皆文件思想
//就通过这一对文件描述符来操作管道对应的内存
if(ret<0){
perror(“pipe”);
return 1;
}
eg; ps aux | grep hehe ps grep都是bash进程 是兄弟关系,也是一种匿名管道符
命名管道
可见于文件系统,因此所有的进程可以通过打开文件,获取到内核管道这块缓冲区对应的描述符,因此命名管道可以用于同一机器上任意进程间通信。
创建命名管道命令: mkfifo [文件名]
创建命名管道接口:int mkfifo(''命名管道文件路径名'',权限)
,成功返回值为0,失败返回值为-1.
EOF:结束标记符
ctrl+z 把进程从前台变成后台(在windows系统中为结束标记符)
fg 把进程从后台变为前台
ctrl+d 输出结束标记符
命名管道特点:
a)单项通信
b)命名管道可以用于随意进程
c)生命周期随进程
d)面向字节流
e)文件打开特性:如果管道文件以只读打开,它将阻塞直到这个文件被以写方式打开;如果文件以只写打开,将阻塞,直到这个文件被以读的方式打开;如果文件以读写方式打开,则不阻塞。
2.system V(Unix中的某个版本,一套标准)进程间通信方式
共享内存
- 共享内存
是进程间通信方式中最快的一种。因为其他几种进程间通信的方式都用到了内核提供的一块共享内存,需要将数据从buffer中拷贝进内核空间,然后再从共享的内存空间拷贝出去,但是**共享内存是通过:先创建共享内存,再通过共享内存映射到进程的虚拟地址空间,然后直接通过虚拟地址访问操作到物理内存,最后解除映射,删除共享内存。**少了两步拷贝步骤,因此速度大大提升。
共享内存中多个进程能够共享同一块物理内存(多个页表映射到同一块物理内存地址),直接通过用户地址空间直接访问。
共享内存的操作: - 创建共享内存:
int shmget(key_t key,size_t,size,int shmflg)
,
key:共享内存在操作系统中的标识符;
size:共享内存大小
shmflg:IPC_CREAT IPC_EXCL/通过方法key_t ftok(const char *pathname(文件名),int proj_id(自己定义数字))
返回值:进程对共享内存的操作句柄
查看操作系统IPC信息命令: ipcs
删除共享内存命令 : ipcrm -m 共享内存标示符
-
映射到虚拟地址空间:shmat(int shmid,const void *shmaddr,int shmflg)
shmid:共享内存操作句柄
shmaddr:用户指定共享内存映射在虚拟地址空间的首地址,通常置空,让操作系统分配
shmflg:如果被指定为SHM_RDONLY—只读 0
返回值:映射首地址 失败:-1 -
操作:内存操作
解出映射后共享内存依然存在 -
解除:shmdt(const void *shmaddr)
shmaddr:映射首地址 -
删除:shmctl(int shmid,int cmd,struct shmid_ds *buf);
shmid:映射首地址
cmd:对共享内存的操作
buf:用于获取共享内存的状态信息
删除共享内存并不会直接删除,而是等待映射链接数为0,并且在等待期间,拒绝新的映射连接
grep -R '字符串' 路径
递归匹配
- 消息队列
消息队列本质上也是一个队列,队列中的元素具备数据类型(业务类型),并且不是严格的先进先出(是指定类型的先进先出),消息队列的生命周期不随进程,而是随内核(手动删掉或关闭操作系统,消息队列才会退出),消息队列是双向通行的
通过相同的IPC_key才能访问相同的消息队列
操作系统在内核创建的一个结构,结构中包含有一个队列,进程就是通过向消息队列中添加数据节点,已经获取节点实现进程间通信.消息队列传输的是一个个有类型的数据块,因为向队列中添加的就是数据块,类型(整数)可以用于定义优先级,用于区分进程,用于区分数据功能.
IPC对象数据结构
struct ipc_perm{
key_t //身份标示符
}
消息队列结构
struct msqid_ds{
}
客户端进程:主动读取数据
-
从标准输入读入数据
-
把数据写到消息队列中
-
从消息队列中华读取数据
-
把结果打印到显示器上
服务器进程:被动的一方 -
从消息队列中读取数据
-
根据读到的数据进行计算,生成响应的数据
-
把响应的数据写回到消息队列
IPC_CREAT 如果消息队列不存在就创建 如果存在就打开
IPC_EXCL 必须配合IPC_CREAT使用,不能单独使用 含义就是如果消息队列存在,打开就会失败eg: key_t key= ftok(FILEPATH,PROJ_ID);// 构建消息队列
if (key < 0){
perror (“ftok”);
}
eg:销毁消息队列 -->IPC_RMID
int Destorymsg(int msgid){
int ret=msgctrl(msgid,IPC_RMID,NULL)
if(ret<0){
perror (“RMID”);
}
}
将msgid看作消息队列的句柄.句柄:操控消息队列的”遥控器”
命令:ipcs _q 查看当前操作系统的消息队列
- 信号量:
是一个有计数器加具有等待队列的计数器,用于对资源进行计数;当获取一个资源,计数-1,如果没有资源,计数为0,为了获取资源因此等待;回收一个资源,计数+1,唤醒死等,唤醒的是信号量等待队列上的进程.
功能是等待与唤醒,用于实现进程间的同步于互斥.
同步:对资源操作时,如果计数为0表示没有资源,不能死等,资源回收后计数+1,被唤醒才能操作.
互斥:计数只有0/1,否则会有资源操作,不要然没资源操作,别人拿走了资源,计数变成0,不能操作死等,意味着别人对资源操作期间谁都无法对资源进行操作
- POSIX(也是另一套标准)进程间通信