目录
- 一、前言
- 二、共享内存原理
- 三、代码
- 1、系统调用接口
- 2、创建共享内存
- 3、将进程挂接到共享内存
- 4、去关联
- 5、释放共享内存
- 6、两个进程通过共享内存通信
- 四、共享内存特点
- 五、共享内存的属性
- 总结
一、前言
我们之前提到过,进程通信是让两个不同的进程看到同一份资源,所以我们给大家讲解了匿名管道和命名管道。
今天我们还有一种方式两个不同的进程看到同一份资源。
那就是共享内存!
二、共享内存原理
我们先来看这张图,我们在物理内存创建一个共享区,我们再让两个不同的进程通过页表分别映射到对应的共享区里,然后返回虚拟地址的起始地址。
这就是共享内存,我们总结分为三步
- 申请内存。
- 将该内存挂接到进程地址空间的共享区。
- 返回虚拟地址首地址。
以上我们就创建好共享内存了,那我们该如何释放共享内存呢?
去关联,我们通过www.devze.com系统调用编程客栈释放共享内存,这个系统调用由操作系统提供,并且进程调用系统调用是由操作系统操作。
我们可能会有很多个共享内存,那操作系统要不要管理所有的共享内存呢?
- 当然要,还是六个字——“先描述,后组织”。
- 由内核结构体描述共享内存。
接下来我们通过写一些代码了解该原理,在写代码之前我们要先了解一些接口。
三、代码
1、系统调用接口
man shmget int shmget(key_t key, size_t size, int shmflg);
我们先把这个key放一放最后再讲
这个size_t size是创建共享内存的大小,单位是字节。
shmflg我们知道,我们这个共享内存创建出来了以后,就不需要再次创建了,只需要获取即可
所以就注定了我们需要如何创建,如何获取。如果需要关注的选项就是下面两个。
- IPC_CREAT(单独使用)的意思是创建一个共享内存,如果这个共享内存存在,直接获取,如果不存在就创建并返回他
- IPC_CREAT | IPC_EXCL(两个一起使用):如果申请的共享额你存不存在,就创建,如果存在就出错返回。它可以确保如果我们申请成功了,这个共享内存一定是一个新的
- IPC_EXCL不单独使用
对于它的返回值
如果成功,他会返回一个共享内存的标识,如果失败,返回-1 (问题二)
这里我想问一下大家?我们怎么判断两个进程是不是看到同一份资源呢?
这就跟key值有关了。
只要两个进程拿到同一个key,就能看到同一份共享内存。
因为它在内核中具有唯一性,能够让不同的进程进行唯一性标识。
第一个进程可以通过key创建共享内存,第二个进程只需要通过拿着同一个key就可以和第一个进程看到同一个共享内存!
那么问题来了,为什么这个key具有唯一性呢?
这里先给大家介绍另一个接口
man ftok key_t ftok(const char *pathname, int proj_id);
其实这是一套算法,把pathname和proj_id进行数值运算,最后得到这个key值,返回这个key。
pathname和proj_id是由用户自由指定的,最后形成这个key。
因为同一个函数中,使用同一个参数可以产生同一个key。我们这就可以确保两个进程具有一样的key了,这就是为什么key具有唯一性。
那为什么key不由操作系统创建呢?而是由用户
因为操作系统并不知道是哪两个进程进行通信,哪几个进程通信由用户决定!
创建好的共享内存中,key在哪呢?
key在共享内存的描述对象中。
这个接下来在写代码的时候给大家具体描述
2、创建共享内存
我们先创建一个共享内存
comm.hpp(这里我们直接用了上节课讲的日志系统)
#include "log.hpp" #include <sys/types.h> #include <sys/ipc.h> #include <sys/ipc.h> #include <sys/shm.h> #include <cstring> const string pathname ="/home/cyp"; const int proj_id = 0x6667; const int size = 4096; Log log; key_t Getkey() { key_t key = ftok(pathname.c_str(),proj_id); if(key < 0) { log.logmessage(Fatal,"ftok error:%s",strerror(errno)); exit(1); } log.logmessage(Info,"key creat success:%d",key); return key; } int GetShareMeHeler(int flag) { key_t key=Getkey(); int shmid = shmget(key,size,flag); if(shmid==-1) { log.logmessage(Fatal,"swww.devze.comhmget error:%s",strerror(errno)); exit(2); } log.logmessage(Info,"creat success:%d",shmid); return shmid; }
processa.cc
#include "comm.hpp" int main() { int shmid = GetShareMeHeler(IPC_CREAT|IPC_EXCL); log.logmessage(Info,"process quit.."); return 0; }
结果如下
这里我们用ipcs -m能查看共享内存资源,我们能看到共享内存被创建出来了。
key VS shmid
- key是操作系统内标定的唯一性。
- shmid只有在进程内表示资源的唯一性。
从上述运行结果看,进程退出了,我们用该命令还能查看到我们在该进程创建的共享内存。
结论 :共享内存的生命周期是随内核的,用户不主动关闭共享内存,共享内存会一直存在。
如果我们想删掉共享内存的话,我们可以用这个指令
iprm -m 删除共享内存资源,后面接着共享内存的标识符
查看共享内存,有几个我们不认识,perms是权Tvbgw限,它其实也是一个文件,我们可以为他设置对应的权限。nattch是关联的意思,意思是当前有几个进程与这块共享内存是关联的
那么我们该怎么设置权限呢?
接下来我们创建共享内存接口,一个是创建,一个是获取。
3、将进程挂接到共享内存
这里我们给大家介绍一个接口
man shmdt void *shmat(int shmid, const void *shmaddr, int shmflg);
- 第一个参数好说,我们知道就是我们刚刚的共享内存的shmid
- 第二个参数shmaddr,代表的含义是我们想将共享内存挂接到哪个位置。即共享区的哪个位置,我们一般可以设置为nullptr,让系统去决定挂到哪里去了
- 第三个参数就是该共享内存的权限,比如只读等等,当然我们也可以设置为0,就是按照默认的来
- 这个返回值voihttp://www.devze.comd*比较像我们以前的malloc,malloc得到是一块虚拟地址,只有当读写的时候才发生缺页中断,才申请内存
我们写一份代码将进程挂接到该共享内存上。
结果如下:
4、去关联
这里给大家再介绍一个接口
man shmdt int shmdt(const void *shmaddr);
这里的shmaddr是我们在挂接的时候返回强转的变量。
这个接口是比较类似于之前free接口的
那么我们这个是怎么知道我们要释放多少呢?
所以这里一定隐藏着我们没有看到的数据。malloc也是一样的
malloc其实会比我们要申请的内存多一点点的。用于存储一些我们看不到的数据。所以malloc其实更适合用于申请大空间,因为这样影响比较小。
我们一般把这些多申请的空间叫做cookie。
这个接口的返回值是成功为0,失败为-1
5、释放共享内存
如果我们每次运行这个进程的时候,总会报错,这个共享内存已经存在,那么我们有没有什么办法能让这个共享内存随着我们的进程一起释放掉呢?
man shmctl int shmctl(int shmid, int cmd, struct shmid_ds *buf);
- 第一个参数我们还是比较清楚的就是共享内存的id
- 第二个参数是我们控制这个共享内存的方法
我们目前只关心这个,它的作用是将共享内存标记为被删除的。而我们删除的时候是不关心它的属性的,所以属性直接设置为nullptr
对于返回值,成功为0,失败为-1
- 第三个参数就是这样一个结构体
这些就是一些属性。这个结构体里面的属性是我们内核当中管理共享内存的一个子集。也就是可以获取共享内存里面的属性
6、两个进程通过共享内存通信
- processa进程接收信息
- processb进程发送消息
shmaddr指定共享内存连接到进程地址空间中的哪个位置。如果设置为NULL,系统会自动选择一个合适的(随机的)地址。
我们之前强转了shmaddr返回了它的地址,我们就能通过它的地址进行通信。
结果如下:
- 它不像管道,没有数据写入,读端就会堵塞。
- 共享内存不管写不写入,我会一直读。
当写端进程执行。
这里我们存在一个问题,当我们又写入又读,不会发生错乱错误吗?
会,所以我们要用管道去维护它。
- 在我们的头文件里增加一个类
- 在processa里增加管道代码
- processb增加向管道写入代码
运行结果如下:
- 写入端还没写,读取端阻塞
- 通信成功
四、共享内存特点
- 共享内存没有同步互斥的保护机制
- 共享内存是随意进程通信中,速度最快的,因为拷贝少
- 共享内存内部数据,由用户自己维护
五、共享内存的属性
如下所示,是我们前面所提及的共享内存的一些属性
在第一个结构体中,最重要的字段是第一个shm_perm,这个还是一个结构体,也就是下面的这个。其他的字段后面都有解释
在这个第二个结构体中,第一个就是key。所以我们应用层中生成的key,最后一定会被写入到内核中。里面的这个mode就是共享内存的权限问题
我们可以直接去看看这些属性
先看看shmctrl接口的控制方法
我们创建了共享内存属性的结构体
运行结果如下:
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程客栈(www.devze.com)。
精彩评论