【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语言实现的多态!

本章完~

相关推荐
JunLan~44 分钟前
Rocky Linux 系统安装/部署 Docker
linux·docker·容器
方竞2 小时前
Linux空口抓包方法
linux·空口抓包
sun0077003 小时前
ubuntu dpkg 删除安装包
运维·服务器·ubuntu
海岛日记3 小时前
centos一键卸载docker脚本
linux·docker·centos
oi773 小时前
使用itextpdf进行pdf模版填充中文文本时部分字不显示问题
java·服务器
AttackingLin3 小时前
2024强网杯--babyheap house of apple2解法
linux·开发语言·python
吃肉不能购4 小时前
Label-studio-ml-backend 和YOLOV8 YOLO11自动化标注,目标检测,实例分割,图像分类,关键点估计,视频跟踪
运维·yolo·自动化
学Linux的语莫5 小时前
Ansible使用简介和基础使用
linux·运维·服务器·nginx·云计算·ansible