消息队列
要实现进程间通信我们必须得让不同的进程看到同一份资源, 根据这个资源的不同(文件缓冲区, 内存块, 队列) 我们将通信方式分为管道,共享内存,以及我们接下来要讲的消息队列。
消息队列的原理
就是让不同的进程能看到同一个队列, 每当想发送数据就将带类型的数据块(这是为了区分消息发送给谁)
链入队列中, 这样每个进程都能从队列中拿到发送给自己的数据。
这种通信方式和之前将的共享内存一样是system V
标准
消息队列相关的接口
创建消息队列
控制消息队列
发送消息到消息队列, 注意我们需要发送的是类似下面那张图的结构体
从消息队列中接收消息
IPC在内核中的数据结构设计
在操作系统中, 所有的IPC资源, 都是在整合进程操作系统的IPC模块中
我们要管理消息队列, 共享内存, 信号量, 其实就是管理对应的数据结构
所以现在的问题就是我们该如何把这些数据结构管理起来呢?
在操作系统当中使用数组
管理起来的。
我们在创建共享内存的时候, 操作系统就会为我们创建出他的_ds结构体
我们就让数组的某个地方, 指向这个结构体的起始位置
后来, 如果我们要创建消息队列, 虽然他们的_ds有所不同, 但是他们的第一个数据类型是一样的, 所以同样可以将他的起始地址填入数组中
对于信号量也同理。
从此往后, 我要管理操作系统中的IPC资源就转化成了对该数组进行增删查改。
而我们要查找某个资源的时候只需要根据提供的key, 然后遍历数组,在指向的第一个结构体中去找到这个key
而这个XXXid(例如 shmid) 就是这个玩意的数组下标
我们可以发现, 其实这里也是用c语言实现的多态。
信号量
当我们的A正在写入, 写入了一部分还没写完就被b拿走了,导致发送和接受的信息都不完整 — 数据不一致问题
管道不会有这种问题, 因为管道有同步与互斥机制, 而共享内存是没有保护机制
的。
- A B 看到的是同一份资源(共享资源), 如果不加以保护就会导致数据不一致问题。
- 加锁 – 互斥访问 – 当我读写的时候不允许别人打扰 也就是
任何时刻都只允许一个执行流访问共享资源
– 互斥 - 共享的, 任何时刻只允许一个执行流访问(执行访问代码)的资源他就是一个 – 临界资源 例如(管道) –
一般是
内存空间。 - 并不是所有的代码都会访问临界资源, 例如100行代码可能只有几行在进行拷贝等操作需要访问临界资源, 我们把访问临界资源的代码称为
临界区
;
解释一个现象: 多进程,多线程, 并发打印的时候 例如打印"hello 1234"
但是显示器上的消息:
1 错乱的
2 混乱的 (可能有的消息没有被完全打印出来)
3 和命令行混在一起
我们想显示器打印数据本质上是往显示器文件里写入数据, 所以我们会先写入显示器文佳的缓冲区内,然后操作系统再把内容刷新到显示器上我们才看到对应的内容,进程要往同一个显示器打印内容, 需要看到同一个显示器文件, 所以显示器文件在这里其实是一个共享资源, 由于没有对他进行保护, 所以导致了数据不一致问题
理解信号量
信号量的本质就是一把计数器, 类似(不是等于)int cnt = n
他是用于描述临界资源中资源数量的多少!
为了防止n + 1 个执行流去访问 n 个资源 而导致多个执行流访问同一个资源 我们就有了一个计数器 。
1 申请成功(计数器不为0) 就代表我具有访问资源的权限了。
2 申请了计数器资源, 我当前访问我要的资源了吗? 没有。 申请了计数器资源的本质是对资源的预定机制
3 计数器可以有效保证进入共享资源的执行流的数量。
4 所以每一个执行流想访问共享资源的时候, 不是直接去访问, 而是先申请计数器资源。就比如看电影要先买票。
这个计数器我们就称为信号量
而如果信号量为1, 也就代表只有一个执行流能访问这个临界资源
我们把值只能为1/0两态的计数器叫做二元信号量
– 他的本质就是一个锁。
计数器为1 本质就是不要把临界资源当做很多块了, 而是当做一个整体来使用。
那么对一个整数做--
操作是安全的嘛?
不是
所以在信号量这里,对于他的操作是进行保护了的。
结论:
信号量对应的接口
申请信号量
第二个参数表示要申请几个信号量, 注意多个信号量和信号量是几不是一个概念
控制信号量、
根据cmd的不同对信号量进行不同的操作 比如 设置, 删除, 获取属性
设置信号量
其中sembuf
是需要我们自己定义的结构体, 他的结构是这样的
如果我们只需要一个信号量第一个参数就传0
就相当于这个信号量在数组里的下标
sem_op 我们一般设置为1/-1 1
表示对信号量实行++,也就是p操作-1
表示对信号量进行-- ,也就是v操作