Linux mmap 机制:从 read/write 底层流程到手写 malloc 内存分配

1. 在谈 mmap 之前

1.1 系统调用 read 的过程

在进程想要读取某个文件时(当然也可以是其他资源,因为 Linux 下 一切皆文件,这里指的是磁盘文件),会发起 read 系统调用。CPU 会从用户态转换到内核态,伴随着 CPU 上下文的切换、快表缓存的失效等;进入内核态后,接下来通过进程在 CPU 的 CR3 寄存器中保留的页表物理地址,找到进程想要访问的文件内容在页缓存中的物理地址。如果找到了,就直接把内容返回给用户态(当然这中间还包含着信号的处理流程,可能又涉及多次用户态、内核态的转化);如果没找到,就再到磁盘中加载。

1.2 系统调用 write 的过程

大体上和 read 相同,不过不同在于:CPU 通过 页表找到页缓存中的内容并作出改变后,write 的调用流程就结束了(页缓存是在磁盘和内存之间加了个中间层,存在于物理内存之上,所有进程对于磁盘的访问操作都会优先从这个页缓存中进行,如果没有的话,再从磁盘中加载进来。在这里我们不难发现,一个进程对页缓存的更改,会对其他进程的访问同样造成影响,即"没有持久化的硬盘操作")。之后内核会自行把页缓存中的内容更改落盘

2. mmap 文件映射

2.1 什么是 mmap

mmap 是 Linux 为了解决上面频繁进行系统调用开销过大而使用的另一个系统调用,包含在 <sys/mman.h> 中。mmap所做的事情,简单概括一下,就是直接把页缓存中的文件,通过页表,直接映射到进程的地址空间当中(更精确的,会把它映射到虚拟地址空间的堆和栈中间的位置)。

2.2 mmap 相比 read 和 write 提供了什么优势

从前面介绍的 read 和 write 的调用流程,我们不难发现,后两者在坏情况下涉及内核到用户、内存到硬盘的两次交互,而且都是比较费时的操作。有了 mmap 之后,我们的操作直接就可以落实到物理内存的页缓存之上,也就是减少了一次拷贝。同时不只是提供更高效率的写文件,也可以通过参数实现在物理内存上映射一块没有后端存储的区域给进程的地址空间(C语言的malloc就是用了 mmap)

2.3 系统调用接口

2.3.1 mmap

cpp 复制代码
void *mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off);
  • addr,表示想让当前进程地址空间的具体哪个区域映射到物理内存,如果给空指针的话,有内核决定
  • len,表示映射区域的长度,或者说映射区域的大小
  • prot,表示映射区域具有的权限,一般读写权限即可,设为 PROT_READ、PROT_WRITE(注意,这里的权限要和 fildes 指定的文件描述符权限匹配,不能这里允许写但是文件描述符只允许读)
  • flags,指定映射的其他属性,比如对映射的改变是否共享,通过 MAP_SHARED、MAP_PRIVATE 指定
  • fildes,要操作的具体文件的文件描述符(Linux下一切皆文件,文件描述符也可能对应设备,比如0、1、2,如果不关联文件、设备,比如 malloc 的话,就可以设置为 -1,表示不需要后端存储设备)
  • off,偏移量,映射起始位置相对于文件开头的偏移量,0表示从头开始映射

2.3.2 munmap

用来释放 mmap,需要传入 mmap 返回的文件映射区域指针,以及映射区域的大小

cpp 复制代码
int munmap(void *addr, size_t len);

2.4 【示例】最简版 malloc

malloc 其实就是为调用它的进程的物理空间中,塞了一块只有它能访问、修改的内存块,这恰好就可以通过我们的 mmap 来实现,不过需要注意的是,这样的纯内存映射需要通过 mmap 的参数指定为匿名映射,即不对应磁盘文件

cpp 复制代码
void* my_malloc(int size){  
   if(size <= 0){  
       perror("incorrect size");  
       return nullptr;  
   }  
   void* ret_ptr = ::mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);  
   if(ret_ptr == MAP_FAILED){  
       perror("mmap fail");  
       return nullptr;  
   }  
   return ret_ptr;  
}  
  
void my_free(void* ptr, int size){  
   if(ptr == nullptr || size <= 0){  
       return;  
   }  
   ::munmap(ptr, size);  
}
相关推荐
阳光九叶草LXGZXJ1 小时前
达梦数据库-报错-15-列【XXX】长度超出定义
linux·运维·数据库·sql·学习
奔跑的大白啊1 小时前
多容器共享目录权限踩坑记
linux·ubuntu·共享目录·linux权限·docker 容器化部署
黄大刀2 小时前
DataPulse一款开箱即用的SCADA(四)-历史记录
运维·scada·动环
正在走向自律4 小时前
AI数据分析与可视化:从基础到应用实践
服务器·人工智能·python·机器学习·数据分析·pandas
OnlineProxy4 小时前
亚马逊多账号运营:如何在规避“关联封号”风险的同时实现电商规模化扩张
服务器·数据库·redis
归秋1424 小时前
2026 企业 AI 办公工具选型指南:从评估框架到产品适配
大数据·运维·人工智能
Android系统攻城狮5 小时前
Linux Gstreamer深度解析之gst_audio_encoder_set_frame_max调用流程与实战(六十一)
android·linux·运维·音视频·gstreamer音视频·音视频进阶·gstreamer音视频进阶
H.莓飛5 小时前
【C++】命名空间、缺省参数、函数重载与引用
linux·开发语言·c++·visual studio
91刘仁德5 小时前
IP协议详解:从IP协议头到网段划分、路由与NAT
linux·服务器·网络·网络协议·tcp/ip
辻弋2015 小时前
五年前的旅行视频糊成马赛克?Video2X用Real-ESRGAN逐帧重建细节,但只支持Windows、集显用户建议直接放弃
服务器·数据库·windows·游戏引擎·电脑