共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式。两个不同进程A、B共享内存的意思是,同一块物理内存被映射到进程A、B各自的进程地址空间。进程A可以即时看到进程B对共享内存中数据的更新,反之亦然。由于多个进程共享同一块内存区域,必然需要某种同步机制,互斥锁和信号量都可以。
采用共享内存通信的一个显而易见的好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝。对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而共享内存则只拷贝两次数据[1]:一次从输入文件到共享内存区,另一次从共享内存区到输出文件。实际上,进程之间在共享内存时,并不总是读写少量数据后就解除映射,有新的通信时,再重新建立共享内存区域。而是保持共享区域,直到通信完毕为止,这样,数据内容一直保存在共享内存中,并没有写回文件。共享内存中的内容往往是在解除映射时才写回文件的。因此,采用共享内存的通信方式效率是非常高的。

1.mmap系统调用

mmap系统调用是的是的进程间通过映射同一个普通文件实现共享内存.普通文件被映射到进程地址空间后,进程可以向像访问普通内存一样对文件进行访问,不必再调用read,write等操作.与mmap系统调用配合使用的系统调用还有munmap,msync等.

实际上,mmap系统调用并不是完全为了用于共享内存而设计的.它本身提供了不同于一般对普通文件的访问方式,是进程可以像读写内存一样对普通文件操作.而Posix或System V的共享内存则是纯粹用于共享内存的,当然mmap实现共享内存也是主要应用之一.

#include <sys/mman.h>
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);

各个参数的说明如下:

  • start:映射区的开始地址,设置为0时表示由系统决定映射区的起始地址.
  • length:映射区的长度.长度单位是以内存页为单位.
  • prot:期望的内存保护标志,不能与文件的打开模式冲突.是以下的某个值,可以通过or运算合理地组合在一起.
  • PROT_EXEC //页内容可以被执行
  • PROT_READ //页内容可以被读取
  • PROT_WRITE //页可以被写入
  • PROT_NONE //页不可访问
  • flags:指定映射对象的类型,映射选项和映射页是否可以共享.它的值可以是一个或者多个以下位的组合体.
  • MAP_FIXED //使用指定的映射起始地址,如果由start和len参数指定的内存区重叠于现存的映射空间,重叠部分将会被丢弃.如果指定的起始地址不可用,操作将会失败.并且起始地址必须落在页的边界上.
  • MAP_SHARED //与其它所有映射这个对象的进程共享映射空间.对共享区的写入,相当于输出到文件.直到msync()或者munmap()被调用,文件实际上不会被更新.
  • MAP_PRIVATE //建立一个写入时拷贝的私有映射.内存区域的写入不会影响到原文件.这个标志和以上标志是互斥的,只能使用其中一个.
  • MAP_DENYWRITE //这个标志被忽略.
  • MAP_EXECUTABLE //同上
  • MAP_NORESERVE //不要为这个映射保留交换空间.当交换空间被保留,对映射区修改的可能会得到保证.当交换空间不被保留,同时内存不足,对映射区的修改会引起段违例信号.
  • MAP_LOCKED //锁定映射区的页面,从而防止页面被交换出内存.
  • MAP_GROWSDOWN //用于堆栈,告诉内核VM系统,映射区可以向下扩展.
  • MAP_ANONYMOUS //匿名映射,映射区不与任何文件关联.
  • MAP_ANON //MAP_ANONYMOUS的别称,不再被使用.
  • MAP_FILE //兼容标志,被忽略.
  • MAP_32BIT //将映射区放在进程地址空间的低2GB,MAP_FIXED指定时会被忽略.当前这个标志只在x86-64平台上得到支持。
  • MAP_POPULATE //为文件映射通过预读的方式准备好页表.随后对映射区的访问不会被页违例阻塞.
  • MAP_NONBLOCK //仅和MAP_POPULATE一起使用时才有意义.不执行预读,只为已存在于内存中的页面建立页表入口.
  • fd:有效的文件描述词.一般是由open()函数返回,其值也可以设置为-1,此时需要指定flags参数中的MAP_ANON,表明进行的是匿名映射.
  • offset:被映射对象内容的起点.一般设为0,表示从文件头开始映射.

函数的返回值为最后文件映射到进程空间的地址,进程可以直接操作起始地址为该值的有效地址.系统调用mmap用于共享内存时有下面两种常用的方式:

  • 1)使用普通文件提供的内存映射:适用于任何进程之间.此时,需要打开或创建一个文件,然后再调用mmap,这种方式有许多特点和要注意的地方,我们在后面或举例子说明.
  • 2)使用特殊文件提东匿名内存映射:适用于具有亲缘关系的进程之间.由于父子进程特殊的亲缘关系,在父进程中吊牌用mmap,然后调用fork.那么在调用fork之后,子进程急促继承父进程匿名映射后的地址空间,同样也继承mmap返回的地址,这样,父子进程就可以通过映射区进行通信了.注意,mmap返回的地址,需要由父进程共同维护.

对于任意的两个进程可以使用第一种方式,而对于具有亲缘关系的进程实现共享内存最好的方式应该是采用匿名内存映射的方式.此时,不必指定具体的文件,只要设定相应的标志即可,后面有相应的例子.

3.munmap系统调用

该系统调用在进程地址空间中解除一个映射关系,当映射关系解除后,对原来映射地址的访问将导致段错误发生.其函数原型为:

int munmap(void *start, size_t length);
其中,参数addr是调用mmap时返回的地址,len是映射区的大小.
4.msync系统调用
一般来说,进程在映射空间对共享内容的改变并不直接写回到磁盘文件中,往往在调用munmap后才执行该操作.可以通过调用msync实现磁盘上文件内容与共享内存区的内容一致.该函数的原型如下:

int msync ( void * addr, size_t len, int flags);
addr:文件映射到进程空间的地址;
len:映射空间的大小;
flags:刷新的参数设置,可以取值MS_ASYNC/MS_SYNC/MS_INVALIDATE
取值为MS_ASYNC(异步)时,调用会立即返回,不等到更新的完成;
取值为MS_SYNC(同步)时,调用会等到更新完成之后返回;
取MS_INVALIDATE(通知使用该共享区域的进程,数据已经改变)时,在共享内容更改之后,使得文件的其他映射失效,从而使得共享该文件的其他进程去重新获取最新值.

范例1:两个进程通过映射普通文件实现共享内存通信

范例1包含两个子程序:map_normalfile1.c及map_normalfile2.c。编译两个程序,可执行文件分别为map_normalfile1及map_normalfile2。两个程序通过命令行参数指定同一个文件来实现共享内存方式的进程间通信。map_normalfile2试图打开命令行参数指定的一个普通文件,把该文件映射到进程的地址空间,并对映射后的地址空间进行写操作。map_normalfile1把命令行参数指定的文件映射到进程地址空间,然后对映射后的地址空间执行读操作。这样,两个进程通过命令行参数指定同一个文件来实现共享内存方式的进程间通信。

下面是两个程序代码:

/*-------------map_normalfile1.c-----------*/
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
typedef struct{
char name[4];
int age;
}people;
main(int argc, char** argv) // map a normal file as shared mem:
{
int fd,i;
people *p_map;
char temp; fd=open(argv[1],O_CREAT|O_RDWR|O_TRUNC,00777);
lseek(fd,sizeof(people)*5-1,SEEK_SET);
write(fd,"",1); p_map = (people*) mmap( NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,
MAP_SHARED,fd,0 );
close( fd );
temp = 'a';
for(i=0; i<10; i++)
{
temp += 1;
memcpy( ( *(p_map+i) ).name, &temp,2 );
( *(p_map+i) ).age = 20+i;
}
printf(" initialize over \n ");
sleep(10);
munmap( p_map, sizeof(people)*10 );
printf( "umap ok \n" );
}
/*-------------map_normalfile2.c-----------*/
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
typedef struct{
char name[4];
int age;
}people;
main(int argc, char** argv) // map a normal file as shared mem:
{
int fd,i;
people *p_map;
fd=open( argv[1],O_CREAT|O_RDWR,00777 );
p_map = (people*)mmap(NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,
MAP_SHARED,fd,0);
for(i = 0;i<10;i++)
{
printf( "name: %s age %d;\n",(*(p_map+i)).name, (*(p_map+i)).age );
}
munmap( p_map,sizeof(people)*10 );
}

1、 最终被映射文件的内容的长度不会超过文件本身的初始大小,即映射不能改变文件的大小;

2、 可以用于进程通信的有效地址空间大小大体上受限于被映射文件的大小,但不完全受限于文件大小。打开文件被截短为5个people结构大小,而在map_normalfile1中初始化了10个people数据结构,在恰当时候(map_normalfile1输出initialize over 之后,输出umap ok之前)调用map_normalfile2会发现map_normalfile2将输出全部10个people结构的值,后面将给出详细讨论。 
注:在linux中,内存的保护是以页为基本单位的,即使被映射文件只有一个字节大小,内核也会为映射分配一个页面大小的内存。当被映射文件小于一个页面大小时,进程可以对从mmap()返回地址开始的一个页面大小进行访问,而不会出错;但是,如果对一个页面以外的地址空间进行访问,则导致错误发生,后面将进一步描述。因此,可用于进程间通信的有效地址空间大小不会超过文件大小及一个页面大小的和。

3、 文件一旦被映射后,调用mmap()的进程对返回地址的访问是对某一内存区域的访问,暂时脱离了磁盘上文件的影响。所有对mmap()返回地址空间的操作只在内存中有意义,只有在调用了munmap()后或者msync()时,才把内存中的相应内容写回磁盘文件,所写内容仍然不能超过文件的大小

linux 进程间通信 共享内存 mmap的更多相关文章

  1. Linux进程间通信—共享内存

    五.共享内存(shared memory) 共享内存映射为一段可以被其他进程访问的内存.该共享内存由一个进程所创建,然后其他进程可以挂载到该共享内存中.共享内存是最快的IPC机制,但由于linux本身 ...

  2. linux下共享内存mmap和DMA(直接访问内存)的使用 【转】

    转自:http://blog.chinaunix.net/uid-7374279-id-4413316.html 介绍Linux内存管理和内存映射的奥秘.同时讲述设备驱动程序是如何使用“直接内存访问” ...

  3. linux 进程间通信 共享内存 shmat

    系统调用mmap()通过映射一个普通文件实现共享内存.系统V则是通过映射特殊文件系统shm中的文件实现进程间的共享内存通信.也就是说,每个共享内存区域对应特殊文件系统shm中的一个文件(这是通过shm ...

  4. linux进程间通信-共享内存

    转载:http://www.cnblogs.com/fangshenghui/p/4039720.html 一 共享内存介绍 共享内存可以从字面上去理解,就把一片逻辑内存共享出来,让不同的进程去访问它 ...

  5. Linux进程间通信 共享内存+信号量+简单样例

    每个进程都有着自己独立的地址空间,比方程序之前申请了一块内存.当调用fork函数之后.父进程和子进程所使用的是不同的内存. 因此进程间的通信,不像线程间通信那么简单.可是共享内存编程接口能够让一个进程 ...

  6. Linux 进程间通信 共享内存

    1.特点: 1)共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝.如管道当在内核空间创建以后,用户空间需要内存  拷贝,需要拷贝数据,所以效率低. 2)为了在多个进 ...

  7. Linux环境进程间通信: 共享内存

    Linux环境进程间通信: 共享内存 第一部分 共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式.两个不同进程A.B共享内存的意思是,同一块物理内存被映射到进程A.B各自的进程地址空间.进 ...

  8. Linux IPC 共享内存

    共享内存 共享内存(shared memory)是最简单的Linux进程间通信方式之一. 使用共享内存,不同进程可以对同一块内存进行读写. 由于所有进程对共享内存的访问就和访问自己的内存空间一样,而不 ...

  9. C# 进程间通信(共享内存)

    原文:C# 进程间通信(共享内存) 进程间通信的方式有很多,常用的方式有: 1.共享内存(内存映射文件,共享内存DLL). 2.命名管道和匿名管道. 3.发送消息 本文是记录共享内存的方式进行进程间通 ...

随机推荐

  1. Jersey实现跨服务器上传图片:UniformInterfaceException:403 Forbidden

    jersey.api.client.UniformInterfaceException :returned a response status of 403 Forbidden 图片服务器:端口808 ...

  2. 使用Python对植物大战僵尸学习研究

    根据上一篇 使用Python读写游戏1 中,使用Python win32库,对一款游戏进行了读内存 操作. 今天来写一下对内存进行写的操作 正文 要进行32位的读写,首先了解一下要用到的几个函数,通过 ...

  3. 从Linux源码看Socket(TCP)的bind

    从Linux源码看Socket(TCP)的bind 前言 笔者一直觉得如果能知道从应用到框架再到操作系统的每一处代码,是一件Exciting的事情. 今天笔者就来从Linux源码的角度看下Server ...

  4. day45 Pyhton 数据库Mysql 02

    一.前期回顾 数据库 mysql的安装 配置环境 为什么要用数据库? 稳定性 一致性 并发 存取数据效率高 数据库的分类 关系型数据库 mysql oracle sqlserver 非关系型数据库 r ...

  5. 每个人都可以用C语言写的推箱子小游戏!今天你就可以写出属于自己项目~

    C语言,作为大多数人的第一门编程语言,重要性不言而喻,很多编程习惯,逻辑方式在此时就已经形成了.这个是我在大一学习 C语言 后写的推箱子小游戏,自己的逻辑能力得到了提升,在这里同大家分享这个推箱子小游 ...

  6. 【C语言/C++程序员编程】一小时做出来的数字雨(一颗开花的树)!

    相信大家看过许许多多的关于计算机黑客.骇客.人工智能.AI方面的电影,每当黑客入侵某个五角大楼,某个网站时,都会出现这样一副画面: 入侵 或者这样的: 数字雨 然后就轻而易举的成功入侵夺取管理员权限了 ...

  7. Android Jetpack从入门到精通(深度好文,值得收藏)

    前言 即学即用Android Jetpack系列Blog的目的是通过学习Android Jetpack完成一个简单的Demo,本文是即学即用Android Jetpack系列Blog的第一篇. 记得去 ...

  8. nginx安全:配置网站图片防盗链

    一,为什么要做防盗链? 1,什么是盗链? 比如某人有一个A网站, 他不愿自己存储图片,(因为磁盘和带宽都有成本) 就在自己A网站的页面上直接插入B网站的图片, 从而为自己吸引流量,这就是盗链 2,为什 ...

  9. ansible2.9.5使用become参数实现sudo功能

    一,为什么要使用sudo? 1, 生产环境中,为了安全因素,我们不会直接使用root来登录到server, 确实有需要的情况下,我们再使用sudo切换到root权限. 所以很多ansible的演示直接 ...

  10. IDEA 半天卡住buid(编译)不动——解决办法(适用于maven及gradle)及定位思路

    [号外号外!] 最终解决办法并不复杂,关键在于"遇见问题,怎么样层层分析,多条路径试错,最终解决问题的思路或者能力"--资深码农的核心竞争力之一 背景 今天结束完最近2个月的一个项 ...