一、共享内存
1、直接原理
进程间通信的本质是:先让不同的进程,看到同一份资源!!
我们要把这句话奉若圭臬一般
到了共享内存了支持双向通信能读也能写,但是一般都是一个读一个写
要想通信先看到同一个份资源,则
OS帮助申请内存,通过页表挂接到进程地址空间中,给应用层返回起始虚拟地址
如果要释放共享内存:先去关联,再释放共享内存
上面的操作都是进程直接做的吗?不是。直接由操作系统来做。
因为不能让进程直接malloc,你这样申请了那这块空间就是你的,会破坏了进程的独立性
所以必须利用系统调用,这样创建出来的共享内存就不属于进程私有的
操作系统要不要管理所有的共享内存呢??先描述,在组织
内核结构体描述共享内存,就得有结构体,这个共享内存多大,谁申请的,当前几个进程和我关联…
然后把所有的结构体用链表数组管理起来
所以在OS层面上,对共享内存的管理行为,变成对某种数据结构的增删查改
接下来要学习共享内存,要么就应用层面上学,系统调用接口搞懂
往底层学,就要找对应的先描述在组织是怎么做的!
在释放共享内存时,为什么先去关联,再释放共享内存?
因为共享内存的管理属性结构体中同样也维护了这块内存的引用计数,有多少个进程引用了它
万一别的进程还在引用着你难道直接释放吗?不可能
所以利用属性中引用计数,让引用计数–,等引用计数–到0在释放
共享内存的这块物理内存只是单纯保存数据,引用计数放在属性结构体中
2、直接代码
在物理内存中的这块共享内存呢,如果有人创建出来了,别人下一次就不需要再创建了直接获取就行了
也就会有如何创建,如何获取的概念
系统调用接口shmget
OS中会有很多共享内存,你怎么保证让不同的进程看到同一个共享内存呢??
shmget参数中的key,有进程用这个key创建,你拿着这个key去OS中 的共享内存中去获取,找到了那你们两个不就看到的是同一个内存吗
所以谈谈key:
1.key是一个数字,这个数字是几,不重要。关键在于它必须在内核中具有唯一性,能够让不同的进程进行唯一性标识
2.第一个进程可以通过key创建共享内存,第二个之后的进程,只要拿着同一个key就可以和第一个进程看到同一个共享内存了!
3.对于一个已经创建好的共享内存,key在哪?key在共享内存的描述对象中!
4.第一次创建的时候,必须有一个key了。怎么有?
利用算法ftok来生成一个冲突率小的key
这个Key值干嘛那么费劲让用户去设置,设置完成还有可能出现冲突,让OS生成一个key行不行?
OS不清楚哪个进程和哪个进程要通信,只有用户最清楚
这个key如果第一次创建共享内存由OS生成,并且我这个进程也拿到了key,但是另一个进程怎么知道对应的共享内存的key是多少呢?那有人说你把这个key传给另一个进程不就行了,可是我们正在解决的就是进程间通信的问题,这不就鸡生蛋,蛋生鸡了吗
所以与其说key由用户自由指定,不如说用户约定的,只要他们两个约定了同一个key,OS有属性Key相同的共享内存,那他们两个进程一定能找到
所以不需要系统随机指定key,因为系统随机不解决把这个key传递给另一个进程的目的,我们通过约定的方式让他们两个形成同一个key。所以必须由用户层下达给OS
pathname 和 proj_id让那几个进程看到,那哪几个进程就能通信!
Key vs shmid
key也就是在创建时用,往后就都不用了,控制共享内存都用shmid
key是给OS用的
共享内存标识符shmid是用户层用它控制共享内存的
现象
创建共享内存的进程退了,可是利用
ipcs -m 查OS共享内存发现 共享内存仍然存在
说明
共享内存的生命周期是随内核的!
用户不主动关闭,共享内存会一直存在。
除非内核重启(用户释放)
因为你通完信走了,如果关闭了,上面还有数据的话,下次还想用就用不了了。
所以创建好一直存在,你想用就挂接,不用用户直接释放(ipcrm -m)
ipcrm -m key/shmid
那释放用key还是shmid呢?
一定是shmid,因为命令行也属于用户层并不属于OS,key是给OS用的
设置权限
共享内存的大小设置
共享内存的大小一般建议是4096的整数倍
如果设置为4097,实际上操作系统给你的是4096*2的大小
到此我们把共享内存创建出来,下一步就是把共享内存和我们的进程挂接起来
系统调用shmat
去关联
1、进程直接退出,也就去关联了,但是比较野蛮
2、shmdt去关联
问题:shmdt你只有起始地址,没有大小那你怎么知道释放多少空间?
共享内存先前说了,OS维护了它管理属性结构体,一定记录了共享内存多大,这是用户层看不到的,你只需要告诉我起始地址就行,所以共享内存必须连续
删除共享内存shmctl
删除的时候根本不关注属性了,第三个参数传NUll就行
共享内存整个生命周期已经写完了
,现在的工作是两个进程实现通信,另一个进程就要获取创建好的共享内存然后挂接,完成通信得去挂接.
processb就如此设置,不用负责删除,有processa呢
利用脚本查看共享内存生命周期和挂接数
while :; do ipcs -m | head -3 && ipcs -m |grep ljh; sleep 1;done
共享内存的访问,已然是挂接到进程的地址空间里,那这个大内存的虚拟地址可以直接使用访问了,不需要系统调用
一旦有了共享内存,挂接到自己的地址空间中,你直接把他当成你的内存空间来用即可!不需要调用系统调用
一旦有人把数据写入到共享内存,其实我们立马能看到了!!
不需要经过系统调用,直接就能看到数据了!
3、共享内存的特性,扩展代码
1.共享内存没有同步互斥之类的保护机制
你不写我也敢读,两个读写进程你跑你的我跑我的,所以共享内存需要保护起来
2.共享内存是所有的进程间通信中,速度最快的!
本质原因是拷贝次数少
对比管道需要调用系统调用,写把用户层缓冲区的数据拷贝到管道里拿也得拷贝出来,管道并不像共享内存能直接使用虚拟地址。
共享内存挂接到虚拟地址空间就跟我们直接用malloc的一段空间没任何区别
共享内存挂接到地址空间,一方写进地址空间,另一方进程也不需要拷贝直接就能看到写入的数据(因为共享内存大小固定已然是挂接好了,写的数据我都能直接通过地址空间看见)
3.共享内存内部的数据,由用户自己维护!
我们可以利用之前的命名管道来对共享内存做保护,写端在写完之后才发送管道中一个码,读端再接受到这个消息后才开始共享内存的读取,不然就会被管道read阻塞的特性阻塞住
代码
https://gitee.com/ljh0617/linux_test/tree/af0ecf622ee98b4a88ccf7c1c8d0842e397bc943/11-21