匿名管道和命名管道都是基于文件的进程间通信,SystemV方案是在OS层面专门为进程间通信设计的一个方案,然后通过系统调用(system call)给用户提供通信接口
SystemV方案包含三种:共享内存、消息队列、信号量
下面要说的就是共享内存的创建和释放
目录
一、什么是共享内存??
1、共享内存的定义
由于进程通信的本质是要让两个不同的进程看到同一份资源,我们可以在物理内存上开辟一块空间,这块空间被称为共享内存,然后让这两个进程通过某种方式都能访问到这块内存,这样的话,两个进程之间就可以通信了
2、共享内存的特点
第一,和创建进程类似,进程被创建的时候,会被分配一个pid来标识这个进程的唯一性,同时也方便OS管理这些进程,因此共享内存在被创建的时候,会被分配一个“ID”来标识唯一性
第二,共享内存可以允许存在多个,为了区分这些共享内存,我们上面引入了“ID”的概念,但是要如何让两个进程连上同一个共享内存呢??
就好比,我要和人solo(通信),我创建了一个房间(共享内存),这个房间就有了房间号(共享内存的ID),是个人都能进这个房间,根本没法通信,所以我们要设置房间密码。因此为了通信,我们需要两样东西,一个是房间号,一个是房间密码
二、使用共享内存的准备和收尾工作
当我们需要使用共享内存时,我们要做的准备工作是:
— 通过某种调用,在内存中开辟一块空间(shmget)
— 通过某种调用,让两个进程挂接到这个新开辟的空间上(shmat)
当我们不需要使用共享内存时,我们需要做的收尾工作是:
— 断开进程和共享内存之间的关联(shmdt)
— 释放共享内存(shmctl)
每一步都对应着一个系统调用接口,下面要说的就是这四个系统调用接口
三、shmget函数(shared memory get)
这是共享内存的创建函数,调用以后会向内核申请内存,但是需要注意的是,共享内存是以“页”为单位的,一页是4KB = 4096bytes,所以一般建议申请共享内存的大小是4KB的整数倍!
如果申请了4097个字节,那么OS会给你4096*2个字节的空间
1、 参数解析
下面是shmget函数的声明以及要用到的头文件
(1) 第一个参数 key
第一个参数是唯一标识编号,也就是前面说到的房间密码,这个是由用户自己设置的,一般是通过ftok函数来,也可以自己随意设置一个整数
ftok函数的第一个参数:路径名
ftok函数的第二个参数:项目ID
关于这个函数无需想的太复杂,简单来说就是,从路径名中取出一部分,然后再从ID中取出一部分,最后再把两部分组合一下形成一个整数,我们就把这个整数当作“房间密码”
注意:ftok被不同进程调用,只要路径名和ID是一样的,生成的整数就是一样的
(2) 第二个参数 size
第二个参数是开辟共享内存的大小,一般建议是4KB的整数倍(原因详见第三部分开头)
(3) 第三个参数 shmget
第三个参数是创建共享内存的方式以及设置权限
类似于write函数的第二个参数,属于位运算输入
IPC_CREAT:可以单独使用,如果共享内存不存在,则重新开辟,函数返回值是新开辟的共享内
存的ID;如果已经存在,则沿用已有的共享内存,函数返回值是已有的共享内存的
ID
IPC_EXCL:无法单独使用,要配合IPC_CREAT使用,即 IPC_CREAT | IPC_EXCL
IPC_CREAT | IPC_EXCL:如果共享内存不存在,则重新开辟,函数返回值是新开辟的共享内
存的ID;如果已经存在,则报错
IPC_CREAT | IPC_EXCL | 0664:开辟共享内存的同时,设置共享内存的访问权限
2、返回值解析
如果共享内存开辟成功,则返回共享内存的ID(即房间号);否则返回 -1.
3、使用shmget函数
下面我们在使用shmget函数的同时要验证一下上面红色的注意事项
和之前的命名管道通信一样,继续使用server.c 和 client.c 来通信,公共数据放在common.h
现在我们可以来看一下测试的结果,我们会发现服务端和客户端使用的密码是一样的,这也就验证了上面红色部分
我们在命令行输入下面的指令查看创建好的共享内存
ipcs #显示SystemV方案的全部,即共享内存、消息队列、信号量
ipcs -m # -m 显示共享内存
ipcs -q # -q 显示共享消息队列
ipcs -s # -s 显示共享信号量
4、共享内存的生命周期
从上面的测试结果,我们会发现,进程退出以后,共享内存依然存在,换到我们之前的匿名管道,管道的生命周期是随进程的,进程退出以后,管道也就不存在了
共享内存不属于任何进程,不归进程管理,除非调用释放函数,否则要不要释放是OS决定的,因此共享内存的生命周期是随内核的!!
释放的方式有三种:
- 关机
- 调用释放共享内存的函数 shmctl
- 命令行释放
ipcrm -m 1 # ipcrm -m 共享内存的id
四、shmctl(shared memory control)
这是控制共享内存的函数,目前我们只需要知道,怎么通过这个函数释放共享内存即可
1、参数解析
下面是共享内存控制函数的函数声明及需要的头文件
第一个参数是 共享内存的id
第二个参数是 控制指令(我们选择删除共享内存的那个指令)
第三个参数是 shmid所指向的共享内存的地址,既然空间被释放以后,地址就赋值为null
2、返回值解析
释放成功则返回 0;释放失败返回 -1
3、使用shmctl 函数
我们在原本的server.c中加入下面的内容
我们发现,共享内存最终被释放了
五、为什么共享内存的ID是0,1,2 ... ?
我们观察上面的图,会发现shmid是 0,1,2这样的整数,和前面的文件一样,很容易联想到数组的下标。这确实是数组的下标,但是这个数组中存放着什么呢??
最开始也说了,共享内存以结构体的形式被管理着,消息队列和信号量也是如此,下面依次是共享内存、消息队列、信号量的结构体。
首先需要明确的是,上面这些共享内存的结构体shmid_ds、消息队列的结构体msgid_ds、信号量的结构体semid_ds都不是关键,关键的是这个ipc_perm
其次,每种资源都嵌套了ipc_perm结构体,我们只要知道了 各自ipc_perm的地址,就能通过C++切片寻找到每种资源的地址
因此,对每种资源结构体的管理就变成了对ipc_perm的管理 ——》 这里的shmid就是这个存储ipc_perm结构体地址的数组下标