简介
进程之间具有独立性,无法直接通信,因为每个进程都有一个自己独立的虚拟地址空间。
因此让进程间能够通信,本质上就是给进程间提供一块公共的区域,让需要通信的进程都能够访问这块区域,从而就能实现通信。
根据通信应用场景不同,提供的方式也有多种,如:数据传输、数据共享、协调控制
进程间常见的4种通信方式:
数据传输:管道、消息队列
数据共享:共享内存
进程控制:信号量
· 四种进程间通信方式★
一、管道
1.相关特性
使用场景:数据传输
★特性:半双工通信(可以选择方向的单向通信)
若管道中没有数据,read默认会阻塞等待,直到读取到数据后返回;
若管道中数据写满,write默认会阻塞,直到数据被读出有空闲空间。
管道的读写是一种字节流传输服务,数据会在缓冲区堆积,并且遵循先进先出规则。
若管道所有写端被关闭,read读取完缓冲区数据后,继续读取read将不再阻塞,而是返回0
(所有管道read返回0,表示的是继续读取没有意义)。
若管道所有读端被关闭,write继续写入数据,则会触发异常,退出程序。
★本质:内核中的一块缓冲区(内核空间的一块内存)
分类:
匿名管道:内核中的缓冲区没有标识符
命名管道:内核中的缓冲区具有标识符
2.匿名管道
因为没有标识符,所以只能用于具有亲缘关系的进程间通信。
只有通过子进程复制父进程的方式获取操作句柄。
(父进程创建管道后,在创建子进程时,子进程会复制父进程pcb中的大部分信息,其中也包括父进程的管道操作句柄,这样父子进程中的管道操作句柄访问的就是同一个内核缓冲区,从而实现进程间通信)
3.命名管道
命名管道的标识符是一个可见于文件系统的特殊管道文件,多个进程通过打开同一个管道文件,访问内核中的同一块缓冲区。
注意:管道文件只是为了让多个进程找到内核中的同一块缓冲区。
4.管道操作★
4.1匿名管道
int pipe(int pipefd[2]);
功能:创建一个匿名管道
pipefd[0]:用于从管道中读取数据;
pipefd[1]:用于向管道中写入数据。
返回值:
成功:返回0;
失败:返回-1。
4.2命名管道
int mkfifo(const char* pathname, mode_t mode);
功能:创建一个命名管道的标识符(命名管道文件)
pathname:带有路径文件名称
mode:文件权限
返回值:
成功,返回0;
失败,返回-1;
EEXIST,文件已存在。
注意:mkfifo只是创建了一个管道的标识符,后续对于命名管道的操作类似于对普通文件操作。
打开特性:
如果管道文件被只读打开,就会阻塞,直到管道文件被以写方式也打开;
如果管道文件被只写打开,也会阻塞,直到管道文件被以读方式也打开。
5.管道总结★
管道的本质就是内核中的一块缓冲区,分为匿名管道和命名管道。其中匿名管道只能用于具有亲缘关系的进程间通信。
特性:
1)半双工通信;
2) 提供字节流传输服务:
先进先出;
基于连接:所有读端关闭write异常,所有写端关闭read返回0不阻塞
3)生命周期跟随进程
4)自带同步与互斥:
互斥:资源在同一时间只有一个进程可以访问
管道的写入操作大小不超过PIPE_BUF(默认4096字节)大小时保证原子操作
(原子操作:要么一次性完成,无法被打断;要么不做。)
同步:通过某些条件限制,让进程对资源访问更加合理
若管道中没有数据,则read阻塞;
若管道中数据已满,则write阻塞。
二、共享内存
1.相关特性
使用场景:实现进程间的数据共享
★特性:最快的进程间通信方式
原理:
申请一块物理内存,需要进行数据共享的进程将同一块物理内存映射到自己的虚拟地址空间,然后通过虚拟地址直接访问。相较于其他通信方式,没有了两次用户空间与内核空间之间的数据拷贝操作,因此速度最快。
2.操作流程
1)创建/打开共享内存
2)将共享内存映射到虚拟地址空间
3)共享内存操作
4)解除映射关系
5)删除共享内存
3.操作接口
3.1创建或打开操作
int shmget (key_t key, size_t size, int shmflag); key:标识符,多个进程通过标识符打开同一个共享内存; size:共享内存大小; shmflag:打开方式&权限; 打开方式:IPC_CREAT,......; 权限:0777,......;
返回值:
成功,返回操作句柄(非负整数);
失败,返回-1。
3.2建立映射关系操作
void *shmat(int shmid, void *addr, int shmflag); shmid:shmget返回的操作句柄; addr:指定映射首地址(通常置NULL); shmflag:要进行的操作 SHM_RDONLY:只读; 默认给0:可读可写;
返回值:
成功,返回映射的首地址;
失败,返回(void*)-1。
3.3解除映射关系操作
int shmdt(void *shm_start); shm_start:shmat映射的首地址。
3.4删除共享内存操作
int shmct(int shmid, int cmd, struct shmid_ds *buf); shmid:shmget返回的句柄; cmd:要对共享内存的操作 删除:IPC_RMID ......; buf:用于设置或获取共享内存信息,不使用则设置为NULL。
返回值:对于删除操作
成功:返回0;
失败:返回-1。
★注意:
共享内存的删除,实际上并不会立即删除共享内存,而是将共享内存标记为被删除状态,拒绝后续的新建映射,等到映射连接数为0时,自动释放共享内存段。
4.共享内存总结
4.1本质原理
共享内存的本质就是开辟了一块物理内存,将其映射到多个进程的虚拟地址空间中,进程通过映射后的虚拟地址直接访问同一块物理内存,实现数据共享。
4.2特性
最快的进程间通信方式(相较于其他方式,少了用户空间与内核空间之间的两步数据拷贝操作)。
生命周期随内核。
4.3注意事项
多个进程对共享内存进行操作,存在操作安全隐患。
三、消息队列
使用场景:用于实现进程间的数据传输
本质:内核中的一个优先级队列
原理:多个进程通过访问同一个内核中的消息队列,向队列中添加节点、获取节点,实现数据传输
特性:
消息队列自带同步与互斥;
生命周期随内核。
四、信号量
使用场景:用于实现进程间的同步与互斥
本质:内核中的一个计数器和pcb等待队列
原理:针对资源进行计数,实现同步与互斥
操作:PV操作
P操作:在获取或者访问资源前进行P操作
对计数器进行判断,若小于等于0,则阻塞进程并且计数器-1;
若计数器大于0,则计数器-1,操作正确返回。
V操作:在产生资源之后进行V操作
计数器加1,并唤醒一个等待中的进程。
同步实现:
对资源进行计数,在资源获取之前进行P操作,在产生资源后进行V操作。
互斥实现:
将资源计数器初始化为1,表示资源只有一个。
在访问数据前进行P操作,访问数据完毕之后进行V操作,从而实现同一个资源同一时间内,只有一个进程能够进行访问。
五、进程间通信资源的命令操作
1. ipcs 查看进程间通信资源
语法:ipcs [选项]
-m:共享内存
-s:信号量
-q:消息队列
2. ipcrm 删除进程间通信资源
语法:ipcrm [选项] 资源id