【Linux加餐】mmap文件映射

🎬 个人主页艾莉丝努力练剑
专栏传送门 :《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及进程地址空间布局,线程封装

🗡博主在这里放了一只小狗,大家看完了摸摸小狗放松一下吧!🗡 ૮₍ ˶ ˊ ᴥ ˋ˶₎ა

相关推荐
叶子野格2 小时前
《C语言学习:编程例题》8
c语言·开发语言·c++·学习·算法·visual studio
returnthem2 小时前
运维笔记:Shell 脚本入门到实践
运维·笔记
6Hzlia2 小时前
【Hot 100 刷题计划】 LeetCode 152. 乘积最大子数组 | C++ 动态规划 (绝妙 swap 翻转技巧)
c++·leetcode·动态规划
HockerF2 小时前
cpu原理到c/c++指针
c语言·c++
久绊A2 小时前
Linux 跨服务器导出 Docker 容器内文件
linux·docker
迷你可可小生2 小时前
面经学习(二)
学习·算法
John.Lewis2 小时前
C++加餐课-二叉树:进阶算法
数据结构·c++·算法
DeepHacking2 小时前
Ubuntu 22.04 安装 Allow Locked Remote Desktop 扩展:解决锁屏后 mstsc 无法连接的问题
linux·运维·ubuntu
广州灵眸科技有限公司2 小时前
瑞芯微(EASY EAI)RV1126B QT GUI例程方案
linux·服务器·开发语言·网络·人工智能·qt·物联网