由于mmap极为重要,所以单独拿出来作为一个注意点。

mmapmemory map 的缩写,它将内核中的一块内存(这里是 Framebuffer)映射到用户进程的地址空间,使得可以直接像访问普通数组一样读写这块内存,而不需要通过 read/write 系统调用。

说到mmap就不得不说零拷贝(zero-copy),举一些例子,但我们重点将mmap功能。

实现方式CPU拷贝次数系统调用适用场景硬件要求
mmap+write12小文件、需修改数据
sendfile0~11静态文件传输(HTTP服务器)可选DMA
splice01~2通用内核态数据传输
MSG_ZEROCOPY01大消息网络发送网卡支持
io_uring00 (共享内存)现代高性能异步I/O内核5.1+
RDMA00 (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 显存:图形程序中上传纹理或顶点数据。

4. 动态链接库的加载

当你运行一个程序时,操作系统加载 .so (共享库) 或 .dll 文件时,底层通常也使用 mmap

  • 原理:将库文件映射到内存,多个进程可以共享同一份库文件的物理页面(只读部分),节省内存。

5. 大内存分配(匿名映射)

当程序请求大块内存(例如 malloc 超过一定阈值,通常是 128KB 或 1MB)时,glibc 的 malloc 内部会使用 mmap 而不是 brk/sbrk

  • 参数fd 设为 -1flags 设为 MAP_ANONYMOUS | MAP_PRIVATE
  • 优势:释放方便(直接 munmap),不会造成堆内存碎片。

总结:什么时候该用 mmap

场景推荐指数原因
大文件随机读写⭐⭐⭐⭐⭐避免多次 seek 和拷贝,利用 OS 页缓存机制。
进程间共享大数据⭐⭐⭐⭐⭐最快的 IPC 方式,零拷贝。
操作硬件设备/显存⭐⭐⭐⭐⭐用户态直接访问硬件内存的唯一标准方式。
小文件顺序读写⭐⭐read/write 更简单,且 mmap 有缺页中断开销。
网络传输⭐⭐⭐结合 sendfilewrite 可实现零拷贝发送。