📖 推荐阅读:《Yocto项目实战教程:高效定制嵌入式Linux系统》
🎥 更多学习视频请关注 B 站:嵌入式Jerry
mmap 全面解析:原理、用法与实战
mmap(内存映射)是 Unix/Linux 下进行高效文件操作、进程间通信和内存管理的强大工具。它不仅能让文件操作变得像操作内存一样简单,还能支持共享内存、内存分配等高级场景。
一、什么是 mmap?
mmap
(memory map)是 Linux 系统调用,能把文件或设备的内容映射到进程的虚拟内存空间,使我们像操作普通内存一样访问文件或外设数据。
mmap 的作用
- 高效文件访问:直接在内存中修改、读取文件内容,无需多余的读写系统调用。
- 共享内存:支持多个进程间共享同一段物理内存,提升进程间通信效率。
- 大块内存分配:适用于分配大于
malloc
限制的内存块,灵活扩展程序能力。 - 硬件寄存器映射:驱动开发、嵌入式应用中,直接操作物理地址。
二、mmap 的核心原理
- mmap 返回一个虚拟内存地址(虚拟空间),实际内容和底层文件或设备相关联。
- 所有操作系统都通过页表(Page Table)管理虚拟地址与物理地址的映射。
- mmap 分配的内存空间与传统的“堆”与“栈”区域分离,由内核管理,不受
malloc
和free
控制。
三、mmap 基本用法
mmap 原型
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
addr
:期望映射的起始地址(通常设为 NULL 让系统自动分配)length
:映射内存区域大小(字节数,常为页对齐大小)prot
:访问权限(如 PROT_READ, PROT_WRITE)flags
:映射方式(如 MAP_SHARED, MAP_PRIVATE, MAP_ANONYMOUS)fd
:被映射文件描述符(若是匿名映射则为 -1)offset
:文件起始偏移(通常为 0)
四、典型实战代码
1. 文件映射示例
下面代码把一个文本文件映射到内存,直接修改文件内容:
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <string.h>
int main() {
const char *filepath = "test.txt";
int fd = open(filepath, O_RDWR | O_CREAT, 0666);
if (fd < 0) { perror("open"); return 1; }
// 保证文件有足够空间
ftruncate(fd, 4096);
// 映射文件到内存
char *map = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (map == MAP_FAILED) { perror("mmap"); close(fd); return 1; }
// 写入内容
strcpy(map, "Hello, mmap! 内存映射很高效。\n");
msync(map, 4096, MS_SYNC); // 确保同步到磁盘
printf("文件内容:%s\n", map);
munmap(map, 4096);
close(fd);
return 0;
}
运行后,test.txt
会直接被改写,且不需要常规的 write
操作。
2. 匿名内存分配
无需文件,直接分配内存区域:
void *ptr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (ptr == MAP_FAILED) { /* 错误处理 */ }
strcpy((char*)ptr, "匿名映射内存示例");
puts((char*)ptr);
munmap(ptr, 4096);
3. 物理内存/设备寄存器映射(嵌入式常用)
例如访问物理地址,需 root 权限(以 /dev/mem 为例):
int fd = open("/dev/mem", O_RDWR | O_SYNC);
void *reg = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0x3F200000);
// 直接操作 reg 指向的物理区域
munmap(reg, 4096); close(fd);
(一般只在驱动开发或嵌入式底层使用)
五、mmap 常见问题与解答
Q1:mmap 分配的是堆空间吗?
不是。
- mmap 分配的空间既不属于“堆”也不属于“栈”,而是进程虚拟地址空间中的独立区域。
Q2:mmap 能直接访问物理内存吗?
普通情况不能。
- 只有映射
/dev/mem
、设备节点等时,才会直接访问物理内存,多用于驱动/硬件操作。
Q3:mmap 为什么效率高?
- 避免了内核与用户空间频繁拷贝,文件内容直接映射到进程空间,读写像内存操作一样高效。
- 系统只在需要时加载相应内存页,提升性能和资源利用率。
Q4:mmap 用于共享内存怎么做?
- 多个进程 mmap 同一个文件或匿名共享区域(使用 MAP_SHARED),即可实现数据同步和进程间通信。
Q5:mmap 分配的内存如何释放?
- 用
munmap(addr, length)
释放。 - 注意不要越界访问已解除映射的内存。
六、使用 mmap 的注意事项
-
同步问题
- 如果用 MAP_SHARED 映射,需要
msync
保证内存和文件同步。
- 如果用 MAP_SHARED 映射,需要
-
多进程同步
- 多进程同时访问时需加锁或使用原子操作。
-
大块内存分配
- mmap 适合大内存(如 128KB 以上),小内存用 malloc 更合适。
-
安全性
- 避免非法指针操作和释放后访问(悬挂指针问题)。
-
跨平台兼容性
- Windows 等平台也有 mmap 类似机制(如
MapViewOfFile
),但接口和用法略有不同。
- Windows 等平台也有 mmap 类似机制(如
七、结语与建议
- mmap 适合用于大文件高效访问、进程间共享、内存映射外设等场景。
- 使用 mmap 能让你的程序充分发挥 Linux 虚拟内存的威力,写出更高效和现代的系统代码。
- 实际开发时,关注同步、权限、异常处理等细节,避免越界和资源泄漏问题。
推荐练习
- 尝试用 mmap 映射一个大文件,统计文件中的某个字符出现次数。
- 试试用匿名 mmap 分配一块内存,实现父子进程通信。
- 编写一个只读 mmap 工具,实现文件零拷贝快速读取。
希望这篇博文让你彻底掌握 mmap 的原理与实践!如需深入进阶代码、性能分析或跨平台用法,欢迎留言交流!
📖 推荐阅读:《Yocto项目实战教程:高效定制嵌入式Linux系统》
🎥 更多学习视频请关注 B 站:嵌入式Jerry