mmap
Contents
mmap
mmap 函数是 unix/linux 下的系统调用
mmap() 函数用来将文件或者设备映射到内存中。
mmap 的特点是按需调页。最开始只申请 vma(Virtual Memory Area),并不调真正的页。当对某些页进行引用的时候,会引起一个缺页中断,再将页面调入到内存当中,这样避免了对内存的浪费。
内存映射, 是将用户空间的一段内存区域映射到内核空间, 映射成功后,用户对这段内存区域的修改可以直接反映到内核空间,同样,内核空间对这段区域的修改也直接反映用户空间。 那么对于 内核空间 <—-> 用户空间 两者之间需要大量数据传输等操作的话效率是非常高的。
mmap 是一种内存映射文件的方法, 即将一个文件或者其它对象映射到进程的地址空间, 实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上, 即完成了对文件的操作而不必再调用 read, write 等系统调用。相反,内核空间对这段区域的修改也直接反映用户空间,从而可以实现不同进程间的文件共享
mmap 优点
对文件的读取操作跨过了页缓存, 减少了数据的拷贝次数, 用内存读写取代I/O读写, 提高了文件读取效率。
实现了用户空间和内核空间的高效交互方式。两空间的各自修改操作可以直接反映在映射的区域内, 从而被对方空间及时捕捉。
提供进程间共享内存及相互通信的方式。不管是父子进程还是无亲缘关系的进程,都可以将自身用户空间映射到同一个文件或匿名映射到同一片区域。从而通过各自对映射区域的改动, 达到进程间通信和进程间共享的目的。 同时, 如果进程A和进程B都映射了区域C, 当A第一次读取C 时通过缺页从磁盘复制文件页到内存中;但当B 再读C 的相同页面时, 虽然也会产生缺页异常,但是不再需要从磁盘中复制文件过来, 而可直接使用已经保存在内存中的文件数据。
可用于实现高效的大规模数据传输。 内存空间不足,是制约大数据操作的一个方面,解决方案往往是借助硬盘空间协助操作,补充内存的不足。但是进一步会造成大量的文件I/O操作,极大影响效率。这个问题可以通过mmap映射很好的解决。换句话说,但凡是需要用磁盘空间代替内存的时候, mmap 都可以发挥其功效。
操作文件就像操作内存一样,适合于对较大文件的读写。
用户也可创建匿名内存映射, 该映射没有对应的文件, 可用于存放程序数据。在 Linux中,若通过 malloc()请求一大块内存,C 运行库将创建一个匿名内存映射,而不使用堆内存。“大块” 意味着比阈值 MMAP_THRESHOLD还大,缺省为128KB,可通过 mallopt()调整。
mmap将一个文件或者其它对象映射进内存。文件被映射到多个页上,如果文件的大小不是所有页的大小之和,最后一个页不被使用的空间将会清零。munmap执行相反的操作,删除特定地址区域的对象映射。
prot: 期望的内存保护标志,不能与文件的打开模式冲突。是以下的某个值,可以通过or运算合理地组合在一起
PROT_EXEC //页内容可以被执行
PROT_READ //页内容可以被读取
PROT_WRITE //页可以被写入
PROT_NONE //页不可访问
mmap 和常规文件操作(页缓存)的区别
对 linux 文件系统不了解的朋友, 请参阅我之前写的博文《从内核文件系统看文件读写过程》, 我们首先简单的回顾一下常规文件系统操作 (调用read/fread等类函数) 中,函数的调用过程:
- 进程发起读文件请求。
- 内核通过查找进程文件符表,定位到内核已打开文件集上的文件信息,从而找到此文件的inode。
- inode 在 address_space 上查找要请求的文件页是否已经缓存在页缓存中。如果存在, 则直接返回这片文件页的内容。
- 如果不存在, 则通过 inode 定位到文件磁盘地址, 将数据从磁盘复制到页缓存。之后再次发起读页面过程,进而将页缓存中的数据发给用户进程。
总结来说, 常规文件操作为了提高读写效率和保护磁盘,使用了页缓存机制。这样造成读文件时需要先将文件页从磁盘拷贝到页缓存中, 由于页缓存处在内核空间, 不能被用户进程直接寻址, 所以还需要将页缓存中数据页再次拷贝到内存对应的用户空间中。这样, 通过了两次数据拷贝过程, 才能完成进程对文件内容的获取任务。写操作也是一样,待写入的 buffer 在内核空间不能直接访问,必须要先拷贝至内核空间对应的主存, 再写回磁盘中 (延迟写回) , 也是需要两次数据拷贝。
而使用 mmap 操作文件中, 创建新的虚拟内存区域和建立文件磁盘地址和虚拟内存区域映射这两步,没有任何文件拷贝操作。而之后访问数据时发现内存中并无数据而发起的缺页异常过程,可以通过已经建立好的映射关系, 只使用一次数据拷贝,就从磁盘中将数据传入内存的用户空间中,供进程使用。
总而言之,常规文件操作需要从磁盘到页缓存再到用户主存的两次数据拷贝。而mmap操作文件,只需要从磁盘到用户主存的一次数据拷贝过程。说白了,mmap的关键点是实现了用户空间和内核空间的数据直接交互而省去了空间不同数据不通的繁琐过程。因此mmap效率更高。
mmap的缺点:
- 文件如果很小,比如60bytes,由于在内存当中的组织都是按页组织的,将文件调入到内存当中是一个页4k,这样其他的 4096-60=4036 bytes 的内存空间就会浪费掉了。
- 而且文件无法完成拓展,因为mmap到内存的时候,你所能够操作的范围就确定了,无法增加文件的长度。
- 如果系统频繁的使用mmap操作,而且每次mmap的size都不同,那么就会使得内存可能缺少足够的连续的内存空间。
那又怎么了,人家就是用来操作大型数据的。 当mmap的文件是page size的整数倍的时候,使用mmap调用看起来是最合适的,不会造成浪费。 你用其他方式来进行大量数据传递简直不理智。
怎么用 1、开启文件空间映射函数mmap()
|
|
解除映射函数munmap()
|
|
|
|
VMA
Linux将地址空间中的区域称为 Virtual Memory Area, 简称VMA,使用struct vm_area_struct来描述。
进程地址空间中,我们常见的代码段,数据段,bss段等,实际上都是一段地址空间区域。Linux将地址空间中的区域称为 Virtual Memory Area, 简称VMA,使用 struct vm_area_struct来描述。
mmap用于内存映射,也就是将一段区域映射到自己的进程地址空间中,分为两种:
文件映射: 将文件区域映射到进程空间,文件存放在存储设备上; 匿名映射:没有文件对应的区域映射,内容存放在物理内存上; 同时,针对其他进程是否可见,又分为两种:
私有映射:将数据源拷贝副本,不影响其他进程; 共享映射:共享的进程都能看到; 根据排列组合,就存在以下几种情况了:
私有匿名映射: 通常分配大块内存时使用,堆,栈,bss段等; 共享匿名映射:常用于父子进程间通信,在内存文件系统中创建/dev/zero设备; 私有文件映射:常用的比如动态库加载,代码段,数据段等; 共享文件映射:常用于进程间通信,文件读写等;
https://www.cnblogs.com/LoyenWang/p/12037658.html https://www.cnblogs.com/huxiao-tee/p/4660352.html http://blog.csdn.net/dlutbrucezhang/article/details/9080173
http://mengqiucheng.blog.51cto.com/3917331/739359
《Unix Network programming》
Author -
LastMod 2017-02-10