共享内存:mmap函数实现

内存映射的应用:

  • 以页面为单位,将一个普通文件映射到内存中,通常在需要对文件进行频繁读写时使用,这样用内存读写取代I/O读写,以获得较高的性能;
  • 将特殊文件进行匿名内存映射,可以为关联进程提供共享内存空间;
  • 为无关联的进程提供共享内存空间,一般也是将一个普通文件映射到内存中。

相关API

#include <sys/mman.h>

void *mmap(void *addr, size_t length, int prot, int flags,
          int fd, off_t offset);
void *mmap64(void *addr, size_t length, int prot, int flags,
          int fd, off64_t offset);
int munmap(void *addr, size_t length);

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

mmap函数说明:

  1. 参数 addr 指明文件描述字fd指定的文件在进程地址空间内的映射区的开始地址,必须是页面对齐的地址,通常设为 NULL,让内核去选择开始地址。任何情况下,mmap 的返回值为内存映射区的开始地址。
  2. 参数 length 指明文件需要被映射的字节长度。off 指明文件的偏移量。通常 off 设为 0 。
    • 如果 len 不是页面的倍数,它将被扩大为页面的倍数。扩充的部分通常被系统置为 0 ,而且对其修改并不影响到文件。
    • off 同样必须是页面的倍数。通过 sysconf(_SC_PAGE_SIZE) 可以获得页面的大小。
  3. 参数 prot 指明映射区的保护权限。通常有以下 4 种。通常是 PROT_READ | PROT_WRITE 。
    • PROT_READ 可读
    • PROT_WRITE 可写
    • PROT_EXEC 可执行
    • PROT_NONE 不能被访问
  4. 参数 flag 指明映射区的属性。取值有以下几种。MAP_PRIVATE 与 MAP_SHARED 必选其一,MAP_FIXED 为可选项。
    • MAP_PRIVATE 指明对映射区数据的修改不会影响到真正的文件。
    • MAP_SHARED 指明对映射区数据的修改,多个共享该映射区的进程都可以看见,而且会反映到实际的文件。
    • MAP_FIXED 要求 mmap 的返回值必须等于 addr 。如果不指定 MAP_FIXED 并且 addr 不为 NULL ,则对 addr 的处理取决于具体实现。考虑到可移植性,addr 通常设为 NULL ,不指定 MAP_FIXED。
  5. 当 mmap 成功返回时,fd 就可以关闭,这并不影响创建的映射区。

munmap函数说明:

进程退出的时候,映射区会自动删除。不过当不再需要映射区时,可以调用 munmap 显式删除。当映射区删除后,后续对映射区的引用会生成 SIGSEGV 信号。

msync函数说明:

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

代码实例:

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

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

/*-------------map_normalfile1.c-----------*/
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>

typedef struct{
    char name[4];
    int  age;
}people;

int main(int argc, char** argv) // map a normal file as shared mem:
{
    int fd,i;
    people *p_map;
    char temp[2] = {‘\0‘};

    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[0] = ‘a‘;
    for(i=0; i<15; i++)
    {
        temp[0] += 1;
        memcpy( ( *(p_map+i) ).name, &temp[0],2 );
        ( *(p_map+i) ).age = 20+i;
    }
    printf("initialize over\n");
    sleep(10);
    munmap( p_map, sizeof(people)*10 );
    printf( "umap ok \n" );

    return 0;
}
/*-------------map_normalfile2.c-----------*/
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>

typedef struct{
    char name[4];
    int  age;
}people;

int main(int argc, char** argv) // map a normal file as shared mem:
{
    int fd,i;
    people *p_map;
    char temp[2] = {‘\0‘};

    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[0] = ‘a‘;
    for(i=0; i<15; i++)
    {
        temp[0] += 1;
        memcpy( ( *(p_map+i) ).name, &temp[0],2 );
        ( *(p_map+i) ).age = 20+i;
    }
    printf("initialize over\n");
    sleep(10);
    munmap( p_map, sizeof(people)*10 );
    printf( "umap ok \n" );

    return 0;
}

map_normalfile1首先打开或创建一个文件,并把文件的长度设置为5个people结构大小.mmap映射10个people结构大小的内存,利用返回的地址开始设置15个people结构。然后睡眠10S,等待其他进程映射同一个文件,然后解除映射。

通过实验,在map_normalfile1输出initialize over 之后,输出umap ok之前,运行map_normalfile2 file,可以输出设置好的15个people结构

在map_normalfile1 输出umap ok后,运行map_normalfile2则输出结构,前5个people是已设置的,后10结构为0。

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

2) 可以用于进程通信的有效地址空间大小大体上受限于被映射文件的大小,但不完全受限于文件大小.打开文件的大小为5个people结构,映射长度为10个people结构长度,共享内存通信用15个people结构大小。

在linux中,内存的保护是以页为基本单位的,即使被映射文件只有一个字节大小,内核也会为映射分配一个页面大小的内存。当被映射文件小于一个页面大小时,进程可以对从mmap()返回地址开始的一个页面大小进行访问,而不会出错;但是,如果对一个页面以外的地址空间进行访问,则导致错误发生,后面将进一步描述。因此,可用于进程间通信的有效地址空间大小不会超过文件大小及一个页面大小的和。

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

技巧:

生成固定大小的文件的两种方式:

/*第一种方法*/
fd = open(PATHNAME, O_RDWR | O_CREAT | O_TRUNC, FILE_MODE);
lseek(fd, filesize-1, SEEK_SET);
write(fd, "", 1);

/*第二种方法*/
ftruncate(fd, filesize);

父子进程通过匿名映射实现共享内存

  1. 匿名内存映射 与 使用 /dev/zero 类型,都不需要真实的文件。要使用匿名映射之需要向 mmap 传入 MAP_ANON 标志,并且 fd 参数 置为 -1 。
  2. 所谓匿名,指的是映射区并没有通过 fd 与 文件路径名相关联。匿名内存映射用在有血缘关系的进程间。
#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)
{
  int i;
  people *p_map;
  char temp;
  p_map=(people*)mmap(NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,
       MAP_SHARED|MAP_ANONYMOUS,-1,0);
  if(fork() == 0)
  {
    sleep(2);
    for(i = 0;i<5;i++)
      printf("child read: the %d people‘s age is %d\n",i+1,(*(p_map+i)).age);
    (*p_map).age = 100;
    munmap(p_map,sizeof(people)*10); //实际上,进程终止时,会自动解除映射。
    exit();
  }
  temp = ‘a‘;
  for(i = 0;i<5;i++)
  {
    temp += 1;
    memcpy((*(p_map+i)).name, &temp,2);
    (*(p_map+i)).age=20+i;
  }
  sleep(5);
  printf( "parent read: the first people,s age is %d\n",(*p_map).age );
  printf("umap\n");
  munmap( p_map,sizeof(people)*10 );
  printf( "umap ok\n" );
}

参考:

版权声明:本文为博主原创文章,转载请注明出处

时间: 2024-10-07 18:23:04

共享内存:mmap函数实现的相关文章

共享内存mmap学习 及与 shmxxx操作的区别

上一篇学习了共享内存: http://www.cnblogs.com/charlesblc/p/6142139.html 根据这个 http://blog.chinaunix.net/uid-26335251-id-3493125.html 再来一篇: 1. 共享内存允许两个或多个进程共享一给定的存储区,因为数据不需要来回复制,所以是最快的一种进程间通信机制.共享内存可以通过mmap()映射普通文件(特殊情况下还可以采用匿名映射)机制实现,也可以通过系统V共享内存机制实现. 应用接口和原理很简单

linux共享内存

1) 用ftok()函数获得一个ID号.2) shmget()用来开辟/指向一块共享内存的函数3) shmat()将这个内存区映射到本进程的虚拟地址空间.4) shmdt()函数删除本进程对这块内存的使用5) shmctl() 控制对这块共享内存的使用 1) mmap()系统调用使得进程之间通过映射同一个普通文件实现共享内存.普通文件被映射到进程地址空间后,进程可以像访问普通内存一样对文件进行访问,不必再调用read(),write()等操作2) 系统调用munmap()在进程地址空间中解除一个

Linux进程间通信--shmget()共享内存(二)

共享内存区域是被多个进程共享的一部分物理内存.如果多个进程都把该内存区域映射到自己的虚拟地址空间,则这些进程就都可以直接访问该共享内存区域,从而可以通过该区域进行通信.共享内存是进程间共享数据的一种最快的方法,一个进程向共享内存区域写入了数据,共享这个内存区域的所有进程就可以立刻看到其中的内容.这块共享虚拟内存的页面,出现在每一个共享该页面的进程的页表中.但是它不需要在所有进程的虚拟内存中都有相同的虚拟地址. 图 共享内存映射图 象所有的 System V IPC对象一样,对于共享内存对象的获取

细说linux IPC(五):system V共享内存

system V共享内存和posix共享内存类似,system V共享内存是调用shmget函数和shamat函数.           shmget函数创建共享内存区,或者访问一个存在的内存区,类似系统调用共享内存的open和posix共享内存shm_open函数.shmget函数原型为: #include <sys/ipc.h> #include <sys/shm.h> int shmget(key_t key, size_t size, int shmflg); key: 函

shmget共享内存

一. 共享内存介绍 系统V共享内存指的是把所有共享数据放在共享内存区域(IPC shared memory region),任何想要访问该数据的 进程都必须在本进程的地址空间新增一块内存区域,用来映射存放共享数据的物理内存页面.系统调用mmap()通 过映射一个普通文件实现共享内存.系统V则是通过映射shm文件系统中的文件实现进程间的共享内存通信. 也就是说,每个共享内存区域对应shm文件系统的一个文件. 二.系统V共享内存API 对于系统V共享内存,主要有以下几个API:shmget().sh

Android系统匿名共享内存Ashmem(Anonymous Shared Memory)驱动程序源代码分析

文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6664554 在上一文章Android系统匿名共享内存Ashmem(Anonymous Shared Memory)简要介绍和学习计划中, 我们简要介绍了Android系统的匿名共享内存机制,其中,简要提到了它具有辅助内存管理系统来有效地管理内存的特点,但是没有进一步去了解它是如何实 现的.在本文中,我们将通过分析Android系统的匿名共享内存

IPC_共享内存

在IPC(InterProcess Communication)的通信模式下,不管是使用消息队列还是共享内存,甚至是信号量,每个IPC的对象(object)都有唯一的名字,称为“键”(key).通过“键”,进程能够识别所用的对象.“键”与IPC对象的关系就如同文件名称之于文件,通过文件名,进程能够读写文件内的数据,甚至多个进程能够共用一个文件.而在IPC的通讯模式下,通过“键”的使用也使得一个IPC对象能为多个进程所共用. Linux系统中的所有表示System V中IPC对象的数据结构都包括一

进程通信之共享内存

共享内存 共享内存就是允许两个不相关的进程访问同一个逻辑内存.共享内存是在两个正在运行的进程之间共享和传递数据的一种非常有效的方式.不同进程之间共享的内存通常安排为同一段物理内存.进程可以将同一段共享内存连接到它们自己的地址空间中,所有进程都可以访问共享内存中的地址,就好像它们是由用C语言函数malloc分配的内存一样.而如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程. 共享内存并未提供同步机制,也就是说,在第一个进程结束对共享内存的写操作之前,并无自

linux使用共享内存通信的进程同步退出问题

两个甚至多个进程使用共享内存(shm)通信,总遇到同步问题.这里的“同步问题”不是说进程读写同步问题,这个用信号量就好了.这里的同步问题说的是同步退出问题,到底谁先退出,怎么知道对方退出了.举个例子:进程负责读写数据库A,进程B负责处理数据.那么进程A得比进程B晚退出才行,因为要保存进程B处理完的数据.可是A不知道B什么时候退出啊.A.B是无关联的进程,也不知道对方的pid.它们唯一的关联就是读写同一块共享内存.正常情况下,进程B在共享内存中写个标识:进程A你可以退出了,也是可以的.不过进程B可