由于mmap极为重要,所以单独拿出来作为一个注意点。
mmap 是 memory map 的缩写,它将内核中的一块内存(这里是 Framebuffer)映射到用户进程的地址空间,使得可以直接像访问普通数组一样读写这块内存,而不需要通过 read/write 系统调用。
说到mmap就不得不说零拷贝(zero-copy),举一些例子,但我们重点将mmap功能。
| 实现方式 | CPU拷贝次数 | 系统调用 | 适用场景 | 硬件要求 |
|---|---|---|---|---|
mmap+write | 1 | 2 | 小文件、需修改数据 | 无 |
sendfile | 0~1 | 1 | 静态文件传输(HTTP服务器) | 可选DMA |
splice | 0 | 1~2 | 通用内核态数据传输 | 无 |
MSG_ZEROCOPY | 0 | 1 | 大消息网络发送 | 网卡支持 |
io_uring | 0 | 0 (共享内存) | 现代高性能异步I/O | 内核5.1+ |
RDMA | 0 | 0 (bypass) | 超低延迟、远程内存访问 | 专用网卡 |
mmap应用场景
1. 高性能文件 I/O(替代 read/write)
这是 mmap 最经典的用途。
- 传统方式 (
read/write):磁盘 -> 内核页缓存 (Page Cache) -> 用户缓冲区。数据需要在内核态和用户态之间拷贝两次。 mmap方式:磁盘 -> 内核页缓存 <-> 用户虚拟地址。用户进程直接访问内核页缓存映射的地址,零拷贝(Zero-Copy),减少了上下文切换和数据拷贝开销。- 适用场景:
- 大文件的随机读写(如数据库索引文件)。
- 需要频繁读取同一文件的不同部分。
- 注意:对于小文件或顺序读写,
mmap的优势不明显,甚至可能因为缺页中断(Page Fault)而比read慢。
2. 进程间通信(IPC)- 共享内存
mmap 是实现共享内存最标准、最便携的方式。
-
原理:两个或多个进程映射同一个文件(或匿名内存),它们会看到同一块物理内存。一个进程修改数据,另一个进程立即可见。
-
优势:比管道(Pipe)、消息队列(Message Queue)快得多,因为不需要数据拷贝。
-
代码示例:
c// 进程 A 和 进程 B 都执行以下代码 int fd = open("shared_file", O_RDWR | O_CREAT, 0666); void *ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); // 现在 ptr 指向的内存是共享的
3. 设备驱动交互(如 Framebuffer)
这正是你当前代码 001_proj.c 的使用场景。
- 原理:Linux 将硬件设备抽象为文件(如
/dev/fb0,/dev/gpu,/dev/mem)。通过mmap将这些特殊文件映射到用户空间,用户程序可以直接读写硬件寄存器或显存。 - 常见设备:
- Framebuffer (
/dev/fb0):直接操作屏幕像素。 - GPIO / 嵌入式外设:在嵌入式 Linux 中,直接映射物理内存地址来控制硬件寄存器。
- GPU 显存:图形程序中上传纹理或顶点数据。
- Framebuffer (
4. 动态链接库的加载
当你运行一个程序时,操作系统加载 .so (共享库) 或 .dll 文件时,底层通常也使用 mmap。
- 原理:将库文件映射到内存,多个进程可以共享同一份库文件的物理页面(只读部分),节省内存。
5. 大内存分配(匿名映射)
当程序请求大块内存(例如 malloc 超过一定阈值,通常是 128KB 或 1MB)时,glibc 的 malloc 内部会使用 mmap 而不是 brk/sbrk。
- 参数:
fd设为-1,flags设为MAP_ANONYMOUS | MAP_PRIVATE。 - 优势:释放方便(直接
munmap),不会造成堆内存碎片。
总结:什么时候该用 mmap?
| 场景 | 推荐指数 | 原因 |
|---|---|---|
| 大文件随机读写 | ⭐⭐⭐⭐⭐ | 避免多次 seek 和拷贝,利用 OS 页缓存机制。 |
| 进程间共享大数据 | ⭐⭐⭐⭐⭐ | 最快的 IPC 方式,零拷贝。 |
| 操作硬件设备/显存 | ⭐⭐⭐⭐⭐ | 用户态直接访问硬件内存的唯一标准方式。 |
| 小文件顺序读写 | ⭐⭐ | read/write 更简单,且 mmap 有缺页中断开销。 |
| 网络传输 | ⭐⭐⭐ | 结合 sendfile 或 write 可实现零拷贝发送。 |