笔者最近在阅读Aerospike 论文时,发现了Aerospike是利用了Linux 共享内存机制来实现的存储索引快速重建的。这种方式比传统利用索引文件进行快速重启的方式大大提升了效率。(减小了磁盘 i/o,可是缺点是耗费内存,而且服务器一旦重启以后就只能冷重启了~~)而目前笔者工做之中维护的 NoSQL 数据库也是经过一样的机制实现存储索引的快速重建的,工欲善其事,必先利其器。因此笔者花时间调研了一下Linux共享内存的机制,但愿对各位有所帮助~~node
说到共享内存,有过操做系统学习的童靴应该十分熟悉,每每聊到进程之间通讯的4种方式时就能脱口而出(面试最多见的问题之一啊,哈哈哈~~):面试
今天咱们的主角是共享内存。以下图所示,所谓的共享内存,就是由多个进程的虚拟内存空间共同地映射到同一段物理内存空间,来实现内存的共享。
数据库
共享内存一般是 ipc 之中效率最高的方式。Linux 之中实现共享内存的方式一般有以下几类:服务器
咱们平时讨论主要的共享内存就是后面二者,可是其实不管是 System V 仍是 POSIX 形式的共享内存,底层都是基于内存文件系统tmpfs实现的,两者的主要区别是在接口设计上,POSIX旨在提供全部系统都一致的接口,遵循了 Linux 系统之中一切皆为文件的理念。而System V只实现本身的一套内生的IPC逻辑,因此二者在使用上存在一些差别,因为 Aerospike 之中沿用了 System V 的机制,因此笔者后续的介绍也以 System V 的共享内存来展开。app
共享内存虽然给了多进程通讯的效率带来了质的飞跃,可是存在的问题也很明显:每个参与使用共享内存的进程,均可以读取写入数据,这天然而然带来了内存空间等竞争的问题。 (虽然这里能够经过相似于管道的机制来单向通讯来规避竞争的问题,可是额外引入的复杂度和内存占用一样也是问题)因此这里咱们也能够反思共享内存真的是用来进程间通讯的吗?笔者这里反而是这样认为的:经过通讯来共享内存,而不是经过共享内存来通讯。函数
使用共享内存,须要在系统层面进行一些设置。这章须要介绍一些共享内存相关的设置,在 Linux 系统之中和共享内存有关的文件有:学习
/proc/sys/kernel/shmmni:限制整个系统可建立共享内存段个数。 /proc/sys/kernel/shmall: 限制系统用在共享内存上的内存的页数。 /proc/sys/kernel/shmmax:限制一个共享内存段的最大长度,字节为单位。
在使用共享内存时,咱们能够修改上述文件来知足咱们的设置需求。这里要注意的是,上述的配置文件是临时性的,重启以后就失效了。若是须要永久性设置这些参数,能够修改/etc/sysctl.conf来完成共享内存的设置。操作系统
共享内存本质上是对内存空间的使用,同时也是 ipc 的方式之一,因此咱们可使用对应的 Linux 命令来查看对应共享内存的使用:命令行
free 能够显示系统的内存占用,共享内存的内存占用会归类在 shared,buffer/cache列
而更为详尽的共享内存的数据,能够经过ipcs -m的命令来进行展现。
这里简单介绍一下,共享内存各个列所表明的含义:设计
在这里若是须要清理对应的共享内存,能够借助命令ipcrm -m [shmid]来回收对应的内存空间。
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命令删除的话,那么对应的共享内存将一直保留直到系统被关闭。
到此为止,笔者展开聊了聊 Linux 共享内存的做用,而且对如何操做共享内存进行了介绍,同时但愿你们可以在实际开发工做以后可以很好的掌握共享内存这个「利器」,让开发工做事倍功半~~