问题

mmap 内存映射,是越过了操作系统,直接通过内存访问文件吗?

回答
mmap 内存映射,是不是绕过了操作系统,直接用内存访问文件?

简单来说,不是。mmap 并没有 绕过操作系统,而是充分利用了操作系统的能力来提供一种高效的文件访问方式。它也不是直接用内存访问文件,而是通过将文件的内容映射到进程的虚拟地址空间中,然后我们就可以像操作内存一样操作这块映射的区域。

要弄清楚这一点,我们需要理解几个核心概念:

1. 操作系统在文件 I/O 中的角色

传统的文件读写方式(比如 `read()` 和 `write()` 函数)是这样的一个流程:

用户空间 (User Space): 你的程序运行在这个区域。
内核空间 (Kernel Space): 操作系统核心代码运行在这个区域。

当你调用 `read(fd, buffer, size)` 时,会发生以下事情:

1. 系统调用: 程序从用户空间切换到内核空间,执行 `read` 系统调用。
2. 磁盘读取: 操作系统负责打开文件(通过文件描述符 `fd`),然后从磁盘上读取数据块。
3. 内核缓冲区: 读取到的数据 首先会被放入内核的一个缓冲区(也称为页缓存 Page Cache)。这是操作系统为了提高效率而做的。如果数据已经在页缓存中,就不用再次从磁盘读取。
4. 数据复制: 操作系统将内核缓冲区中的数据 复制 到你提供的用户空间缓冲区 `buffer` 中。
5. 用户空间访问: 程序才能在用户空间访问到这部分数据。

你看,这个过程涉及了两次数据复制(一次从磁盘到内核缓冲区,一次从内核缓冲区到用户缓冲区),以及用户空间和内核空间之间的上下文切换。对于大量的小块读写,这会带来一定的开销。

2. mmap 的“魔法”:虚拟内存与页表

mmap 的核心在于它利用了虚拟内存的机制。我们来拆解一下:

虚拟内存: 现代操作系统都使用虚拟内存。每个进程都有自己独立的虚拟地址空间,这个空间很大,远远超过了物理内存的大小。进程看到的地址是虚拟地址。
物理内存 (RAM): 实际的内存条。
页 (Page): 内存和磁盘上的数据都是以固定大小的块(通常是 4KB)来管理的,这些块称为“页”。
页表 (Page Table): 操作系统为每个进程维护一个页表,这个表将虚拟地址映射到物理地址。当进程访问一个虚拟地址时,CPU 会通过页表查找对应的物理地址。
缺页中断 (Page Fault): 如果进程访问的虚拟地址对应的物理内存页 不在物理内存中(可能是因为数据还没加载进来,或者被换出去了),CPU 会触发一个“缺页中断”。操作系统会捕获这个中断,找到需要的数据所在的磁盘位置,将其加载到物理内存的一个空闲页中,更新页表,然后让进程继续执行,仿佛什么都没发生一样。

3. mmap 如何工作

`mmap(addr, length, prot, flags, fd, offset)` 这个函数做了什么?

1. 建立映射: 当你调用 `mmap` 时,你是在告诉操作系统:“我想把文件 `fd` 从 `offset` 开始的 `length` 大小的内容,映射到我的进程的虚拟地址空间中的一个区域(由 `addr` 指定,或者让系统自己分配)。”
2. 不立即读取: 重点来了! `mmap` 并不会立即将文件内容读取到物理内存中。它只是在你的进程的页表中做了一个标记,说明这个虚拟地址范围对应着文件 `fd` 的哪一部分。
3. 像访问内存一样访问: 现在,你可以直接通过这个映射的虚拟地址来访问文件内容了。例如,你可以写 `char c = mapped_pointer[i];`。
4. 缺页中断启动读取: 当你第一次访问这个映射区域的某个地址时,就会发生缺页中断。
操作系统捕获到缺页中断。
它会根据你传递给 `mmap` 的信息(文件 `fd`、`offset`、`length`)以及发生中断的虚拟地址,计算出该访问的是文件的哪一部分。
操作系统从磁盘上读取这一小块(一页)文件数据,将其载入到物理内存的一个页中。
操作系统更新进程的页表,将这个虚拟地址映射到刚刚加载到物理内存的那个页。
然后,CPU 重新执行刚才失败的内存访问指令,这次就能成功了。

4. mmap 的优势

减少数据复制: 传统 `read` 需要将数据复制到内核缓冲区,再复制到用户缓冲区。mmap 的关键优势在于,当文件数据被映射到虚拟地址空间后,操作系统可以直接将内核页缓存中的数据页与进程的虚拟地址关联起来,避免了用户空间和内核空间之间的数据复制。 你直接操作的就是内核页缓存中的数据。
按需加载 (Lazy Loading): 文件内容只在你真正访问到的时候才会被加载到物理内存。这对于访问大文件中的一小部分非常有益,避免了不必要的内存占用和磁盘 I/O。
共享内存: mmap 是实现进程间共享内存的一种常用方式。不同的进程可以映射同一个文件(或匿名的 mmap),这样它们就可以通过操作映射的内存区域来高效地共享数据。
写回机制: 当你修改了映射的内存区域时(如果映射是可写的 `PROT_WRITE`),操作系统会在适当的时候(比如内存压力大、或者调用 `msync()` 时)将修改过的内存页写回磁盘。这个过程也是由操作系统管理的。

总结:不是绕过,而是合作

mmap 并不是 绕过了操作系统。恰恰相反,它是一种非常“聪明”地利用了操作系统提供的虚拟内存管理能力的方式。

操作系统 负责:
管理页表,将虚拟地址映射到物理地址。
处理缺页中断,负责从磁盘读取数据到物理内存。
管理物理内存的分配和回收。
在必要时将修改过的内存页写回磁盘。
处理文件打开、关闭、定位等基础操作。

你的程序 负责:
调用 `mmap` 建立映射关系。
像操作普通内存一样访问映射的虚拟地址。
通过 `msync()` 等函数主动同步,或者等待操作系统自动同步。
调用 `munmap()` 来解除映射。

所以,与其说是“越过了操作系统”,不如说 mmap 是与操作系统紧密协作,通过更底层的内存管理机制来提供一种比传统 `read`/`write` 更高效、更灵活的文件访问手段。它将文件 I/O 的概念“提升”到了内存管理的层面,让我们可以用操作内存的简单方式来处理文件数据。

网友意见

user avatar

补充一点:题主可能问的是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. 零拷贝(实际上是通过映射)问题

user avatar

这个问题我看见了,本来觉得没必要回答,结果这几天总有不同的人发来回答邀请,好几个还是我比较熟悉的经常关注我的人,鉴于此我决定还是说两句吧(不想看我发牢骚的就直接跳到下面的分割线)。真不是我挑三拣四,也不是我高冷不想回答,只是这种就一个标题,然后标题还问的不明不白的问题,真的很容易就忽略而刷过去了。这应该也是这个问题被回答的较少的原因之一。

我看到 @北极 老兄已经回答这个问题了,他回答的对于这个问题来说已经算解释的够多的了,而且我看他也是靠“猜”来估计这个问题是想问什么的,冲这份辛苦大家多给点点赞吧。有时候大家可以站在我们这些经常回答问题的人的立场想一下,你就看到这么一个问题,问题题目写着“mmap 内存映射,是越过了操作系统,直接通过内存访问文件吗?”读完这句话脑子里的第一反应就是“什么意思??[手动黑人问号]”,然后我试图通过打开问题描述获取一些细节,了解一下问题的背景,通过上下文猜一下这个问题想问什么,结果什么细节描述都没有。那么单凭这个标题,我给你们拆开分析一下我看到的是什么。一共就两个逗号隔开了三段,我们一一看:

  • 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(加载、回写文件等)等操作系统的概念。再看看这个问题的题目,不知道此刻的大家能不能理解我们。

类似的话题

  • 回答
    mmap 内存映射,是不是绕过了操作系统,直接用内存访问文件?简单来说,不是。mmap 并没有 绕过操作系统,而是充分利用了操作系统的能力来提供一种高效的文件访问方式。它也不是直接用内存访问文件,而是通过将文件的内容映射到进程的虚拟地址空间中,然后我们就可以像操作内存一样操作这块映射的区域。要弄清楚.............

本站所有内容均为互联网搜索引擎提供的公开搜索信息,本站不存储任何数据与内容,任何内容与数据均与本站无关,如有需要请联系相关搜索引擎包括但不限于百度google,bing,sogou

© 2025 tinynews.org All Rights Reserved. 百科问答小站 版权所有