4. mmap 返回值与错误处理
mmap() 系统调用的返回值是判断映射是否成功的关键依据。与普通系统调用返回 -1 表示失败不同,mmap() 在失败时返回一个特殊的宏常量 MAP_FAILED(通常为 (void *) -1),成功时返回映射区的起始虚拟地址。理解返回值的含义以及常见 errno 的触发场景,是编写健壮内存映射程序的基础。
核心原则: 每次调用
mmap() 后,必须立即检查返回值是否等于 MAP_FAILED,并通过 errno 获取具体错误原因,避免对无效地址进行操作导致段错误。
4.1 返回值语义
| 返回值 | 类型 | 含义 | 后续操作 |
|---|---|---|---|
| 有效地址 | void * |
映射成功,返回映射区域的起始虚拟地址 | 可对该地址进行读写(根据权限) |
| MAP_FAILED | void *(通常为 (void *)-1) |
映射失败,不分配任何内存 | 检查 errno 确定失败原因 |
#include <sys/mman.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
void *addr = mmap(NULL, 4096, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (addr == MAP_FAILED) {
fprintf(stderr, "mmap failed: %s (errno=%d)\n",
strerror(errno), errno);
// 根据 errno 执行不同的恢复策略
return -1;
}
// 映射成功,正常使用 addr
printf("Mapping at %p\n", addr);
4.2 常见 errno 及触发场景
不同的参数组合和系统状态会导致不同的 errno。下表列出了 mmap() 中最常见的四种错误码及其典型触发原因:
| errno | 含义 | 典型触发场景 | 排查方向 |
|---|---|---|---|
| EINVAL | 无效参数 |
|
检查参数对齐和合法性 |
| ENOMEM | 内存不足 |
|
检查系统内存、ulimit 和映射数量限制 |
| EACCES | 权限不足 |
|
检查文件权限和挂载选项 |
| EBADF | 无效文件描述符 |
|
检查 fd 的来源和生命周期 |
调试技巧: 使用
perror("mmap") 或 strerror(errno) 可以快速输出人类可读的错误描述。在开发阶段,建议将 errno 的数值也打印出来,方便查阅内核文档。
4.3 错误处理流程图
下图展示了 mmap() 调用后的完整错误处理决策路径:
4.4 典型错误处理模式
在实际工程中,推荐使用以下结构化错误处理模式,确保每个 mmap() 调用都有对应的错误恢复路径:
void *safe_mmap(void *addr, size_t length, int prot, int flags,
int fd, off_t offset) {
// 1. 前置校验:length 不能为 0
if (length == 0) {
errno = EINVAL;
return MAP_FAILED;
}
// 2. 调用 mmap
void *mapped = mmap(addr, length, prot, flags, fd, offset);
// 3. 失败处理
if (mapped == MAP_FAILED) {
switch (errno) {
case EINVAL:
fprintf(stderr, "Invalid parameters: check length alignment and flags\n");
break;
case ENOMEM:
fprintf(stderr, "Out of memory: check system resources\n");
// 可尝试缩小映射大小后重试
break;
case EACCES:
fprintf(stderr, "Permission denied: check file permissions\n");
break;
case EBADF:
fprintf(stderr, "Bad file descriptor\n");
break;
default:
fprintf(stderr, "Unexpected mmap error: %d\n", errno);
break;
}
}
return mapped;
}
常见陷阱:
- 不要直接比较
addr == -1:应始终使用addr == MAP_FAILED,因为MAP_FAILED是(void *) -1,类型安全。 - 不要忽略
EINTR:虽然mmap()通常不会被信号中断,但在某些特殊内核配置下可能返回EINTR,建议在循环中重试。 - MAP_FIXED 的地址冲突:使用
MAP_FIXED时,如果指定地址区域已被占用且无法覆盖,返回MAP_FAILED且errno为EINVAL。
4.5 错误码与系统资源关系
下表总结了 mmap() 错误码与底层系统资源限制之间的对应关系,帮助开发者快速定位问题根源:
| 资源类型 | 相关限制 | 触发 errno | 查看命令 |
|---|---|---|---|
| 虚拟地址空间 | 进程地址空间大小(32 位约 3GB) | ENOMEM | ulimit -v |
| 物理内存 + 交换 | 系统可用内存不足 | ENOMEM | free -h |
| 映射段数量 | vm.max_map_count(默认 65530) |
ENOMEM | cat /proc/sys/vm/max_map_count |
| 文件权限 | 文件读/写/执行权限 | EACCES | ls -l |
| 文件描述符 | 进程打开文件数限制 | EBADF | ulimit -n |
| 页对齐 | offset 必须是页大小整数倍 | EINVAL | getconf PAGE_SIZE |
通过系统化地理解返回值语义和 errno 分类,开发者可以在 mmap() 失败时快速定位问题,并采取合适的恢复策略,从而构建出更稳定、更可靠的内存映射程序。