【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 已经分配,就可以直接从对应的物理页的偏移位置读取数据。
在这个过程中,虚拟页有三种状态:
- 未分配虚拟页:指的是没有使用 mmap 建立相应的
vm_area_struct
,因此也就没有对应到具体的页表项。 - 已分配虚拟页,未映射到物理页:表示已经使用了 mmap 建立的
vm_area_struct
,虚拟页可以映射到对应的页表项,但页表项尚未指向具体的物理页。 - 已分配虚拟页,已映射到物理页:表示已经使用了 mmap 建立的
vm_area_struct
,虚拟页可以映射到对应的页表项,并且页表项已经指向具体的物理页。
mmap 函数可以将虚拟地址映射到一个后备文件或者一个匿名文件。当操作系统分配物理内存时,实际上会利用匿名文件的 mmap 来完成内存分配。
mmap和虚拟内存管理
用户进程的虚拟内存管理是通过Linux内核中的mm_struct
结构来表示一个用户进程的虚拟内存地址空间。该结构包含了几个重要的字段来描述不同区域的地址范围和属性。
-
start_code
和end_code
:指定了进程的代码段的起始地址和结束地址,用于表示可执行代码的边界。一旦ELF二进制文件映射到虚拟内存后,这些地址就不会再改变。 -
start_data
和end_data
:指定了进程数据段的起始地址和结束地址,用于表示已初始化数据的边界。类似于代码段,这些地址在映射后也不再改变。 -
start_brk
和brk
:指定了堆的起始地址和结束地址,用于表示动态分配内存的边界。start_brk
表示堆的初始位置,在进程的整个生命周期中保持不变,而brk表示堆的结束位置,会随着堆的长度动态改变。 -
stack_top
:指定了栈的起始位置,一般位于用户进程地址空间的顶部,用于存放函数调用的栈帧。 -
task_size
:指定了用户进程地址空间的长度,即用户空间的顶部边界。 -
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
是用于描述用户进程虚拟内存区域的结构体:
-
vm_start
和vm_end
:表示区域的起始位置和结束位置,用于确定区域的边界。这两个字段确保了不同的vm_area_struct
之间不会出现交叉的情况,从而清晰地划分了各个虚拟内存区域。 -
vm_page_prot
:表示了该区域的页的访问权限,包括读、写、执行等。这些权限信息将影响到用户进程对该区域的访问行为。 -
shared
:处理具有后备文件的内存映射。它将该区域与后备文件的address_space
地址空间进行关联,以便在需要时能够正确地读取和写入数据。 -
anon_vma_node
和anon_vma
:处理匿名文件共享内存映射的情况。当多个虚拟内存区域映射到同一物理内存页时,这些映射将保存在一个链表中,并由anon_vma_node
进行管理,确保它们之间的正确关联。 -
vm_pgoff
和vm_file
:处理具有后备文件的内存映射的情况。vm_pgoff
表示了该映射在文件中的页偏移量,而vm_file
则包含了打开文件file实例的相关信息,以便在需要时能够正确地定位和操作对应的文件数据。
这些字段的信息使得vm_area_struct
能够全面描述用户进程的虚拟内存区域,包括区域的边界、访问权限、关联文件信息以及共享情况,为内核提供了管理和操作虚拟内存的重要依据。
对于有后备文件的映射,内核利用优先查找树结构来加速确定一个文件和所有映射到这个文件的虚拟内存区域vm_area_struct
实例的关系,从而可以方便地获取所有映射到这个文件的进程信息。这种优先查找树结构能够高效地管理文件和映射关系,提高查找效率和操作性能。
同时,内核提供了一系列函数用于对虚拟内存区域vm_area_struct
进行操作,包括创建、删除、合并、查找等功能。这些函数可以帮助内核有效地管理用户进程的虚拟内存区域,确保内存映射的正确性和一致性。
另外,mmap是C标准库提供给用户程序的函数,用于通过内存映射建立文件地址空间和虚拟内存区域的映射关系。通过mmap函数,用户程序可以将文件映射到自身的虚拟内存空间中,实现了方便的文件访问和操作。这种映射关系的建立是通过内核提供的相关功能实现的,确保了对文件数据的高效管理和访问。
mmap的4种类型
mmap函数在Linux系统中用于创建内存映射,可以分为有后备文件的映射和匿名文件的映射,每种映射又有私有映射和共享映射之分,因此mmap可以创建4种类型的映射。
-
有后备文件的共享映射:多个进程的
vm_area_struct
指向同一个物理内存区域,一个进程对文件内容的修改会被其他进程看到,并且这些修改会被写回到后备文件中。 -
有后备文件的私有映射:多个进程的
vm_area_struct
指向同一个物理内存区域,但采用写时拷贝的方式。当一个进程对文件内容做修改,不会被其他进程看到,并且对文件的修改也不会被写回到后备文件。当内存不足时,私有映射的页被交换到交换区。这种映射常用于加载共享代码库。 -
匿名文件的共享映射:内核创建一个初始为0的物理内存区域,然后多个进程的
vm_area_struct
指向这个共享的物理内存区域。对该区域内容的修改对所有进程可见,而且在页回收时被交换到交换区。 -
匿名文件的私有映射:内核创建一个初始为0的物理内存区域,对该区域内容的修改只对创建者进程可见。在页回收时,这种映射也会被交换到交换区。
malloc()
函数底层使用了匿名文件的私有映射来分配大块内存。
这些不同类型的映射提供了灵活的内存管理方式,使得进程可以根据需要选择适合的映射方式来处理内存数据,并且能够满足不同场景下的内存管理需求。
内核对堆空间的管理
从内核管理用户进程虚拟地址空间的角度来看,内存映射是主要的手段,通过建立vm_area_struct
结构来分配虚拟内存区域。对于堆空间的分配,主要通过brk
系统调用来实现。brk
系统调用本质上也是利用了匿名文件的私有映射机制,它分配并初始化为0的物理内存页,然后建立相应的vm_area_struct
,最后更新页表结构。
brk
系统调用分配的内存最小单位是页,需要按页对齐。在内核的视角下,每次对堆空间的分配至少是一页大小,即以页面为单位进行扩展。换句话说,更细粒度的字节级内存分配是由C语言标准库实现的,而在内核层面,堆空间的分配是以页面为单位进行管理的。这种设计确保了内核对虚拟内存的有效管理,并提供了一种简单且高效的方式来处理用户进程的内存分配需求。
参考:Linux内核源码分析(内存调优/文件系统/进程管理/设备驱动/网络协议栈)教程
Linux内核源码学习
相关文章:
【Linux 内核源码分析】内存映射(mmap)机制原理
内存映射(mmap)是 Linux 内核的一个重要机制,它为程序提供了一种将文件内容直接映射到进程虚拟地址空间的方式。同时内存映射也是虚拟内存管理和文件 IO 的重要组成部分。 在 Linux 中,虚拟内存管理是基于内存映射来实现的。在调用 mmap 函数时…...

贪心算法之合并区间
“任世界多宽广,停泊在这港口~” 区间问题,涉及到最多的就是 取交集 和 并集的概念。我们使用C排序算法后,其默认规则就是按照 “左排序”进行的。因而,我们实质上注意的是每一个区间的 右端点,根据题目要求ÿ…...

Eclipse - Colors and Fonts
Eclipse - Colors and Fonts References 编码最好使用等宽字体,Ubuntu 下自带的 Ubuntu Mono 可以使用。更换字体时看到名字里面带有 Mono 的基本都是等宽字体。 Window -> Preferences -> General -> Appearance -> Colors and Fonts -> C/C ->…...

java 数据结构LinkedList类
目录 什么是LinkedList 链表的概念及结构 链表的结构 无头单向非循环链表 addFirst方法(头插法) addLast方法(尾插法) addIndex方法 contains方法 removeAllKey方法 size和clear方法 链表oj题 无头双向非循环链表 ad…...

第五次作业(防御安全)
需求: 1.办公区设备可以通过电信链路和移动链路上网(多对多的NAT,并且需要保留一个公网IP 不能用来转换) 2.分公司设备可以通过总公司的移动链路和电信链路访问到DMZ区的http服务器 3.分公司内部的客户端可以通过公网地址访问到内部的服务…...

阿里云香港轻量应用服务器是什么线路?
阿里云香港轻量应用服务器是什么线路?不是cn2。 阿里云香港轻量服务器是cn2吗?香港轻量服务器不是cn2。阿腾云atengyun.com正好有一台阿里云轻量应用服务器,通过mtr traceroute测试了一下,最后一跳是202.97开头的ip,1…...

C# Winform .net6自绘的圆形进度条
using System; using System.Drawing; using System.Drawing.Drawing2D; using System.Windows.Forms;namespace Net6_GeneralUiWinFrm {public class CircularProgressBar : Control{private int progress 0;private int borderWidth 20; // 增加的边框宽度public int Progr…...

Git基本操作(超详细)
文章目录 创建Git本地仓库配置Git配置命令查看是否配置成功重置配置 工作区、暂存区、版本库添加文件--场景一概述实例操作 查看.git文件添加文件--场景二修改文件版本回退撤销修改情况⼀:对于工作区的代码,还没有 add情况⼆:已经 add &#…...

【AGI视频】Sora的奇幻之旅:未来影视创作的无限可能
在五年后的未来,科技的发展为影视创作带来了翻天覆地的变化。其中,Sora视频生成软件成为了行业的翘楚,引领着全新的创作潮流。Sora基于先进的Transformer架构,将AI与人类的创造力完美结合,为观众带来了前所未有的视听盛…...

Docker部署nginx
搜索镜像 docker search nginx 下载拉取nginx镜像 docker pull nginx 查看镜像 docker images 启动容器 docker run -d --name nginx01 -p 3344:80 nginx 外部端口需要在服务器安全组中设置,使用docker镜像nginx以后台模式启动一个容器,并将容器…...
C++Qt——自定义信号与槽
自定义信号与槽 自定义信号与槽是实现对象间通信的一种机制,比如按钮和窗口间的通信。 一、定义信号 Signal关键字声明的类成员函数。不需要实现,只需要声明。 signals:void mySignals();//定义信号,不用实现二、定义槽 可以使任何普通成员函数&…...
提高项目的性能和响应速度的方法
目录 引言 一、代码优化 二、数据库优化 三、缓存技术: 四、异步处理 1. 将耗时的操作改为异步处理 1.1 文件上传 1.2 邮件发送 2. 使用消息队列实现异步处理 2.1 配置消息队列 2.2 发送消息 2.3 接收消息并处理 五、负载均衡和集群 1. 负载均衡 1.1 …...
QT学习事件
一、事件处理过程 众所周知 Qt 是一个基于 C 的框架,主要用来开发带窗口的应用程序(不带窗口的也行,但不是主流)。 我们使用的基于窗口的应用程序都是基于事件,其目的主要是用来实现回调(因为只有这样程序…...
第13章 网络 Page818 UDP(和TCP的比较)
TCP核心类 asio::ip::tcp::socket;//网络套接字 asio::ip::tcp::endpoint;//边接端地址 asio::ip::tcp::resolver;//地址解析器 asio::ip::tcp::acceptor;//连接接受器 UPD核心类 asio::ip::udp::socket;//网络套接字 asio::ip::udp::endpoint;//边接端地址 asio::ip::udp::…...

EMQX Enterprise 5.4 发布:OpenTelemetry 分布式追踪、OCPP 网关、Confluent 集成支持
EMQX Enterprise 5.4.0 版本已正式发布! 新版本提供 OpenTelemetry 分布式追踪与日志集成功能,新增了开放充电协议 OCPP 协议接入能力,并为数据集成添加了 Confluent 支持。此外,新版本还进行了多项改进以及 BUG 修复,…...
记录 | C++ cout.setf(ios::fixed)
cout.setf(ios::fixed); 是在 C 中使用的一个标准库函数,用于将流的输出格式设置为"fixed" "fixed"格式指定输出浮点数时,小数点后的位数是固定的。这意味着,无论输出的数字有多少位小数,小数点后都会保留相…...

Eclipse 创建 Hello World 工程
Eclipse 创建 Hello World 工程 1. Hello WorldReferences Download and install the Eclipse IDE. 1. Hello World Eclipse -> double click -> Launch 单击蓝色方框 (右上角) 最大化 IDE File -> New -> C Project -> Finish Project name:工程名…...
【前端工程化面试题】vite热更新原理
vite 在开发阶段,运行 vite 命令,会启动一个开发服务器,vite 在开发阶段是一个服务器 依赖 esm: vite 在开发阶段使用 esm 作为开发时的模块系统。esm 具有动态导入的能力,这使得在代码中引入模块时可以动态地加载新的…...
【leetcode】判断二叉树是否完全二叉树
递归方式判断二叉树是否完全二叉树 bool TreeComplete(TreeNode* root) {if (root ! NULL) {if (root->left NULL && root->right ! NULL) {return false; // 左子树空}else if (root->left NULL && root->right NULL) {return true; // 左右子…...
Java多线程系列——内存模型JMM
目录 核心思想 关键概念 1. 可见性 2. 原子性 3. 有序性 工作原理 并发工具类 对并发编程的影响 同步策略 JMM的实践意义 结语 Java内存模型(Java Memory Model, JMM)是Java并发编程中的核心概念,其定义了Java虚拟机(JV…...

日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻
在如今就业市场竞争日益激烈的背景下,越来越多的求职者将目光投向了日本及中日双语岗位。但是,一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧?面对生疏的日语交流环境,即便提前恶补了…...

【入坑系列】TiDB 强制索引在不同库下不生效问题
文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...
多场景 OkHttpClient 管理器 - Android 网络通信解决方案
下面是一个完整的 Android 实现,展示如何创建和管理多个 OkHttpClient 实例,分别用于长连接、普通 HTTP 请求和文件下载场景。 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas…...

Opencv中的addweighted函数
一.addweighted函数作用 addweighted()是OpenCV库中用于图像处理的函数,主要功能是将两个输入图像(尺寸和类型相同)按照指定的权重进行加权叠加(图像融合),并添加一个标量值&#x…...
大语言模型如何处理长文本?常用文本分割技术详解
为什么需要文本分割? 引言:为什么需要文本分割?一、基础文本分割方法1. 按段落分割(Paragraph Splitting)2. 按句子分割(Sentence Splitting)二、高级文本分割策略3. 重叠分割(Sliding Window)4. 递归分割(Recursive Splitting)三、生产级工具推荐5. 使用LangChain的…...

定时器任务——若依源码分析
分析util包下面的工具类schedule utils: ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类,封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz,先构建任务的 JobD…...

江苏艾立泰跨国资源接力:废料变黄金的绿色供应链革命
在华东塑料包装行业面临限塑令深度调整的背景下,江苏艾立泰以一场跨国资源接力的创新实践,重新定义了绿色供应链的边界。 跨国回收网络:废料变黄金的全球棋局 艾立泰在欧洲、东南亚建立再生塑料回收点,将海外废弃包装箱通过标准…...

Ascend NPU上适配Step-Audio模型
1 概述 1.1 简述 Step-Audio 是业界首个集语音理解与生成控制一体化的产品级开源实时语音对话系统,支持多语言对话(如 中文,英文,日语),语音情感(如 开心,悲伤)&#x…...

在WSL2的Ubuntu镜像中安装Docker
Docker官网链接: https://docs.docker.com/engine/install/ubuntu/ 1、运行以下命令卸载所有冲突的软件包: for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done2、设置Docker…...

OPENCV形态学基础之二腐蚀
一.腐蚀的原理 (图1) 数学表达式:dst(x,y) erode(src(x,y)) min(x,y)src(xx,yy) 腐蚀也是图像形态学的基本功能之一,腐蚀跟膨胀属于反向操作,膨胀是把图像图像变大,而腐蚀就是把图像变小。腐蚀后的图像变小变暗淡。 腐蚀…...