Linux 程序设计1:深入浅出 Linux 共享内存

时间:2022-10-30 00:23:32

笔者最近在阅读Aerospike 论文时,发现了Aerospike是利用了Linux 共享内存机制来实现的存储索引快速重建的。这种方式比传统利用索引文件进行快速重启的方式大大提高了效率。(减少了磁盘 i/o,但是缺点是耗费内存,并且服务器一旦重启之后就只能冷重启了~~)而目前笔者工作之中维护的 NoSQL 数据库也是通过同样的机制实现存储索引的快速重建的,工欲善其事,必先利其器。所以笔者花时间调研了一下Linux共享内存的机制,希望对各位有所帮助~~

1.共享内存简介

说到共享内存,有过操作系统学习的童靴应该十分熟悉,往往聊到进程之间通信的4种方式时就能脱口而出(面试最常见的问题之一啊,哈哈哈~~):

  • 共享内存
  • 消息队列
  • 信号量
  • Socket

今天我们的主角是共享内存。如下图所示,所谓的共享内存,就是由多个进程的虚拟内存空间共同地映射到同一段物理内存空间,来实现内存的共享。

Linux 程序设计1:深入浅出 Linux 共享内存

共享内存通常是 ipc 之中效率最高的方式。Linux 之中实现共享内存的方式通常有如下几类:

  • mmap内存共享映射 (通常用于父子进程之间的内存共享,存在一定局限性,后文不表
  • System V的共享内存
  • POSIX共享内存

我们平时讨论主要的共享内存就是后面两者,但是其实无论是 System V 还是 POSIX 形式的共享内存,底层都是基于内存文件系统tmpfs实现的,二者的主要区别是在接口设计上,POSIX旨在提供所有系统都一致的接口,遵循了 Linux 系统之中一切皆为文件的理念。而System V只实现自己的一套内生的IPC逻辑,所以两者在使用上存在一些差异,由于 Aerospike 之中沿用了 System V 的机制,所以笔者后续的介绍也以 System V 的共享内存来展开。

共享内存虽然给了多进程通信的效率带来了质的飞跃,但是存在的问题也很明显:每一个参与使用共享内存的进程,都可以读取写入数据,这自然而然带来了内存空间等竞争的问题。虽然这里可以通过类似于管道的机制来单向通信来规避竞争的问题,但是额外引入的复杂度和内存占用同样也是问题)所以这里我们也可以反思共享内存真的是用来进程间通信的吗?笔者这里反而是这样认为的:通过通信来共享内存,而不是通过共享内存来通信

2.共享内存的设置与查看

使用共享内存,需要在系统层面进行一些设置。这章需要介绍一些共享内存相关的设置,在 Linux 系统之中和共享内存有关的文件有:

  /proc/sys/kernel/shmmni:限制整个系统可创建共享内存段个数。

  /proc/sys/kernel/shmall: 限制系统用在共享内存上的内存的页数。

  /proc/sys/kernel/shmmax:限制一个共享内存段的最大长度,字节为单位。

在使用共享内存时,我们可以修改上述文件来满足我们的设置需求。这里要注意的是,上述的配置文件是临时性的,重启之后就失效了。如果需要永久性设置这些参数,可以修改/etc/sysctl.conf来完成共享内存的设置。

共享内存本质上是对内存空间的使用,同时也是 ipc 的方式之一,所以我们可以使用对应的 Linux 命令来查看对应共享内存的使用:

free 可以显示系统的内存占用,共享内存的内存占用会归类在 shared,buffer/cache

Linux 程序设计1:深入浅出 Linux 共享内存

而更为详尽的共享内存的数据,可以通过ipcs -m的命令来进行展示。

Linux 程序设计1:深入浅出 Linux 共享内存

这里简单介绍一下,共享内存各个列所代表的含义:

  • key:共享内存的key,后文会通过程序来解释 key 的含义。
  • shmil:共享内存的编号。
  • owner:创建的共享内存的用户。
  • perms:共享内存的权限,基于用户的。
  • bytes:共享内存的大小。
  • nattch:连接到共享内存的进程数。
  • status:共享内存的状态,显示“dest”表示共享内存段已经被删除,但是还有别的引用,共享内存是通过引用计数的方式来决定生命周期,一旦程序应用内存地址的计数为0,操作系统会回收对应的内存资源。

在这里如果需要清理对应的共享内存,可以借助命令ipcrm -m [shmid]来回收对应的内存空间。

3.共享内存的使用

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

int shmctl(int shmid, int cmd, struct shmid_ds *buf);

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

int shmdt(const void *shmaddr);

extern key_t ftok (const char *__pathname, int __proj_id)

万事俱备,现在我们要来介绍一下如何在对应的代码之中使用共享内存,主要涉及上述五个函数,我们通过一个简单的 demo 来介绍这些函数:

int shmget(key_t key, size_t size, int shmflg)是申请共享内存的函数,这里需要理解的是key这个参数,它本身是一个 int 类型,这个 key_t参数是通过key_t ftok (const char *__pathname, int __proj_id)产生的,这里的pathname指的是一个固定的路径,proj_id则表示对应项目的 id。所以在一个操作系统内,如何让两个不相关(没有父子关系)的进程可以共享一个内存段呢?Bingo!就是通过这个 key_t类型让所有的进程都唯一映射到对应内存空间,这里就是通过对应的文件路径项目 id来产生对应的key

所以说,在一个使用到共享内存的程序之中,需要程序设定一个文件路径和一个项目的proj_id,来获取系统之中确定一段共享内存的key。这里需要注意的是ftok需要指定一个存在并且进程可以访问的pathname路径。因为 ftok使用的是指定文件的inode编号。所以,用了不同的文件名同样可能得到相同的key,因为可以通过硬链接的方式让不同的文件名指向相同 inode 编号文件。

    key_t shm_key;

    proj_id = 111;

    if ((shm_key = ftok("/home/happen", proj_id)) == -1) {
exit(1);
} shm_id = shmget(shm_key, sizeof(int), IPC_CREAT|IPC_EXCL|0600);
if (shm_id < 0) {
exit(1);
}

ok,获取了共享内存之后,我们需要将这部分共享内存的地址映射到当前进程的内存空间之上,需要借助这个函数void *shmat(int shmid, const void *shmaddr, int shmflg)返回对应进程内存空间的指针,来对这部分内存进行操作。

   shm_p = (int *)shmat(shm_id, NULL, 0);
if ((void *)shm_p == (void *)-1) {
exit(1);
}

这里可以用过shmflg来设定对应内存空间的读写权限,这里我们取的是0,代表对应的空间有读写权限。SHM_RDONLY可以设置为只读权限。之后我们就可以对对应的内存空间进行操作了:

    *shm_p = 100;

    if (shmdt(shm_p) < 0) {
perror("shmdt()");
exit(1);
} if (shmctl(shm_id, IPC_RMID, NULL) < 0) {
perror("shmctl()");
exit(1);
} return 0;

在使用完共享内存之后,需要使用int shmdt(const void *shmaddr)解除内存空间的映射,否则虚拟内存地址的泄漏,导致没有可用地址可用。shmdt仅仅只是解除共享内存空间和进程地址的映射,而想要删除一个共享内存需要使用int shmctl(int shmid, int cmd, struct shmid_ds *buf)函数进行处理同时也可以在命令行中使用第二小节的ipcrm命令来删除指定的共享内存。在这里必须强调的是,如果没有显式用shmctl或ipcrm命令删除的话,那么对应的共享内存将一直保留直到系统被关闭。

4.小结

到此为止,笔者展开聊了聊 Linux 共享内存的作用,并且对如何操作共享内存进行了介绍,同时希望大家能够在实际开发工作之后能够很好的掌握共享内存这个「利器」,让开发工作事倍功半~~

Linux 程序设计1:深入浅出 Linux 共享内存的更多相关文章

  1. 【网络编程基础】Linux下进程通信方式(共享内存,管道,消息队列,Socket)

    在网络课程中,有讲到Socket编程,对于tcp讲解的环节,为了加深理解,自己写了Linux下进程Socket通信,在学习的过程中,又接触到了其它的几种方式.记录一下. 管道通信(匿名,有名) 管道通 ...

  2. linux下的进程间通信之共享内存

    概念:这种机制允许两个或多个进程通过把公共数据结构放入一个共享内存区来访问它们.如果进程要访问这种数据结构所在的共享内存区,就必须在自己的地址空间中增加一个新线性区,新线性区映射与这个共享内存区相关的 ...

  3. Linux IPC实践&lpar;10&rpar; --Posix共享内存

    1. 创建/获取一个共享内存 #include <sys/mman.h> #include <sys/stat.h> /* For mode constants */ #inc ...

  4. linux c编程:Posix共享内存区

    Posix共享内存区:共享内存是最快的可用IPC形式.它允许多个不相关(无亲缘关系)的进程去访问同一部分逻辑内存.如果需要在两个进程之间传输数据,共享内存将是一种效率极高的解决方案.一旦这样的内存区映 ...

  5. 撸代码--linux进程通信(基于共享内存)

    1.实现亲缘关系进程的通信,父写子读 思路分析:1)首先我们须要创建一个共享内存. 2)父子进程的创建要用到fork函数.fork函数创建后,两个进程分别独立的执行. 3)父进程完毕写的内容.同一时候 ...

  6. Linux系统编程——进程间通信:共享内存

    概述 url=MdyPihmS_tWLwgWL5CMzaTrwDFHu6euAJJUAjKvlzbJmRw7RfhmkBWwAloo7Y65hLY-kQdHsbqWYP2wc2fk8yq"& ...

  7. linux网络编程之posix共享内存

    今天继续研究posix IPC对象,这次主要是学习一下posix共享内存的使用方法,下面开始: 下面编写程序来创建一个共享内存: 编译运行: 那posix的共享内存存放在哪里呢?上节中学的posix的 ...

  8. linux 两个进程通过 共享内存 通信例子

    例子1:两个进程通过共享内存通信,一个进程向共享内存中写入数据,另一个进程从共享内存中读出数据 文件1 创建进程1,实现功能,打印共享内存中的数据 #include <stdio.h> # ...

  9. 【Linux程序设计】之Linux库函数的使用,多文件程序开发,静态与共享函数

    这个系列的博客贴的都是我大二的时候学习Linux系统高级编程时的一些实验程序,都挺简单的.贴出来纯粹是聊胜于无. 实验题目:Linux基础程序设计综合实验 实验目的:熟悉并掌握Linux库函数的使用, ...

  10. Linux mmap 要主动释放共享内存

    #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/sta ...

随机推荐

  1. 关于dll

    今日看到一个不带dllmain的dll,忽然觉得有点奇怪,然后查了一下,原来dll还可以不需要dllmain,甚至可以自己定义入口 先mark以下的资料,有空再总结一下...同时dll劫持,有必要亲身 ...

  2. 设计模式学习之建造者模式(Builder&comma;创建型模式)(6)

    假如我们需要建造一个房子,并且我们也不知道如何去建造房子,所以就去找别人帮我们造房子 第一步: 新建一个房子类House,里面有房子该有的属性,我们去找房子建造者接口HouseBuilder,我们要建 ...

  3. iOS8中用UIVisualEffectView实现高斯模糊视图&lpar;毛玻璃效果&rpar;

    UIBlurEffect *beffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleDark]; UIVisualEffectView *vi ...

  4. js 点击按钮显示下拉菜单

    <li> <a id = "rank" onclick="showGroup()"></a></li><l ...

  5. percona-toolkit系列之介绍和安装&lpar;mysql复制工具&rpar;

    percona-toolkit使用教程(一) 一.percona-toolkit简介 percona-toolkit是一组高级命令行工具的集合,用来执行各种通过手工执行非常复杂和麻烦的mysql和系统 ...

  6. 【贪心】bzoj 3709&colon;&lbrack;PA2014&rsqb;Bohater

    3709: [PA2014]Bohater Time Limit: 5 Sec  Memory Limit: 128 MBSec  Special JudgeSubmit: 653  Solved:  ...

  7. iOS---There was an internal API error 错误

    There was an internal API error. 错误原因:把Product Name作为程序名称,程序名称错乱 解决方法:检查Product Name, 不要包含中文以及特殊字符.在 ...

  8. 10&period;3-uC&sol;OS-III内部任务管理(TCB)

    任务控制块 TCB 1.任务控制块是被uC/OS-III用于维护任务的一个结构体.每个任务都必须有自己的 TCB. uC/OS-III 在 RAM 中分配 TCB.当调用uC/OS-III提供的与任务 ...

  9. C&num;3&period;0新特性&colon;隐式类型、扩展方法、自动实现属性,对象&sol;集合初始值设定、匿名类型、Lambda,Linq,表达式树、可选参数与命名参数

    一.隐式类型var 从 Visual C# 3.0 开始,在方法范围中声明的变量可以具有隐式类型var.隐式类型可以替代任何类型,编译器自动推断类型. 1.var类型的局部变量必须赋予初始值,包括匿名 ...

  10. 转 关于window10安装jdk,配置环境变量,javac不是内部或外部命令,也不是可运行的程序 或批处理文件的细节问题。

    今日拿到一台新的window10笔记本电脑,非常熟练的安装了JDK(因为在学校经常给同学安装JDK - -)但是发现java java -version命令都可以使用,唯独javac命令出现不是内部或 ...