深入剖析mmap原理 - 从三个关键问题说起

news2025/2/14 5:49:56

作者:招财二师兄
链接:https://www.jianshu.com/p/eece39beee20
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

对于mmap,您是否能从原理上解析以下三个问题:

1:mmap比物理内存+swap空间大情况下,是否有问题?
2:MAP_SHARED,MAP_PRIVATE,MAP_ANONYMOUS,MAP_NORESERVE到底有什么区别?
3:常听说mmap的读写比传统的系统调用(read, write)快,但真的是这样子吗?原因是什么?
要解决这些疑问,可能还需要在操作系统层面多了解。本文将尝试通过这些问题深入剖析,希望通过这篇文章,能使大家对mmap有较深入的认识,也能在存储引擎的设计中,有所参考

背景
最近在研发分布式日志存储系统,这是一个基于Raft协议的自研分布式日志存储系统,Logstore则是底层存储引擎。

Logstore中,使用mmap对数据文件进行读写。Logstore的存储结构简化如下图:

在这里插入图片描述
Logstore使用了Segments Files + Index Files的方式存储Log,Segment File是存储主体,用于存储Log数据,使用定长的方式,默认每个512M,Index File主要用于Segment File的内容检索。

Logstore使用mmap的方式读写Segment File,Segments Files的个数,主要取决于磁盘空间或者业务需求,一般情况下,Logstore会存储1T~5T的数据。

什么是mmap
我们先看看什么是mmap。

在<<深入理解计算机系统>>这本书中,mmap定义为:Linux通过将一个虚拟内存区域与一个磁盘上的对象(object)关联起来,以初始化这个虚拟内存区域的内容,这个过程称为内存映射(memory mapping)。

在Logstore中,mapping的对象是普通文件(Segment File)。

mmap的原理
mmap在进程虚拟内存做了什么
我们先来简单看一下mapping一个文件,mmap做了什么事情。如下图所示:

在这里插入图片描述
假设我们mmap的文件是FileA,在调用mmap之后,会在进程的虚拟内存分配地址空间,创建映射关系。

这里值得注意的是,mmap只是在虚拟内存分配了地址空间,举个例子,假设上述的FileA是2G大小

[dragon@xxx.xxx] ls -lat FileA

2147483648 Apr 25 10:22 FileA

在mmap之后,查看mmap所在进程的maps描述,可以看到

[dragon@xxx.xxx] cat maps
....
7f35eea8d000-7f366ea8d000 rw-s 00000000 08:03 13110516 FileA
....

由上可以看到,在mmap之后,进程的地址空间7f35eea8d000-7f366ea8d000被分配,并且map到FileA,7f366ea8d000减去7f35eea8d000,刚好是2147483648(ps: 这里是整个文件做mapping)

mmap在物理内存做了什么
在Linux中,VM系统通过将虚拟内存分割为称作虚拟页(Virtual Page,VP)大小固定的块来处理磁盘(较低层)与上层数据的传输,一般情况下,每个页的大小默认是4096字节。同样的,物理内存也被分割为物理页(Physical Page,PP),也为4096字节。

上述例子,在mmap之后,如下图:

在这里插入图片描述
在mmap之后,并没有在将文件内容加载到物理页上,只上在虚拟内存中分配了地址空间。当进程在访问这段地址时(通过mmap在写入或读取时FileA),若虚拟内存对应的page没有在物理内存中缓存,则产生"缺页",由内核的缺页异常处理程序处理,将文件对应内容,以页为单位(4096)加载到物理内存,注意是只加载缺页,但也会受操作系统一些调度策略影响,加载的比所需的多,这里就不展开了。
(PS: 再具体一些,进程在访问7f35eea8d000这个进程虚拟地址时,MMU通过查找页表,发现对应内容未缓存在物理内存中,则产生"缺页")

缺页处理后,如下图:在这里插入图片描述
mmap的分类
我认为从原理上,mmap有两种类型,一种是有backend,一种是没有backend。

有backend在这里插入图片描述
这种模式将普通文件做memory mapping(非MAP_ANONYMOUS),所以在mmap系统调用时,需要传入文件的fd。这种模式常见的有两个常用的方式,MAP_SHARED与MAP_PRIVATE,但它们的行为却不相同。

  1. MAP_SHARED

这个方式我认为可以从两个角度去看:

进程间可见:这个被提及太多,就不展开讨论了
写入/更新数据会回写backend,也就是回写文件:这个是很关键的特性,是在Logstore设计实现时,需要考虑的重点。Logstore的一个基本功能就是不断地写入数据,从实现上看就是不断地mmap文件,往内存写入/更新数据以达到写入文件的目的。但物理内存是有限的,在写入数据超过物理内存时,操作系统会进行页置换,根据淘汰算法,将需要淘汰的页置换成所需的新页,而恰恰因为是有backend的,所以mmap对应的内存是可以被淘汰的(若内存页是"脏"的,则操作系统会先将数据回写磁盘再淘汰)。这样,就算mmap的数据远大于物理内存,操作系统也能很好地处理,不会产生功能上的问题。
2) MAP_PRIVATE

这是一个copy-on-write的映射方式。虽然他也是有backend的,但在写入数据时,他会在物理内存copy一份数据出来(以页为单位),而且这些数据是不会被回写到文件的。这里就要注意,因为更新的数据是一个副本,而且不会被回写,这就意味着如果程序运行时不主动释放,若更新的数据超过可用物理内存+swap space,就会遇到OOM Killer。

无backend
无backend通常是MAP_ANONYMOUS,就是将一个区域映射到一个匿名文件,匿名文件是由内核创建的。因为没有backend,写入/更新的数据之后,若不主动释放,这些占用的物理内存是不能被释放的,同样会出现OOM Killer。

mmap比内存+swap空间大情况下,是否有问题
到这里,这个问题就比较好解析了。我们可以将此问题分离为:

虚拟内存是否会出问题
物理内存是否会出问题
– 虚拟内存是否会出问题:

回到上述的"mmap在进程虚拟内存做了什么",我们知道mmap会在进程的虚拟内存中分配地址空间,比如1G的文件,则分配1G的连续地址空间。那究竟可以maping多少呢?在64位操作系统,寻址范围是2^64 ,除去一些内核、进程数据等地址段之外,基本上可以认为可以mapping无限大的数据(不太严谨的说法)。

– 物理内存是否会出问题
回到上述"mmap的分类",对于有backend的mmap,而且是能回写到文件的,映射比内存+swap空间大是没有问题的。但无法回写到文件的,需要非常注意,主动释放。

MAP_NORESERVE
MAP_NORESERVE是mmap的一个参数,MAN的说明是"Do not reserve swap space for this mapping. When swap space is reserved, one has the guarantee that it is possible to modify the mapping."。

我们做个测试:

场景A:物理内存+swap space: 16G,映射文件30G,使用一个进程进行mmap,成功后映射后持续写入数据
场景B:物理内存+swap space: 16G,映射文件15G,使用两个进程进行mmap,成功后映射后持续写入数据
在这里插入图片描述
从上述测试可以看出,从现象上看,NORESERVE是绕过mmap的校验,让其可以mmap成功。但其实在RESERVE的情况下(序列4),从测试结果看,也没有保障。

mmap的性能
mmap的性能经常与系统调用(write/read)做对比。

我们将读写分开看,先尝试从原理上分析两者的差异,然后再通过测试验证。

mmap的写性能
我们先来简单讲讲write系统调用写文件的过程:

在这里插入图片描述
Step1:进程(用户态)调用write系统调用,并告诉内核需要写入数据的开始地址与长度(告诉内核写入的数据在哪)。
Step2:内核write方法,将校验用户态的数据,然后复制到kernel buffer(这里是Page Cache)。
[ ps: 特意查了ext4 write的内核实现,write是直接将user buffer copy到page中 ]
Step3: 由操作系统调用,将脏页回写到磁盘(通常这是异步的)
再来简单讲讲使用mmap时,写入文件流程:

Step1:进程(用户态)将需要写入的数据直接copy到对应的mmap地址(内存copy)
Step2:
2.1) 若mmap地址未对应物理内存,则产生缺页异常,由内核处理
2.2) 若已对应,则直接copy到对应的物理内存
Step3:由操作系统调用,将脏页回写到磁盘(通常这是异步的)
系统调用会对性能有影响,那么从理论上分析:

若每次写入的数据大小接近page size(4096),那么write调用与mmap的写性能应该比较接近(因为系统调用次数相近)
若每次写入的数据非常小,那么write调用的性能应该远慢于mmap的性能。
下面我们对两者进行性能测试:

场景:对2G的文件进行顺序写入(go语言编写)

每次写入大小 | mmap 耗时 | write 耗时
--------------- | ------- | -------- | --------
| 1 byte | 22.14s | >300s
| 100 bytes | 2.84s | 22.86s
| 512 bytes | 2.51s | 5.43s
| 1024 bytes | 2.48s | 3.48s
| 2048 bytes | 2.47s | 2.34s
| 4096 bytes | 2.48s | 1.74s
| 8192 bytes | 2.45s | 1.67s
| 10240 bytes | 2.49s | 1.65s

可以看到mmap在100byte写入时已经基本达到最大写入性能,而write调用需要在4096(也就是一个page size)时,才能达到最大写入性能。

从测试结果可以看出,在写小数据时,mmap会比write调用快,但在写大数据时,反而没那么快(但不太确认是否go的slice copy的性能问题,没时间去测C了)。

测试结果与理论推导吻合。

mmap的读性能
我们还是来简单分析read调用与mmap的流程:

在这里插入图片描述
从图中可以看出,read调用确实比mmap多一次copy。因为read调用,进程是无法直接访问kernel space的,所以在read系统调用返回前,内核需要将数据从内核复制到进程指定的buffer。但mmap之后,进程可以直接访问mmap的数据(page cache)。

从原理上看,read性能会比mmap慢。

接下来实测一下性能区别:

场景:对2G的文件进行顺序读取(go语言编写)
(ps: 为了避免磁盘对测试的影响,我让2G文件都缓存在pagecache中)

每次读取大小 | mmap 耗时 | write 耗时
--------------- | ------- | -------- | --------
| 1 byte | 8215.4ms | > 300s
| 100 bytes | 86.4ms | 8100.9ms
| 512 bytes | 16.14ms | 1851.45ms
| 1024 bytes | 8.11ms | 992.71ms
| 2048 bytes | 4.09ms | 636.85ms
| 4096 bytes | 2.07ms | 558.10ms
| 8192 bytes | 1.06ms | 444.83ms
| 10240 bytes | 867.88µs | 475.28ms

由上可以看出,在read上面,mmap比write的性能差别还是很大的。测试结果与理论推导吻合。

结束语
对mmap的深入了解,能帮助我们在设计存储系统时,更好地进行决策。
比如,假设需要设计一个底层的数据结构是B+ Tree,node操作以Page单位的单机存储引擎,根据上述推论,写入使用系统调用,而读取使用mmap,可以达到最优的性能。而LMDB就是如此实现的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/645352.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CentOS阿里镜像源

阿里CentOS镜像源位置&#xff1a;http://mirrors.aliyun.com/centos/?spma2c6h.25603864.0.0.50d03715CS95s4 CentOS下载最小位置&#xff1a; https://mirrors.aliyun.com/centos/7.9.2009/isos/x86_64/?spma2c6h.25603864.0.0.5e38f5advNCSXC 如图&#xff1a;

用敏捷工具Leangoo领歌做敏捷需求管理

传统的瀑布工作模式使用详细的需求说明书来表达需求&#xff0c;需求人员负责做需求调研&#xff0c;根据调研情况编制详细的需求说明书&#xff0c;进行需求评审&#xff0c;评审之后签字确认交给研发团队设计开发。在这样的环境下&#xff0c;需求文档是信息传递的主体&#…

vue H5项目如何在PC端时居中展示,而不全屏拉伸

目录 1、场景再现&#xff1a;在PC端时 H5项目布局被拉伸2、代码实现3、最终效果图4、疑惑 1、场景再现&#xff1a;在PC端时 H5项目布局被拉伸 2、代码实现 在App.vue中的写入&#xff0c;vue2 的在mounted中写入&#xff0c;vue3的写在onMounted写入。 逻辑讲解&#xff1a…

Linux之进程掩码 umask

目录 Linux之进程掩码 umask 最大权限 umask unmask作用 语法格式 参数及作用 umask存放位置 案例 示例1 --- 在shell进程中创建文件 示例2 --- 修改shell umask值&#xff08;临时&#xff09; 示例3 --- 修改shell umask值&#xff08;永久&#xff09; 示例4 ---…

机器人项目创新课题汇总提示

创新课题推荐自己思考并给出&#xff0c;如下案例仅供参考&#xff1a; 不想看&#xff0c;不愿意做&#xff0c;就遵循自己内心想法&#xff0c;做自己喜欢的事情吧。 题目和描述&#xff1a; 自动导航机器人&#xff1a;设计一种能够自主导航的机器人&#xff0c;可以在不需…

实验篇(7.2) 14. 站对站安全隧道 - 走对方宽带上网(FortiGate-IPsec) ❀ 远程访问

【简介】前面实验已经知道&#xff0c;FortiClient客户端拨号到远端防火墙&#xff0c;包括上网流量等所有流量都可以通过隧道到达远端防火墙&#xff0c;并从对方宽带上网。那么两台防火墙之间连接的安全隧道&#xff0c;可以实现这个功能吗&#xff1f; 实验要求与环境 OldMe…

BTree和B+Tree详解

BTree和BTree详解 B树索引是B树在数据库中的一种实现&#xff0c;是最常见也是数据库中使用最为频繁的一种索引。B树中的B代表平衡(balance)&#xff0c;而不是二叉(binary)&#xff0c;因为B树是从最早的平衡二叉树演化而来的。在讲B树之前必须先了解二叉查找树、平衡二叉树(…

群晖nas(DS423+)和百度云盘互相自动备份

群晖nas提供了云同步功能&#xff0c;使用该功能&#xff0c;可以将百度云盘和群晖nas设置成互为备份&#xff0c;这样我们nas上的的重要数据就有多了一层保护。 通过设置&#xff0c;可以将nas上的某个目录同步到百度云盘的一个目录中&#xff0c;同步的方向可以自行定义&…

springmvc整合thymeleaf

概述 Thymeleaf提供了一组Spring集成&#xff0c;使您可以将其用作Spring MVC应用程序中JSP的全功能替代品。 这些集成将使您能够&#xff1a; Controller像使用JSP一样&#xff0c;将Spring MVC 对象中的映射方法转发到Thymeleaf管理的模板。在模板中使用Spring表达式语言&…

win10中部署个人邮件服务器hMailServer

一、安装邮件服务器hMailServer hMailServer是一个免费的开源电子邮件服务器,适用于Microsoft Windows,本次实践以Windows10为例。hMailServer支持常见的电子邮件协议(IMAP、SMTP 和 POP3),并且可以轻松地与许多现有的 Web 邮件系统集成。它具有灵活的垃圾邮件保护,可以附…

Allegro PCB设计中:结构文件DXF导入、更新、PCB板框更改

1.将结构工程师输出的DXF文件导入到Allegro PCB设计中;文章来源地址https://www.yii666.com/blog/453846.html?action=onAll 2.结构文件DXF多次更改导致PCB板框尺寸涉及的修改; 1.Allegro导入结构DXF文件 Step1:首先进行单位精度设置,我们一般设置mil小数点二位;mm设置…

ROS-melodic:源码安裝teb_local_planner算法、替换DWA算法

一.安裝teb_local_planner算法 源码下载地址&#xff1a;GitHub - rst-tu-dortmund/teb_local_planner: An optimal trajectory planner considering distinctive topologies for mobile robots based on Timed-Elastic-Bands (ROS Package) 注意选择对应ROS版本的代码。 放在…

宝塔安装yapi

宝塔安装部署YApi图文教程&#xff0c;YApi旨在为开发、产品、测试人员提供更优雅的接口管理服务。可以帮助开发者轻松创建、发布、维护 API。 官方文档&#xff1a;https://hellosean1025.github.io/yapi/ 1.给宝塔安装PM2管理器同时安装node 下面看一下使用宝塔的PM2管理器…

掌握SQL注入利器:SQLMap使用入门教程及技巧分享

1、SQLMap安装与使用 SQLMap 是一个功能强大的自动化 SQL 注入工具&#xff0c;它可以用于检测和利用 SQL 注入漏洞。以下是 SQLMap 的使用教程的基本步骤&#xff1a; 1.安装 SQLMap&#xff1a; 首先&#xff0c;你需要从 SQLMap 的官方网站下载并安装 SQLMap 工具。根据你…

#2023开放原子全球开源峰会之旅

#2023我在开源峰会 2023开放原子全球开源峰会参会指南 嗨咯&#xff0c;大家好&#xff01; 6月11号&#xff0c;是一年一度的开放原子大会&#xff0c;有幸参加&#xff0c;很开心&#xff01; 文章目录 1、逛展区&#xff08;领周边&#xff09;环节1.1 CSDN展区1.2 阿里云 …

frp实现内网穿透(内网服务器到公网访问的方案)

目录 背景: 一、frp的简介 二、Frp Server的配置 三.Frp Client的配置 背景: 我使用python写了一个http后端&#xff0c;如代码所示&#xff0c;ip为10.1.136.73&#xff0c;port为8000&#xff0c;现在需要把http后端在公网可以被使用。一个较简单的方案是通过frp实现内网…

Centos7/Centos8安装Mysql8

​ 1.检测系统。 是否已经安装过mysql或其依赖&#xff0c;若已装过要先将其删除&#xff0c;否则第4步使用yum安装时会报错&#xff1a; # yum list installed | grep mysql若出现如下所示&#xff0c;则用第三行中的命令删掉mysql&#xff1a; # yum list installed | gr…

为什么超三成制造企业上市公司选择用友U9 cloud?

导读&#xff1a;30%制造企业上市公司和40%专精特新制造业上市公司都选择用友U9 cloud 当前&#xff0c;数智化转型已经成为中国制造重构竞争力、实现高质量发展的必经之路。《“十四五”智能制造发展规划》提出&#xff0c;到2025年&#xff0c;70%的规模以上制造业企业基本实…

命名管道:FIFO

至此&#xff0c;我们还只能在相关的程序之间传递数据&#xff0c;即这些程序是由一个共同的祖先进程启动的。但如果我们想在不相关的进程之间交换数据&#xff0c;这还不是很方便。 我们可以用FIFO文件来完成这项工作&#xff0c;它通常也被称为命名管道&#xff08;named pip…

一文看尽所有生成式模型:9大类别21个模型全回顾! DALL-E 2、Text-to-3D模型

DALL-E 2 由OpenAI开发的DALL-E 2能够从由文本描述组成的提示中生成原始、真实、逼真的图像和艺术&#xff0c;而且OpenAI已经对外提供了API来访问该模型。 DALL-E 2特别之处在于它能够将概念、属性和不同风格结合起来&#xff0c;其能力源于语言-图像预训练模型CLIP神经网络…