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

Linux内存映射原理

目录

一、为什么需要mmap,传统读文件的缺陷是什么?

二、mm_struct的各个区域划分

三、内存映射的基本原理

(1)分配虚拟地址区间

(2)建立地址、文件的映射关系

(3)缺页中断:触发数据加载

四、文件映射的关键特性:不止步于“读写文件”

(1)两种映射类型:文件映射与匿名映射

(2)映射标志:控制 “修改是否同步到文件”

(3)数据同步:何时写回磁盘?

四、文件映射的优势

1. 减少数据拷贝,提升效率​

2. 简化编程:用 “内存操作” 代替 “文件操作”​

3. 支持高效共享:多进程共享数据​

五、内存映射的使用场景

1. 大文件处理(GB 级)​

2. 进程间通信(IPC)​

3. 设备操作(内存映射 IO)        ​


        在Linux中,内存映射(mmap)是一种让进程像访问物理内存一样操作文件或者其他设备的机制。他跳过了传统文件读写的内核缓冲区拷贝,大幅提升了数据交互的效率,同时也是进程间通信的高效手段。对于新手而言,理解mmap原理不仅能掌握一种高效的进程间通信手段,还能加深对虚拟地址空间的理解。

一、为什么需要mmap,传统读文件的缺陷是什么?

        我们先来看看传统的read/write是如何读写文件的。

当读取一个文件的时候,数据会经历3次拷贝:

(1)内核把磁盘数据读入到读缓冲区(内核空间的一块内存)

(2)内核再把数据从内核空间的读缓冲区拷贝到用户空间定义的内存中(C语言层面的缓冲区)。

(3)从C语言层面的缓冲区读到上层应用中。

        写入一个文件时则恰恰相反,不过这两种都有一个很明显的问题:数据要在用户空间和内核空间之间来回拷贝,如果处理大文件(比如几个 GB 的日志、数据库文件),频繁的拷贝会严重消耗 CPU 和内存带宽。

        而内存映射的核心思想是:把文件或设备的一部分直接 “映射” 到进程的虚拟地址空间。此后,进程操作这块虚拟内存时,就像直接操作文件或设备本身 —— 无需read/write,也无需数据拷贝。

二、mm_struct的各个区域划分

        在之前的文章中我们曾提及过用户虚拟地址空间的划分,即又mm_struct宏观管理、vm_area_struct精细管理。

struct mm_struct {/* 1. 内存区域管理核心 */struct vm_area_struct *mmap;       // 所有内存区域链表(堆/栈/文件映射等)struct rb_root mm_rb;              // 内存区域红黑树(快速查找)int map_count;                     // 内存区域总数/* 2. 程序代码与数据段(可执行文件加载区域) */unsigned long start_code;          // 代码段起始地址(.text段)unsigned long end_code;            // 代码段结束地址unsigned long start_data;          // 数据段起始地址(.data/.bss段)unsigned long end_data;            // 数据段结束地址/* 3. 堆区域 */unsigned long start_brk;           // 堆起始地址(固定)unsigned long brk;                 // 堆当前结束地址(可扩展)/* 4. 栈区域 */unsigned long start_stack;         // 用户栈起始地址(高地址)unsigned long stack_limit;         // 栈的最低地址限制(栈向下生长的边界)/* 5. 文件映射与共享内存区域 */unsigned long mmap_base;           // 文件映射区起始地址(mmap分配的地址从此开始)struct list_head mmap_shared;      // 共享映射区域链表(如共享库、共享内存)/* 6. 命令行参数与环境变量区域(用户态初始化数据) */unsigned long arg_start;           // 命令行参数起始地址unsigned long arg_end;             // 命令行参数结束地址unsigned long env_start;           // 环境变量起始地址unsigned long env_end;             // 环境变量结束地址/* 7. 页表与地址空间控制 */pgd_t *pgd;                        // 页全局目录(虚拟地址转物理地址的根)unsigned long task_size;           // 进程虚拟地址空间总大小(如32位4GB)/* 8. 引用与同步 */atomic_long_t mm_users;            // 共享该内存空间的进程数(如线程)atomic_long_t mm_count;            // 自身引用计数struct rw_semaphore mmap_sem;      // 保护内存区域操作的信号量
};

大致的示意图如下:

        可以看到,一个进程PCB中会有一个mm_struct,而一个mm_struct会划分为多个区域,其中就有一个区域叫做文件映射区,但是文件映射区和其他的区域大不相同,比如堆区、栈区有自己的指针,每一个进程必定会有,所以有brk、start_stack这种指针。而文件映射区纯粹依赖于vm_area_struct链表的管理,在没有文件映射的情况下,不会创建vm_area_struct来精细描述,也就没有文件映射区了。(所有区域中仅仅栈、堆必定有专用指针,其余任何区域都纯粹依赖于vm_area_struct来管理,特点是使用时才创建,不存在时则无这个区域)

        注意一点,如果有多个文件映射到这个进程,则每一个文件都有一个vm_area_atruct结构体,因为在上图中我们可以看出来一个vm_area_struct结构体只有一个struct file指针(即一个文件指针)。

       

三、内存映射的基本原理

        内存映射的本质是在进程的虚拟地址空间和文件 / 设备的物理存储之间,建立一座 “映射桥梁”。具体可以拆分为三个关键步骤:

(1)分配虚拟地址区间

        当进程调用mmap函数时,内核会在进程的虚拟地址空间中,划分出一块连续的 “空闲虚拟地址区间”,尽管虚拟地址空间已经有一部分被划分为了各个区域和自己的vm_area_struct,但是总归有没有使用到的地方,就给了文件映射区,这个区域通常我们让内核自己去找,而非手动设置。这块区间的大小与要映射的文件 / 设备大小(或指定的长度)一致,比如映射一个 10MB 的文件,就会分配 10MB 的虚拟地址。​

        注意:此时只是分配了 “虚拟地址”,并没有实际占用物理内存,就像给你一张 “提货单”,但货物还没送到仓库。

(2)建立地址、文件的映射关系

        内核会在进程的 “页表”(记录虚拟地址与物理地址对应关系的表格)中,添加一系列 “映射记录”:虚拟地址区间中的每个 “页”(通常 4KB),都会对应文件中的一个 “块”(比如文件的第 0-4095 字节对应虚拟地址的第 0-4095 字节)。​

此时,虚拟地址和文件内容的映射关系已建立,但数据还没加载到物理内存。​

        注意:这一步并未填写页表,页表仍然为空。只是会根据mmap的各个参数,填写到vm_area_struct中,如文件偏移量、映射长度等。

(3)缺页中断:触发数据加载

        当进程访问这块虚拟地址时(比如读取某个字节),CPU 会通过页表查找对应的物理地址。但此时物理地址还未分配(因为数据没加载),CPU 会触发 “缺页中断”,让内核处理:​

  • 内核会从磁盘读取文件中对应的块,加载到物理内存的某个页框(物理内存的最小单位);​
  • 填写页表,将虚拟地址与刚分配的物理页框关联;​
  • 中断结束后,进程继续访问,此时就能读到物理内存中的数据了。​

后续再访问同一部分数据时,因为已经在物理内存中,就不会触发缺页中断,直接通过页表映射访问即可。

四、文件映射的关键特性:不止步于“读写文件”

内存映射的强大之处,在于它的灵活性和多场景适配,核心特性包括:

(1)两种映射类型:文件映射与匿名映射

  • 文件映射:将磁盘文件映射到虚拟地址空间,进程对虚拟地址的修改会同步到文件(取决于映射标志)。这是最常用的场景,比如编辑大文件时,编辑器会通过 mmap 映射文件,避免一次性加载整个文件到内存。​
  • 匿名映射:不关联任何文件,而是映射一块 “匿名内存”(由内核分配物理内存)。这种方式常用于进程间通信(通过共享匿名内存),或动态分配大块内存(比malloc更高效)。
  • 在进程间通信方面,匿名映射比文件映射更加优秀,因为他会省去磁盘IO和文件管理的开销。普通的文件映射用于进程间通信,本质是多个进程映射到同一个物理地址,但是这个地址里面的内容最终还是会从物理内存拷贝到磁盘中,如果频繁修改,则可能产生大量的开销。同时,如果你仅仅使用他通信,最后还需要处理文件残留的问题。

        如果是匿名映射则少了写回磁盘的部分:简单来说,匿名映射是轻量级、纯内存的共享方式,适合临时、高频的进程通信;而映射普通文件则更适合需要持久化数据的场景。        

(2)映射标志:控制 “修改是否同步到文件”

  • MAP_SHARED:进程对虚拟地址的修改会同步到文件,且其他映射该文件的进程也能看到修改(共享变更)。比如多进程协作编辑同一个文件时,用这个标志能实时同步变更。​
  • MAP_PRIVATE:进程的修改不会同步到文件,也不会被其他进程看到(私有副本)。内核会在进程首次修改时,创建物理内存的 “私有副本”(写时复制,Copy-On-Write),避免影响原文件和其他进程。常用于临时读写文件,但不想污染源文件。

(3)数据同步:何时写回磁盘?

        用MAP_SHARED映射时,修改不会立即写入磁盘,而是先存在物理内存的 “页缓存” 中,由内核在合适的时机(比如内存不足、调用msync函数、关闭映射)同步到磁盘。这与传统文件读写的 “延迟写” 机制一致,保证效率的同时减少磁盘 IO。

四、文件映射的优势

        相比read/write等传统 IO 函数,内存映射的核心优势体现在:​

1. 减少数据拷贝,提升效率​

        传统 IO 需要 “用户缓冲区←→内核缓冲区” 的拷贝,而内存映射通过虚拟地址直接访问内核页缓存(物理内存中的文件数据),跳过了用户态与内核态之间的拷贝。对于大文件(比如 1GB 以上),这种效率提升非常明显。​

2. 简化编程:用 “内存操作” 代替 “文件操作”​

进程可以像操作数组一样操作文件,比如:​

// 映射文件后,直接通过指针修改虚拟地址
char *addr = mmap(...);
addr[100] = 'A';  // 相当于修改文件的第100个字节

无需调用lseek定位、read/write传输,代码更简洁。

3. 支持高效共享:多进程共享数据​

        多个进程可以映射同一个文件(MAP_SHARED标志),实现 “共享内存”:一个进程的修改会自动同步到其他进程(因为共享物理内存中的页缓存)。这比管道、消息队列等 IPC 方式更高效,是数据库、分布式系统中进程协作的常用手段。

五、内存映射的使用场景

内存映射不是 “银弹”,但在以下场景中能发挥最大价值:​

1. 大文件处理(GB 级)​

        比如数据库读取超大表文件、日志系统分析海量日志。传统 IO 会因频繁拷贝导致效率低下,而 mmap 只需加载当前访问的部分数据(按需加载),适合处理远大于物理内存的文件。​

2. 进程间通信(IPC)​

        多进程需要高频共享数据时(比如渲染引擎的多个线程共享帧缓存),用MAP_SHARED映射同一个文件(或匿名内存),比用管道传输数据更高效(无拷贝、低延迟)。​

3. 设备操作(内存映射 IO)        ​

        Linux 中很多设备(如显卡、网卡)的寄存器和缓冲区会被映射到物理地址空间,进程可以通过 mmap 将这些物理地址映射到虚拟地址,直接操作设备(比如向显卡寄存器写入像素数据),这是设备驱动开发的核心方式。

http://www.dtcms.com/a/294268.html

相关文章:

  • 基于MCP架构的LLM-Agent融合—构建AI Agent的技术体系与落地实践
  • day060-zabbix监控各种客户端
  • 力扣MySQL(1)
  • python 字符串常用处理函数
  • Zookeeper学习专栏(七):集群监控与管理
  • 解决代码生成过程虚拟总线信号无法直接传递给自定义总线信号问题的方法
  • Python curl_cffi库详解:从入门到精通
  • Redis能完全保证数据不丢失吗?
  • 基于OpenOCD 的 STM32CubeIDE 开发烧录调试环境搭建 DAPLINK/STLINK
  • 《计算机网络》实验报告六 电子邮件
  • 【轨物方案】分布式光伏电站运维升级智能化系列:老电站的数智化重生
  • Zabbix 企业级分布式监控
  • Axios 响应拦截器
  • dfaews
  • vue3笔记(2)自用
  • 设备虚拟化技术
  • 筛选数据-group_concat
  • Go语言实现对象存储——下载任意图片,保存到阿里云存储,并将URL保存到数据库。
  • 【数据库】国产数据库的新机遇:电科金仓以融合技术同步全球竞争
  • Pycaita二次开发基础代码解析:图层管理、基准控制与特征解析深度剖析
  • lwIP学习记录5——裸机lwIP工程学习后的总结
  • 【C++】类和对象(中)构造函数、析构函数
  • 海信IP501H-IP502h_GK6323处理器-原机安卓9专用-优盘卡刷固件包
  • ZLMediaKit流媒体服务器WebRTC页面显示:使用docker部署
  • Android多开实现方案深度分析
  • Android13重置锁屏(2)
  • 论文略读:Knowledge is a Region in Weight Space for Finetuned Language Models
  • springboot集成LangChain4j
  • 世博会无法在Android上启动项目:无法连接到TCP端口5554:连接被拒绝
  • 2025暑期—05神经网络-BP网络