Linux 共享内存 详解
一、什么是共享内存区
共享内存区是最快的可用IPC形式。它允许多个不相关的进程去访问同一部分逻辑内存。如果需要在两个运行中的进程之间传输数据,共享内存将是一种效率极高的解决方案。一旦这样的内存区映射到共享它的进程的地址空间,这些进程间数据的传输就不再涉及内核。这样就可以减少系统调用时间,提高程序效率。
共享内存是由IPC为一个进程创建的一个特殊的地址范围,它将出现在进程的地址空间中。其他进程可以把同一段共享内存段“连接到”它们自己的地址空间里去。所有进程都可以访问共享内存中的地址。如果一个进程向这段共享内存写了数据,所做的改动会立刻被有访问同一段共享内存的其他进程看到。
要注意的是共享内存本身没有提供任何同步功能。也就是说,在第一个进程结束对共享内存的写操作之前,并没有什么自动功能能够预防第二个进程开始对它进行读操作。共享内存的访问同步问题必须由程序员负责。可选的同步方式有互斥锁、条件变量、读写锁、纪录锁、信号灯。
在将共享内存前我们要先来介绍下面几个函数。
二、mmap
mmap函数把一个文件或一个Posix共享内存区对象映射到调用进程的地址空间。使用该函数有三个目的:
1.使用普通文件以提供内存映射I/O
2.使用特殊文件以提供匿名内存映射。
3.使用shm_open以提供无亲缘关系进程间的Posix共享内存区。
名称:: |
mmap |
功能: |
把I/O文件映射到一个存储区域中 |
头文件: |
#include <sys/mman.h> |
函数原形: |
void *mmap(void *addr,size_t len,int prot,int flag,int filedes,off_t off); |
参数: |
addr 指向映射存储区的起始地址 len 映射的字节 prot 对映射存储区的保护要求 flag flag标志位 filedes 要被映射文件的描述符 off 要映射字节在文件中的起始偏移量 |
返回值: |
若成功则返回映射区的起始地址,若出错则返回MAP_FAILED |
addr参数用于指定映射存储区的起始地址。通常将其设置为NULL,这表示由系统选择该映射区的起始地址。
filedes指要被映射文件的描述符。在映射该文件到一个地址空间之前,先要打开该文件。len是映射的字节数。
off是要映射字节在文件中的起始偏移量。通常将其设置为0。
prot参数说明对映射存储区的保护要求。可将prot参数指定为PROT_NONE,或者是PROT_READ(映射区可读),PROT_WRITE(映射区可写),PROT_EXEC(映射区可执行)任意组合的按位或,也可以是PROT_NONE(映射区不可访问)。对指定映射存储区的保护要求不能超过文件open模式访问权限。
flag参数影响映射区的多种属性:
MAP_FIXED返回值必须等于addr.因为这不利于可移植性,所以不鼓励使用此标志。
MAP_SHARED这一标志说明了本进程对映射区所进行的存储操作的配置。此标志指定存储操作修改映射文件。
MAP_PRIVATE本标志导致对映射区建立一个该映射文件的一个私有副本。所有后来对该映射区的引用都是引用该副本,而不是原始文件。
要注意的是必须指定MAP_FIXED或MAP_PRIVATE标志其中的一个,指定前者是对存储映射文件本身的一个操作,而后者是对其副本进行操作。
mmap成功返回后,fd参数可以关闭。该操作对于由mmap建立的映射关系没有影响。为从某个进程的地址空间删除一个映射关系,我们调用munmap.
名称:: |
munmap |
功能: |
解除存储映射 |
头文件: |
#include <sys/mman.h> |
函数原形: |
int munmap(caddr_t addr,size_t len); |
参数: |
addr 指向映射存储区的起始地址 len 映射的字节 |
返回值: |
若成功则返回0,若出错则返回-1 |
其中addr参数是由mmap返回的地址,len是映射区的大小。再次访问这些地址导致向调用进程产生一个SIGSEGV信号。
如果被映射区是使用MAP_PRIVATE标志映射的,那么调用进程对它所作的变动都被丢弃掉。
内核的虚存算法保持内存映射文件(一般在硬盘上)与内存映射区(在内存中)的同步(前提它是MAP_SHARED内存区)。这就是说,如果我们修改了内存映射到某个文件的内存区中某个位置的内容,那么内核将在稍后某个时刻相应地更新文件。然而有时候我们希望确信硬盘上的文件内容与内存映射区中的文件内容一致,于是调用msync来执行这种同步。
名称:: |
msync |
功能: |
同步文件到存储器 |
头文件: |
#include <sys/mman.h> |
函数原形: |
int msync(void *addr,size_t len,int flags); |
参数: |
addr 指向映射存储区的起始地址 len 映射的字节 prot flags |
返回值: |
若成功则返回0,若出错则返回-1 |
其中addr和len参数通常指代内存中的整个内存映射区,不过也可以指定该内存区的一个子集。flags参数为MS_ASYNC(执行异步写),MS_SYNC(执行同步写),MS_INVALIDATE(使高速缓存的数据实效)。其中MS_ASYNC和MS_SYNC这两个常值中必须指定一个,但不能都指定。它们的差别是,一旦写操作已由内核排入队列,MS_ASYNC即返回,而MS_SYNC则要等到写操作完成后才返回。如果还指定了MS_INVALIDATE,那么与其最终拷贝不一致的文件数据的所有内存中拷贝都失效。后续的引用将从文件取得数据。
名称:: |
memcpy |
功能: |
复制映射存储区 |
头文件: |
#include <string.h> |
函数原形: |
void *memcpy(void *dest,const void *src,size_t n); |
参数: |
dest 待复制的映射存储区 src 复制后的映射存储区 n 待复制的映射存储区的大小 |
返回值: |
返回dest的首地址 |
memcpy拷贝n个字节从dest到src。
下面就是利用mmap函数影射I/O实现的cp命令。
- #include <unistd.h>
- #include <fcntl.h>
- #include <sys/mman.h>
- #include <sys/stat.h>
- #include <sys/types.h>
- int main(int argc,char *argv[])
- {
- int fdin,fdout;
- void *arc,dst;
- struct stat statbuf;
- if(argc!=3)
- {
- printf(“please input two file!\n”);
- exit(1);
- }
- if((fdin=open(argv[1],O_RDONLY))<0) /*打开原文件*/
- perror(argv[1]);
- if((fdout=open(argv[2],O_RDWR|O_CREAT|O_TRUNC))<0)/*创建并打开目标文件*/
- perror(argv[2]);
- if(fstat(fdin,&statbuf)<0) /*获得文件大小信息*/
- printf(“fstat error”);
- if(lseek(fdout,statbuf.st_size-1,SEEK_SET)==-1)/*初始化输出映射存储区*/
- printf(“lseek error”);
- if(write(fdout,”1”)!=1)
- printf(“write error”);
- if((src=mmap(0,statbuf.st_size,PROT_READ,MAP_SHARED,fdin,0))==MAP_FAILED)
- /*映射原文件到输入的映射存储区*/
- printf(“mmap error);
- if((dst=mmap(0,statbuf.st_size,PROT_READ|PROT_WRITE,MAP_SHARED,fdout,0)) ==MAP_FAILED) /*映射目标文件到输出的映射存储区*/
- printf(“mmap error);
- memcpy(dst,src,statbuf.st_size);/*复制映射存储区*/
- munmap(src,statbuf.st_size); /*解除输入映射*/
- munmap(dst,statbuf.st_size); /*解除输出映射*/
- close(fdin);
- close(fdout);
- }
下面是运行结果:
#cc –o mycp mycp.c
#./mycp test1 test2
三、posix共享内存函数
posix共享内存区涉及两个步骤:
1、指定一个名字参数调用shm_open,以创建一个新的共享内存区对象或打开一个以存在的共享内存区对象。
2、调用mmap把这个共享内存区映射到调用进程的地址空间。传递给shm_open的名字参数随后由希望共享该内存区的任何其他进程使用。
名称:: |
shm_open |
功能: |
打开或创建一个共享内存区 |
头文件: |
#include <sys/mman.h> |
函数原形: |
int shm_open(const char *name,int oflag,mode_t mode); |
参数: |
name 共享内存区的名字 cflag 标志位 mode 权限位 |
返回值: |
成功返回0,出错返回-1 |
oflag参数必须含有O_RDONLY和O_RDWR标志,还可以指定如下标志:O_CREAT,O_EXCL或O_TRUNC.
mode参数指定权限位,它指定O_CREAT标志的前提下使用。
shm_open的返回值是一个整数描述字,它随后用作mmap的第五个参数。
名称:: |
shm_unlink |
功能: |
删除一个共享内存区 |
头文件: |
#include <sys/mman.h> |
函数原形: |
int shm_unlink(const char *name); |
参数: |
name 共享内存区的名字 |
返回值: |
成功返回0,出错返回-1 |
shm_unlink函数删除一个共享内存区对象的名字,删除一个名字仅仅防止后续的open,mq_open或sem_open调用取得成功。
下面是创建一个共享内存区的例子:
- #include <sys/mman.h>
- #include <stdio.h>
- #include <fcntl.h>
- int main(int argc,char **argv)
- {
- int shm_id;
- if(argc!=2)
- {
- printf(“usage:shm_open <pathname>\n”);
- exit(1);
- }
- shm_id=shm_open(argv[1],O_RDWR|O_CREAT,0644);
- printf(“shmid:%d\n”,shm_id);
- shm_unlink(argv[1]);
- }
下面是运行结果,注意编译程序我们要加上“-lrt”参数。
#cc –lrt –o shm_open shm_open.c
#./shm_open test
shm_id:3
四、ftruncate和fstat函数
普通文件或共享内存区对象的大小都可以通过调用ftruncate修改。
名称:: |
ftruncate |
功能: |
调整文件或共享内存区大小 |
头文件: |
#include <unistd.h> |
函数原形: |
int ftruncate(int fd,off_t length); |
参数: |
fd 描述符 length 大小 |
返回值: |
成功返回0,出错返回-1 |
当打开一个已存在的共享内存区对象时,我们可调用fstat来获取有关该对象的信息。
名称:: |
fstat |
功能: |
获得文件或共享内存区的信息 |
头文件: |
#include <unistd.h> #include <sys/types.h> #include <sys/stat.h> |
函数原形: |
int stat(const char *file_name,struct stat *buf); |
参数: |
file_name 文件名 buf stat结构 |
返回值: |
成功返回0,出错返回-1 |
对于普通文件stat结构可以获得12个以上的成员信息,然而当fd指代一个共享内存区对象时,只有四个成员含有信息。
struct stat{
mode_t st_mode;
uid_t st_uid;
gid_t st_gid;
off_t st_size;
};
- #include <unistd.h>
- #include <sys/type.h>
- #include <sys/stat.h>
- #include <sys/types.h>
- #include <fcntl.h>
- #include <sys/mman.h>
- int main(int argc,char **argv)
- {
- int shm_id;
- struct stat buf;
- if(argc!=2)
- {
- printf(“usage:shm_open <pathname>\n”);
- exit(1);
- }
- shm_id=shm_open(argv[1],O_RDWR|O_CREAT,0644);/*创建共享内存*/
- ftruncate(shm_id,100);/*修改共享内存的打开*/
- fstat(shm_id,&buf); /*把共享内存的信息记录到buf中*/
- printf(“uid_t:%d\n”,buf.st_uid); /*共享内存区所有者ID*/
- printf(“git_t:%d\n”,buf.st_gid); /*共享内存区所有者组ID*/
- printf(“size :%d\n”,buf.st_size); /*共享内存区大小*/
- }
下面是运行结果:
#cc –lrt –o shm_show shm_show.c
#./shm_show test
uid_t:0
git_t:0
size:100
五、共享内存区的写入和读出
上面我们介绍了mmap函数,下面我们就可以通过这些函数,把进程映射到共享内存区。
然后我们就可以通过共享内存区进行进程间通信了。
下面是共享内存区写入的例子:
- #include <sys/stat.h>
- #include <fcntl.h>
- #include <sys/mman.h>
- #include <unistd.h>
- int main(int argc,char **argv)
- {
- int shm_id;
- struct stat buf;
- char *ptr;
- if(argc!=2)
- {
- printf(“usage:shm_open <pathname>\n”);
- exit(1);
- }
- shm_id=shm_open(argv[1],O_RDWR|O_CREAT,0644);/*创建共享内存区*/
- ftruncate(shm_id,100);/*修改共享区大小*/
- fstat(shm_id,&buf);
- ptr=mmap(NULL,buf.st_size,PROT_READ|PROT_WRITE,MAP_SHARED,shm_id,0);/*连接共享内存区*/
- strcpy(ptr,”hello linux”);/*写入共享内存区*/
- printf(“%s\n”,ptr);/*读出共享内存区*/
- shm_unlink(argv[1]);/*删除共享内存区*/
- }
下面是运行结果:
#cc –lrt –o shm_write shm_write.c
#./shm_write test
hello linux
六、程序例子
下面是利用pisix共享内存区实现进程间通信的例子:服务器进程读出共享内存区内容,然后清空。客户进程向共享内存区写入数据。直到用户输入“q”程序结束。程序用posix信号量实现互斥。
- /*server.c服务器程序*/
- #include <sys/stat.h>
- #include <fcntl.h>
- #include <sys/mman.h>
- #include <unistd.h>
- #include <semaphore.h>
- int main(int argc,char **argv)
- {
- int shm_id;
- char *ptr;
- sem_t *sem;
- if(argc!=2)
- {
- printf(“usage:shm_open <pathname>\n”);
- exit(1);
- }
- shm_id=shm_open(argv[1],O_RDWR|O_CREAT,0644);/*创建共享内存区*/
- ftruncate(shm_id,100);/*调整共享内存区大小*/
- sem=sem_open(argv[1],O_CREAD,0644,1);/*创建信号量*/
- ptr=mmap(NULL,100,PROT_READ|PROT_WRITE,MAP_SHARED,shm_id,0);/*连接共享内存区*/
- strcpy(ptr,”\0”);
- while(1)
- {
- if((strcmp(ptr,”\0”))==0)/*如果为空,则等待*/
- continue;
- else
- {
- if((strcmp(ptr,”q\n”))==0)/*如果内存为q\n退出循环*/
- break;
- sem_wait(sem);/*申请信号量*/
- printf(“server:%s”,ptr);/*输入共享内存区内容*/
- strcpy(ptr,”\0”);/*清空共享内存区*/
- sem_pose(sem);/*释放信号量*/
- }
- sem_unlink(argv[1]);/*删除信号量*/
- shm_unlink(argv[1]);/*删除共享内存区*/
- }
- }
客户端程序:
- /*user.c 客户端程序*/
- #include <sys/stat.h>
- #include <fcntl.h>
- #include <sys/mman.h>
- #include <unistd.h>
- #include <semaphore.h>
- #include <stdio.h>
- int main(int argc,char **argv)
- {
- int shm_id;
- char *ptr;
- sem_t *sem;
- if(argc!=2)
- {
- printf(“usage:shm_open <pathname>\n”);
- exit(1);
- }
- shm_id=shm_open(argv[1],0);/*打开共享内存区
- sem=sem_open(argv[1],0);/*打开信号量*/
- ptr=mmap(NULL,100,PROT_READ|PROT_WRITE,MAP_SHARED,shm_id,0);/*连接共享内存区*/
- while(1)
- {
- sem_wait(sem);/*申请信号量*/
- fgets(ptr,10,stdin);/*从键盘读入数据到共享内存区*/
- printf(“user:%s”,ptr);
- if((strcmp(ptr,”q\n”))==0)
- exit(0);
- sem_pose(sem);/*释放信号量*/
- sleep(1);
- }
- exit(0);
- }
#cc –lrt –o server server.c
#cc –lrt –o user user.c
#./server test&
#./user test
输入:abc
user:abc
server:abc
输入:123
user:123
server:123
输入:q
user:q
Linux 共享内存 详解的更多相关文章
- Linux 共享内存详解一
共享内存段被多个进程附加的时候,如果不是所有进程都已经调用shmdt,那么删除该共享内存段时,会出现一个临时的不完整的共享内存段(key值是0),无法彻底删除.只有当所有进程都调用shmdt,这个临时 ...
- PHP共享内存详解
前言 在PHP中有这么一族函数,他们是对UNIX的V IPC函数族的包装. 它们很少被人们用到,但是它们却很强大.巧妙的运用它们,可以让你事倍功半. 它们包括: 信号量(Semaphores) 共享内 ...
- Linux内存详解
--Linux内存详解 -----------------2014/05/24 Linux的内存上表现的不像windows那么直观,本文准备详细的介绍一下Linux的内存. 请看这下有linux命令f ...
- Linux /dev目录详解和Linux系统各个目录的作用
Linux /dev目录详解(转http://blog.csdn.net/maopig/article/details/7195048) 在linux下,/dev目录是很重要的,各种设备都在下面.下面 ...
- linux lsof命令详解
linux lsof命令详解 简介 lsof(list open files)是一个列出当前系统打开文件的工具.在linux环境下,任何事物都以文件的形式存在,通过文件不仅仅可以访问常规数据,还可以访 ...
- Linux lsof命令详解和使用示例【转】
所以如传输控制协议 (TCP) 和用户数据报协议 (UDP) 套接字等,系统在后台都为该应用程序分配了一个文件描述符,无论这个文件的本质如何,该文件描述符为应用程序与基础操作系统之间的交互提供了通用接 ...
- JVM内存详解-阅读笔记
- [转贴]linux lsof命令详解
linux lsof命令详解 https://www.cnblogs.com/sparkbj/p/7161669.html 简介 lsof(list open files)是一个列出当前系统打开文件的 ...
- Linux常用命令详解—基于CentOS7
## Linux 目录- /:根目录,一般只存放目录,不存放文件- /bin -> /usr/bin:可执行二进制文件的目录,也是常用命令目录,如常用的命令 ls.cat.mv 等- /boot ...
随机推荐
- 各种电子面单Api接口免费对接-快宝开放平台
1.什么是电子面单? 快递公司联合向商家提供的一种通过热敏纸打印输出纸质物流面单的物流服务,并且承载分单自动化算法等数据服务,是快递行业赋能的基础产品和服务. 2.电子面单长什么样? 各快递公司有自己 ...
- spring的事务配置方法
spring事务的配置有两种方式 1.xml配置的声明式事务配置 (1)配置数据源信息dataSource(使用阿里的数据源) <bean id="dataSource" c ...
- cassandra 3.x官方文档(6)---内部原理之存储引擎
写在前面 cassandra3.x官方文档的非官方翻译.翻译内容水平全依赖本人英文水平和对cassandra的理解.所以强烈建议阅读英文版cassandra 3.x 官方文档.此文档一半是翻译,一半是 ...
- iOS开源加密相册Agony的实现(三)
简介 虽然目前市面上有一些不错的加密相册App,但不是内置广告,就是对上传的张数有所限制.本文介绍了一个加密相册的制作过程,该加密相册将包括多密码(输入不同的密码即可访问不同的空间,可掩人耳目).Wi ...
- hive中的NULL(hive空值处理)
HIVE表中默认将NULL存为\N,可查看表的源文件(hadoop fs -cat或者hadoop fs -text),文件中存储大量\N, 这样造成浪费大量空间.而且用java.python直接进入 ...
- mybatis insert 返回主键
分享牛,分享牛原创.ssm整合的时候,我们操作mybatis insert 的时候,需要返回插入的主键,因为主键是自增的,这个时候怎么办呢?很简单看一下下面的代码示例: 1.1.1. 代码定义 pub ...
- mysql进阶(二十八)MySQL GRANT REVOKE用法
mysql进阶(二十八)MySQL GRANT REVOKE用法 MySQL的权限系统围绕着两个概念: 认证->确定用户是否允许连接数据库服务器: 授权->确定用户是否拥有足够的权限执 ...
- Android使用shape制作圆形控件及添加弹跳动画
--------本来为作者原创,未经同意禁止转载 前言:我们在很多时候都需要在res/drawable文件夹下创建相应的xml文件来为控件添加一些样式效果,比如按钮按下时的按钮样式变化.或者指定按钮的 ...
- T-SQL注意事项(1)——SET NOCOUNT ON的去与留
前言 用了一段时间T-SQL之后,哪怕自己没用过,也多多少少看过SSMS中的SET NOCOUNT ON命令,很多性能优化文章中都有提到这个东西,它们建议尽可能使用这个命令减少网络传输的压力,那么今天 ...
- JBOSS EAP 6 系列五 Managed domains 管理域最主要的功能是“统一部署,统一配置”
摘要 本文首先介绍Managed Domain的概念,管理域最主要的功能是"统一部署,统一配置".接下来通过一个实例在"统一配置"部分实现一个双机配置起来的域, ...