一.何谓system V?
这是一套进程间通信的共享内存的标准。
二.原理
AB进程的处于虚拟地址空间共享区的空间,都通过页表映射到物理内存的同一块区域,这就保证了不同进程看到了同一份资源,进而实现了进程间通信。
注意:
1.开辟空间,建立映射等一系列操作,是由OS完成的,用户通过系统调用来间接实现以上操作。
2.内核设定上这个空间(step1)是允许共享的,不会因AB进程进行写时拷贝,导致改变其中一个进程的指向,进而无法通信。
3.通信完成后释放空间时,AB进程都是把申请的虚拟地址空间free掉,然后去掉页表上的映射关系,共享内存没有指针指向它(是否在使用共享内存),自然而然被OS释放掉,那么操作系统怎么知道有没指针指向共享内存呢?答案是引用计数!当引用计数减到0,就没有进程使用共享内存,即没有进程的指针指向共享内存,这时OS就会释放共享内存。
4.先描述,再组织
三.共享内存接口的使用
1.创建共享内存——shmget
形参解析
①.size
共享内存的大小
②.shmflg
标记位,一个宏,有两种:IPC_CREAT和IPC_EXCL
IPC_CREAT:创建一个共享内存,共享内存不存在,就先创建;如果存在,就打开这个已经存在共享内存,并返回。
IPC_EXCL:单用是滚木,毫无意义;需用“|”配合IPC_CREAT使用。即IPC_CREAT|IPC_EXCL。
共享内存不存在,就创建共享内存;如果存在,shmget出错返回!保证了shmget只要成功返回,就一定是一个新的共享内存!
③.key
在说这个参数之前,我们先引出两个问题:
1.我们如何去评估一个共享内存是否存在?
2.怎么保证要进行共享内存通信的两个进程,拿到的是同一个共享内存呢?
问题的答案,就是这个key!key可以标识共享内存的唯一性。既然有唯一性,就代表着其必然存在。这里需要注意一个点,key并非是内核生成的,而是在用户层生成,当生成以后,要手动传给shmget这个系统调用!
那为什么是在用户层生成呢?
进程A调用shmget接口,创建了共享内存空间,但是进程B怎么连接上这个空间呢?首先,让进程B再去调用shmget接口,是绝对行不通的,这只能构建新的共享内存空间。那么在系统层面,把key传给B进程,反正key能保证共享内存的唯一性,这不就成了吗?还是不行!因为在系统层面想把key从进程A传给B,是要基于双方已经建立通信的!
所以AB进程在建立通信之前,会在用户层约定一个key(在用户层约定是不用建立通信的),A进程把key传给系统中,对内存空间进行描述的结构体,作为识别共享内存空间的身份证;随后在用户层拿到key的进程B,就会根据这个key,找到对应的共享内存空间了。
那么如何约定的呢?这里用了另一种通信方式——命名管道!只要让AB打开同一路径下的同一管道文件,就可以实现key的传递。
注意:共享内存包括描述共享内存的struct+其指向的共享内存空间!
④.返回值
那么shmget创建成功以后,这个int返回值是啥?反正不会是key,key是给内核用的,用户不会去使用key,这是一个用户层的整数值(标识值)。在用户这,是通过这个返回值来标识共享内存的;后续用户在调用其他系统调用时,也是通过这个返回值,来访问共享内存。
2.生成key——ftok
理论上说,key可以随意生成,但是会带来冲突,所以我们一般使用一个接口(内含算法)——ftok,来为我们构建冲突较小的key。这玩意是个纯用户层面的算法。
pathname字符串和id都可以瞎写,但是建议pathname就写当前工作目录的路径"."。算法会将这俩货做混合,最终构建出我们的key。如果还是发生冲突,就更改两个形参,以保证key的唯一性。
构建key成功,就会返回key。
2.5.去关联——shmdt
在对共享内存进行删除之前,还需先将虚拟地址空间与共享内存去关联。
3.删除共享内存
进程结束,如果没有删除共享内存,其还会一直存在!!这跟文件不一样,文件的引用计数为0时,代表打开文件的所以进程全部退出,就会释放文件。所以共享内存的生命周期随内核!
①.删除共享内存的指令——ipcrm
ipcrm -m + shmid(即shmget的返回值,再说一遍,不是key!!!!)
那么怎么看到shmid呢?答案是指令ipcs -m:
注意:
指令是针对用户层面的封装,是运行于用户层面的!所以要用shmid管理运行内存!
②.删除共享内存的接口——shmctl
这是个共享内存控制函数,通过不同的控制命令(形参cmd),可以对共享内存完成不同操作,最后一个形参事关共享内存的属性,进程删除操作的时候可以置为空。
cmd分三种:
- IPC_STAT:获取共享内存信息,内核把数据写入 buf ,buf不能为NULL- IPC_SET:修改共享内存的权限、属主;从buf读取配置,buf不能为NULL
-IPC_RMID:标记删除共享内存段,buf传 NULL 即可【最重要】
形参buf指向描述共享内存的结构体shimd_ds,该结构体包含共享内存许多属性:
创建共享内存的进程在shmget以后,会把key设置到ipc_perm结构体的_key上。获取共享内存的进程会拿着key去与shimd_ds的_key进行对比,如果相同,就可以知道访问的是同一个共享内存。
4.将共享内存空间映射到进程地址空间——shmat
返回值:返回起始的虚拟地址。进程可以通过该起始虚拟地址,进而访问到对应的共享内存空间。
如果单纯从接口名来看:at是attach的意思,就是将共享内存挂载到进程的地址空间中。
形参解析:
shmaddr:虚拟地址的使用情况,OS最清楚,用户不能盲目设置一个虚拟地址,所以使用接口时,就将参数设置为nullptr。
shmflg:既想对共享内存读,也想对其写,所以设为0,使用默认配置。
四.关于读写
在搭建信道的时候,用到了大量的系统调用,但是在对共享内存进行读写操作时,是没有用到系统调用的!是直接对共享内存相关联的虚拟地址进行操作的!
以上确实是有系统调用,但它属于搭建信道的范畴。
这里进行写操作的时候,将虚拟地址空间当成一个大的字符串,用for循环进行一个个字符的写入:
读取是直接获取虚拟地址空间的初始值,这时的虚拟地址空间,已经被写入了:
总结:
五.共享内存通信的特点和弊端
共享内存通信有个弊端,通信双方的进程,是没有同步机制这个保护机制的!一个写的进程压根没有生成,另一个读的进程生成以后,不会阻塞,等待写入,而是去不断地读取。这会导致数据不一致,比如A进程打算写aabb,结果写完aab,就立马被B进程读了去(AB是并发运行的,你读你的,我写我的)。
在AB进程间再创建一条命名管道,A进程会在写入共享内存的时候,倘若写完自己想要写的,就通过管道,向B发通知;B进程不准直接读共享内存,而是要等待管道的通知;通知到了,B进程才被唤醒,去共享内存读取。