转载

shm 共享内存简单学习

作者: tiankonguse | 更新日期:

之前了解cache的时候, 我曾说过:共享内存也仅仅只是一片内存而已. 现在来看看共享内存的基本操作吧.

背景

前段时间, 我了解了共享内存,并写了一篇笔记: cache 的简单认识与思考 .

简单的说就是cache的数据储存在共享内存中, 而这片内存和程序中的内存其实是一样的,只是操作方式不同而已.

我今天花费了几个小时学习了一下共享内存,并简单的封装了一下, 然后为之后的 cache 做准备吧.代码在 github 上

下面我们就来简单的学习一下这些操作方式吧, 这是菜鸟级的记录, 大神直接忽视吧!

下面主要分两部分:

  1. 共享内存的基本操作
  2. 共享内存的实践与应用

基本操作

共享内存大概由四个操作组成: 申请查询内存、程序连接映射内存、程序断开映射内存、管理共享内存.

申请查询内存

shmget 函数用来申请查询内存.

#include <sys/ipc.h> #include <sys/shm.h>  int shmget(key_t key, size_t size, int shmflg); 

可以看到, 这个操作需要三个参数.

  • key 用来唯一确定这片内存, 比如我们申请了三片内存, 我们怎么区分这三片内存呢? 就是依靠这个key.
    如果我们传入0, 那么含义大概就是让系统帮我们申请一片内存. 一般不建议这样做.
  • size 就是我们申请内存的大小
    假设 key 代表的内存不存在, 则我们可以申请 size 大小的内存.
    假设 key 代表的内存已存在, 如果我们的size和存在的size相同, 则可以申请成功, 如果size不同, 则申请失败.
    简单的理解就是 key 和 size 唯一确定一片内存, 但是 key 不能重复.
  • shmflg flag 参数.
    对于linux中的flag参数, 一般都是使用位压缩把一些含义储存在一个参数上的.
    比如 IPC_CREAT 代表内存不存在时创建, 没这个参数时不存在就返回错误啦.
  • 返回值 这个操作会返回一个id, 我们一般称为 shmid. 可以理解为这个内存的内部唯一标识吧

程序连接映射内存

我们申请了共享内存并不代表我们可以直接使用这片内存.

大家都知道, 一个进程只能操作自己范围内的地址空间, 而申请的那片内存就不在自己的管辖范围内.这个时候我们就需要通过一种方法, 把那片内存映射到自己的范围内了.

shmat 函数用于把共享内存映射到进程的地址空间.

#include <sys/types.h> #include <sys/shm.h>  void *shmat(int shmid, const void *shmaddr, int shmflg); 

这个操作同样需要三个参数.

  • shmid : 我们申请内存时, 返回的shmid
  • shmaddr 定共享内存出现在进程内存地址的什么位置,直接指定为NULL让内核自己决定一个合适的地址位置.
  • shmflg flag 参数. 比如 SHM_RDONLY 代表只读.
  • 返回值 映射后进程内的地址指针, 代表内存的头部地址

程序断开映射内存

我们使用完了内存, 可能需要断开映射内存来节省资源.

shmdt 函数可以实现这个功能.

#include <sys/types.h> #include <sys/shm.h>  int shmdt(const void *shmaddr); 

这个操作只需要一个参数,就是这个内存地址的指针.

管理共享内存

管理内存大概分这么三个部分: 查询共享内存的状态, 更新共享内存的状态, 删除这片共享内存.

shmctl 函数可以用来管理共享内存.

#include <sys/ipc.h> #include <sys/shm.h>  int shmctl(int shmid, int cmd, struct shmid_ds *buf); 

这里只来看看 cmd 参数可以使用的值吧.如下

  • IPC_STAT 查询共享内存的状态, 结果会填充在第三个参数 buf 内
  • IPC_SET 更新共享内存的状态, 根据第三个参数来更新
  • IPC_RMID 删除这片共享内存

实践与应用

学完了共享内存的基本知识, 如果不来敲几行代码怎么可以呢?

仔细想想, 共享内存的作用就是用来临时储存数据的, 说的高大上点就是进程间通信的.

于是我是实现了这么几个小程序, 可以加深对共享内存的理解.

父子进程通信

源代码见 github

核心代码如下:

Shm shm; pid = fork(); if (pid == 0) {  char *shmaddr = shm.getAdr();  if (NULL == shmaddr) {   printf("getAdr error. err=%s/n", shm.getLastError());   return -1;  }  snprintf(shmaddr, SIZE, "this is parent, but will print in child !");  printf("father end/n");  return 0; } else if (pid > 0) {  sleep(3);  char *shmaddr = shm.getAdr();  if (NULL == shmaddr) {   printf("getAdr error. err=%s/n", shm.getLastError());   return -1;  }  printf("%s/n", shmaddr);  printf("child end/n");  shm.delShm(); }  

上面代码片段的含义是: 创建一个共享内存类的实例, 这样这个实例在 fork 的时候就会复制给子进程了, 进而可以和父进程共享这个内存了.

在父进程内, 先把内存映射到父进程,然后填充进一些信息.

在子进程内, 先等几秒, 这样就可以保证父进程把信息写进内存了, 然后子进程也把内存映射到子进程, 最后输出内存中的信息.

输出大概如下

tiankonguse:fork $ ./fork  iShmID = 10813468 father end this is parent, but will print in child ! child end 

进程间通信

进程间通信和父子进程通信没什么大的区别.

只要知道共享内存的key, 就可以读写同一片内存了, 进而就可以通信了.

源代码见 github

核心代码如下:

写进程:

Shm shm; Time *p_time; p_time = (Time *) shm.getAdr(); if (NULL == p_time) {  printf("getAdr error. err=%s/n", shm.getLastError());  return -1; } srand(time(NULL)); for (int i = 0; i < TIME_NUM; i++) {  struct timeval start;  gettimeofday(&start, NULL);  (p_time + i)->sec = start.tv_sec;  (p_time + i)->usec = start.tv_usec;  (p_time + i)->val = rand() % 100; }  

读进程:

Shm shm; Time *p_time; p_time = (Time *) shm.getAdr(); if (NULL == p_time) {  printf("getAdr error. err=%s/n", shm.getLastError());  return -1; } for (int i = 0; i < TIME_NUM; i++) {  printf("i=%d sec=%lld usec=%lld val=%lld/n", i, (p_time + i)->sec,    (p_time + i)->usec, (p_time + i)->val); }  

大家可以看到, 这个和上面的父子进程没什么区别.

只是这里储存的不是字符串了, 而是若干个 sizeof(Time) 大小的内存了.

输出大概如下:

tiankonguse:readwrite $ make g++ -O0 -o read read.cpp -I../../../include/  -L../../../lib/ -static -lshm -pthread g++ -o write write.cpp -I../../../include/  -L../../../lib/ -static -lshm -pthread  tiankonguse:readwrite $ ./write  key=50462721 iShmID = 10944523  tiankonguse:readwrite $ ./read  key=50462721 iShmID = 10944523 i=0 sec=1441286802 usec=81659 val=56 i=1 sec=1441286802 usec=81673 val=31 i=2 sec=1441286802 usec=81674 val=57 

尾记

现在我对共享内存的接触还不多, 理解也可能有误, 欢迎大家吐槽.

下一步就开始进入cache 中 hash 的实现了.

正文到此结束
Loading...