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_READPROT_WRITE(注意,这里的权限要和 fildes 指定的文件描述符权限匹配,不能这里允许写但是文件描述符只允许读)
  • flags,指定映射的其他属性,比如对映射的改变是否共享,通过 MAP_SHAREDMAP_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);  
}
相关推荐
草莓熊Lotso1 小时前
【Linux网络】UDP Socket 编程全解析:从回显服务到通用字典服务,从零实现工业级代码
linux·运维·服务器·数据库·c++·单片机·udp
一只积极向上的小咸鱼3 小时前
Codex 在 VS Code + ModelArts 场景下的登录与配置总结
linux·运维·windows
Waay7 小时前
Linux Shell 知识点考评(一):grep 文本搜索(附答案)
linux·运维·服务器
jamon_tan7 小时前
Linux下串口RAW模式设置
linux
碧海银沙音频科技研究院8 小时前
基于VMware虚拟机ubuntu开发博通BK7258方法
linux·运维·ubuntu
云边有个稻草人10 小时前
【Linux系统】进程地址空间
linux·虚拟地址空间·进程地址空间·虚拟地址空间是怎么实现的?·为什么要有虚拟地址空间?·怎么理解虚拟地址空间?
weixin_5142531810 小时前
511-qwen3.5-patch
服务器
谁似人间西林客12 小时前
工厂大脑如何让汽车制造告别“救火式”运维?
运维·汽车·制造
飞飞传输13 小时前
数字化科研提速关键 构建安全可控一体化跨网数据传输体系
大数据·运维·安全