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!