补充一点:题主可能问的是mmap的原理,但是我原来答的是为什么要提供mmap这个接口。
先说原理部分吧:原理部分就是相当于把文件内容映射成一个内存页,当访问的对应的内存页的时候,触发缺页中断,然后由操作系统走文件系统驱动去读取文件。这个过程中,操作系统和文件系统都全程参与了,并没有越过操作系统或者文件系统。
下面是我原来的回答,就是解释为什么要有mmap:
要知道,Windows的文件系统驱动,默认都是PAGING_IO模式,按道理说Windows上的文件系统访问,默认都是mmap形式的变种,那么为什么各个操作系统又额外提供一套mmap的API呢?
我们正常读写一个文件,用户代码通常需要先申请一块内存,然后把这块内存下发给内核,然后通过文件系统代码读、写文件。但是这么做有一个性能问题。用户代码的这块内存,通常是不对齐的(不对齐到4K页),那么当文件系统要通过DMA的方式访问磁盘的时候,这块内存通常是不能直接作为DMA buffer下发到硬件驱动里。于是,用户要读一个文件,就需要内核先准备一个对齐的内存下发给驱动(也就是block cache),驱动再下发给硬件,读好数据以后,再从内核的这个内存里复制到用户的内存,需要1-2次内存拷贝(memcpy),如果数据量比较巨大的话,拷贝的开销是很大的。
而mmap就不一样,mmap的内存是由内核初始化的,用户代码只是拿到了一个fd,内核初始化这块内存以后,是可以直接交给文件系统,甚至是块设备驱动使用的,加上Linux的零拷贝(zero copy)技术,可以减少或者避免memcpy动作。同时这块内存又是对用户直接可见的,从内核态切换到用户态的过程中,不需要执行任何拷贝动作。
举个实际例子:
mmap的回写的调用栈(基于当前Linux内核代码),以EXT4文件系统为例,如果要刷一下mmap的数据,那么调用栈是:
generic_writepages -> write_cache_pages -> __writepage -> ext4_writepage -> ext4_bio_write_page
这期间内存的脏页(dirty-page),也是用户可以直接访问的内存页,是被直接放到bio队列里下发下去的,中间没有memcpy的动作,而如果用户调用的是普通的write系统调用,那么就存在着至少一次memcpy(系统调用从用户态到内核态的过程),这里的性能是有损耗的。
另外,需要提醒的是,不同文件系统/操作系统对mmap的优化程度不一样,但大体思路上都是一致的,通过按页对齐的方式分配内存,减少内存拷贝的开销。
当然了,减小memcpy开销是mmap的最初目的,DMA的优化肯定是mmap后来的行为,但目前看到的操作系统都会针对mmap做一些专门的DMA优化,提高读写性能。
如果申请一个大内存,把文件内容全部复制到内存里操作,是不是跟mmap效果一样?答案是不一样的。大内存并非是4K对齐的,虽然操作内存缓存的过程中性能是很高,但是如果需要刷缓存的时候,仍然需要从用户态到内核态做一次拷贝、转换,性能损失仍然不小。
所以mmap能解决的性能问题包括:
1. 用户态到内核态的过程中,内存拷贝问题
2. 内核态把脏数据写回到块设备的过程中,内存拷贝的问题
3. 4K对齐问题
4. 零拷贝(实际上是通过映射)问题
这个问题我看见了,本来觉得没必要回答,结果这几天总有不同的人发来回答邀请,好几个还是我比较熟悉的经常关注我的人,鉴于此我决定还是说两句吧(不想看我发牢骚的就直接跳到下面的分割线)。真不是我挑三拣四,也不是我高冷不想回答,只是这种就一个标题,然后标题还问的不明不白的问题,真的很容易就忽略而刷过去了。这应该也是这个问题被回答的较少的原因之一。
我看到 @北极 老兄已经回答这个问题了,他回答的对于这个问题来说已经算解释的够多的了,而且我看他也是靠“猜”来估计这个问题是想问什么的,冲这份辛苦大家多给点点赞吧。有时候大家可以站在我们这些经常回答问题的人的立场想一下,你就看到这么一个问题,问题题目写着“mmap 内存映射,是越过了操作系统,直接通过内存访问文件吗?”读完这句话脑子里的第一反应就是“什么意思??[手动黑人问号]”,然后我试图通过打开问题描述获取一些细节,了解一下问题的背景,通过上下文猜一下这个问题想问什么,结果什么细节描述都没有。那么单凭这个标题,我给你们拆开分析一下我看到的是什么。一共就两个逗号隔开了三段,我们一一看:
我的内心OS: 嗯……这个问题想问关于mmap内存映射的问题。
我的内心OS: mmap本来就是操作系统实现的一个系统调用,怎么绕过操作系统?是单独实现一个没有操作系统的mmap操作?没有操作系统没有进程管理、内存管理你还搞mmap干嘛?
我的内心OS: 文件本来就是操作系统中文件系统模块提出来的概念,而访问文件必然要通过内存来访问,没有内存CPU怎么对文件进行读写?
所以整个读下来后我就想到一句回答,那就是“mmap越不过操作系统,是通过内存访问文件。”我觉得这样回答没什么意义,所以干脆就把问题忽略了,这就是我看到这个问题或类似这样的问题后脑子里的一阵“操作”。
我不是针对这个问题的提问者,我只是说这种具有普遍性的现象。因为我发现当一个人有一个简单质朴的问题A的时候,通常都不会直接问出问题A,而是会基于自己一知半解的理解把问题转变为问题C来问。就像这个问题,我估计就是想问mmap的工作原理是什么?或者说mmap是怎么读写文件中的数据的?如果你这么问,问题至少相对较清晰(虽然这么问会显得问题有些大,要不要解释mmap的原理,要解释多深入多透彻,这些可能还要看各位回答者们看到问题时的兴致有多高了)。但是人们就是不愿意直接问出问题A,就要加入自己的“理解”更深入的问。加入自己的理解没有问题,但是你要保证你的“理解”能让别人产生共鸣,要么你基于通识的理解,这样比较容易达成共识。要么你详细描述一下你比较private的个人理解过程,以尽力让别人有可能和你产生共鸣。如果你两个都不占,那这种问题的转换就会很容易把一个问题转变成一个不知所云的问题。
为了避免管理员又把我的回答打上答非所问的标签给折叠掉(说多了都是泪),下面是一段机械性的回答:
mmap在类Unix系统(以及其它实现了此操作的系统)中是一个用于memory-mapped file I/O的其中一个系统调用。它的作用就是将文件中的一段内容和一段内存逻辑地址关联,达到一个先占位但什么都不做的目的。就好像你要安排会议室的座位,你画了一个会议室图,将与会嘉宾的名字按照“礼仪风俗”排列标记在图上的座位上。这就是mmap的主要目的,这样后面如果再有人想坐某个座位时你可以告诉他“这个位子已经占了”。注意这时候你可能连会议室的椅子都还没摆呢。内存映射后续的操作就好像是谁来给谁座一样,等到开会了,你看谁来了,来一个在相应位置摆好一把椅子,把水放好,请人坐下。对应计算机的操作就是你访问哪块映射好的文件内容(哪个客人来了)的时候,就通过一个缺页异常中断(招乎你)分配一段实际的物理内存(把椅子搬来)把这块内容加载到这块内存中(请人坐下)。
如果你只是读不修改,那事后释放回收内存就行。如果你修改了这块内存,那么这块内存就会标记为“脏”,表示有改动的数据与文件的原内容不一样了。脏页会被定期扫描并写回到原文件所在的存储器上,或者等到最后访问结束释放内存页之前也会检查是否需要回写,需要回写的就先回写后再释放。
这种技术在操作系统中其实很常用,不是光用户层通过mmap系统调用来使用。比如即使是程序执行的时候也不是所有的程序(程序、共享库、数据文件等)都第一时间全部一股脑加载到内存,大部分其实都只是映射。仔细想想你们打游戏的时候是不是经常看到让人头疼的“Loading ...”的字样,有时开个门都loading一下,这loading的是什么,一部分就是下一个场景要执行的程序(还有贴图、数据之类的),这段程序之前只是映射好了(或者被交换出去了),它们还在外存储器中没有实际存在在内存里,在你需要这段内容的时候才加载进来。
所以这里肉眼可见的就已经用到了内存管理(地址映射、缺页处理等)、I/O(加载、回写文件等)等操作系统的概念。再看看这个问题的题目,不知道此刻的大家能不能理解我们。