
🎬 个人主页 :艾莉丝努力练剑
❄专栏传送门 :《C语言》《数据结构与算法》《C/C++干货分享&学习过程记录》
《Linux操作系统编程详解》《笔试/面试常见算法:从基础到进阶》《Python干货分享》
⭐️为天地立心,为生民立命,为往圣继绝学,为万世开太平
🎬 艾莉丝的简介:

文章目录
- 前言
- [1 ~> 为什么要学习 mmap?](#1 ~> 为什么要学习 mmap?)
-
- [1.1 传统 I/O 的"搬运之苦"](#1.1 传统 I/O 的“搬运之苦”)
- [1.2 mmap 的"空间重叠"艺](#1.2 mmap 的“空间重叠”艺)
- [2 ~> 函数原型与核心参数](#2 ~> 函数原型与核心参数)
-
- [2.1 函数原型与核心参数](#2.1 函数原型与核心参数)
- [2.2 参数深度解析](#2.2 参数深度解析)
- [3 ~> 实战演练:mmap 的两种经典用法](#3 ~> 实战演练:mmap 的两种经典用法)
-
- [3.1 文件写入映射(数据持久化)](#3.1 文件写入映射(数据持久化))
- [3.2 读取映射(获取文件属性)](#3.2 读取映射(获取文件属性))
- [4 ~> 硬核进阶:模拟实现 malloc](#4 ~> 硬核进阶:模拟实现 malloc)
-
- [4.1 模拟代码](#4.1 模拟代码)
- [4.2 GDB 调试小故事:消失的 1024](#4.2 GDB 调试小故事:消失的 1024)
- [5 ~> 底层原理:vm_area_struct](#5 ~> 底层原理:vm_area_struct)
- [6 ~> 总结与课后思考](#6 ~> 总结与课后思考)
-
- [6.1 总结](#6.1 总结)
- [6.2 待完善的硬核课题](#6.2 待完善的硬核课题)
- 结尾
前言
在 Linux 系统编程的硬核世界里,如果说 read / write 是我们开启文件大门的钥匙,那么 mmap 就是一道直接穿透墙壁的"传送门"。它让我们可以像操作内存中的数组一样直接读写磁盘文件,优雅而高效。
1 ~> 为什么要学习 mmap?
1.1 传统 I/O 的"搬运之苦"
在传统的 read / write 模式下,数据要经历漫长的旅程:内核先从磁盘读到内核缓冲区,再从内核缓冲区拷贝到用户缓冲区。这种"二次拷贝"不仅浪费 CPU,还占用大量内存带宽。
1.2 mmap 的"空间重叠"艺
mmap(Memory Map)的核心思想是:将文件或设备的内容直接映射到进程的虚拟地址空间。
- 高效访问 : 读写文件就像读写内存,无需调用
read/write,消除了用户态与内核态之间的数据拷贝。 - 共享内存: 不同进程可以映射同一个文件,从而实现高效的数据共享。
2 ~> 函数原型与核心参数
2.1 函数原型与核心参数
要掌握这道传送门,首先得看懂它的"咒语":
cpp
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
int munmap(void *addr, size_t length);
2.2 参数深度解析
(1) addr :映射的"建议"起始地址。通常传入 NULL,让系统帮我们选一个最合适的。
(2) length :映射的字节数。注意:系统分配是以"页"(通常是 4KB )为单位的,即使你只申请 3500 字节,系统也会向上舍入分配 4096 字节。
(3) prot :内存保护权限。常用组合:PROT_READ | PROT_WRITE(可读可写)。
(4) flags:映射特性。
MAP_SHARED:共享映射。对内存的修改会同步到原始文件中,且对其他进程可见。MAP_PRIVATE:私有映射。采用**写时复制(COW)**机制。你的修改只存在于内存副本中,不会影响磁盘文件。MAP_ANONYMOUS:匿名映射。不关联任何文件,常用于实现 malloc。
(5) fd :已打开文件的文件描述符。如果是匿名映射则传 -1。
(6) offset:映射的起始偏移量。
3 ~> 实战演练:mmap 的两种经典用法
3.1 文件写入映射(数据持久化)
在这个示例中,我们先用 ftruncate 扩充文件大小(mmap 无法映射空文件),然后直接通过指针写入字母。
cpp
// 核心逻辑摘录
int fd = open("log.txt", O_RDWR | O_CREAT | O_TRUNC, 0666); [cite: 747, 1156]
ftruncate(fd, 4096); // 必须调整文件大小,否则无法映射
char *shmaddr = (char*)mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); [cite: 755, 1170]
// 像操作数组一样操作文件
for(char c = 'a'; c <= 'z'; c++) {
shmaddr[c - 'a'] = c; // 直接写入内存,系统会自动同步到磁盘 [cite: 762, 1189]
}
munmap(shmaddr, 4096); // 善后工作 [cite: 772, 1199]
close(fd); [cite: 782, 1203]
3.2 读取映射(获取文件属性)
读取时,我们常用 fstat 获取文件的真实大小,确保映射范围准确。
cpp
struct stat st;
fstat(fd, &st); // 获取文件状态 [cite: 846, 847, 1260, 1264]
char *shmaddr = (char*)mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0); [cite: 849, 1265]
std::cout << "File content: " << shmaddr << std::endl; // 直接打印 [cite: 854, 1278]
4 ~> 硬核进阶:模拟实现 malloc
你可能不知道,你平时调用的 malloc,在分配大块内存时,底层其实就是用的 mmap 的匿名映射(MAP_ANONYMOUS)。
4.1 模拟代码
cpp
void *my_malloc(size_t size) {
// 匿名映射,不关联文件 (fd = -1)
void *ptr = mmap(NULL, size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); [cite: 884, 1295]
return (ptr == MAP_FAILED) ? nullptr : ptr; [cite: 884, 1295]
}
void my_free(void *ptr, size_t size) {
munmap(ptr, size); [cite: 896, 1295]
}
4.2 GDB 调试小故事:消失的 1024
我们在代码中只申请了 1024 字节,但在 GDB 中查看 info proc mapping 时,却发现分配的长度(Size)是 0x1000(即 4096 字节)。
结论 : 无论你申请多小,内核都会以"页"为最小单位进行对齐。这就是为什么 mmap 被认为比 brk 更"重"一些。
5 ~> 底层原理:vm_area_struct
在内核眼里,mmap 到底做了什么? 在进程的虚拟地址空间描述符 mm_struct 中,每一个 mmap 成功的区域都会对应一个 vm_area_struct 结构体。
vm_start&vm_end:记录了这一块虚拟内存的起止边界。vm_file:这个指针指向了被映射的文件对象。
当进程访问这块虚拟地址时,如果发现物理内存还没加载,就会触发缺页中断 。内核此时顺着 vm_file 找到文件,把数据加载到物理内存中,并填好页表。至此,虚拟地址与磁盘数据完美"接头"。
6 ~> 总结与课后思考
6.1 总结
- MAP_SHARED 像是在云端协同办公,大家改的内容都能实时保存到源文件。
- MAP_PRIVATE 像是你下载了一个副本,随便你怎么改,源文件巍然不动(写时复制)。
6.2 待完善的硬核课题
如果我们要处理一个 10GB 的超大文件,能否将其"打散"成多个小块,通过mmap映射到不同的虚拟内存区域进行并行管理?这一直是高性能服务器开发中的核心命题。
结尾
uu们,本文的内容到这里就全部结束了,艾莉丝在这里再次感谢您的阅读!
|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ### 艾莉丝努力练剑 C/C++ & Linux 底层探索者 | 一个正在努力练剑的技术博主 *** ** * ** *** 👀 【关注】 跟随我一起深耕技术领域,见证每一次成长。 ❤️ 【点赞】 让优质内容被更多人看见,让知识传递更有力量。 ⭐ 【收藏】 把核心知识点存好,在需要时随时查、随时用。 💬 【评论】 分享你的经验或疑问,评论区一起交流避坑! 不要忘记给博主"一键四连"哦! "今日练剑达成!"
"技术之路难免有困惑,但同行的人会让前进更有方向。" |
结语:希望对学习Linux相关内容的uu有所帮助,不要忘记给博主"一键四连"哦!
往期回顾:
【Linux线程】Linux系统多线程(四):线程ID及进程地址空间布局,线程封装
🗡博主在这里放了一只小狗,大家看完了摸摸小狗放松一下吧!🗡 ૮₍ ˶ ˊ ᴥ ˋ˶₎ა
