这节我们开始学习System V IPC方案。
分别是共享内存,消息队列与信号量
会着重讲解共享内存,但是消息队列与信号量只会说明一下原理。
原因:System V是新设计的一套标准
- 与文件的整合度不高
- 只能进行本地通信
更何况,我们现在有了网络,可以进行更强大的通信。
目录
共享内存:
原理:
首先我们要明确一点,进程间具有独立性,那我们通信必须要申请一块公共内存。下图就是一个简略的简述两个独立的A与B进程
我们回想一下动态库的加载,我们将加载到内存中的动态库可以分别通过页表映射到A与B的共享区,故动态库也叫做共享库(被多个进程同时拥有)。
那么共享内存当然也可以被加载到共享区,这样两个进程就可以有一块公共的内存空间了!
所以,通过地址空间的映射,让A和B看到同一份内存就叫做共享内存
理解:
-
所有的操作都是OS来完成的。-->因为OS是软硬件的管理者,
-
OS可以做,但是OS不知道什么时候做,所以OS需要提供系统调用接口,这样就可以由进程发起
-
不仅仅只有A与B进程可以通过共享内存进行通信,同理D和C,E和F也都可以!
-
OS注定要对共享内存进行管理。因为有的共享内存刚创建,有的要删除,有的要通信,这就需要管理起来,而管理就需要先描述在组织。
-
共享内存 = 内存空间(数据)+ 共享内存属性
代码 + 理论:
shmget:
先来看看如何申请共享内存
我们先来分别理解一下
size
:这个代表你想要多大字节的内存
shmflg
:这是最主要的两个标志
与文件系统调用的传参是很相似的,都是本质都是位图的应用!
我们将最主要的两个标志分别组合看看代表什么含义:
IPC_CREAT:代表目标共享内存没有就创建,有了就获取
IPC_EXCL:单独使用无意义
IPC_CREAT | IPC_EXCL:代表目标共享内存没有就创建,有了就报错。
什么?那第三个有什么用吗?
答案是有的,他们分别代表了不同的职责。
IPC_CREAT代表的是总能获取一个共享内存,
而两者相或代表这个共享内存一定是全新的,
也可以分别对应上我们的client与server。
我们一般进行创建的时候最好加上权限,也就是|上一个0x666
之类的
key:
是表示共享内存的唯一标识符
那么该如何理解呢?
我们以一个小三段论来理解(是什么,为什么,怎么办)
是什么:当你想要创建一个共享内存时,OS怎么知道你想创建的是否已经存在了呢?
故需要一个标识符来进行标识!
为什么:为什么这个标识符要由用户生成?
如果是由OS自动生成,当A成功申请了一个共享内存,当要与B建立联系时,必然就要知道这块内存在哪里,可是这是由OS生成,B就没办法知道,也就建立不了联系了。
所以要由用户生成,使用同一个key进行一个规则上的约束,进而互相找到对方。
怎么办:我们使用OS提供的ftok接口进行生成key。
pathname与proj_id自定义即可
接下来我们代码操作一下
分别在客服端与服务端打印出来是一样的数字
接下来我们进行shmget:
那么问题来了,返回值是啥?
返回值也是共享内存标识符,但和key完全不一样,
我们进行打印一下返回值。
我们另外发现即使创建这个共享内存的进程已经结束了,但是共享内存依旧不可以重新创建,这也就说明共享内存的生命周期是随进程的,不随着进程的结束而结束。
那么这两个标识符到底有什么区别呢?
再开始之前我们先看一下共享内存的删除。
指令删除语法:
c
ipcrm -m shmid
经过测试我们发现只有这个返回值shmid才可以删除掉,而key是无法做到的。
现在就可以正式回答这个问题了:
我们用户生成一个标识符给内核用,而内核生成一个标识符给用户用,这也侧面进行了解耦,类似文件中的struct file*与fd
可是使用指令删除也太挫了,我们学习一下如何在进程中删除~
shmtcl:
没错,虽然没有直接进行删除的接口,但是ctl也就是control的简写,使用这个接口也可以进行除了删除之外的操作。
我们先来了解一下shmctl的各个参数:
shmid:没什么好说的,就是我们刚刚shmget的返回值
cmd:对这个shmid标识的共享内存进行各种操作的控制参数,我们用的删除即是下图的IPC_RMID(rm是remove,id是immediate)
buf:是一个输入/输出型参数,可以传一个结构体进行对内核数据的获取,也可以进行控制里面的共享内存的各种参数,这里我们不需要管,直接传个nullptr即可~
代码:
shmat && shmdt:
shmget是进行对共享内存的创建,而at是进行挂载,将创建好的共享内存映射到共享区!
参数解释:
shmid:没什么好说的,就是get的返回值
ahmaddr:想要对共享内存挂载的地址,这里我们不需要(传nullptr即可)
shmflg:对于共享内存的权限,同样不需要(传0即可)
void* :虚拟地址空间的共享内存返回值(类似malloc的返回值)
于是我们终于可以进行通信了,以上也都是通信的准备工作~
但是开始之前我们需要将Getshm改动一下,分别适配server,client
下图是进行通信的代码,client是写数据,而server是接收数据
现象:可以发现共享内存并没有像管道一样提供一些保护机制,比如管道没数据就不会读,每次读完就会清空数据...
于是我们得到了一个结论:共享内存并没有提供数据的保护机制。
但是她就没有优点吗,答案是否定的。
他是所有IPC中最快的通信方式,因为他没有调用系统调用接口,不存在各种缓冲区考来考去,大大减少了拷贝!
此外,虽然共享内存没有提供保护机制,但是我们可以使用一些手段进行对其进行保护,例如我们的命名管道~
消息队列:
我们接下来的消息队列与信号量都是讲解一下原理,不进行操作~
原理:
同样,这是由OS本身申请,由进程发起。
注意:消息队列也可以有多个,所以也需要进行管理,先描述再组织!
接口:
msgget:
相信key与flg已经不用介绍了,与shm是非常相似的
msgctl:
也是与shm一致!
msgsnd && msgrcv:
msgp:
是指向消息块的结构体。
当我们想使用时, 像下图一样使用即可
msgsz:结构体的大小,我们直接sizeof即可获得
msgflg:权限
msgtyp:数据块的类型
指令操作:
与共享内存相似,将m改为q即可,shmid改为msgid。
注意:消息队列的声明周期也是随内核
信号量:
一些概念的渗透:
对于信号量的理解:
我们回顾一下shm与pipe。都是对一块内存的整体使用
那我们是不是可以将这大块资源分成一个个小块,让多个执行流访问同一块资源,在公共资源的基础上保证并发度。
所以信号量就是对不整体使用,对小资源进行管理的一种IPC,本质就是计数器。
我们来举个例子:
我们看电影都会买票,只要你买了票不管你去不去,这个座位都是你的。
但是对于这种事件我们最怕一件事:那就是只有25个座位却卖出26张票。
那我们如何进行预防呢?
就是设置一个计数器count = 25。卖出一张票就--,如果count == 0就不卖了。
在这个例子中:
结论:保证资源被合理使用要做到以下两点
- 先"买票"
- 让执行流(进程)与资源一一对应。
所以访问共享资源我们要
- 申请信号量
- 访问共享内存
- 释放信号量
所以信号量的本质就是对资源的预定。
注意:对于信号量我们也分为两种:多元与二元信号量。
多元就像25张票的例子,
而二元呢?就像是一个vip放映厅,只允许一个人进去看,对资源的整体进行使用--->对应的其实就是互斥
我们现在也就从生活中的例子过渡到信号量了
疑问:
刚刚我说信号量本质是一个计数器,那么我可以使用一个全局变量来当做信号量?
答案是否定的
- 全局不能被所有进程看到,只有父子进程或其他亲缘关系的进程可以看到,但是会发生写时拷贝,各玩各的,非亲缘关系的进程更不用说了
- 对一个变量的++/--操作是非原子的
非原子是什么意思?
转变到汇编语句实际上是有超过2句代码以上的,
像我们定义一个变量并直接初始化是只有一句汇编语句的,也就是原子的
虽然都是IPC范畴内的,但是他与共享内存和消息队列还是有不一样的,并不是用来传递消息,而是用来进行同步互斥
但最终还是IPC范畴的,因为他是公共资源~
现在我们还有最后一个问题:
信号量也是公资源,需要用它来保护其他公共资源,那她自己怎么保护自己的资源?
他的++/--操作被设置为原子的,叫做PV操作!
接口 && 指令:
待更新~
OS对于这三者的管理:
其实在共享内存哪里我们草草的涉及了一下一个很关键的东西。
在shmctl中有一个shmid_ds的结构体。
这个结构体的第一个成员是一个perm结构体
而这个东西不仅仅是只有shm有,其余两个也都有!
我们来看一个图(是一个关于IPC的 内核图)
可以看到有一个ipc_ids的结构体(最上边)内指向有一个ipc_id_ary的结构体。这个结构体内有个柔性数组[0] + size。
这个柔性数组每一个成员是一个个指针,是一个个的kern_ipc_perm的指针,也就是我们用户层各个ds结构体的第一个成员,当我们像利用ctl函数进行拷贝时,实际上就是去内核拷贝一份这个给用户。
实际上假如当我们申请一个信号量,就会在柔性数组中寻找一个未被使用的位置指向你申请的sem或者shm或者msg,所以shmid...就是柔性数组下标。
这也就说明我们的这个柔性数组把这个IPC资源统一管理起来l!
所以我们的key也会相互冲突(对柔性数组进行遍历检查),毕竟用的也是同一个算法嘛~
那么我们现在还有一个问题,这个柔性数组内的指针怎么知道自己指向什么?所以我们就可以肯定这个结构体内肯定有进行标识的字段!
至于如何变为对应的结构体(shm,msg,sem),我们就用指针的强转咯~
所以这也就是用C语言实现的多态!
本章完~