【Linux】进程间通信(System V IPC)

这节我们开始学习System V IPC方案。

分别是共享内存,消息队列与信号量

会着重讲解共享内存,但是消息队列与信号量只会说明一下原理。

原因:System V是新设计的一套标准

  1. 与文件的整合度不高
  2. 只能进行本地通信

更何况,我们现在有了网络,可以进行更强大的通信。

目录

共享内存:

原理:

首先我们要明确一点,进程间具有独立性,那我们通信必须要申请一块公共内存。下图就是一个简略的简述两个独立的A与B进程

我们回想一下动态库的加载,我们将加载到内存中的动态库可以分别通过页表映射到A与B的共享区,故动态库也叫做共享库(被多个进程同时拥有)。

那么共享内存当然也可以被加载到共享区,这样两个进程就可以有一块公共的内存空间了!

所以,通过地址空间的映射,让A和B看到同一份内存就叫做共享内存

理解:

  1. 所有的操作都是OS来完成的。-->因为OS是软硬件的管理者,

  2. OS可以做,但是OS不知道什么时候做,所以OS需要提供系统调用接口,这样就可以由进程发起

  3. 不仅仅只有A与B进程可以通过共享内存进行通信,同理D和C,E和F也都可以!

  4. OS注定要对共享内存进行管理。因为有的共享内存刚创建,有的要删除,有的要通信,这就需要管理起来,而管理就需要先描述在组织。

  5. 共享内存 = 内存空间(数据)+ 共享内存属性


代码 + 理论:

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就不卖了。


在这个例子中:

结论:保证资源被合理使用要做到以下两点

  • 先"买票"
  • 让执行流(进程)与资源一一对应。

所以访问共享资源我们要

  1. 申请信号量
  2. 访问共享内存
  3. 释放信号量

所以信号量的本质就是对资源的预定。

注意:对于信号量我们也分为两种:多元与二元信号量。

多元就像25张票的例子,

而二元呢?就像是一个vip放映厅,只允许一个人进去看,对资源的整体进行使用--->对应的其实就是互斥

我们现在也就从生活中的例子过渡到信号量了


疑问:

刚刚我说信号量本质是一个计数器,那么我可以使用一个全局变量来当做信号量?

答案是否定的

  1. 全局不能被所有进程看到,只有父子进程或其他亲缘关系的进程可以看到,但是会发生写时拷贝,各玩各的,非亲缘关系的进程更不用说了
  2. 对一个变量的++/--操作是非原子的

非原子是什么意思?

转变到汇编语句实际上是有超过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语言实现的多态!

本章完~

相关推荐
matlab的学徒13 分钟前
Web与Nginx网站服务(改)
linux·运维·前端·nginx·tomcat
Insist75318 分钟前
prometheus安装部署与alertmanager邮箱告警
linux·运维·grafana·prometheus
BAGAE1 小时前
MODBUS 通信协议详细介绍
linux·嵌入式硬件·物联网·硬件架构·iot·嵌入式实时数据库·rtdbs
灿烂阳光g1 小时前
SELinux 策略文件编写
android·linux
xqlily1 小时前
Linux操作系统之Ubuntu
linux·运维·ubuntu
阿部多瑞 ABU1 小时前
《基于国产Linux的机房终端安全重构方案》
linux·安全
倔强的石头1062 小时前
【Linux指南】Makefile入门:从概念到基础语法
linux·运维·服务器
ajassi20002 小时前
linux C 语言开发 (七) 文件 IO 和标准 IO
linux·运维·服务器
程序猿编码2 小时前
基于 Linux 内核模块的字符设备 FIFO 驱动设计与实现解析(C/C++代码实现)
linux·c语言·c++·内核模块·fifo·字符设备
一只游鱼2 小时前
Zookeeper介绍与部署(Linux)
linux·运维·服务器·zookeeper