三、关于mmap系统调用

2024-05-10 21:38
文章标签 系统 调用 mmap

本文主要是介绍三、关于mmap系统调用,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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

  1. mmap比物理内存+swap空间大情况下,是否有问题?
  2. MAP_SHARED,MAP_PRIVATE,MAP_ANONYMOUS,MAP_NORESERVE到底有什么区别?
  3. 常听说mmap的读写比传统的系统调用(read, write)快,但真的是这样子吗?原因是什么?

要解决这些疑问,可能还需要在操作系统层面多了解。本文将尝试通过这些问题深入剖析,希望通过这篇文章,能使大家对mmap有较深入的认识,也能在存储引擎的设计中,有所参考。

背景

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

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

logstore mmap.png

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做了什么事情。如下图所示:

map file.png

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

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

[dragon@xxx.xxx] ls -lat FileA2147483648 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之后,如下图:

virtual-physical.png

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

缺页处理后,如下图:

virtual-physical assign.png

mmap的分类

我认为从原理上,mmap有两种类型,一种是有backend,一种是没有backend。

有backend

backend mmap.png

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

1) MAP_SHARED

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

  1. 进程间可见:这个被提及太多,就不展开讨论了
  2. 写入/更新数据会回写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空间大情况下,是否有问题

到这里,这个问题就比较好解析了。我们可以将此问题分离为:

  1. 虚拟内存是否会出问题
  2. 物理内存是否会出问题

-- 虚拟内存是否会出问题:

回到上述的"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,成功后映射后持续写入数据

场景序列映射类型结果
A1MAP_PRIVATEmmap报错
A2MAP_PRIVATE + MAP_NORESERVEmmap成功,在持续写入情况下,遇到OOM Killer
A3MAP_SHAREDmmap成功,在持续写入正常
B4MAP_PRIVATEmmap成功,在持续写入情况下,有一个进程会遇到OOM Killer
B5MAP_PRIVATE + MAP_NORESERVEmmap成功,在持续写入情况下,有一个进程会遇到OOM Killer
B6MAP_SHAREDmmap成功,在持续写入正常

从上述测试可以看出,从现象上看,NORESERVE是绕过mmap的校验,让其可以mmap成功。但其实在RESERVE的情况下(序列4),从测试结果看,也没有保障。

mmap的性能

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

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

mmap的写性能

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

write process.png

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

再来简单讲讲使用mmap时,写入文件流程:

  1. Step1:进程(用户态)将需要写入的数据直接copy到对应的mmap地址(内存copy)
  2. Step2:
    2.1) 若mmap地址未对应物理内存,则产生缺页异常,由内核处理
    2.2) 若已对应,则直接copy到对应的物理内存
  3. Step3:由操作系统调用,将脏页回写到磁盘(通常这是异步的)

系统调用会对性能有影响,那么从理论上分析:

  1. 若每次写入的数据大小接近page size(4096),那么write调用与mmap的写性能应该比较接近(因为系统调用次数相近)
  2. 若每次写入的数据非常小,那么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 process.png

从图中可以看出,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就是如此实现的

这篇关于三、关于mmap系统调用的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/977625

相关文章

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

基于人工智能的图像分类系统

目录 引言项目背景环境准备 硬件要求软件安装与配置系统设计 系统架构关键技术代码示例 数据预处理模型训练模型预测应用场景结论 1. 引言 图像分类是计算机视觉中的一个重要任务,目标是自动识别图像中的对象类别。通过卷积神经网络(CNN)等深度学习技术,我们可以构建高效的图像分类系统,广泛应用于自动驾驶、医疗影像诊断、监控分析等领域。本文将介绍如何构建一个基于人工智能的图像分类系统,包括环境

水位雨量在线监测系统概述及应用介绍

在当今社会,随着科技的飞速发展,各种智能监测系统已成为保障公共安全、促进资源管理和环境保护的重要工具。其中,水位雨量在线监测系统作为自然灾害预警、水资源管理及水利工程运行的关键技术,其重要性不言而喻。 一、水位雨量在线监测系统的基本原理 水位雨量在线监测系统主要由数据采集单元、数据传输网络、数据处理中心及用户终端四大部分构成,形成了一个完整的闭环系统。 数据采集单元:这是系统的“眼睛”,

嵌入式QT开发:构建高效智能的嵌入式系统

摘要: 本文深入探讨了嵌入式 QT 相关的各个方面。从 QT 框架的基础架构和核心概念出发,详细阐述了其在嵌入式环境中的优势与特点。文中分析了嵌入式 QT 的开发环境搭建过程,包括交叉编译工具链的配置等关键步骤。进一步探讨了嵌入式 QT 的界面设计与开发,涵盖了从基本控件的使用到复杂界面布局的构建。同时也深入研究了信号与槽机制在嵌入式系统中的应用,以及嵌入式 QT 与硬件设备的交互,包括输入输出设

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

如何在页面调用utility bar并传递参数至lwc组件

1.在app的utility item中添加lwc组件: 2.调用utility bar api的方式有两种: 方法一,通过lwc调用: import {LightningElement,api ,wire } from 'lwc';import { publish, MessageContext } from 'lightning/messageService';import Ca

【区块链 + 人才服务】可信教育区块链治理系统 | FISCO BCOS应用案例

伴随着区块链技术的不断完善,其在教育信息化中的应用也在持续发展。利用区块链数据共识、不可篡改的特性, 将与教育相关的数据要素在区块链上进行存证确权,在确保数据可信的前提下,促进教育的公平、透明、开放,为教育教学质量提升赋能,实现教育数据的安全共享、高等教育体系的智慧治理。 可信教育区块链治理系统的顶层治理架构由教育部、高校、企业、学生等多方角色共同参与建设、维护,支撑教育资源共享、教学质量评估、

软考系统规划与管理师考试证书含金量高吗?

2024年软考系统规划与管理师考试报名时间节点: 报名时间:2024年上半年软考将于3月中旬陆续开始报名 考试时间:上半年5月25日到28日,下半年11月9日到12日 分数线:所有科目成绩均须达到45分以上(包括45分)方可通过考试 成绩查询:可在“中国计算机技术职业资格网”上查询软考成绩 出成绩时间:预计在11月左右 证书领取时间:一般在考试成绩公布后3~4个月,各地领取时间有所不同

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识

计算机毕业设计 大学志愿填报系统 Java+SpringBoot+Vue 前后端分离 文档报告 代码讲解 安装调试

🍊作者:计算机编程-吉哥 🍊简介:专业从事JavaWeb程序开发,微信小程序开发,定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事,生活就是快乐的。 🍊心愿:点赞 👍 收藏 ⭐评论 📝 🍅 文末获取源码联系 👇🏻 精彩专栏推荐订阅 👇🏻 不然下次找不到哟~Java毕业设计项目~热门选题推荐《1000套》 目录 1.技术选型 2.开发工具 3.功能