3. mmap 系统调用概述:原型、映射类型与权限标志

mmap(memory map)是 Linux 内核提供的、用于在进程虚拟地址空间与文件或匿名物理内存之间建立直接映射关系的系统调用。理解 mmap 与 munmap 的原型、映射分类以及权限标志位的组合语义,是掌握用户态高性能内存管理的基础。

3.1 mmap / munmap 系统调用原型

mmap 的核心功能是在进程的虚拟地址空间中分配一段连续区域,并将其与某个文件描述符(或匿名页)建立映射。munmap 则用于解除映射,释放虚拟地址区间。

#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);
参数 含义 典型值 / 说明
addr 映射起始地址(提示) 通常传 NULL,由内核选择;非 NULL 时需配合 MAP_FIXED
length 映射长度(字节) 必须为页大小(4096)的整数倍,否则内核会向上取整
prot 映射区域访问权限 PROT_READ / PROT_WRITE / PROT_EXEC / PROT_NONE
flags 映射类型与行为标志 MAP_SHARED / MAP_PRIVATE / MAP_ANONYMOUS / MAP_FIXED 等
fd 文件描述符 文件映射时有效;匿名映射传 -1
offset 文件内偏移 必须为页大小整数倍;通常为 0
返回值:成功时返回映射区域的起始虚拟地址(void*);失败时返回 MAP_FAILED(即 (void*)-1),并设置 errno。

3.2 文件映射与匿名映射

根据映射后端物理内存的来源,mmap 分为两大类:文件映射和匿名映射。两者的核心区别在于是否关联一个已打开的文件描述符。

3.2.1 文件映射(File-backed Mapping)

将文件的一段内容直接映射到进程的虚拟地址空间。对映射区域的读写会直接反映到文件中(MAP_SHARED),或通过写时拷贝(Copy-on-Write)产生私有副本(MAP_PRIVATE)。

  • 典型用途:文件 I/O 加速、共享库加载、大文件随机访问。
  • 关键参数:fd 必须为有效文件描述符;offset 指定文件内起始位置。
  • 性能优势:避免用户态与内核态之间的数据拷贝,利用内核页缓存(Page Cache)实现按需加载。

3.2.2 匿名映射(Anonymous Mapping)

不关联任何文件,映射区域由内核分配物理页并初始化为零。通常用于进程间共享内存(配合 MAP_SHARED)或动态内存分配(如 glibc 的 malloc 在大块分配时内部使用 mmap)。

  • 典型用途:进程间通信(共享内存)、大块堆内存分配、线程栈创建。
  • 关键参数:fd 传 -1;flags 必须包含 MAP_ANONYMOUS。
  • 性能优势:延迟分配(Demand Paging),仅在首次访问时触发缺页中断并分配物理页。
工程经验:在性能敏感场景中,文件映射应优先使用 MAP_SHARED 以利用内核的脏页回写机制;匿名映射配合 MAP_POPULATE 标志可预分配物理页,避免运行时缺页抖动。
文件映射与匿名映射结构对比 文件映射 进程虚拟地址 映射区域 内核页缓存 (Page Cache) 磁盘文件 (fd, offset) fd 有效,MAP_SHARED 或 MAP_PRIVATE 匿名映射 进程虚拟地址 映射区域 物理内存页 (零初始化) fd = -1,MAP_ANONYMOUS 无磁盘文件关联 按需分配物理页 可用于共享内存或 malloc 大块

3.3 映射权限与标志位

mmap 的 prot 参数控制映射区域的访问权限,flags 参数控制映射的共享属性、内存分配策略以及内核行为。正确组合这两个参数是避免段错误、数据损坏或性能退化的关键。

3.3.1 权限位(prot)

标志 值 含义
PROT_READ 0x1 允许读取映射区域
PROT_WRITE 0x2 允许写入映射区域
PROT_EXEC 0x4 允许执行映射区域中的指令
PROT_NONE 0x0 禁止任何访问(常用于 guard page)
注意:文件映射的权限不能超过打开文件时使用的 open() 模式。例如,以只读方式打开的文件,即使 mmap 指定 PROT_WRITE,内核也会返回 EACCES 错误。

3.3.2 核心标志位(flags)

标志 分类 语义说明
MAP_SHARED 共享类型 映射区域的修改对所有共享该映射的进程可见,并会同步回文件(文件映射)
MAP_PRIVATE 共享类型 写时拷贝(Copy-on-Write),修改仅对当前进程可见,不写回文件
MAP_ANONYMOUS 映射类型 创建匿名映射,忽略 fd 参数;通常与 MAP_SHARED 或 MAP_PRIVATE 组合
MAP_FIXED 地址约束 强制使用 addr 指定的地址,若地址不可用则失败;谨慎使用
MAP_POPULATE 预分配 在 mmap 返回时预先分配物理页,避免后续缺页中断(适用于实时场景)
MAP_NORESERVE 交换策略 不预留交换空间,允许超额承诺(overcommit),适用于大块稀疏映射
MAP_LOCKED 锁定 将映射页锁定在物理内存中,禁止换出(需特权或 RLIMIT_MEMLOCK)

3.3.3 常见组合模式

  • 文件 I/O 加速:MAP_SHARED | PROT_READ | PROT_WRITE,配合文件描述符,实现零拷贝读写。
  • 进程间共享内存:MAP_ANONYMOUS | MAP_SHARED | PROT_READ | PROT_WRITE,用于无亲缘关系进程间的数据交换。
  • 私有匿名堆:MAP_ANONYMOUS | MAP_PRIVATE | PROT_READ | PROT_WRITE,glibc malloc 在分配大于 128KB 的内存时内部使用此组合。
  • 代码加载与执行:MAP_PRIVATE | PROT_READ | PROT_EXEC,用于动态库映射,支持写时拷贝。
mmap 标志位组合决策流程 mmap 调用入口 是否关联文件? 是 文件映射 否 匿名映射 MAP_SHARED / PRIVATE? MAP_SHARED 写回文件 MAP_PRIVATE 写时拷贝 MAP_SHARED / PRIVATE? MAP_SHARED 共享内存 MAP_PRIVATE 私有堆/栈 组合 PROT_READ / PROT_WRITE / PROT_EXEC 控制访问权限

3.4 munmap 与映射生命周期管理

munmap 用于解除映射,释放虚拟地址区间。调用后,进程对该区域的访问将触发 SIGSEGV。内核会立即释放对应的物理页(对于文件映射,脏页会被标记为需要回写)。

  • 参数要求:addr 必须是 mmap 返回的起始地址,length 必须与 mmap 时一致(或为页对齐值)。
  • 部分解除:Linux 支持对映射区域的部分解除,但 addr 和 length 必须页对齐。
  • 自动解除:进程退出时,所有映射区域会被内核自动解除;但显式调用 munmap 是良好的资源管理习惯。
性能要点:频繁的 mmap / munmap 操作会引入 TLB 刷新和页表更新开销。在需要反复映射同一文件的场景中,建议复用映射区域(通过 mremap 调整大小)或使用内存池缓存映射句柄。