mmap和munmap接口详解
mmap munmap接口介绍
mmap 和 munmap 是 Unix/Linux 系统编程中用于管理内存映射的核心系统调用。它们提供了在进程的虚拟地址空间和资源(通常是文件)之间建立直接关联的机制,从而高效地进行文件 I/O 或创建匿名内存区域。
1. mmap - 建立内存映射
1 |
|
- 功能: 将文件或设备的一部分(或匿名内存)映射到调用进程的虚拟地址空间中的一段连续区域。
- 参数:
addr(建议地址):进程希望映射开始的建议起始地址。通常设置为NULL,让内核自动选择合适地址。如果指定非NULL值,内核会尽量满足(需满足对齐等约束),但不保证。length(映射长度):要映射的字节数。必须大于 0。prot(保护权限):指定映射区域的访问权限。使用位掩码|组合:PROT_READ:页面可读。PROT_WRITE:页面可写。PROT_EXEC:页面可执行(包含机器代码)。PROT_NONE:页面不可访问(极少单独使用)。
flags(映射标志):控制映射的类型和行为。使用位掩码|组合:MAP_SHARED:共享映射。对映射区域的修改会写回到底层文件(或共享内存对象),对其他映射了同一文件的进程可见。文件修改时间通常也会更新。用于文件 I/O 和进程间通信 (IPC)。MAP_PRIVATE:私有映射(写时复制)。对映射区域的修改不会写回文件。进程会获得原文件内容的私有副本,修改只影响该副本。其他进程不可见。常用于加载程序代码/只读数据或创建临时内存副本。MAP_ANONYMOUS/MAP_ANON:创建匿名映射。映射区域不与任何文件关联,初始化为零。fd参数被忽略,通常设为 -1。offset应为 0。用于动态分配大块内存(如 malloc 底层可能使用)、进程间共享内存 (IPC) 或 BSS 段初始化。MAP_FIXED:强制使用指定地址。如果addr不为 NULL,则内核必须将映射精确放在该地址。如果该地址已被占用或不符合对齐要求,调用会失败。谨慎使用,可能导致地址冲突。MAP_POPULATE(Linux 特定):预读页表项。对于文件映射,尝试在mmap返回前预读文件内容(预填充页表),减少后续缺页异常。对于匿名映射,会预先分配物理内存(零填充)。可能阻塞。MAP_NORESERVE:不预留交换空间。告诉内核不要为这个映射预留后备的交换空间。如果物理内存耗尽,访问未分配的页面可能导致SIGSEGV。常用于MAP_PRIVATE | MAP_ANONYMOUS映射,确保内存耗尽时立即失败。MAP_LOCKED(Linux 特定):锁定内存。尝试将映射的页面锁定在物理内存中,防止被换出到交换空间(需要CAP_IPC_LOCK能力或RLIMIT_MEMLOCK限制足够)。MAP_HUGETLB(Linux 特定):使用大页。使用巨页 (Huge Pages) 进行映射,减少页表开销,提高大内存区域性能。
fd(文件描述符):要映射的文件的描述符。对于MAP_ANONYMOUS映射,必须设为 -1。offset(文件偏移):从文件开头算起的偏移量,表示映射应从文件的哪个位置开始。必须是系统页大小 (sysconf(_SC_PAGE_SIZE)) 的整数倍。通常为 0 表示映射整个文件(或从开头)。
- 返回值:
- 成功:返回映射区域的起始地址(一个指针)。
- 失败:返回
MAP_FAILED(通常是(void *) -1),并设置errno(如EACCES,EBADF,EINVAL,ENFILE,ENODEV,ENOMEM,EPERM,ETXTBSY等)。
- 工作原理:
- 内核在进程的虚拟地址空间中寻找一段足够大的、连续的、满足
addr建议和权限要求的空闲区域。 - 在内核的页表中创建映射条目,将这些虚拟页面对应到物理内存页面(或即将对应)。此时尚未分配物理页面。
- 当进程首次访问映射区域中的某个页面时,触发**缺页异常 (Page Fault)**。
- 内核处理缺页异常:
- **文件映射 (
MAP_SHARED/MAP_PRIVATE)**:将文件offset对应位置的数据读取到物理内存页面。 - **匿名映射 (
MAP_ANONYMOUS)**:分配一个物理页面(通常清零)。 - **
MAP_PRIVATE**:如果是写访问,内核会为该页面创建一份私有副本(写时复制 - Copy-on-Write)。
- **文件映射 (
- 内核更新页表,建立虚拟地址到物理地址的映射。
- 进程恢复执行,访问成功。
- 内核在进程的虚拟地址空间中寻找一段足够大的、连续的、满足
2. munmap - 解除内存映射
1 |
|
- 功能: 解除之前由
mmap建立的映射关系。 - 参数:
addr:要解除映射区域的起始地址。必须是之前mmap返回的地址值。length:要解除映射的字节数。不需要精确匹配原始mmap的长度,但必须包含在原始映射范围内且起始地址对齐到页边界。通常直接使用mmap返回的地址和原始长度。
- 返回值:
- 成功:返回 0。
- 失败:返回 -1,并设置
errno(通常是EINVAL,表示地址无效或未对齐)。
- 工作原理:
- 内核查找覆盖地址范围
[addr, addr + length - 1]的所有映射。 - 移除这些映射对应的页表项。
- 释放与这些映射关联的进程虚拟地址空间。
- 释放物理资源:
- 对于
MAP_PRIVATE映射或匿名映射:如果这些物理页面只被当前进程的该映射引用(且未被锁定),则释放物理内存(或标记为可回收)。 - 对于
MAP_SHARED文件映射:物理页面可能被其他映射引用(如其他进程或内核缓存),因此不会立即释放。脏页面(被修改过)会由内核异步写回文件(除非使用msync强制同步)。
- 对于
- 进程后续访问该地址范围会导致
SIGSEGV(段错误)。
- 内核查找覆盖地址范围
关键点与常见用途
高效文件 I/O:
MAP_SHARED: 提供对文件内容的直接内存访问,避免了read/write的系统调用开销和用户/内核空间之间的数据拷贝。对大文件随机访问或频繁读写局部区域特别高效。修改自动反映到文件(通过内核脏页回写机制)。MAP_PRIVATE: 高效创建文件内容的只读或私有可写副本,无需显式malloc+read。常用于加载程序代码/库。
进程间通信 (IPC):
MAP_SHARED | MAP_ANONYMOUS: 创建不依赖文件的匿名共享内存区域。父进程映射后fork,子进程自动继承映射,父子进程可通过该区域通信。也可用于无亲缘关系进程(需配合其他 IPC 机制传递地址/描述符,如shm_open+ftruncate+mmap创建 POSIX 共享内存对象)。MAP_SHARED文件映射:多个进程映射同一文件(或部分)到一个共享内存区域进行通信。
动态内存分配:
MAP_PRIVATE | MAP_ANONYMOUS: 分配大块内存的底层机制之一(如malloc在分配超大块时可能使用mmap)。分配的内存初始化为零。释放时调用munmap。
特殊用途:
- 映射设备内存到用户空间进行直接访问(需要驱动支持)。
- 实现零拷贝网络/存储 (Zero-Copy)。
重要注意事项
- 对齐:
offset必须是页大小的整数倍。addr和length在munmap时最好也保持页对齐。 - 资源管理:
mmap分配的是虚拟地址空间。物理内存按需分配(缺页异常)。munmap是释放映射的唯一正确方式。 - 同步 (
msync): 对MAP_SHARED映射,修改不会立即写回磁盘(由内核异步处理)。如果需要确保数据落盘,需调用msync(addr, length, MS_SYNC)。 - 文件描述符 (
fd):mmap之后立即close(fd)通常是安全的,映射依然有效(内核内部维护引用)。 - 地址空间碎片: 频繁
mmap/munmap不同大小的区域可能导致虚拟地址空间碎片化,影响后续大块内存映射。 - 错误处理: 务必检查
mmap返回值是否为MAP_FAILED并处理errno。检查munmap返回值。 - 权限冲突:
prot和flags的设置必须与打开文件fd时的模式兼容(如O_RDONLY打开的文件不能PROT_WRITE)。
简单示例 (C)
1 |
|
这个示例打开一个文件,将其整个内容映射到进程的地址空间进行只读访问,打印前 10 个字节,然后解除映射。注意错误处理和资源清理 (close, munmap)。
理解 mmap 和 munmap 对于进行高效系统编程、文件操作和进程间通信至关重要。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Helloeuler!
