【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及进程地址空间布局,线程封装

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

相关推荐
Hical6120 分钟前
实测:C++20 协程 vs Go Gin vs Rust Actix,谁的 Web 性能更强?
c++
爱编码的小八嘎26 分钟前
C语言完美演绎9-23
c语言
草莓熊Lotso32 分钟前
《告别 “会用不会讲”:C++ string 底层原理拆解 + 手撕实现,面试 / 开发都适用》
开发语言·c++·面试
Qt程序员34 分钟前
网络 I/O 面试必考点:从多进程多线程到异步 I/O 与多路复用
linux·网络编程·多线程·epoll·网络io·阻塞io·io_uring
逆羽飘扬37 分钟前
【AI Infra面试】基础学习汇总篇
人工智能·学习
会编程的土豆39 分钟前
【数据结构与算法】空间复杂度从入门到面试:不仅会算,还要会解释
数据结构·c++·算法·面试·职场和发展
学习是种信仰40 分钟前
远程控制服务器开关机——Wake-on-LAN(WOL 局域网唤醒)
服务器
张槊哲1 小时前
C++ 进阶指南:如何丝滑地理解与实践多线程与多进程
开发语言·c++·算法
model20051 小时前
虚拟环境安装yolo26
linux·运维·服务器
雪度娃娃1 小时前
Effective Modern C++——型别推导
开发语言·c++