1.mmap


  mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的虚拟地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用read,write等系统调用函数。

  mmap()函数返回一个指针ptr,它指向进程虚拟地址空间的一个地址,这样进程就可以直接通过ptr对文件进行读写。

  另外建立映射时并没有数据拷贝,此时和物理内存无关。只有操作ptr进行数据读写的时候,才会把数据读写到物理内存,修改过的脏页面并不会立即更新回文件中,而是有一段时间的延迟,可以调用msync()来强制同步, 这样所写的内容就能立即保存到文件里了。

  使用mmap需要注意的一个关键点是,mmap映射区域大小必须是物理页大小(page_size)的整倍数(32位系统中通常是4k字节)。原因是,内存的最小粒度是页,而进程虚拟地址空间和内存的映射也是以页为单位。为了匹配内存的操作,mmap从磁盘到虚拟地址空间的映射也必须是页,例如映射的文件大小是5000字节,但是需要用两个页,所以实际映射的内存为8192字节。另外如果映射的大小大于文件大小,则操作超出的部分会返回异常。

  映射建立之后,即使文件关闭,映射依然存在,另外如果文件的大小一直在扩张,只要在映射区域范围内的数据,进程都可以合法得到,这和映射建立时文件的大小无关。因为映射的是磁盘的地址,不是文件本身,和文件句柄无关。同时可用于进程间通信的有效地址空间不完全受限于被映射文件的大小,因为是按页映射。

2.相关函数


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

    参数addr表示使用某个特定的内存地址(当前进程的某个虚拟地址),通常传NULL。

    参数length表示映射的内存长度。

    参数prot用于设置访问这段内存的权限。包括:PROT_EXEC(执行)、PROT_READ(读取)、 PROT_WRITE(写入)、 PROT_NONE(不可访问)。

    参数flags用于控制程序对内存段改变的影响。

 

int msync ( void * addr, size_t len, int flags)

    进程在映射空间的对共享内容的改变并不直接写回到磁盘文件中,往往在调用munmap()后才执行该操作。可以通过调用msync()函数来实现磁盘文件内容与共享内存区中的内容一致,即同步操作。

    内存段需要同步的部分由addr和len确定。flags参数控制修改的具体方式,可用的值为:MS_ASYNC(异步写入)/MS_SYNC(同步写入)/MS_INVALIDATE(从文件中读回数据)。

int munmap(void *addr, size_t length);

    该函数用于解除映射关系,释放内存段。

3.mmap和常规文件操作的区别


  常规文件操作,也就是buffer IO,为了提高读写效率保护磁盘,使用了页缓存机制,这样造成读文件时需要先将文件页从磁盘拷贝到页缓存中,由于页缓存处在内核空间,不能被用户进程直接寻址,所以还需要将页缓存中数据页再次拷贝到内存对应的用户空间中。这样,通过了两次数据拷贝过程,才能完成进程对文件内容的获取任务。写操作也是一样,待写入的buffer在内核空间不能直接访问,必须要先拷贝至内核空间对应的主存,再写回磁盘中(延迟写回),也是需要两次数据拷贝。

  而使用mmap操作文件中,创建新的虚拟内存区域和建立文件磁盘地址和虚拟内存区域映射这两步,没有任何文件拷贝操作。而之后访问数据时发现内存中并无数据而发起的缺页异常过程,可以通过已经建立好的映射关系,只使用一次数据拷贝,就从磁盘中将数据传入内存的用户空间中,供进程使用。

  总而言之,常规文件操作需要从磁盘到页缓存再到用户主存的两次数据拷贝。而mmap操控文件,只需要从磁盘到用户主存的一次数据拷贝过程。mmap的关键点是实现了用户空间和内核空间的数据直接交互而省去了空间不同数据不通的繁琐过程。因此mmap效率更高。

4.mmap用于共享内存的两种方式


  一是使用普通文件,多个进程通过映射到同一个普通文件,进行通信。

  二是使用匿名映射,此时用于父子进程中,首先在父进程调用mmap(),之后调用fork(),子进程继承父进程匿名映射后的地址空间,同样也继承mmap()返回的地址,这样,父子进程就可以通过映射区域进行通信了。

5.问题


  问题1:文件大小5000字节,映射大小为10000字节,由于会映射2个页也就是8192字节,所以操作8192字节之内的数据不会发生错误(5000-8192之内会读到全0,并且写这部分内存时不会同步到文件),但是读写8192之后的数据会发生SIGBUS错误。