Linux

关注公众号 jb51net

关闭
首页 > 网站技巧 > 服务器 > Linux > linux共享内存

Linux共享内存原理及分析

作者:每天敲200行代码

文章介绍了共享内存作为进程通信方式,通过物理内存映射实现进程间共享资源,其核心步骤为申请内存、挂接虚拟地址、去关联释放,需用户指定key值确保唯一性,操作系统管理其生命周期,共享内存速度快但需自行维护同步机制

一、前言

我们之前提到过,进程通信是让两个不同的进程看到同一份资源,所以我们给大家讲解了匿名管道和命名管道。

今天我们还有一种方式两个不同的进程看到同一份资源。

那就是共享内存

二、共享内存原理

我们先来看这张图,我们在物理内存创建一个共享区,我们再让两个不同的进程通过页表分别映射到对应的共享区里,然后返回虚拟地址的起始地址。

这就是共享内存,我们总结分为三步

以上我们就创建好共享内存了,那我们该如何释放共享内存呢?

去关联,我们通过系统调用释放共享内存,这个系统调用由操作系统提供,并且进程调用系统调用是由操作系统操作。

我们可能会有很多个共享内存,那操作系统要不要管理所有的共享内存呢?

接下来我们通过写一些代码了解该原理,在写代码之前我们要先了解一些接口。

三、代码

1、系统调用接口

man shmget  
int shmget(key_t key, size_t size, int shmflg);

我们先把这个key放一放最后再讲

这个size_t size是创建共享内存的大小,单位是字节。 

shmflg我们知道,我们这个共享内存创建出来了以后,就不需要再次创建了,只需要获取即可

所以就注定了我们需要如何创建,如何获取。如果需要关注的选项就是下面两个。

对于它的返回值

如果成功,他会返回一个共享内存的标识,如果失败,返回-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,"shmget 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

从上述运行结果看,进程退出了,我们用该命令还能查看到我们在该进程创建的共享内存。

结论 :共享内存的生命周期是随内核的,用户不主动关闭共享内存,共享内存会一直存在。

如果我们想删掉共享内存的话,我们可以用这个指令

iprm -m 删除共享内存资源,后面接着共享内存的标识符

查看共享内存,有几个我们不认识,perms是权限,它其实也是一个文件,我们可以为他设置对应的权限。nattch是关联的意思,意思是当前有几个进程与这块共享内存是关联的

那么我们该怎么设置权限呢?

接下来我们创建共享内存接口,一个是创建,一个是获取。

3、将进程挂接到共享内存

这里我们给大家介绍一个接口

man shmdt
void *shmat(int shmid, const void *shmaddr, int shmflg);

我们写一份代码将进程挂接到该共享内存上。

结果如下:

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);

我们目前只关心这个,它的作用是将共享内存标记为被删除的。而我们删除的时候是不关心它的属性的,所以属性直接设置为nullptr

对于返回值,成功为0,失败为-1

这些就是一些属性。这个结构体里面的属性是我们内核当中管理共享内存的一个子集。也就是可以获取共享内存里面的属性

6、两个进程通过共享内存通信

 shmaddr指定共享内存连接到进程地址空间中的哪个位置。如果设置为NULL,系统会自动选择一个合适的(随机的)地址。

我们之前强转了shmaddr返回了它的地址,我们就能通过它的地址进行通信。

结果如下:

当写端进程执行。 

这里我们存在一个问题,当我们又写入又读,不会发生错乱错误吗?

会,所以我们要用管道去维护它。

运行结果如下:

 

四、共享内存特点

五、共享内存的属性

如下所示,是我们前面所提及的共享内存的一些属性

在第一个结构体中,最重要的字段是第一个shm_perm,这个还是一个结构体,也就是下面的这个。其他的字段后面都有解释

在这个第二个结构体中,第一个就是key。所以我们应用层中生成的key,最后一定会被写入到内核中。里面的这个mode就是共享内存的权限问题

我们可以直接去看看这些属性

先看看shmctrl接口的控制方法

我们创建了共享内存属性的结构体 

运行结果如下:

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

您可能感兴趣的文章:
阅读全文