1、进行虚拟内存结构体

  前文《虚拟内存管理》我们讲到每个进程的结构体task_struct中都有一个内存描述结构体mm_struct。 mm_struct此结构体描述了进程占用虚拟内存的情况,包含每个内存段的起始地址、结束地址。

  如下图,除了固定的代码段、数据段、BSS段、堆、栈,还有一个MMAP 段,此段为程序员自己通过mmap()函数主动生成的一个段,用于将磁盘文档映射到内存中以增加文件访问效率(也可进行进程通信)的一种手段。

  上述每个段都会生成一个VMA结构体。

2、mmap()的使用方法

  void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);

  start :  指向欲映射的内存起始地址,通常设为 NULL,代表让系统自动选定地址,映射成功后返回该地址。

   length:  代表将文件中多大的部分映射到内存。

   prot  :  映射区域的保护方式。可以为以下几种方式的组合:

        PROT_EXEC 映射区域可被执行

        PROT_READ 映射区域可被读取

        PROT_WRITE 映射区域可被写入

        PROT_NONE 映射区域不能存取——需要小于等于文件打开的权限一致

   flags :  影响映射区域的各种特性。在调用mmap()时必须要指定MAP_SHARED 或MAP_PRIVATE。

        MAP_FIXED 如果参数start所指的地址无法成功建立映射时,则放弃映射,不对地址做修正。通常不鼓励用此旗标。

        MAP_SHARED 对映射区域的写入数据会复制回文件内,而且允许其他映射该文件的进程共享。

        MAP_PRIVATE 对映射区域的写入操作会产生一个映射文件的复制,即私人的“写入时复制”(copy on write)对此区域作的任何修改都不会写回原来的文件内容。

        MAP_ANONYMOUS建立匿名映射。此时会忽略参数fd,不涉及文件,而且映射区域无法和其他进程共享。

        MAP_DENYWRITE只允许对映射区域的写入操作,其他对文件直接写入的操作将会被拒绝。

        MAP_LOCKED 将映射区域锁定住,这表示该区域不会被置换(swap)。

   fd    :  要映射到内存中的文件描述符。如果使用匿名内存映射时,即flags中设置了MAP_ANONYMOUS,fd设为-1。有些系统不支持匿名内存映射,则可以使用fopen打开/dev/zero文件,然后对该文件进行映射,可以同样达到匿名内存映射的效果。

   offset:文件映射的偏移量,通常设置为0,代表从文件最前方开始对应,offset必须是PAGE_SIZE的整数倍。

返回值:若映射成功则返回映射区的内存起始地址,否则返回MAP_FAILED(-1),错误原因存于errno 中。

int munmap(void *start, size_t length);——解除mmap的映射

int msync ( void * addr , size_t len, int flags);  ——文件回写

3、mmap()的作用,将文件或者其他对象映射到内存中,使文件操作像通过指针操作内存一样简单快速

  其主要有两个用途,下面我们就从这两个方面来讲述:

  • 内存映射
  • 进程通信

4、内存映射,传统文件访问缓慢,内存映射后操作文件速度加快

  传统文件访问的缺点:

  • 效率低:应用程序通过read,write,ioctl来访问硬件设备,它们都要经过两次的数据拷贝,一次是用户空间和内核空间的数据拷贝,另外一次是内核空间和硬件之间的数据拷贝。
  • 浪费空间: 访问文件的传统方法是用open打开它们, 如果有多个进程访问同一个文件, 则每一个进程在自己的地址空间都包含有该文件的副本,这不必要地浪费了存储空间.  

    下图说明了两个进程同时读一个文件的同一页的情形. 系统要将该页从磁盘读到高速缓冲区中, 每个进程再执行一个存储器内的复制操作将数据从高速缓冲区读到自己的地址空间. 

  

  

通过将磁盘文件映射到内存中,从而使文件访问更加高效简单:
  众多内存数据库如MongoDB操作数据,就是把文件磁盘内容映射到内存中进行处理

5、共享内存实现进行通信

进程A和进程B都将该页映射到自己的地址空间, 当进程A第一次访问该页中的数据时, 它生成一个缺页中断. 内核此时读入这一页到内存并更新页表使之指向它.以后,

当进程B访问同一页面而出现缺页中断时, 该页已经在内存, 内核只需要将进程B的页表登记项指向次页即可。

6、共享内存比管道的优势:共享内存比管道和消息队列效率高

  共享内存和消息队列,FIFO,管道传递消息的区别:
  ——消息队列,FIFO,管道的消息传递方式一般为
    1:服务器得到输入
    2:通过管道,消息队列写入数据,通常需要从进程拷贝到内核。
    3:客户从内核拷贝到进程
    4:然后再从进程中拷贝到输出文件
  上述过程通常要经过4次拷贝,才能完成文件的传递。
  ——共享内存只需要
    1:从输入文件到共享内存区
    2:从共享内存区输出到文件

下面是举例:

7、mmap注意事项

 

 最终被映射文件的内容的长度不会超过文件本身的初始大小,即映射不能改变文件的大小。文件被映射部分而不是整个文件决定了进程能够访问的空间大小

 8、mmap的实现

  • 映射时,在虚拟地址空间中为你创建虚拟映射区域

    进程在用户空间调用库函数mmap

    在当前进程的虚拟地址空间中,寻找一段空闲的满足要求的连续的虚拟地址
    为此虚拟区分配一个vm_area_struct结构,接着对这个结构的各个域进行了初始化

  • 调用内核空间的系统调用函数mmap,实现文件物理地址和进程虚拟地址的一一映射关系

    完成以上两部后,这片虚拟地址并没有任何数据关联到主存中。

  • 进程发起对这片映射空间的访问,引发缺页异常,实现文件内容到物理内存(主存)的拷贝

  进程的读或写操作访问虚拟地址空间这一段映射地址,通过查询页表,
    发现这一段地址并不在物理页面上。因为目前只建立了地址映射,真正
    的硬盘数据还没有拷贝到内存中,因此引发缺页异常。处理缺页异常时
    发现对于的数据还没有写入内存,于是会将文件数据换入内存。
  调页过程先在交换缓存空间(swap cache)中寻找需要访问的内存页,如
    果没有则调用nopage函数把所缺的页从磁盘装入到主存中