linux mmap 底层实现

mmap 是 Linux 提供的内存映射系统调用 ,核心作用是:将「内核中的一段内存 / 磁盘文件」,直接映射到当前进程的虚拟地址空间;进程访问这段虚拟地址,等价于直接访问映射的内核内存 / 磁盘文件,全程无内核缓冲区中转、无数据拷贝,是高性能内存操作 / 进程通信的基石。

mmap 函数原型

mmap 两大核心参数

prot= 访问权限,flags= 映射属性,这两个参数是 mmap 的灵魂,不同组合直接决定 mmap 是「文件映射」「匿名内存分配」「共享内存」还是「私有内存」,所有用法都是这两个参数的组合:

prot 访问权限(4 种基础值,可位或组合)

  • PROT_READ :映射区可读
  • PROT_WRITE:映射区可写
  • PROT_EXEC :映射区可执行(存储代码时用)
  • PROT_NONE :映射区无任何权限(占位用)

规则:权限不能超过文件的打开权限(比如文件只读,prot 不能设 PROT_WRITE),否则 mmap 失败。

flags 映射属性

这是 mmap 最核心的参数,决定映射的「内存归属、共享特性、写时行为」,分为两大核心分类:

  1. 共享属性(二选一,必选)
    • MAP_SHARED共享映射 → 进程对映射区的修改,会同步到内核 / 磁盘文件 / 其他进程,是「内存共享」的核心标志;
    • MAP_PRIVATE私有映射 → 进程对映射区的修改,仅当前进程可见,内核会触发「写时复制 (COW)」创建副本,不影响原数据;
  2. 内存来源(可选,组合使用)
    • MAP_ANONYMOUS / MAP_ANON匿名映射 → 无磁盘文件作为映射源,内存来源是「内核物理内存」,fd 必须传 - 1,offset 传 0;
    • MAP_FIXED:强制映射到指定的 addr 地址,不推荐(容易地址冲突);

共享看 MAP_SHARED/MAP_PRIVATE,内存来源看 MAP_ANONYMOUS

mmap 核心特性

  • 映射地址天然对齐 :mmap 返回的虚拟地址,必然是 4KB 物理页面对齐(Linux 最小内存页单位);
  • 按需分配物理内存 :mmap 调用时仅分配虚拟地址空间,不分配物理内存;进程第一次访问映射地址时触发「缺页异常」,内核才分配物理页并建立映射;
  • 无数据拷贝:所有映射访问都是「虚拟地址→物理地址」直接访问,无内核缓冲区中转,无数据拷贝,这是 mmap 高性能的核心原因;
  • 映射大小按页对齐:即使 length 传 1,内核也会按 4KB 向上对齐分配虚拟地址,物理内存也是按需按页分配;
  • munmap 是必须的 :进程退出时内核会自动解除映射,但手动调用munmap能及时释放虚拟地址空间和物理内存,避免内存泄漏。

mmap 内核底层实现

步骤 1:用户态发起 mmap 系统调用,内核接收参数

进程调用mmap(NULL, len, prot, flags, fd, 0),触发软中断进入内核态,内核的sys_mmap()函数接收所有参数,并做合法性校验:

  • 校验 prot 权限是否合法、是否与文件 fd 的打开权限匹配;
  • 校验 flags 参数组合是否合法(比如 MAP_ANONYMOUS 时 fd 必须是 - 1);
  • 校验 length 是否大于 0;

步骤 2:内核分配「虚拟地址空间」

内核在进程的mm_struct中,查找一段连续的、未被使用的虚拟地址空间,大小为「按 4KB 向上对齐后的 length」,这段地址是进程私有的,不会与其他 VMA 重叠。

步骤 3:内核创建「虚拟内存区域 (VMA)」

内核为本次映射创建一个新的 struct vm_area_struct(VMA 结构体),并填充关键属性:

  • 虚拟地址起始 / 结束地址、映射长度;
  • 访问权限:prot 对应的读写执行权限;
  • 映射属性:VM_SHARED/VM_PRIVATE(对应 flags)、VM_ANON(匿名映射)/VM_MAPPED(文件映射);
  • 关联资源:文件映射关联对应的文件 inode;匿名映射关联内核的物理内存池;最后将该 VMA 加入进程的 VMA 链表中,此时仅分配了虚拟地址,无任何物理内存分配

步骤 4:内核返回映射的虚拟地址到用户态

内核将分配的虚拟地址起始值,通过系统调用返回给用户态进程,进程拿到该地址后,即可开始访问。

此时进程的虚拟地址空间已经分配完成,但物理内存还是空的,页表中也没有对应的映射关系!

步骤 5:进程第一次访问映射地址,触发「缺页异常」

进程执行 *(char*)addr = 10; 访问映射地址,CPU 的 MMU(内存管理单元)在进程的多级页表 (PGD/PUD/PMD/PTE) 中查找该虚拟地址,发现无对应的物理页映射 ,触发 缺页异常 (page fault),CPU 暂停进程执行,进入内核态处理异常。

步骤 6:内核处理缺页异常,分配物理内存 + 建立页表映射

这是 mmap 的核心步骤,内核的do_page_fault()函数处理异常,完成「物理内存分配 + 页表映射」:

  1. 内核根据虚拟地址找到对应的 VMA 结构体,确认该地址是合法的映射地址;
  2. 内核从物理内存伙伴系统中,分配一块空闲的 4KB 物理页帧;
  3. 内核修改进程的多级页表:将当前虚拟地址,通过页表项 (PTE) 映射到刚分配的物理页帧的物理地址;
  4. 如果是文件映射 :内核还会从磁盘读取对应的数据到物理页中(仅一次);如果是匿名映射:物理页初始化为全 0;
  5. 页表映射建立完成后,内核返回用户态,恢复进程执行。

步骤 7:进程正常访问映射内存,无内核介入

进程再次执行访问指令,MMU 通过页表直接找到物理地址,直接读写物理内存,无任何内核介入,无数据拷贝,效率和访问进程私有内存完全一致;

步骤 8:munmap 的内核流程

调用munmap(addr, len)时,内核执行:

  1. 根据虚拟地址找到对应的 VMA 结构体;
  2. 删除该 VMA,释放对应的虚拟地址空间;
  3. 清空进程页表中对应的映射关系;
  4. 将物理页帧归还给内核(匿名映射)/ 标记为脏页等待刷盘(文件映射);
  5. 物理页会被加入内核 LRU 链表 ,内存紧张时被 kswapd 回收,也可以用mlock()锁定物理页避免回收。

核心场景

  • 匿名私有 → malloc 大内存;
  • 匿名共享 → 父子进程轻量共享;
  • 文件私有 → 高性能读文件;
  • 文件共享 → Posix 共享内存(/dev/shm),任意进程共享;
相关推荐
刘某某.2 小时前
linux 常用命令学习
linux·运维·学习
“αβ”2 小时前
传输层协议--TCP协议
linux·服务器·网络·网络协议·tcp/ip·http·https
万叶学编程2 小时前
Navicat连接Linux主机(MySQL)失败
linux·运维·服务器
Coder个人博客2 小时前
Linux6.19-ARM64 crypto NH-Poly1305 NEON子模块深入分析
linux·网络·算法·车载系统·系统架构·系统安全·鸿蒙系统
weixin_462446232 小时前
Python 实战:将 HTML 表格一键导出为 Excel(xlsx)
linux·python·excel·pandas
济6173 小时前
linux 系统移植(第九期)----Linux 内核顶层 Makefile- make xxx_defconfig 过程-- Ubuntu20.04
linux·运维·服务器
宴之敖者、3 小时前
Linux——yum和vim
linux·运维·服务器
人道领域3 小时前
JavaWeb从入门到进阶(Maven依赖管理)
linux·python·maven
大柏怎么被偷了3 小时前
【Linux】信号
linux·运维·服务器