Linux进程间通信(IPC)编程实践(六)共享内存的使用-mmap

共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据(如图)。

Linux进程间通信(IPC)编程实践(六)共享内存的使用-mmap

共享内存和其他进程间通信方式的比较:

用管道或者消息队列传递数据:

Linux进程间通信(IPC)编程实践(六)共享内存的使用-mmap

使用共享内存传递数据:

Linux进程间通信(IPC)编程实践(六)共享内存的使用-mmap

共享内存生成之后,传递数据并不需要再走Linux内核,共享内存允许两个或多个进程共享一个给定的存储区域,数据并不需要在多个进程之间进行复制,因此,共享内存的速度更快!

mmap函数及其相关系统调用

mmap()系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以向访问普通内存一样对文件进行访问,不必再调用read(),write()等操作。

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

[cpp] view plain copy
  1. #include <sys/mman.h>    
  2. void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);    
  3. int munmap(void *addr, size_t length);    

参数:

    addr:  要映射的起始地址, 通常指定为NULL, 让内核自动选择;

    length: 映射到进程地址空间的字节数;

    prot: 映射区保护方式(见下);

    flags: 标志(见下);

    fd: 文件描述符;

    offset: 从文件头开始的偏移量;

mmap返回值:

    成功: 返回映射到的内存区的起始地址;

    失败: 返回MAP_FAILED;

Linux进程间通信(IPC)编程实践(六)共享内存的使用-mmap

内存映射文件示意图:

Linux进程间通信(IPC)编程实践(六)共享内存的使用-mmap

注意: 内存映射时, 是以页面(4K)作为单位,也就是会对齐到页面的整数倍地址,映射区域至少是一个页面,哪怕len<一个页面。

lseek定位出一段空洞空间,最后写入\0。然后使用mmap进行映射,对文件的操作就相当于对内存的访问,结束之后用munmap解除映射。

(一)两个进程通过映射普通文件实现共享内存通信

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

下面是两个程序代码:

[cpp] view plain copy
  1. /*-------------map_normalfile1.c-----------*/    
  2. #include <sys/mman.h>    
  3. #include <sys/types.h>    
  4. #include <fcntl.h>    
  5. #include <unistd.h>    
  6. typedef struct{    
  7.     char name[4];    
  8.     int  age;    
  9. }people;    
  10. main(int argc, char** argv) // map a normal file as shared mem:    
  11. {    
  12.     int fd,i;    
  13.     people *p_map;    
  14.     char temp;    
  15.         
  16.     fd=open(argv[1],O_CREAT|O_RDWR|O_TRUNC,00777);    
  17.     lseek(fd,sizeof(people)*5-1,SEEK_SET);    
  18.     write(fd,"",1);    
  19.         
  20.     p_map = (people*) mmap( NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0 );    
  21.     close( fd );    
  22.     temp = 'a';    
  23.     for(i=0; i<10; i++)    
  24.     {    
  25.         temp += 1;    
  26.         memcpy( ( *(p_map+i) ).name, &temp,2 );    
  27.         ( *(p_map+i) ).age = 20+i;    
  28.     }    
  29.     printf(" initialize over \n ");    
  30.     sleep(10);    
  31.     munmap( p_map, sizeof(people)*10 );    
  32.     printf( "umap ok \n" );    
  33. }    
  34. /*-------------map_normalfile2.c-----------*/    
  35. #include <sys/mman.h>    
  36. #include <sys/types.h>    
  37. #include <fcntl.h>    
  38. #include <unistd.h>    
  39. typedef struct{    
  40.     char name[4];    
  41.     int  age;    
  42. }people;    
  43. main(int argc, char** argv) // map a normal file as shared mem:    
  44. {    
  45.     int fd,i;    
  46.     people *p_map;    
  47.     fd=open( argv[1],O_CREAT|O_RDWR,00777 );    
  48.     p_map = (people*)mmap(NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);    
  49.     for(i = 0;i<10;i++)    
  50.     {    
  51.     printf( "name: %s age %d;\n",(*(p_map+i)).name, (*(p_map+i)).age );    
  52.     }    
  53.     munmap( p_map,sizeof(people)*10 );    
  54. }    

在map_normalfile1输出initialize over 之后,输出umap ok之前,在另一个终端上运行map_normalfile2 /tmp/test_shm,将会产生如下输出(为了节省空间,输出结果为稍作整理后的结果):

name: b	age 20;	name: c	age 21;	name: d	age 22;	name: e	age 23;	name: f	age 24;
name: g	age 25;	name: h	age 26;	name: I	age 27;	name: j	age 28;	name: k	age 29;

在map_normalfile1 输出umap ok后,运行map_normalfile2则输出如下结果:

name: b	age 20;	name: c	age 21;	name: d	age 22;	name: e	age 23;	name: f	age 24;
name:	age 0;	name:	age 0;	name:	age 0;	name:	age 0;	name:	age 0;

从程序的运行结果中可以得出的结论

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()时,才把内存中的相应内容写回磁盘文件,所写内容仍然不能超过文件的大小。

(二)父子进程通过匿名映射实现共享内存(匿名映射只能是父子进程间)

[cpp] view plain copy
  1. #include <sys/mman.h>    
  2. #include <sys/types.h>    
  3. #include <fcntl.h>    
  4. #include <unistd.h>    
  5. typedef struct{    
  6.     char name[4];    
  7.     int  age;    
  8. }people;    
  9. main(int argc, char** argv)    
  10. {    
  11.     int i;    
  12.     people *p_map;    
  13.     char temp;    
  14.     p_map=(people*)mmap(NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1,0);    
  15.     if(fork() == 0)    
  16.     {    
  17.         sleep(2);    
  18.         for(i = 0;i<5;i++)    
  19.             printf("child read: the %d people's age is %d\n",i+1,(*(p_map+i)).age);    
  20.         (*p_map).age = 100;    
  21.         munmap(p_map,sizeof(people)*10); //实际上,进程终止时,会自动解除映射。    
  22.         exit();    
  23.     }    
  24.     temp = 'a';    
  25.     for(i = 0;i<5;i++)    
  26.     {    
  27.         temp += 1;    
  28.         memcpy((*(p_map+i)).name, &temp,2);    
  29.         (*(p_map+i)).age=20+i;    
  30.     }    
  31.     sleep(5);    
  32.     printf( "parent read: the first people,s age is %d\n",(*p_map).age );    
  33.     printf("umap\n");    
  34.     munmap( p_map,sizeof(people)*10 );    
  35.     printf( "umap ok\n" );    
  36. }    

考察程序的输出结果,体会父子进程匿名共享内存:

child read: the 1 people's age is 20
child read: the 2 people's age is 21
child read: the 3 people's age is 22
child read: the 4 people's age is 23
child read: the 5 people's age is 24
parent read: the first people,s age is 100
umap
umap ok

msync函数

  1. int msync(void *addr, size_t length, int flags);  

对映射的共享内存执行同步操作,内存同步到磁盘

参数:

    addr: 内存起始地址;

    length: 长度

    flags: 选项

flags

说明

MS_ASYNC

执行异步写

MS_SYNC

执行同步写, 直到内核将数据真正写入磁盘之后才返回

MS_INVALIDATE

使高速缓存的数据失效

返回值:

    成功: 返回0;

    失败: 返回-1;