当前位置: 首页 > news >正文

【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内核源码学习

相关文章:

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

内存映射(mmap)是 Linux 内核的一个重要机制,它为程序提供了一种将文件内容直接映射到进程虚拟地址空间的方式。同时内存映射也是虚拟内存管理和文件 IO 的重要组成部分。 在 Linux 中,虚拟内存管理是基于内存映射来实现的。在调用 mmap 函数时&#xf…...

贪心算法之合并区间

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

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 修复&#xff0c…...

记录 | 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…...

深入理解 Vue3 中的 setup 函数

💗💗💗欢迎来到我的博客,你将找到有关如何使用技术解决问题的文章,也会找到某个技术的学习路线。无论你是何种职业,我都希望我的博客对你有所帮助。最后不要忘记订阅我的博客以获取最新文章,也欢…...

【QT+QGIS跨平台编译】之三十六:【RasterLite2+Qt跨平台编译】(一套代码、一套框架,跨平台编译)

文章目录 一、RasterLite2介绍二、文件下载三、文件分析四、pro文件五、编译实践一、RasterLite2介绍 RasterLite2是一个开源的轻量级栅格数据库,可以用于存储和管理各种类型的栅格数据,包括卫星遥感图像、数字高程模型等。 与传统的GIS数据存储方式不同,RasterLite2采用基…...

java面试题:分布式和微服务的区别

1 分布式和微服务概念不同 微服务架构是架构设计方式,是设计层面的东西,一般考虑如何将系统从逻辑上进行拆分,也就是垂直拆分。 分布式系统是部署层面的东西,即强调物理层面的组成,即系统的各子系统部署在不同计算机…...

GO语言的变量与常量

1.变量 go是一个静态语言 变量必须先定义后使用变量必须要有类型 定义变量的方式: var 名称 类型 var 名称 值 名称 :值 例如: var num int 这样就存了一个num类型为int的变量 var num 1 上面使用简化的定义通过num自动判断后面的类型为int并…...

java面试多线程篇

文章说明 在文章中对所有的面试题都进行了难易程度和出现频率的等级说明 星数越多代表权重越大,最多五颗星(☆☆☆☆☆) 最少一颗星(☆) 1.线程的基础知识 1.1 线程和进程的区别? 难易程度:☆☆…...

Anaconda + VS Code 的安装与使用

目录 一. Anaconda 是什么二. Anaconda 的安装1. 下载安装包2. 安装3. 检查 三. Anaconda 的使用1. 创建虚拟环境2. 激活虚拟环境3. 包管理4. 列举虚拟环境5. 退出虚拟环境6. 删除虚拟环境 四. VS Code 开发1. 安装插件2. 打开工作区3. 选择解释器 五. VS Code 个性化设置1. 切…...

Python爬虫html网址实战笔记

仅供学习参考 一、获取文本和链接 import requests from lxml import htmlbase_url "https://abcdef自己的网址要改" response requests.get(base_url) response.encoding utf-8 # 指定正确的编码方式tree html.fromstring(response.content, parserhtml.HTML…...

C++ 调用js 脚本

需求: 使用Qt/C 调用js 脚本。Qt 调用lua 脚本性能应该是最快的,但是需要引入第三方库,虽然也不是特别麻烦,但是调用js脚本,确实内置的功能(C 调用lua 脚本-CSDN博客) 步骤: 1&…...

Vscode python pyside6 制作视频播放器

一、界面如下 包含控件 qcombox、qtablewidget、qpushbotton、qverticalslider 二、运行代码 media_player.py import sysfrom PySide6 import QtWidgets from PySide6.QtWidgets import * from PySide6.QtMultimedia import * from PySide6.QtMultimediaWidgets import QVi…...

纯前端低代码平台demo,vue框架,nodejs,简单的pm2纯前端部署实践

文章目录 目录结构说明本地运行项目启动后的页面demo前端部署打包pm2nginx 后话 前段时间开发了一个纯前端的低代码平台demo,vue框架,nodejs,pm2纯前端部署实践。为此记录一下开发过程以及各方面遇到的问题,并作说明。 表单用了若…...