Linux进程间通信--内存映射

一  内存映射概述

从原理上讲,Linux系统利用已有的存储管理机制可以很自然的实现进程间的共享存储。对于一段物理存储空间,只需通过进程的虚存管理机构就可以映射到各自的3G用户地址空间中。通过这种映射,在不同进程看来“私有”的数据事实上是同一段内存单元,它们被这些不同的进程所共享。

在Linux系统实际运行时,内存中的页面要经常被换入或换出,共享存储区中的页面也不例外。一般而言,内存页面的换入/换出过程采用两种方式进行:

1. 普通页面因长时间未得到访问而被内核线程kswaps在系统空闲而得到调度时换出内存到磁盘上的页面交换区,或因为进程访问的页面不在内存引起缺页从而将曾被换出到页面交换区的页面重新换入。

2.针对某个被打开的磁盘文件在内存中的页缓冲,在内资源不足而需要增加空闲页面时,由内核线程bdflush在系统空闲而得到调度时按照LRU算法将“脏”页面写回磁盘文件以回收空闲页面(或由用户强制“刷出”页面),或者因进程所读文件某段数据不在内存而启动磁盘IO读文件数据到内存中的文件缓冲区。

方式1本身就是操作系统已实现的页调度机制,从用户角度来看,它是完全透明、不必额外关心的底层功能;而方式2依托某一种文件系统,需显式创建磁盘文件。因此该方式实现的页面交换功能对用户并不透明,需用户干涉。但从功能角度来看,它们却具有共同的本质:磁盘到物理内存之间的动态页面交换。

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

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

二 内存映射的原理

“映射”主要是指 硬盘上文件 的位置与进程 逻辑地址空间中一块大小相同的区域之间的一一对应,如下图所示。这种对应关系纯属是逻辑上的概念,物理上是不存在的,原因是进程的逻辑地址空间本身就是不存在。在内存映射的过程中,并没有实际的数据拷贝,文件没有被载入内存,只是逻辑上被放入了内存,具体到代码,就是建立并初始化了相关的数据结构(struct address_space),这个过程有系统调用mmap()实现,所以建立内存映射的效率很高。

 内存映射原理  

既然建立内存映射没有进行实际的数据拷贝,那么进程又怎么能最终直接通过内存操作访问到硬盘上的文件呢?那就要看内存映射之后的几个相关的过程了。

mmap()会返回一个指针ptr,它指向进程逻辑地址空间中的一个地址,这样以后,进程无需再调用read或write对文件进行读写,而只需要通过ptr就能够操作文件。但是ptr所指向的是一个逻辑地址,要操作其中的数据,必须通过MMU将逻辑地址转换成物理地址,如上图过程2所示。

前面讲过,建立内存映射并没有实际拷贝数据,这时,MMU在地址映射表中是无法找到与ptr相对应的物理地址的,也就是MMU失败,将产生一个缺页中断,缺页中断的中断响应函数会在swap中寻找相对应的页面,如果找不到(也就是该文件从来没有被读入内存的情况),则会通过mmap()建立的映射关系,从硬盘上将文件读取到物理内存中,如上图过程3所示。

如果在拷贝数据时,发现物理内存不够用,则会通过虚拟内存机制(swap)将暂时不用的物理页面交换到硬盘上,如上图过程4所示。

在Linux系统中,设计mmap()系统调用的本意是提高文件操作的效率。通过mmap(),进程可以把一个文件的内容映射到它的虚存空间并以访问内存的方式实现文件的读写操作,这为文件读写提供了极大的方便;如果多个不同的进程通过这种方式映射同一个文件,则可以共享该文件对应的物理存储空间。 如下图所示:

注:在进程的地址空间中,栈的下方是内存映射段,内核直接将文件的内容映射到内存,通过虚存管理机构将进程中映射的地址转换为内存中的物理地址。

这恰恰完成了上述共享存储机制中的两个关键要素:底层的页面换入换出(磁盘文件->内存)功能以及物理存储(内存)到进程空间的映射,并最终实现了一种进程间通信的手段。这种共享存储的功能虽然是mmap()系统调用的“副产品”,但无论从功能角度还是从实现原理角度来看,它都具备共享存储的特征。

使用mmap对于设备文件,最大的优点就是用户空间可以直接访问设备内存;普通文件被映射到进程地址空间后,进程进程访问文件的速度也变快,不必再调read(),write()等系统调用,可以用memcpy,strcpy等操作写文件,写完后用msync()同步一下。mmap()的这种能力用于显示适配器一类的设备,屏幕帧的像素不再需要从一个用户空间到内核空间的复制过程。

从代码层面上看,从硬盘上将文件读入内存,都要经过文件系统进行数据拷贝,并且数据拷贝操作是由文件系统和硬件驱动实现的,理论上来说,拷贝数据的效率是一样的。但是通过内存映射的方法访问硬盘上的文件,效率要比read和write系统调用高,这是为什么呢?原因是read()是系统调用,其中进行了数据拷贝,它首先将文件内容从硬盘拷贝到内核空间的一个缓冲区,如下图过程1,然后再将这些数据拷贝到用户空间,如下图中过程2,在这个过程中,实际上完成了 两次数据拷贝 ;而mmap()也是系统调用,如前所述,mmap()中没有进行数据拷贝,真正的数据拷贝是在缺页中断处理时进行的,由于mmap()将文件直接映射到用户空间,所以中断处理函数根据这个映射关系,直接将文件从硬盘拷贝到用户空间,只进行了 一次数据拷贝 。因此,内存映射的效率要比read/write效率高。

 read系统调用原理

小结:使用内存映射文件处理存储于磁盘上的文件时,将不必再对文件执行I/O操作,使得内存映射文件在处理大数据量的文件时能起到相当重要的作用。

三、代码实现

1、mmap系统调用

#include <sys/mman.h>

void* mmap ( void * addr , size_t len , int
prot , int flags ,int fd , off_t offset );

内存映射函数mmap, 负责把文件内容映射到进程的虚拟内存空间, 通过对这段内存的读取和修改,来实现对文件的读取和修改,而不需要再调用read,write等操作。

参数含义:

addr: 指定映射的起始地址,
通常设为NULL, 由系统指定。

 length:   映射到内存的文件长度,即可用访问的数据量

  prot: 映射区的保护方式,它是下列常值的按位OR结果

PROT_EXEC: 映射区可被执行

PROT_READ: 映射区可被读取

PROT_WRITE: 映射区可被写入

PROT_NONE  映射区不可访问.

flags: 映射区的特性, 可以是: MAP_SHARED、MAP_PRIVATE、MAP_FIXED

MAP_SHARED:对此区域所做的修改内容奖写入文件内;允许其他映射该文件的进程共享,意思是:n个mmap.out程序在运行,这n个进程的“虚拟内存区域”的物理空间空间都相同。

MAP_PRIVATE:对此区域所做的修改不会更改原来的文件内容,对映射区的写入操作会产生一个映射区的复制(copy-on-write);意思是:n个mmap.out程序在运行,但是虚拟内存区域的物理地址会被内核另外分配。

 fd: 由open返回的文件描述符, 代表要映射的文件。

offset: 以文件开始处的偏移量, 必须是分页大小的整数倍, 通常为0, 表示从文件头开始映射。

返回值:返回成功----函数的返回值为最后文件映射到进程空间的地址,进程可直接操作起始地址为该值的有效地址,该内存区域与可以通过一个打开的文件描述符访问的文件内容相关联。如果失败返回MAP_FAILED(-1),错误原因存于errno 中。

2、munmap 函数

int munmap(void *addr, size_t length);

用于取消参数addr所指的映射内存起始地址,参数length则是欲取消的内存大小。当进程结束或利用exec相关函数来执行其他程序时,映射内存会自动解除,但关闭对应的文件描述符时不会解除映射。

3、msync函数

int msync(const void *start, size_t length, int flags);

把在该内存段的某个部分的或整段中的修改写回到被映射的文件中(或者从被映射文件中读出)。

参数含义:

start  修改部分的起始地址,length为长度。

flags则有三个:

MS_ASYNC : 采用异步写方式,请Kernel快将资料写入,发出回写请求后立即返回。

MS_SYNC :  采用同步写范式,在msync结束返回前,将资料写入。

MS_INVALIDATE:通知使用该共享区域的进程,数据已经修改,在共享内容更改之后,使得文件的其他映射失效,从而使得共享该文件的其他进程去重新获取最新值。

#include <unistd.h>
#include <stdio.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <stdlib.h>

typedef struct {
    int integer;
    char string[24];
}RECORD;

#define NRECORDS (100)

int main(int argc,char**argv)
{
    RECORD record,*mapped;
    int i,f;
    FILE *fp;

    //打开初始化文件
    fp = fopen("record.dat","w+");
    for (i=0;i<NRECORDS;i++)
    {
        record.integer = i;
        sprintf(record.string,"RECORD-%d",i);
        fwrite(&record,sizeof(record),1,fp);
    }
    fclose(fp);

    //把第43条记录中的整数值由43修改为143,并把它写入第43条记录中的字符串
    fp = fopen("record.dat","r+");
    fseek(fp,43*sizeof(record),SEEK_SET);
    fread(&record,sizeof(record),1,fp);

    record.integer = 143;
    sprintf(record.string,"RECORD-%d",record.integer);

    fseek(fp,43*sizeof(record),SEEK_SET);
    fwrite(&record,sizeof(record),1,fp);
    fclose(fp);

    //把这些记录映射到内存中,然后访问第43条记录,把它的整数值修改为243
    f = open("record.dat",O_RDWR);
    mapped = (RECORD*)mmap(0,NRECORDS*sizeof(record),PROT_READ|PROT_WRITE,MAP_SHARED,f,0);
    mapped[43].integer = 243;
    sprintf(mapped[43].string,"RECORD-%d",mapped[43].integer);

    msync((void*)mapped,NRECORDS*sizeof(record),MS_ASYNC);
    munmap((void*)mapped,NRECORDS*sizeof(record));
    close(f);

    exit(0);

    return 0;
}

#include <unistd.h>
#include <stdio.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>

int main(int argc,char**argv)
{
    int fd; //文件描述符
    char* mapped, *p;
    int flength      = 1024;
    void *start_addr = 0;

    if (argc < 2)
    {
        printf("argc less than 2\n");
        exit(-1);
    }

    fd = open(argv[1],O_RDWR|O_CREAT,S_IRUSR|S_IWUSR);
    flength = lseek(fd,1,SEEK_END);
    write(fd,"\0",1); //在文件末尾添加一个空字符
    lseek(fd,0,SEEK_SET);
    mapped = mmap(start_addr,flength,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
    printf("%s\n",mapped);

    while((p=strstr(mapped,"test")))
    {
        memcpy(p,"map",3);
        p+=3;
    }
    munmap((void*)mapped,flength);
    close(fd);

    return 0;
}

转载:http://blog.csdn.net/zhangzhebjut/article/details/39100373

时间: 2024-12-14 19:28:07

Linux进程间通信--内存映射的相关文章

linux 进程间通信——内存共享映射mmap和munmap

IPC三种通信机制是指:信号量.共享内存.消息队列, 信号量:通过操作系统中的PV操作来实现: 共享内存:申请一块内存,进程A往共享内存中写,其他的进程就可以通过读出共享内存中的内容来获取进程A所传送的信息: 消息队列:创建一个消息队列,进程A往队列里面写,那么进程B通过读队列中的容来获取进程A传送的信息. mmap可以把磁盘文件的一部分直接映射到内存,这样文件中的位置直接就有对应的内存地址,对文件的读写可以直接用指针来操做而不需要read/write函数. #include <sys/mman

linux mmap 内存映射【转】

转自:http://blog.csdn.net/xyyangkun/article/details/7830313 [-] mmap vs readwritelseek mmap vs malloc mmap共享内存进程通信 总结 http://www.perfgeeks.com/?p=723 mmap() vs read()/write()/lseek() 通过strace统计系统调用的时候,经常可以看到mmap()与mmap2().系统调用mmap()可以将某文件映射至内存(进程空间),如此

(转)Linux下内存映射文件的用法简介

简介: 内存映射文件与虚拟内存有些类似,通过内存映射文件可以保留一个地址空间的区域,同时将物理存储器提交给此区域,只是内存文件映射的物理存储器来自一个已经存在于磁盘上的文件,而非系统的页文件,而且在对该文件进行操作之前必须首先对文件进行映射,就如同将整个文件从磁盘加载到内存.由此可以看出,使用内存映射文件处理存储于磁盘上的文件时,将不必再对文件执行I/O操作(虚拟内存需要从磁盘加在到物理内存中),这意味着在对文件进行处理时将不必再为文件申请并分配缓存,所有的文件缓存操作均由系统直接管理,由于取消

Linux IPC实践(8) --共享内存/内存映射

概述 共享内存区是最快的IPC形式.一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据(如图). 共享内存 VS. 其他IPC形式 用管道/消息队列传递数据 用共享内存传递数据 共享内存生成之后,传递数据并不需要再走Linux内核,共享内存允许两个或多个进程共享一个给定的存储区域,数据并不需要在多个进程之间进行复制,因此,共享内存的传输速度更快! mmap内存映射 将文件/设备空间映射到共享内存区 #incl

Linux进程间通信--mmap共享内存(一)

共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式.两个不同进程A.B共享内存的意思是,同一块物理内存被映射到进程A.B各自的进程地址空间.进程A可以即时看到进程B对共享内存中数据的更新,反之亦然.由于多个进程共享同一块内存区域,必然需要某种同步机制,互斥锁和信号量都可以. 采用共享内存通信的一个显而易见的好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝.对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而共享内存则只拷贝两次数据[1]:一次从输入

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

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

Linux进程间通信--共享内存

一.共享内存定义 (百度百科)共享内存指在多处理器的计算机系统中,可以被不同中央处理器访问的大量内存.由于多个CPU需要快速访问存储器,这样就要对存储器进程缓存.任何一个缓存的数据被更新后,由于其他处理器也可能要存取,共享内存就需要立即更新,否则,不同的处理器可能用到不同的数据. 在Linux系统中,共享内存允许一个进程或多个进程共享一个给定的存储区(共享内存).不同进程之间共享的内存通常安排为同一段物理地址.进程可以将同一段共享内存连接到它们自己的地址空间中,所有进程都可以访问共享内存中的地址

Linux - 内存映射

一  内存映射概述 从原理上讲,Linux系统利用已有的存储管理机制可以很自然的实现进程间的共享存储.对于一段物理存储空间,只需通过进程的虚存管理机构就可以映射到各自的3G用户地址空间中.通过这种映射,在不同进程看来"私有"的数据事实上是同一段内存单元,它们被这些不同的进程所共享. 在Linux系统实际运行时,内存中的页面要经常被换入或换出,共享存储区中的页面也不例外.一般而言,内存页面的换入/换出过程采用两种方式进行: 1. 普通页面因长时间未得到访问而被内核线程kswaps在系统空

嵌入式 Linux进程间通信(八)——共享内存

嵌入式 Linux进程间通信(八)--共享内存 一.共享内存 共享内存允许两个或更多进程共享给定的内存区,数据不需要在不同进程间进行复制,是最快的进程间通信方式.使用共享内存唯一需要注意的是多个进程之间对给定存储区的同步访问,但共享内存本身没有提供同步机制,通常使用信号量来实现对共享内存访问的同步. 共享内存编程流程:创建共享内存.映射共享内存.使用共享内存.撤销映射操作.删除共享内存 1.创建共享内存 #include <sys/ipc.h> #include <sys/shm.h&g