进程间通信
进程间通信(IPC):操作系统为用户提供的几种进程间通信:
主要因为进程之间具有独立性(每个进程都有自己的虚拟地址空间),因此无法直接通信所以才需要操作系统提供进程间通信方式,实现进程间通信
访问的都是自己的虚拟地址,而不是直接访问物理内存,无法访问同一块区域,因此无法实现时数据通信
操作系统针对不同的通信场景提供了多种不同的通信方式:数据传输–管道/消息队列;数据共享–共享内存 进程控制—信号量
从unix而来的管道;systemV标准的共享内存/消息队列/信号量;
管道:
本质:内核中的一块缓冲区;多个进程若访问同一个管道(一块缓冲区)就可以实现通信;
种类:匿名管道/命名管道
区别:有没有标识符,匿名管道没有标识符,命名管道有标识符,这个标识符是个可见于文件系统的管道文件
匿名管道:内核中的缓冲区没有具体的标识符;因此只有勇于具有亲缘关系的进程间通信;因为只有通过子进程复制父进程的方式获取到管道的操作句柄;因为父进程创建管道的时候会返回管道的操作句柄,而操作句柄就是文件描述符
如何创建匿名管道:
int pipe(int pipefd[2]);pipefd[2]–具有两个int型结点的数组的首地址,用于接收创建管道返回的操作句柄
pipefd[0]–用于从管道中读取数据 pipefd[1]–用于向管道中写入数据
管道是一个单向的资源传输,自身并不限制资源的传输方向;管道是一个半双工通信(可以选择方向的单向传输);
管道的读写特性:
1.若管道中没有数据,则调用read读取数据会阻塞
2.若管道中数据满了,则调用write写入数据会阻塞;管道是一块缓冲区(内存空间),并非无限大 阻塞:为了完成一个功能,发起调用,若当前不具备完成条件,则一直等待
3.若管道的所有读端pipefd[0]被关闭,则继续调用write会产生异常导致进程退出
4.若管道的所有写端pipefd[1]被关闭,则继续调用read,read读完管道内的数据后不再阻塞,而是返回0;
命令行中管道符的实现:ps -ef | grep ssh
ps -ef :默认将结果打印到标准输出 prep ssh:默认从标准输入读取数据进行过滤
ps进程如何将输出结果,并不写入标准输出而是写入到管道中呢?
将标准输出重定向到管道写入端
grep进程如何做到不从标准输入读取数据,而是从管道读取数据呢?
将标准输入重定向到管道读取数据
grep过滤了结果后并不会退出,循环读取数据进行过滤;因此ps进程退出后需要关闭所有写端;告诉grep没人写了
命名管道:内核中的缓冲区,这块缓冲区具有标识符;–可用于同一主机上的任意进程间通信 这个标识符是一个可见于文件系统的管道文件,能够被其它进程找到并打开管道文件则可以获得管道的操作句柄 多个进程通过命名管道通信是通过打开命名管道文件访问同一块内核中的缓冲区实现通信
可以通过命令:mkfifo 创建管道文件
代码中的操作:int mkfifo(char *filename,mode_t mode);
filename:管道文件名称 mode:管道文件权限; 成功返回0;失败返回-1
管道特性:
1.管道是半双工通信
2.读写特性:管道没有数据则read阻塞/管道数据满了则write阻塞/关闭所有读端write会出发异常导致进程退出/关闭所有写端read会返回0不再阻塞
3.生命周期随进程–管道是一块缓冲区,所有打开管道的进程退出,资源就会释放
4.管道提供字节流服务:可靠的,有序的,基于连接的字节流服务(数据可以在缓冲区中堆积很多–收发数据的灵活性)
5.管道自带同步与互斥
同步:通过条件判断实现对临界资源访问的合理性
互斥:通过唯一访问实现对临界资源访问的安全性
临界资源:大家都能访问到的数据
原子操作:不能被打断的操作,指的是一个操作要么一次完成,要么就不做
共享内存:
特性:共享内存是最快的进程间通信方式
本质原理:在物理内存上开辟一块内存空间,多个进程可以将同一块物理内存空间映射到自己的虚拟地址空间,通过自己的虚拟地址直接访问这块空间,通过这种方式实现数据共享。
管道的通信中:涉及到两次用户态与内核态之间的数据拷贝;将数据写入管道,从管道读取数据
相较于管道这种通信少了两次用户态与内核态之间的数据拷贝操作,因此速度快
共享内存流程:
1.创建共享内存–在物理内存上开辟空间
int shmget(key_t key,size_t size,int shmflg)
key:内核中共享内存的标识符–多个进程通过相同的标识符才能打开通一块共享内存
size:共享内存的大小–以内存页为单位进行分配(4096字节)
shmflg:IPC_CREAT-存在则打开,不存在则创建 | IPC_EXCL与IPC_CREAT同时使用,若存在则报错,不存在则创建
返回值:返回一个非负整数–共享内存的操作句柄;失败则返回-1
1.2 生成一个 key值:key_t ftok(const char *pathname,int proj_id);
通过inode节点号与 projid生成一个key
2.进程将共享内存映射到自己的虚拟地址空间
void *shmat(int shmid,const void shmaddr,int shmflg)
shmid:shmget返回的共享内存操作句柄
shmaddr:共享内存映射在虚拟地址空间的首地址–通常置NULL
shmflg:映射成功之后对共享内存可以进行的操作
返回值:返回共享内存映射在虚拟地址空间的中的首地址–通过这个首地址进行后续操作的内存操作,失败返回(void)-1
3.基本的内存操作都可以对这块空间进行操作
4.接触虚拟地址空间与共享内存的映射关系
shmaddr:映射在虚拟地址空间中的首地址
成功返回0;失败返回-1
5.释放共享内存资源:shmctl(int shmid,int cmd,struct shmid_ds *buf);
shmid:共享内存操作句柄
cmd:对共享内存想要进行的操作IPC_RMID-删除共享内存
buf:用于获取/设置共享内存信息的结构,不使用则置NULL
成功返回0;失败返回1
共享内存删除的时候,并不会立即被删除,只是将其状态置为被销毁状态,移除标识–为了不让这个共享内存继续被其它进程映射然后等到当前共享内存映射连接数为0的时候,才真正删除这块共享内存
操作系统中进程间通信资源的命令操作:
ipcs:查看进程间通信资源
-m:查看共享内存
-q:查看消息队列
-s:查看信号量
ipcrm:删除进程间通信资源
-m
共享内存特性:
1.最快的进程间通信
2.生命周期随内核
注意:共享内存并没有自带同步与互斥–多个进程进行访问的时候存在安全问题
消息队列:
内核中的一个优先级队列;多个进程通过访问同一个队列,进行添加节点或者获取节点实现进程间通信
消息队列流程:
1.创建消息队列–在内核中创建一个优先级队列
2.进程可以向队列中添加/删除节点
3.删除消息队列
消息队列特性:
1.自带同步与互斥
2.生命周期随内核
信号量:是用于实现进程间的同步与互斥(共享内存本身不提供同步与互斥,操作存在安全隐患,因此需要使用信号量来保护对共享内存的操作)
同步:通过条件判断实现临界资源的合理性
互斥:通过同一时间唯一访问实现临界资源的安全性
信号量本质:一个内核中的计数器+pcb等待队列
计数>0才能访问;获取一个资源,计数-1;
计数<=0不能访问;计数-1;然后pcb状态置为可中断休眠状态,加入等待队列
其他进程产生了资源,计数+1;若计数>0则什么都不干;若计数<0则从等待队列中唤醒一个pcb去获取资源
信号量实现同步:通过计数器对资源进行计数;计数>0表示能获取;计数<=0表示不能获取通过等待接口使进程等待加入等待队列;等到有资源的时候唤醒
信号量实现互斥:保证同一时间只有一个进程能够访问资源
只需要保证信号量的资源数不会大于1就可以实现