使用mmap实现文件映射

1 文件映射

传统文件访问方式是, 首先用open系统调用打开文件, 然后使用read, write以及lseek等调用进行顺序或者随即的I/O. 这种方式是非常低效的, 每一次I/O操作都需要一次系统调用. 另外, 如果若干个进程访问同一个文件, 每个进程都要在自己的地址空间维护一个副本, 浪费了内存空间. 系统调用mmap()可以将某文件映射至内存(进程空间),如此可以把对文件的操作转为对内存的操作,以此避免更多的lseek()与read()、write()操作,这点对于大文件或者频繁访问的文件而言尤其受益。

文件映射一方面给用户提供了一组措施, 好似用户将文件映射到自己地址空间的某个部分, 使用简单的内存访问指令读写文件;另一方面, 它也可以用于内核的基本组织模式, 在这种模式种, 内核将整个地址空间视为诸如文件之类的一组不同对象的映射.

2Linux中 VM的实现

  一个进程应该包括一个mm_struct(memory manage struct), 该结构是进程虚拟地址空间的抽象描述, 里面包括了进程虚拟空间的一些管理信息: start_code, end_code, start_data, end_data, start_brk, end_brk等等信息. 另外, 也有一个指向进程虚存区表(vm_area_struct: virtual memory area)的指针, 该链是按照虚拟地址的增长顺序排列的. 在Linux进程的地址空间被分作许多区(vma), 每个区(vma)都对应虚拟地址空间上一段连续的区域, vma是可以被共享和保护的独立实体, 这里的vma就是前面提到的内存对象. 下面是vm_area_struct的结构, 其中, 前半部分是公共的, 与类型无关的一些数据成员, 如: 指向mm_struct的指针, 地址范围等等, 后半部分则是与类型相关的成员, 其中最重要的是一个指向vm_operation_struct向量表的指针vm_ops, vm_pos向量表是一组虚函数, 定义了与vma类型无关的接口. 每一个特定的子类, 即每种vma类型都必须在向量表中实现这些操作. 这里包括了: open, close, unmap, protect, sync, nopage, wppage, swapout这些操作.

  struct vm_area_struct {
  /*公共的, 与vma类型无关的 */
  struct mm_struct * vm_mm;
  unsigned long vm_start;
  unsigned long vm_end;
  struct vm_area_struct *vm_next;
  pgprot_t vm_page_prot;
  unsigned long vm_flags;
  short vm_avl_height;
  struct vm_area_struct * vm_avl_left;
  struct vm_area_struct * vm_avl_right;
  struct vm_area_struct *vm_next_share;
  struct vm_area_struct **vm_pprev_share;
  /* 与类型相关的 */
  struct vm_operations_struct * vm_ops;
  unsigned long vm_pgoff;
  struct file * vm_file;
  unsigned long vm_raend;
  void * vm_private_data;
  };
  vm_ops: open, close, no_page, swapin, swapout……

3 Linux-mmap介绍

Linux提供了内存映射函数mmap, 它把文件内容映射到一段内存上(准确说是虚拟内存上), 通过对这段内存的读取和修改, 实现对文件的读取和修改。mmap函数是unix/linux下的系统调用,来看《Unix Netword programming》卷二12.2节对mmap的介绍:

  The mmap function maps either a file or a Posix shared memory object into the address space of a process.We use this function for three purposes:
  1. with a regular file to provide memory-mapped I/O
  2. with special files to provide anonymous memory mappings
  3. with shm_open to provide Posix shared memory between unrelated processes
  mmap系统调用并不是完全为了用于共享内存而设计的。它本身提供了不同于一般对普通文件的访问方式,进程可以像读写内存一样对普通文件的操作。而 Posix或系统V的共享内存IPC则纯粹用于共享目的,当然mmap()实现共享内存也是其主要应用之一。
  mmap系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以像访问普通内存一样对文件进行访问,不必再调用read(),write()等操作。

  我们的程序中大量运用了mmap,用到的正是mmap的这种“像访问普通内存一样对文件进行访问”的功能。实践证明,当要对一个文件频繁的进行访问,并且指针来回移动时,调用mmap比用常规的方法快很多。

mmap的实现原理:

1.先通过文件系统定位要映射的文件;

  2.权限检查, 映射的权限不会超过文件打开的方式, 也就是说如果文件是以只读方式打开, 那么则不允许建立一个可写映射;
  3.创建一个vma对象, 并对之进行初始化;
  4.调用映射文件的mmap函数, 其主要工作是给vm_ops向量表赋值;
  5.把该vma链入该进程的vma链表中, 如果可以和前后的vma合并则合并;
  6.如果是要求VM_LOCKED(映射区不被换出)方式映射, 则发出缺页请求, 把映射页面读入内存中.
  

4 mmap使用方法

  用open系统调用打开文件, 并返回描述符fd.
  用mmap建立内存映射, 并返回映射首地址指针start.
  对映射(文件)进行各种操作, 显示(printf), 修改(sprintf).
  用munmap(void *start, size_t lenght)关闭内存映射.
  用close系统调用关闭文件fd.
  注意事项:
  在修改映射的文件时, 只能在原长度上修改, 不能增加文件长度, 因为内存是已经分配好的.
  

具体的函数定义包括:

  (1)void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);
  参数fd为即将映射到进程空间的文件描述字,一般由open()返回,同时,fd可以指定为-1,此时须指定flags参数中的MAP_ANON,表明进行的是匿名映射(不涉及具体的文件名,避免了文件的创建及打开,很显然只能用于具有亲缘关系的进程间通信)。
  len是映射到调用进程地址空间的字节数,它从被映射文件开头offset个字节开始算起。
  prot参数指定共享内存的访问权限。可取如下几个值的或:PROT_READ(可读),PROT_WRITE(可写),PROT_EXEC(可执行),PROT_NONE(不可访问)。
  flags由以下几个常值指定:MAP_SHARED, MAP_PRIVATE, MAP_FIXED。其中,MAP_SHARED,MAP_PRIVATE必选其一,而MAP_FIXED则不推荐使用。
  如果指定为MAP_SHARED,则对映射的内存所做的修改同样影响到文件。如果是MAP_PRIVATE,则对映射的内存所做的修改仅对该进程可见,对文件没有影响。
  offset参数一般设为0,表示从文件头开始映射。

  参数addr指定文件应被映射到进程空间的起始地址,一般被指定一个空指针,此时选择起始地址的任务留给内核来完成。函数的返回值为最后文件映射到进程空间的地址,进程可直接操作起始地址为该值的有效地址。


(2)munmap(void * start, size_t length):

  该调用可以看作是 mmap的一个逆过程. 它将进程中从start开始length长度的一段区域的映射关闭, 如果该区域不是恰好对应一个vma, 则有可能会分割几个或几个vma.

  

(3)msync(void * start, size_t length, int flags):

  把映射区域的修改回写到后备存储中. 因为munmap时并不保证页面回写, 如果不调用msync, 那么有可能在munmap后丢失对映射区的修改. 其中flags可以是MS_SYNC, MS_ASYNC, MS_INVALIDATE, MS_SYNC要求回写完成后才返回, MS_ASYNC发出回写请求后立即返回, MS_INVALIDATE使用回写的内容更新该文件的其它映射. 该系统调用是通过调用映射文件的sync函数来完成工作的.
  
下面这个例子显示了把文件映射到内存的方法
#include <sys/mman.h> /* for mmap and munmap */
  #include <sys/types.h> /* for open */
  #include <sys/stat.h> /* for open */
  #include <fcntl.h>     /* for open */
  #include <unistd.h>    /* for lseek and write */
  #include <stdio.h>
  int main(int argc, char **argv)
  {
  int fd;
  char *mapped_mem, * p;
  int flength = 1024;
  void * start_addr = 0;
  fd = open(argv[1], O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
  flength = lseek(fd, 1, SEEK_END);
  write(fd, "\0", 1); /* 在文件最后添加一个空字符,以便下面printf正常工作 */
  lseek(fd, 0, SEEK_SET);
  mapped_mem = mmap(start_addr, flength, PROT_READ,        //允许读
  MAP_PRIVATE,       //不允许其它进程访问此内存区域
  fd, 0);
  /* 使用映射区域. */
  printf("%s\n", mapped_mem); /* 为了保证这里工作正常,参数传递的文件名最好是一个文本文件 */
  close(fd);
  munmap(mapped_mem, flength);
  return 0;
  }
  编译运行此程序:
  gcc -Wall mmap.c
  ./a.out text_filename
  上面的方法因为用了PROT_READ,所以只能读取文件里的内容,不能修改,如果换成PROT_WRITE就可以修改文件的内容了。又由于 用了MAAP_PRIVATE所以只能此进程使用此内存区域,如果换成MAP_SHARED,则可以被其它进程访问
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值