Linux 中 mmap() 函数的内存映射问题理解?

mmap()这个函数到底是把 硬盘里的数据映射到物理内存(还是虚拟内存?)中,还是把物理内存中的数据映射到虚拟内存中? 有点晕,我猜测是前者,但不确定…
关注者
727
被浏览
293,879
登录后你可以
不限量看优质回答私信答主深度交流精彩内容一键收藏

你的脑子是晕的,被听到的信息搞晕了头脑,我帮你洗一次脑吧。


1. 请放弃虚拟内存这个概念,那个是广告性的概念,在开发中没有意义。开发中只有虚拟空间的概念,进程看到的所有地址组成的空间,就是虚拟空间。虚拟空间是某个进程对分配给它的所有物理地址(已经分配的和将会分配的)的重新映射。

2. mmap的作用,在应用这一层,是让你把文件的某一段,当作内存一样来访问。内核和驱动如何实现的,性能高不高这些问题,这层语义上没有承诺。你基于功能决定怎么用它就好了,少胡思乱想。


有了以上两个,你就可以写好程序了。下面介绍一下Linux的实现细节,权当好玩,如果你搞不清楚前面两条,后面的就不要看,否则你又乱掉了。


3. mmap的工作原理,当你发起这个调用的时候,它只是在你的虚拟空间中分配了一段空间,连真实的物理地址都不会分配的,当你访问这段空间,CPU陷入OS内核执行异常处理,然后异常处理会在这个时间分配物理内存,并用文件的内容填充这片内存,然后才返回你进程的上下文,这时你的程序才会感知到这片内存里有数据

4. 驱动每次读入多少页面,页面的分配算法等等,都不是系统的承诺,不能作为你编程的依赖。这就是前面说的:不要胡思乱想

5. 至于swap分区的作用,参考这里:Linux 是怎样使用内存的? - in nek 的回答


基于题主继续的问题,我们接着来解释一下为什么我建议你放弃虚拟内存而使用虚拟空间的概念。


内存,我们通常指向计算机中的DRAM,上面可以保存数据。为了访问内存,我们对内存进行编址,所有编址的集合,组成内存空间。内存的空间,从总线上看到的结果,我们一般称为物理地址空间,图示如下:


你可以看到,物理地址空间不但包括内存,也包括IO,物理空间的大小和地址总线的长度相关,可以远远大于DRAM的实际大小。


CPU发起访问内存的操作,需要经过MMU的地址翻译,这个翻译本质上是一个转换算法pa=f(va),图示如下:

虚拟地址的空间和指令集的地址长度有关,不一定和物理地址长度一致,比如现在的64位处理器,从VA角度看来,可以访问64位的地址,但地址总线长度只有48位,所以你可以访问一个位于2^52这个位置的地址,但通过MMU的转化,这个地址可能只会进入很低的物理地址上(当然,你也可以强行转化到更高的地址上,只是那个地址会访问失效或者被截断使用而已)。


所以,虚拟空间可以很大,但不表示物理内存也需要很大。每个进程有自己的虚拟空间(切换进程的时候切换MMU的翻译表即可),这些虚拟空间可以映射到物理内存的不同或者相同的位置。示意如下:


Linux执行一个程序,这个程序在磁盘上,为了执行这个程序,需要把程序加载到内存中,这时采用的就是mmap,mmap让虚拟空间和文件的内容组成的空间(我这里称为文件空间)对应,类似这样:

上面展示的是mmap之后的效果,但文件的内容在磁盘上是不能被CPU访问的,所以当CPU真的在这个地址上发起读写执行等操作时,OS会进入异常,异常中会调用文件系统把一页或者多页的文件内容加载到物理内存中,这会变成这样:

你可以从/proc/<pid>/maps看到每个进程的mmap状态,下面是一个init(pid=1)进程的maps文件的内容:

这些分段空间后面的那些,就是每个虚拟空间分段对应的文件。这些文件,称为这片虚拟空间的backlog文件,它的作用是当这些内存需要被使用的时候,从磁盘中把对应的文件内容加载到物理内存中。


这个map表中,部分内存是没有backlog文件的,所有不通过mmap某个文件增加到系统中的用户内存,都是这种类型,比如brk系统调用获得的内存(在很多libc的实现中,malloc通过这个系统调用实现内存增量),这种内存对于这个进程来说,称为“匿名内存”,如果你有swap文件,则Linux会给这些内存分一段swap文件作为匿名内存的backlog文件,这样,当系统内存不足的时候,Linux可以放弃掉一部分物理内存,等后续再从backlog文件中加载,这种backlog文件,有可能是有名文件,也可能是“无名”文件(swap),广告角度说,这个无名文件会被称为虚拟内存,但和你关心的用来加载点什么东西的那个“内存”没有什么关系。


[附录1]跳过Cache的方法

Cache可以通过/proc/sys/vm/drop_caches强行释放,写1释放pagecache,2释放dentries和inode,3释放两者。(这个动作不能释放脏页)

如果写不想要Cache,可以在mount的时候加sync,dirsync参数。