内存映射mmap详解和mmap实现进程间通信-共享内存法

第一部分:Linux内存映射mmap详解

函数原型:void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);

头文件:#include  <sys/mman.h>

参数说明:

  • start:指定文件应被映射到进程空间的起始地址,一般被指定一个空指针,此时选择起始地址的任务留给内核来完成。函数的返回值为最后文件映射到进程空间的地址,进程可直接操作起始地址为该值的有效地址
  • length:是映射到调用进程地址空间的字节数.
  • prot:参数指定共享内存的访问权限。可取如下几个值的或:PROT_READ(可读) , PROT_WRITE (可写), PROT_EXEC (可执行), PROT_NONE(不可访问)
  • flags:由以下几个常值指定:MAP_SHARED , MAP_PRIVATE , MAP_FIXED,其中,MAP_SHARED , MAP_PRIVATE必选其一,而MAP_FIXED则不推荐使用
  • fd:有效的文件描述词。一般是由open()函数返回,其值也可以设置为-1,此时需要指定flags参数中的MAP_ANON,表明进行的是匿名映射。
  • offset:被映射对象内容的起点。

返回值: 成功执行时,mmap()返回被映射区的指针,失败时,mmap()返回MAP_FAILED[其值为(void *)-1]

  • 虚拟地址:由被映射区的指针加上内存对齐的偏移量求得虚拟地址指针。获取虚拟地址后,可通过虚拟地址以内存读写的方式操作所映射的物理地址或文件

注意事项:

1. 使用完需要nt munmap(void* start,size_t length)函数释放

2.用于共享内存需要注意进程间读写数据的同步

3.mmap()必须以PAGE_SIZE为单位进行映射,而内存也只能以页为单位进行映射,若要映射非PAGE_SIZE整数倍的地址范围,要先进行内存对齐,强行以PAGE_SIZE的倍数大小进行映射。

  • 映射文件一般从文件开头开始,不需要对齐。
  • 查看PAGE_SIZE大小方法: 使用 sysconf 函数 或控制台 getconf 命令
  • 内存对齐方法是计算被物理内存的修正起始地址offset:Physical address & ~(PAGE_SIZE-1) ,其中Physical address是要映射的物理起始地址

第二部分:mmap实现进程间通信-共享内存法

mmap操作提供了一种机制,让用户程序直接访问设备内存,这种机制,相比较在用户空间和内核空间互相拷贝数据,效率更高。在要求高性能的应用中比较常用。mmap()系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以像访问普通内存一样对文件进行访问,不必再调用read(),write()等操作,效率可大大提升。

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

(1)使用普通文件提供的内存映射:适用于任何进程之间;此时,需要打开或创建一个文件,然后再调用mmap();

典型调用代码如下:

fd=open(name, flag, mode); 

if(fd<0)

...

ptr=mmap(NULL, len , PROT_READ|PROT_WRITE, MAP_SHARED , fd , 0);

通过mmap()实现共享内存的通信方式有许多特点和要注意的地方,我们将在范例中进行具体说明。

(2)使用特殊文件提供匿名内存映射:适用于具有亲缘关系的进程之间;由于父子进程特殊的亲缘关系,在父进程中先调用mmap(),然后调用fork()。那么在调用fork()之后,子进程继承父进程匿名映射后的地址空间,同样也继承mmap()返回的地址,这样,父子进程就可以通过映射区域进行通信了。注意,这里不是一般的继承关系。一般来说,子进程单独维护从父进程继承下来的一些变量。而mmap()返回的地址,却由父子进程共同维护。 对于具有亲缘关系的进程实现共享内存最好的方式应该是采用匿名内存映射的方式。此时,不必指定具体的文件,只要设置相应的标志即可。

代码示例:

/*-------------map_normalfile1.c-----------*/

#include<sys/mman.h>

#include<sys/types.h>

#include<fcntl.h>

#include<string.h>

#include<stdio.h>

#include<unistd.h>

#include <errno.h>

typedef struct{

    char name[4];

    int age;

}people;

void 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);

    if (p_map == (void *)-1)

    {

        fprintf(stderr, "mmap: %s\n", strerror(errno));

        return ;

    }

    close(fd);

    temp='a';

    for(i=0;i<10;i++)

    {

        temp+=1;

        (*(p_map+i)).name[1] = '\0';

        memcpy((*(p_map+i)).name,&temp,1);

        (*(p_map+i)).age=20+i;

    }

    printf("initializeover\n");

    sleep(10);

    munmap(p_map,sizeof(people)*10);

    printf("umapok\n");

}



/*-------------map_normalfile2.c-----------*/

#include<sys/mman.h>

#include<sys/types.h>

#include<fcntl.h>

#include<stdio.h>

#include<unistd.h>

#include <errno.h>

typedef struct{

    char name[4];

    int age;

}people;

void 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);

    if (p_map == (void *)-1)

    {

        fprintf(stderr, "mmap: %s\n", strerror(errno));

        return ;

    }

    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);

}

代码解释

map_normalfile1.c 首先定义了一个people数据结构,(在这里采用数据结构的方式是因为,共享内存区的数据往往是有固定格式的,这由通信的各个进程决定,采用结构的方式有普遍代表性)。map_normfile1首先打开或创建一个文件,通过在指定位置写入空格,把文件的长度设置为5个people结构大小。然后从mmap()的返回地址开始,设置了10个people结构。然后,进程睡眠10秒钟,等待其他进程映射同一个文件,最后解除映射。 
map_normfile2.c只是简单的映射一个文件,并以people数据结构的格式从mmap()返回的地址处读取10个people结构,并输出读取的值,然后解除映射。 

分别把两个程序编译成可执行文件map_normalfile1和map_normalfile2后,在一个终端上先运行./map_normalfile1 /tmp/test_shm,程序输出结果如下: 
  initialize over 
  umap ok 
  在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;

注意:map_normalfile1的被umap之后,映射内容写入到磁盘位置/tmp/test_shm ,同时从内存中注销。故,此时从map_normalfile2再读共享内存是从磁盘中读取,无法读取到大于文件长度的内容。

结论


  从程序的运行结果中可以得出的结论 
  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()时,才把内存中的相应内容写回磁盘文件,所写内容仍然不能超过文件的大小。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值