【Linux 内核源码分析】内存映射(mmap)机制原理

内存映射(mmap)是 Linux 内核的一个重要机制,它为程序提供了一种将文件内容直接映射到进程虚拟地址空间的方式。同时内存映射也是虚拟内存管理和文件 IO 的重要组成部分。

在 Linux 中,虚拟内存管理是基于内存映射来实现的。在调用 mmap 函数时,会创建一个 vm_area_struct 结构体,该结构体代表了一段连续的虚拟地址空间,它们会相应地映射到一个后备文件或者一个匿名文件的虚拟页。

一个 vm_area_struct 结构体映射到一组连续的页表项,这些页表项指向物理内存中的一页。这样就把一个文件和物理内存页相映射起来。当进程试图访问映射到 vm_area_struct 的虚拟地址空间时,如果该空间没有在内存中,则会发生缺页异常,内核会通过文件系统将文件中对应的数据读入内存。

内存映射的优点是可以有效地减少文件 IO 的次数,提高文件读写性能。同时内存映射还支持多进程共享同一个映射,可以节省内存空间,并且方便不同的进程之间进行通信。

虚拟地址映射的过程涉及到对 vm_area_struct 的匹配以及对页表项的查找和操作。具体来说,当程序访问一个虚拟地址时,系统会根据已有的 vm_area_struct 结构来确定这个虚拟地址是否属于某个区域。

如果没有匹配到相应的 vm_area_struct,就会触发段错误,因为访问了一个未分配的虚拟地址,这表示程序在访问一个未经内核分配的内存区域,是非法的操作。

如果匹配到了相应的 vm_area_struct,系统会根据虚拟地址和页表的映射关系找到对应的页表项PTE。如果 PTE 没有分配,就会触发缺页异常,此时系统会将相应的文件数据加载到物理内存中;如果 PTE 已经分配,就可以直接从对应的物理页的偏移位置读取数据。

在这个过程中,虚拟页有三种状态:

  1. 未分配虚拟页:指的是没有使用 mmap 建立相应的 vm_area_struct,因此也就没有对应到具体的页表项。
  2. 已分配虚拟页,未映射到物理页:表示已经使用了 mmap 建立的 vm_area_struct,虚拟页可以映射到对应的页表项,但页表项尚未指向具体的物理页。
  3. 已分配虚拟页,已映射到物理页:表示已经使用了 mmap 建立的 vm_area_struct,虚拟页可以映射到对应的页表项,并且页表项已经指向具体的物理页。

mmap 函数可以将虚拟地址映射到一个后备文件或者一个匿名文件。当操作系统分配物理内存时,实际上会利用匿名文件的 mmap 来完成内存分配。

mmap和虚拟内存管理

用户进程的虚拟内存管理是通过Linux内核中的mm_struct结构来表示一个用户进程的虚拟内存地址空间。该结构包含了几个重要的字段来描述不同区域的地址范围和属性。

  1. start_codeend_code:指定了进程的代码段的起始地址和结束地址,用于表示可执行代码的边界。一旦ELF二进制文件映射到虚拟内存后,这些地址就不会再改变。

  2. start_dataend_data:指定了进程数据段的起始地址和结束地址,用于表示已初始化数据的边界。类似于代码段,这些地址在映射后也不再改变。

  3. start_brkbrk:指定了堆的起始地址和结束地址,用于表示动态分配内存的边界。start_brk表示堆的初始位置,在进程的整个生命周期中保持不变,而brk表示堆的结束位置,会随着堆的长度动态改变。

  4. stack_top:指定了栈的起始位置,一般位于用户进程地址空间的顶部,用于存放函数调用的栈帧。

  5. task_size:指定了用户进程地址空间的长度,即用户空间的顶部边界。

  6. mmap_base:指定了用户进程虚拟地址空间中用作内存映射部分的基地址。通常情况下,它位于用户地址空间的1/3处,即TASK_SIZE / 3位置。

这些字段中的地址都是用户进程的虚拟地址,通过虚拟地址和页表结构,用户进程可以访问内存。当用户进程访问一个虚拟地址时,会将该地址转换成对应的页表项索引,然后查找页表项中保存的物理内存页的页号,并加上虚拟地址低12位的偏移量,从而确定一个唯一的物理内存地址。

如果物理内存地址所在的页已经存在,就可以返回该物理地址存放的内容。如果不存在,则会触发缺页异常。虚拟内存管理采用按需分配和缺页异常机制来管理页表项并分配对应的物理内存页。当一个虚拟地址对应的页表项不存在时,会先创建页表结构,然后分配物理内存页,并最后修改页表。

除了mm_struct结构,进程的虚拟内存管理还涉及到虚拟内存区域的管理,即通过vm_area_struct结构来管理用户进程的不同虚拟内存区域,如数据段、文本段和共享库等。这些区域通过vm_area_struct结构进行管理和映射。

vm_area_struct

struct vm_area_struct {struct mm_struct *vm_mm;        /* 所属进程的内存描述符 */unsigned long vm_start;         /* 区域起始地址 */unsigned long vm_end;           /* 区域结束地址 */struct vm_area_struct *vm_next, *vm_prev;   /* 双向链表指针 */pgprot_t vm_page_prot;          /* 页保护标志 */unsigned long vm_flags;         /* VMA标志位,如映射类型等 */struct rb_node rb;union {struct {unsigned long shared_vm;     /* 共享区域大小 */} anon_vma;struct vm_userfaultfd_ctx *userfaultfd_ctx;spinlock_t lock;                /* 文件锁 */struct list_head list;          /* 指向共享VMA列表项 */      };#ifdef CONFIG_MMU_NOTIFIERstruct mmu_notifier_mm *mmu_notifier_mm;
#endif#ifdef CONFIG_NUMA_BALANCING/** Virtual memory areas in a shared-memory area. Protected by* mmap_sem and guarded by mm->mmap_lock.** WARNING: Once you add a new member to this group you MUST update* dup_mmap() function!!!** You also have to modify arch_dup_mmap() if your architecture is one* of the architectures which implement that function.** Also, __split_vma() must be taught about how to copy the information.*/unsigned long shared_dirty_pages;unsigned long private_dirty_pages;unsigned long shared_clean_pages;unsigned long private_clean_pages;
#endif /* CONFIG_NUMA_BALANCING */
};

vm_area_struct是一种用于管理用户进程虚拟内存区域的数据结构,它可以以两种不同的组织形式存在。

首先是单链表形式,包含了所有已创建的vm_area_struct实例。这种形式使得可以按照顺序遍历和访问所有的虚拟内存区域。

其次是红黑树形式,用于加速对虚拟内存区域的查找。这种组织形式可以通过快速的二分查找来定位特定的虚拟内存区域,提高了访问效率。

需要注意的是,这两种组织形式都是针对同一份vm_area_struct实例而言的,只是在不同的数据结构中进行组织和管理。

在考虑vm_area_struct和页表之间的关系时,我们可以看到,vm_area_struct本质上表示了用户进程的一段虚拟地址空间。而虚拟地址和页表数组的索引是一一对应的关系。页表数组的最后一级PTE数组的数组项存放着物理内存页的页号,从而建立了虚拟内存地址到物理内存地址的对应关系。

有一种情况是当先有虚拟地址时,通过访问虚拟地址触发缺页异常,然后加载相应的物理内存页,并更新页表,以建立起虚拟地址、页表和物理内存之间的联系。

另一种情况是在进行内存映射(mmap)时,首先从设备加载文件数据,建立address_space和页缓存(物理内存),然后创建vm_area_struct结构,更新页表,并返回相应的虚拟地址。这样就实现了从设备加载文件到建立虚拟地址、页表和物理内存之间联系的过程。

vm_area_struct是用于描述用户进程虚拟内存区域的结构体:

  1. vm_startvm_end:表示区域的起始位置和结束位置,用于确定区域的边界。这两个字段确保了不同的vm_area_struct之间不会出现交叉的情况,从而清晰地划分了各个虚拟内存区域。

  2. vm_page_prot:表示了该区域的页的访问权限,包括读、写、执行等。这些权限信息将影响到用户进程对该区域的访问行为。

  3. shared:处理具有后备文件的内存映射。它将该区域与后备文件的address_space地址空间进行关联,以便在需要时能够正确地读取和写入数据。

  4. anon_vma_nodeanon_vma:处理匿名文件共享内存映射的情况。当多个虚拟内存区域映射到同一物理内存页时,这些映射将保存在一个链表中,并由anon_vma_node进行管理,确保它们之间的正确关联。

  5. vm_pgoffvm_file:处理具有后备文件的内存映射的情况。vm_pgoff表示了该映射在文件中的页偏移量,而vm_file则包含了打开文件file实例的相关信息,以便在需要时能够正确地定位和操作对应的文件数据。

这些字段的信息使得vm_area_struct能够全面描述用户进程的虚拟内存区域,包括区域的边界、访问权限、关联文件信息以及共享情况,为内核提供了管理和操作虚拟内存的重要依据。

对于有后备文件的映射,内核利用优先查找树结构来加速确定一个文件和所有映射到这个文件的虚拟内存区域vm_area_struct实例的关系,从而可以方便地获取所有映射到这个文件的进程信息。这种优先查找树结构能够高效地管理文件和映射关系,提高查找效率和操作性能。

同时,内核提供了一系列函数用于对虚拟内存区域vm_area_struct进行操作,包括创建、删除、合并、查找等功能。这些函数可以帮助内核有效地管理用户进程的虚拟内存区域,确保内存映射的正确性和一致性。

另外,mmap是C标准库提供给用户程序的函数,用于通过内存映射建立文件地址空间和虚拟内存区域的映射关系。通过mmap函数,用户程序可以将文件映射到自身的虚拟内存空间中,实现了方便的文件访问和操作。这种映射关系的建立是通过内核提供的相关功能实现的,确保了对文件数据的高效管理和访问。

mmap的4种类型

mmap函数在Linux系统中用于创建内存映射,可以分为有后备文件的映射和匿名文件的映射,每种映射又有私有映射和共享映射之分,因此mmap可以创建4种类型的映射。

  1. 有后备文件的共享映射:多个进程的vm_area_struct指向同一个物理内存区域,一个进程对文件内容的修改会被其他进程看到,并且这些修改会被写回到后备文件中。

  2. 有后备文件的私有映射:多个进程的vm_area_struct指向同一个物理内存区域,但采用写时拷贝的方式。当一个进程对文件内容做修改,不会被其他进程看到,并且对文件的修改也不会被写回到后备文件。当内存不足时,私有映射的页被交换到交换区。这种映射常用于加载共享代码库。

  3. 匿名文件的共享映射:内核创建一个初始为0的物理内存区域,然后多个进程的vm_area_struct指向这个共享的物理内存区域。对该区域内容的修改对所有进程可见,而且在页回收时被交换到交换区。

  4. 匿名文件的私有映射:内核创建一个初始为0的物理内存区域,对该区域内容的修改只对创建者进程可见。在页回收时,这种映射也会被交换到交换区。malloc()函数底层使用了匿名文件的私有映射来分配大块内存。

这些不同类型的映射提供了灵活的内存管理方式,使得进程可以根据需要选择适合的映射方式来处理内存数据,并且能够满足不同场景下的内存管理需求。

内核对堆空间的管理

从内核管理用户进程虚拟地址空间的角度来看,内存映射是主要的手段,通过建立vm_area_struct结构来分配虚拟内存区域。对于堆空间的分配,主要通过brk系统调用来实现。brk系统调用本质上也是利用了匿名文件的私有映射机制,它分配并初始化为0的物理内存页,然后建立相应的vm_area_struct,最后更新页表结构。

brk系统调用分配的内存最小单位是页,需要按页对齐。在内核的视角下,每次对堆空间的分配至少是一页大小,即以页面为单位进行扩展。换句话说,更细粒度的字节级内存分配是由C语言标准库实现的,而在内核层面,堆空间的分配是以页面为单位进行管理的。这种设计确保了内核对虚拟内存的有效管理,并提供了一种简单且高效的方式来处理用户进程的内存分配需求。

参考:Linux内核源码分析(内存调优/文件系统/进程管理/设备驱动/网络协议栈)教程

Linux内核源码学习

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

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

相关文章

一篇博客教会你使用node多版本管理

文章目录 nvm 简介nvm 安装nvm 使用配置国内镜像显示可以安装的 nodejs安装 nodejs显示已安装的 nodejs切换 nodejs nvm 简介 nvm(Node Version Manager)是 node.js 的版本管理器,可以让我们轻松地在不同的 node.js 版本之间进行切换。 今天…

c语言(指针进阶)

指针 一.什么是字符指针二.使用指针数组模拟二维数组三.函数指针 一.什么是字符指针 字符指针:指向字符型数据的指针变量。每个字符串在内存中都占用一段连续的存储空间,并有唯一确定的首地址。即将字符串的首地址赋值给字符指针,可让字符指针…

vivado RAM HDL Coding Guidelines

从编码示例下载编码示例文件。 块RAM读/写同步模式 您可以配置块RAM资源,为提供以下同步模式给定的读/写端口: •先读取:在加载新内容之前先读取旧内容。 •先写:新内容立即可供阅读先写也是众所周知的如通读。 •无变化&…

树和堆的精讲

𝙉𝙞𝙘𝙚!!👏🏻‧✧̣̥̇‧✦👏🏻‧✧̣̥̇‧✦ 👏🏻‧✧̣̥̇:Solitary_walk ⸝⋆ ━━━┓ - 个性标签 - :来于“云”的“羽球人”。…

告别你的朝九晚五的工作。

告别你的朝九晚五的工作。 以下是6个网站,你可以从任何地方获得100美元到2000美元的报酬: (第3个网站最正规) 1. Honeygain 分享未使用的互联网带宽换取现金。 功能包括推荐系统、JumpTask模式、成就和每日幸运抽奖。 非常适…

机构如何搭建一个在线课程教学平台?

随着数字化教育的兴起,越来越多的教育机构开始考虑建立自己的在线课程教学平台。这一趋势不仅顺应了时代的发展,而且为教育行业带来了诸多便利和优势。构建一个在线教学平台可以帮助机构拓宽服务范围、提升教学质量、增强学生体验,并且能够有…

Qt之Qchar类的接口1

Qt类的构造函数 QChar类提供了许多个不同原型的构造函数,以方便不同场合下使用。 QChar(); //构造一个空字符,即‘\0’ QChar(char ch); //由字符数据ch构造 QChar(uchar ch)…

optuna,一个好用的Python机器学习自动化超参数优化库

🏷️个人主页:鼠鼠我捏,要死了捏的主页 🏷️付费专栏:Python专栏 🏷️个人学习笔记,若有缺误,欢迎评论区指正 前言 超参数优化是机器学习中的重要问题,它涉及在训练模型时选择最优的超参数组合,以提高模型的性能和泛化能力。Optuna是一个用于自动化超参数优化的…

react虚拟dom及实现原理

React的虚拟DOM(Virtual DOM)是一种优化手段,用于提高页面更新的效率。它是在内存中以JavaScript对象的形式维护的一份DOM树的拷贝,通过比较虚拟DOM树的变化并最小化实际DOM操作,从而减少页面重绘和重新布局的开销。 …

美容小程序:让预约更简单,服务更贴心

在当今繁忙的生活节奏中,美容预约常常令人感到繁琐和疲惫。为了解决这个问题,许多美容院和SPA中心已经开始采用美容小程序来简化预约流程,并提供更加贴心的服务。在这篇文章中,我们将引导您了解如何制作一个美容小程序&#xff0c…

2024 年 2 月 TIOBE 指数:最流行的 10 种编程语言

Go 进入了 TIOBE 指数的前 10 名,这是谷歌编程语言有史以来的最高位置。 在 2024 年 2 月的 TIOBE 软件最受欢迎的编程语言列表中,Python、C 和 C 保持了它们的领先地位(图 A)。TIOBE 的专有积分系统考虑了根据多种大型搜索引擎&…

Tailscale实现内网穿透、异地组网、远程访问

文章目录 Tailscale简介主要功能适用场景使用Tailscale的优势如何开始使用Tailscale总结参考资料注册登录Tailscale账号并下载客户端禁用秘钥过期简单使用设备添加 - 组网Linux安装Tailscale,实现设备添加Tailscale 中的 DERP 简介什么是 DERP?DERP 的优势DERP 的工作原理DER…

初识KMP算法

目录 1.KMP算法的介绍 2.next数组 3.总结 1.KMP算法的介绍 首先我们会疑惑,什么是KMP算法?这个算法是用来干什么的? KMP(Knuth-Morris-Pratt)算法是一种用于字符串匹配的经典算法,它的目标是在一个主文本…

MySQL数据库基础(七):DDL数据表操作

文章目录 DDL数据表操作 一、数据表的基本操作 1、数据表的创建 2、查询已创建数据表 3、修改数据表信息 ① 数据表字段添加 ② 修改字段名称或字段类型 ③ 删除某个字段 ④ 修改数据表名称 4、删除数据表 二、字段类型详解 1、整数类型 2、浮点类型 3、日期类型…

机器学习入门--门控循环单元(GRU)原理与实践

GRU模型 随着深度学习领域的快速发展,循环神经网络(RNN)已成为自然语言处理(NLP)等领域中常用的模型之一。但是,在RNN中,如果时间步数较大,会导致梯度消失或爆炸的问题,…

蓝桥杯嵌入式STM32G431RBT6知识点(主观题部分)

目录 1 前置准备 1.1 Keil 1.1.1 编译器版本及微库 1.1.2 添加官方提供的LCD及I2C文件 1.2 CubeMX 1.2.1 时钟树 1.2.2 其他 1.2.3 明确CubeMX路径,放置芯片包 2 GPIO 2.1 实验1:LED1-LED8循环亮灭 ​编辑 2.2 实验2&#xff1a…

Gitlab CI/CD docker命令报错:/usr/bin/bash: line 136: docker:command not found

天行健,君子以自强不息;地势坤,君子以厚德载物。 每个人都有惰性,但不断学习是好好生活的根本,共勉! 文章均为学习整理笔记,分享记录为主,如有错误请指正,共同学习进步。…

深入实战:ElasticSearch的Rest API与迭代器模式在高效查询中的应用

在我们公司,大多数Java开发工程师在项目中都有使用Elasticsearch的经验。通常,他们会通过引入第三方工具包或使用Elasticsearch Client等方式来进行数据查询。然而,当涉及到基于Elasticsearch Rest API的/_sql?formatjson接口时,…

alibaba的fastjson怎么将json字符串转换为范型对象

问题 alibaba的fastjson怎么将json字符串转换为范型对象? import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.TypeReference;public static void main(String[] args) {String jsonStr "{}";ResResult…

2 物理层(三):数据传输的方式,同步传输和异步传输

目录 1 数据的传输方式1.1 并行传输1.2 串行传输 2 同步传输和异步传输2.1 同步传输2.2 异步传输2.3 同步和异步传输对比 1 数据的传输方式 在数据通信中,数据传输方式有并行传输和串行传输两种 1.1 并行传输 定义:并行传输是指数据以成组的方式在多个…