mmap函数详解

1、什么是mmap

        mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。 实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用read,write等系统调用函数。相反,内核空间对这段区域的修改也直接反映用户空间,从而可以实现不同进程间的文件共享。
        下图为mmap基本实现操作:
在这里插入图片描述
由上图可知:
        进程的虚拟地址空间,由多个虚拟内存区域构成。虚拟内存区域是进程的虚拟地址空间中的一个同质区间,即具有同样特性的连续地址范围。上图中所示的text数据段(代码段)、初始数据段、BSS数据段、堆、栈和内存映射,都是一个独立的虚拟内存区域。而为内存映射服务的地址空间处在堆栈之间的空余部分。 linux内核使用vm_area_struct结构来表示一个独立的虚拟内存区域,由于每个不同质的虚拟内存区域功能和内部机制都不同,因此一个进程使用多个vm_area_struct结构来分别表示不同类型的虚拟内存区域。各个vm_area_struct结构使用链表或者树形结构链接,方便进程快速访问。
如下图所示:
在这里插入图片描述
        vm_area_struct结构中包含区域起始和终止地址以及其他相关信息,同时也包含一个vm_ops指针,其内部可引出所有针对这个区域可以使用的系统调用函数。这样,进程对某一虚拟内存区域的任何操作需要用要的信息,都可以从vm_area_struct中获得。mmap函数就是要创建一个新的vm_area_struct结构,并将其与文件的物理磁盘地址相连。


2、mmap内存映射原理

  mmap内存映射的实现过程,总的来说可以分为三个阶段:

2.1、进程启动映射过程,并在虚拟地址空间中为映射创建虚拟映射区域

        进程在用户空间调用函数mmap,原型为:

void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);

        在当前进程的虚拟地址空间中,寻找一段空闲的满足要求的连续的虚拟地址;为此虚拟区分配一个vm_area_struct结构,接着对这个结构的各个域进行了初始化;将新建的虚拟区结构(vm_area_struct)插入进程的虚拟地址区域链表或树中


2.2、调用内核空间的系统调用函数mmap(不同于用户空间函数),实现文件物理地址和进程虚拟地址的一一映射关系

        为映射分配了新的虚拟地址区域后,通过待映射的文件指针,在文件描述符表中找到对应的文件描述符,通过文件描述符,链接到内核“已打开文件集”中该文件的文件结构体(struct file),每个文件结构体维护着和这个已打开文件相关各项信息。
        通过该文件的文件结构体,链接到file_operations模块,调用内核函数mmap,其原型为:

int mmap(struct file *filp, struct vm_area_struct *vma),

        不同于用户空间库函数。 内核mmap函数通过虚拟文件系统inode模块定位到文件磁盘物理地址。 8、通过remap_pfn_range函数建立页表,即实现了文件地址和虚拟地址区域的映射关系。此时,这片虚拟地址并没有任何数据关联到主存中。


2.3、进程发起对这片映射空间的访问,引发缺页异常,实现文件内容到物理内存(主存)的拷贝
ps:前两个阶段仅在于创建虚拟地址区间并完成地址映射,但是并没有将任何文件数据的拷贝至主存真正的文件读取是当进程发起读或写操作时

        进程的读或写操作访问虚拟地址空间这一段映射地址,通过查询页表,发现这一段地址并不在物理页面上。因为目前只建立了地址映射,真正的硬盘数据还没有拷贝到内存中,因此引发缺页异常。缺页异常进行一系列判断,确定无非法操作后,内核发起请求调页过程。
        调页过程先在交换缓存空间(swap cache)中寻找需要访问的内存页,如果没有则调用nopage函数把所缺的页从磁盘装入到主存中。
        之后进程即可对这片主存进行读或者写的操作,如果写操作改变了其内容,一定时间后系统会自动回写脏页面到对应磁盘地址,也即完成了写入到文件的过程。
ps:修改过的脏页面并不会立即更新回文件中,而是有一段时间的延迟,可以调用msync()来强制同步, 这样所写的内容就能立即保存到文件里了。

3、mmap和常规文件操作的区别

常规文件系统操作(调用read/fread等类函数),函数的调用过程:

1、进程发起读文件请求

2、内核通过查找进程文件符号表,定位到内核已打开文件集上的文件信息,从而找到此文件的inode。
3、inode在address_space上查找要请求的文件页是否已经缓存到页缓存中; 如果存在,则直接返回这片文件页的内容

4、如果不存在,则通过inode定位到文件磁盘地址,将数据从磁盘复制到页缓存。之后再次发起读页面过程,进而将页缓存中的数据发给用户进程

对比:
        常规文件操作为了提高读写效率和保护磁盘,使用了页缓存机制。这样造成读文件时需要先将文件页从磁盘拷贝到页缓存中,由于页缓存处在内核空间,不能被用户进程直接寻址,所以还需要将页缓存中数据页再次拷贝到内存对应的用户空间中。这样,通过了两次数据拷贝过程,才能完成进程对文件内容的获取任务。写操作也是一样,待写入的buffer在内核空间不能直接访问,必须要先拷贝至内核空间对应的主存,再写回磁盘中(延迟写回),也是需要两次数据拷贝。
mmap操作文件中,创建新的虚拟内存区域和建立文件磁盘地址和虚拟内存区域映射这两步,没有任何文件拷贝操作。而之后访问数据时发现内存中并无数据而发起的缺页异常过程,可以通过已经建立好的映射关系,只使用一次数据拷贝,就从磁盘中将数据传入内存的用户空间中,供进程使用。

总结:
        常规文件操作需要从磁盘到页缓存再到用户主存的两次数据拷贝;而mmap操作文件只需要从磁盘到用户主存的一次数据拷贝过程,实现了用户空间和内核空间的数据直接交互而省去了空间不同数据不通的繁琐过程,因此mmap的效率更高。

4、mmap优点总结
由上述讨论可知,mmap优点有以下几点:

        对文件的读取操作跨过了页缓存,减少了数据的拷贝次数,用内存读写取代了IO读写,提高了文件读取效率;
        实现了用户空间和内核空间的高效交互方式。两者间的各自修改操作可以直接反映在映射的区域内,从而被对方空间及时捕捉;
        提供进程间共享内存及相关通信的方式。不管是父子进程还是无亲缘关系的进程,都可以将自身用户空间映射到同一个文件或匿名映射到同一片区域,从而通过各自对映射区域的改动,达到进程间通信和进程间共享的目的;
        ps:如果进程A和进程B都映射了区域C,当A第一次读取C时通过缺页从磁盘复制文件页到内存中;但当B再读C的相同页面时,虽然也会产生缺页异常,但是不再需要从磁盘中复制文件过来,而可直接使用已经保存在内存中的文件数据

        可用于实现高效的大规模数据传输。内存空间不足,是制约大数据操作的一个方面,解决方案往往是借助硬盘空间协助操作,补充内存的不足。但是进一步会造成大量的文件IO操作,极大影响效率。这个问题可以通过mmap映射很好的解决

5、mmap相关函数

函数原型:

void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);

返回说明:
        成功执行时,mmap()返回被映射区的指针。失败时,mmap()返回MAP_FAILED[其值为(void *)-1], error被设为以下的某个值:
参数:
        start:映射区的开始地址
        length:映射区的长度
        prot:期望的内存保护标志,不能与文件的打开模式冲突
                PROT_EXEC:页内容可以被执行
                PROT_READ:页内容可以被读取
                PROT_WRITE:页内容可以被写入
                PROT_NONE:页不可访问

        flags:指定映射对象的类型,映射选项和映射页是否可以共享。它的值可以是一个或者多个位的组合体
        fd:有效的文件描述词。如果MAP_ANONYMOUS被设定,为了兼容问题,其值应为-1
        offset:被映射对象内容的起点

相关函数:

int munmap(void *addr, size_t len)

        成功执行时,munmap()返回0,失败时,munmap返回-1,error返回标志和mmap一致;
该调用在进程地址空间中解除一个映射关系,addr是调用mmap()时返回的地址,len是映射区的大小
        当映射关系解除后,对原来映射地址的访问将导致段错误发生

6、mmap使用细节

        使用mmap需要注意的一个关键点是,mmap映射区域大小必须是物理页大小(page_size)的整倍数(32位系统中通常是4k字节)。原因是,内存的最小粒度是页,而进程虚拟地址空间和内存的映射也是以页为单位。为了匹配内存的操作,mmap从磁盘到虚拟地址空间的映射也必须是页。
内核可以跟踪被内存映射的底层对象(文件)的大小,进程可以合法的访问在当前文件大小以内又在内存映射区以内的那些字节。也就是说,如果文件的大小一直在扩张,只要在映射区域范围内的数据,进程都可以合法得到,这和映射建立时文件的大小无关。具体情形参见“情形三”。
映射建立之后,即使文件关闭,映射依然存在。因为映射的是磁盘的地址,不是文件本身,和文件句柄无关。同时可用于进程间通信的有效地址空间不完全受限于被映射文件的大小,因为是按页映射。


7、mmap使用的特例

        情形一:一个文件的大小是5000字节,mmap函数从一个文件的起始位置开始,映射5000字节到虚拟内存中
        分析:因为单位物理页面的大小是4096字节,虽然被映射的文件只有5000字节,但是对应到进程虚拟地址区域的大小需要满足整页大小,因此mmap函数执行后,实际映射到虚拟内存区域8192个 字节,5000~8191的字节部分用零填充。
此时:

        读/写前5000个字节(0~4999),会返回操作文件内容。
        读字节5000-8191时,结果全为0。写5000-8191时,进程不会报错,但是所写的内容不会写入原文件中 。
        读/写8192以外的磁盘部分,会返回一个SIGSECV错误。
在这里插入图片描述

 

        情形二:一个文件的大小是5000字节,mmap函数从一个文件的起始位置开始,映射15000字节到虚拟内存中,即映射大小超过了原始文件的大小。
        分析:由于文件的大小是5000字节,和情形一一样,其对应的两个物理页。那么这两个物理页都是合法可以读写的,只是超出5000的部分不会体现在原文件中。由于程序要求映射15000字节,而文件只占两个物理页,因此8192字节~15000字节都不能读写,操作时会返回异常。如下图所示:
此时:

        进程可以正常读/写被映射的前5000字节(0~4999),写操作的改动会在一定时间后反映在原文件中。
        对于5000~8191字节,进程可以进行读写过程,不会报错。但是内容在写入前均为0,另外,写入后不会反映在文件中。
        对于8192~14999字节,进程不能对其进行读写,会报SIGBUS错误。
        对于15000以外的字节,进程不能对其读写,会引发SIGSEGV错误。

在这里插入图片描述
        情形三:一个文件初始大小为0,使用mmap操作映射了1000*4K的大小,即1000个物理页大约4M字节空间,mmap返回指针ptr。
分析:如果在映射建立之初,就对文件进行读写操作,由于文件大小为0,并没有合法的物理页对应,如同情形二一样,会返回SIGBUS错误。但是如果,每次操作ptr读写前,先增加文件的大小,那么ptr在文件大小内部的操作就是合法的。例如,文件扩充4096字节,ptr就能操作ptr ~ [ (char)ptr + 4095]的空间。只要文件扩充的范围在1000个物理页(映射范围)内,ptr都可以对应操作相同的大小。
这样,方便随时扩充文件空间,随时写入文件,不造成空间浪费。

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Android入门教程||Android 架构||Android 应用程序组件

Android 架构 Android 操作系统是一个软件组件的栈,在架构图中它大致可以分为五个部分和四个主要层。 Linux内核 在所有层的最底下是 Linux - 包括大约115个补丁的 Linux 3.6。它提供了基本的系统功能,比如进程管理,内存管理,设…

Android 面试题 应用程序结构 十一

🔥 Framework主要包含以下模块 🔥 ActivityManagerService 这是一个Activity的管理者,负责管理所有Activity的生命周期。WindowManagerService 它是手机屏幕的的管理者,管理着屏幕的详细情况,所有对屏幕的操作最终都…

express学习笔记4 - 热更新以及express-boom

我们每次改动代码的时候都要重启项目,现在我们给项目添加一个热更新 npm install --save-dev nodemon # or using yarn: yarn add nodemon -D 在package.json添加一行代码 "dev": "nodemon ./bin/www" 重启项目 然后随便做改动&#xff…

【初阶C语言】学会结构体

1.结构体类型的声明 2.结构体初始化 3.结构体成员访问 4.结构体传参 前言:结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。 一、结构体类型的声明 1.结构的声明 结构体声明的模板: struct tag {member-li…

Java版工程行业管理系统源码-专业的工程管理软件-em提供一站式服务

​ Java版工程项目管理系统 Spring CloudSpring BootMybatisVueElementUI前后端分离 功能清单如下: 首页 工作台:待办工作、消息通知、预警信息,点击可进入相应的列表 项目进度图表:选择(总体或单个)项目…

sqlyog导出mysql数据字典

1.打开sqlyog执行sql获取字典数据 SELECTt.COLUMN_NAME AS 字段名,t.COLUMN_TYPE AS 数据类型,CASE IFNULL(t.COLUMN_DEFAULT,Null) WHEN THEN 空字符串 WHEN Null THEN NULL ELSE t.COLUMN_DEFAULT END AS 默认值,CASE t.IS_NULLABLE WHEN YES THEN 是 ELSE 否 END AS 是否…

docker的使用

docker安装 https://docs.docker.com/engine/install/debian/ 设置国内镜像 创建或修改 /etc/docker/daemon.json 文件,修改为如下形式 {"registry-mirrors": ["https://registry.hub.docker.com","http://hub-mirror.c.163.com"…

音频光耦合器

音频光耦合器是一种能够将电信号转换为光信号并进行传输的设备。它通常由发光二极管(LED)和光敏电阻(光电二极管或光敏电阻器)组成。 在音频光耦合器中,音频信号经过放大和调节后,被转换为电流信号&#xf…

opencv基础40-礼帽运算(原始图像减去其开运算)cv2.MORPH_TOPHAT

礼帽运算是用原始图像减去其开运算图像的操作。礼帽运算能够获取图像的噪声信息,或者得到比原始图像的边缘更亮的边缘信息。 例如,图 8-22 是一个礼帽运算示例,其中: 左图是原始图像。中间的图是开运算图像。右图是原始图像减开运…

LeetCode 热题 100 JavaScript--543. 二叉树的直径

给你一棵二叉树的根节点,返回该树的 直径 。 二叉树的 直径 是指树中任意两个节点之间最长路径的 长度 。这条路径可能经过也可能不经过根节点 root 。 两节点之间路径的 长度 由它们之间边数表示。 var diameterOfBinaryTree function(root) {var maxDiameter…

多雷达探测论文阅读笔记:雷达学报 2023, 多雷达协同探测技术研究进展:认知跟踪与资源调度算法

多雷达协同探测技术 原始笔记链接:https://mp.weixin.qq.com/s?__biz=Mzg4MjgxMjgyMg==&mid=2247486627&idx=1&sn=f32c31bfea98b85f2105254a4e64d210&chksm=cf51be5af826374c706f3c9dcd5392e0ed2a5fb31ab20924b7dd38e1b1ae32abe9a48afa8174#rd ↑ \uparrow …

视频过大如何压缩变小?文件压缩技巧分享

如何压缩视频是许多视频编辑者、视频上传者经常遇到的问题,如果你也遇到了这个问题,不用担心,下面将就给大家分享几个视频压缩方法,可以帮助大家轻松地压缩视频,同时保持视频的高清晰度和音频质量。 一、嗨格式压缩大师…

【Kubernetes】

目录 一、Kubernetes 概述1、K8S 是什么?2、为什么要用 K8S?3、Kubernetes 集群架构与组件 二、核心组件1、Master 组件2、Node 组件3、K8S创建Pod的工作流程?(重点)4、K8S资源对象(重点)5、Kubernetes 核…

Java课题笔记~ MyBatis入门

一、ORM框架 当今企业级应用的开发环境中,对象和关系数据是业务实体的两种表现形式。业务实体在内存中表现为对象,在数据库中变现为关系数据。当采用面向对象的方法编写程序时,一旦需要访问数据库,就需要回到关系数据的访问方式&…

偶数科技亮相第十届中国中小企业投融资交易会

第十届中国中小企业投融资交易会暨2023“小企业 大梦想”高峰论坛近日在北京举办。本届大会以“金融活水精准滴灌 专精特新体制增量”为主题,通过展览展示、论坛活动、项目路演、产融对接等形式,搭建了专精特新企业与金融机构之间、与地方政府之间的产融…

虹科活动 | 走进宇通客车-汽车新供应链技术展精彩回顾

引言 7月27日,走进宇通客车-汽车新供应链技术展于宇通研发中心成功举办,本次展会中虹科为大家带来了一体化车载天线与车辆GNSS仿真测试方案,感谢您前来探讨与交流! 精彩产品一览 车辆GNSS仿真测试方案 虹科高性能GNSS模拟器具有灵…

Dockerfile构建LNMP镜像

建立工作目录 [rootlocalhost ~]# mkdir lnmp [rootlocalhost ~]# cd lnmp/ 编写Dockerfile文件 [rootlocalhost lnmp]# vim Dockerfile [rootlocalhost lnmp]# ll 总用量 4 -rw-r--r--. 1 root root 774 8月 3 14:54 Dockerfile [rootlocalhost lnmp]# vim Dockerfile #基础…

web前端转正工作总结范文5篇

web前端转正工作总结(篇1) 来到__有限公司已经三个月了,目前的工作是前端开发,我是一名应届毕业生,之前没有过工作经验,在刚来到__这个大家庭的时候,我就被这里的工作气氛深深地吸引&#xff0…

极狐GitLab 全新「价值流仪表盘」使用指南

本文来源:about.gitlab.com 作者:Haim Snir 译者:极狐(GitLab) 市场部内容团队 GitLab / 极狐GitLab 价值流仪表盘的使用相对简单,这种可以定制化的仪表盘能够让决策者识别数字化转型进程中的趋势及机遇。 如果你已经在用 GitLab…

17、YML配置文件及让springboot启动时加载我们自定义的yml配置文件的几种方式

YML配置文件及加载自定义配置文件的几种方式 ★ YAML配置文件 其实本质和.properties文件的是一样的。 Spring Boot默认使用SnakeYml工具来处理YAML配置文件,SnakeYml工具默认就会被spring-boot-starter导入,因此无需开发者做任何额外配置。 YAML本质…