进程间通信
基本介绍:
操作系统提供给用户的几种进程间的通信方式
进程间通信方式是干什么的:
进程间数据传输 数据共享 进程控制 事件通知
正式因为有不同的需求,因此操作系统之间有人提供了多找不同的进程间的通信方式:
续继承与unix而来的管道(匿名管道/命名管道) 共享内存 消息队列 信号量
为什么操作系统要给用户提供进程间通信方式:
进程的独立性导致进程之间没有办法进行通信——由操作系统来提供一个公共的媒介,来进行通信
通信方式:
管道:(本质是内存的缓冲区)
管道创建在内核态,是一个“半双工通信”(提供双向选择但是只能单向传输)。在传输信息时提供io操作——返回文件描述符作为
句柄(两个文件描述符)一个用于写入数据,另一个用于读数据
匿名管道/命名管道:
命名管道:有名字则可以通过名字来打开相同的管道进行通信
匿名管道没有名字,因此只能通过子进程复制父进程的方式实现通信(复制了文件描述符)
*匿名管道只能同于具有亲缘关系的进程通信
*命名管道可以用于任意进程间通信
匿名管道:
创建匿名管道(在创建子进程之前):
int pipe(int pipefd[2])
pipefd[0]——管道的读取端
pipefd[1]——管道的写入端
返回值 成功:0 失败:-1
读写特性:
1.管道中若没有数据,则read会阻塞,直到读取数据(由数据被写入管道了)。
2.管道中若数据满了,则write会阻塞,直到由空闲空间(有数据被读取走了)。
3.若所有读端被关闭,则wriet会触发异常——SIGPIPE(导致进程退出)
若所有写端被关闭,则read读完数据之后不会阻塞,而是返回0
进程在操作管道的时候如果没有用到某一端,则把这一端关闭
管道符 | 的实现: ps -ef | grep pipe
shell 父进程pipe
ps 子进程1——>将处理结果打印到标准输出——标准输出重定向
grep 子进程2——>循环从标准输入读取数据——标准输入重定向
命名管道:
为管道创建了一个管道文件,这个管道文件就是管道的名字(有名字体现在)
创建名字管道:(本质还是在内核的缓冲区)
int mkfifo(const char* pathname, mode_t mode)
pathname——管道我呢见的路劲名
mode——创建文件的权限
返回值 成功:0 失败:-1
打开特性:
若管道文件文件没有被写的方式打开,则以只读打开则会阻塞;
读写特性:
与匿名管道一样
管道总结:(管道的本质是内核的一块缓冲区)
- 匿名管道只能用与具有亲缘关系的进程间通信/命名管道可以用于任意进程间通信
- 管道的读写特性+命名管道的打开特性
- 管道是半双工通信——可以选方向的单向通信
- 管道的生命周期随进程
- 管道提供流式服务:字节流传输。传输数据的形式以字节流的形式(缺点:不能知道数据的间隔,造成数据粘连)
- 管道自带同步与互斥功能(读写操作数据大小不超过PIPE_BUF(默认4096)大小,读写操作受保护)
互斥:对临界资源(公共资源)的同一时间唯一访问性(我操作的时候别人不能操作;不写则读阻塞)
保证安全
同步:对临界资源(公共资源)的时序可控性(我操作完了别人才能操作)
保证合理
管道的同步与互斥体现在:管道美没有放数据则读阻塞,放了数据之后换对方阻塞,对方读数据
(我放了,你才能读——同步);数据往管道中没有放完别人不能读也不能写(互斥) - 命名管道独有的特性:打开特性
共享内存:(是所有进程间通信方式中最快的一种)
原理:
1.多个进程通过将同一块内存映射到自己的虚拟地址空间中,实现对相同的一块物理内存进行操作,
2.通过这种方式实现多个进程间的数据共享功能
3.因为共享内存是直接通过虚拟地址操作内存实现共享,相较于其他方式少了两次用户态/内核态之间的数据拷贝
操作步骤:
1.创建一块共享内存(一个修改,一个查看)
key_t ftok(const char* pathname, int proj_id)
通过文件inode结点号与proj_id生成一个key值
shmget(key_t key, size_t size, int shmflg)
key:共享内存标识符
size:共享内存大小
shmflg:选项标识
IPC_CREAT:共享内存不存在则创建,存在则打开
IPC_EXCL:与IPC_CREAT同用,则共享内存存在时报错
shm_mode 权限
返回值:标识符(代码中的操作句柄) 失败:-1
进程间通信方式的查看:
ipcs命令:[-m -s ]
可以查看共享内存(nattch表示有多少个进程与它连接)
删除共享内存:ipcrm [-m -s -q] shmid
2.将共享内存映射到虚拟地址空间
void* shmat(int shmid, const void* shmaddr, int shmflg)
shmid:创建共享内存,返回句柄
shmaddr:置空,映射首地址由操作系统分配
shmflg:映射成功后的操作权限
SHM_RDONLY: 只读
0 默认—可读可写
返回值:映射首地址 失败:(void* )-1
3.直接对这块内存进行操作
memcpy...
4.解除映射关系
int shmdt(const void* shmaddr)
shmaddr: 映射首地址
5.删除共享内存(删除时会检测是否还有进程与内存映射,如果还有则拒绝其他进程的映射)
int shmctl(int shmid, int cmd, struct shmid_ds* buf)
shmid: 操作句柄
cmd: 操作类型
IPC_RMID: 删除共享内存
buf: 设置/获取共享内存的信息
共享内存的删除:
不会被直接删除,而是会判断映射连接数是否为0,为0则直接删除,不为0,则拒绝后续其他进程
的后续连接,当映射连接数为0 时自动删除。
消息队列:(消息队列是可以双向通信,传输的是有类型的数据快)
通过 msgget 创建一个队列:
int msgget(key_t key, int msgflg);
利用 msgsnd 来保证获取的数据类型
本质:内核中的优先级队列
信号量:
本质:内核中的一个计数器,并且带有pcb等待队列
计数器:数据资源的计数器
如果获取数据时,先判断计数器是否>0;
若 > 0,则表示有资源,则可以获取数据,并且资源数据-1;
若 <= 0,则表示没有资源,则阻塞等待;
产生数据,直接计数+1,并且唤醒等待的进程(只会唤醒在pcb等待队列上的进程)。
作用:用于实现进程/线程间的同步与互斥
信号量实现同步依靠:等待+唤醒
信号量实现互斥依靠:计数器(只有0/1)+等待+唤醒