上节回顾:上一讲分析了UNIX信号机制中的信号丢失与信号屏蔽问题,指出非实时信号无法排队,同类信号只递送一次,强调信号只适合轻量级通知,不适合高频计数或复杂通信。
1. 主题原理与细节逐步讲解
1.1 什么是内存映射文件(mmap)
mmap是UNIX/Linux下用于将文件或设备映射到进程地址空间的系统调用。- 它允许程序像操作内存一样直接读写文件内容,常用于大文件处理、共享内存、数据库实现等。
1.2 mmap的基本用法
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
int fd = open("test.dat", O_RDWR);
void *ptr = mmap(NULL, filesize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
// ...对ptr进行读写...
munmap(ptr, filesize);
close(fd);
2. 相关C语言典型陷阱/缺陷说明及成因剖析
2.1 文件大小与映射区不一致
- 映射长度必须不超过文件实际大小。否则访问超出部分会导致SIGBUS异常。
- 如果先
mmap后扩展文件,比如用ftruncate,新扩展的部分不会自动映射,访问也会SIGBUS。
2.2 访问未同步到文件
- 用
MAP_PRIVATE映射,所有写入只反映在内存,不会同步到原文件(写时复制/COW)。 - 必须用
MAP_SHARED让修改同步回文件。
2.3 缓存一致性与msync
- 修改映射区后,内核不保证立即写回磁盘,需调用
msync确保数据落盘。 - 程序异常退出,未
msync的数据可能丢失。
2.4 多进程/多线程同步问题
- 多个进程/线程同时操作同一映射区,容易出现数据竞争、一致性问题,需加锁保护。
- 修改映射区的内容不会自动通知其它进程,需借助IPC或信号。
2.5 关闭文件描述符后映射区仍可访问
- 关闭
fd后,只要映射区未munmap,数据仍能访问,但此时刷新同步可能失败,建议先munmap后close(fd)。
2.6 映射区指针越界访问
mmap返回的指针只可访问指定长度,越界会引发SIGSEGV或SIGBUS。
2.7 文件权限与映射权限不匹配
- 文件以只读打开,映射却申请写权限,会失败。
3. 规避方法与最佳设计实践
3.1 严格检查文件大小与映射长度一致
- 映射前用
fstat获取文件大小,或先ftruncate扩展到映射所需长度。
3.2 用MAP_SHARED做文件读写,修改后及时msync
MAP_SHARED用于需要同步回文件的数据。- 修改完毕后调用
msync。
3.3 多线程/进程访问时加锁
- 使用mutex、sem或文件锁保护映射区,避免并发写入引发数据紊乱。
3.4 映射区访问严格限于有效范围
- 仅以
ptr为基地址,访问长度不超过mmap返回的size。
3.5 关闭/回收顺序正确
- 先
munmap,再close(fd),避免同步失效或资源泄露。
3.6 错误处理与异常保护
- 检查
mmap、msync、munmap的返回值,防止未映射或同步失败。
4. 典型错误代码与优化后正确代码对比
错误示例:映射长度大于文件实际大小
int fd = open("test.dat", O_RDWR);
void *ptr = mmap(NULL, 1024, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); // 文件只有512字节
ptr[900] = 'A'; // 可能SIGBUS
问题:访问超出实际文件部分,内核无法映射,导致异常。
正确示例:扩展文件后再映射
int fd = open("test.dat", O_RDWR);
ftruncate(fd, 1024); // 扩展文件到1024字节
void *ptr = mmap(NULL, 1024, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
// 安全访问
ptr[900] = 'A';
msync(ptr, 1024, MS_SYNC); // 修改后同步
munmap(ptr, 1024);
close(fd);
错误示例:用MAP_PRIVATE映射后写入
void *ptr = mmap(NULL, filesize, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
ptr[0] = 'X';
msync(ptr, filesize, MS_SYNC); // 文件内容不会改变
问题:MAP_PRIVATE写入只在内存,文件不会更新。
正确示例:用MAP_SHARED并msync
void *ptr = mmap(NULL, filesize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
ptr[0] = 'X';
msync(ptr, filesize, MS_SYNC); // 文件内容同步更新
5. 底层原理补充说明
mmap本质是将文件页表挂接到进程虚拟地址空间,内核通过页缓存机制实现懒加载和写回。MAP_PRIVATE采用写时复制(COW),修改只在进程私有空间,原文件不变。SIGBUS异常通常由非法内存访问(如越界、未映射页)引发。
6. mmap映射与文件同步关系

7. 总结与实际建议
- mmap映射区长度必须与文件实际大小匹配,否则越界访问会触发SIGBUS。
- 写入映射区需用MAP_SHARED,并及时msync保证落盘。
- 多进程/多线程操作需加锁,防止并发一致性问题。
- 关闭顺序为先munmap再close,避免同步失效或资源泄露。
- 严禁越界访问,所有mmap/munmap/msync返回值必须检查。
内存映射文件极大提升了大数据处理效率,但也带来了多平台、并发和异常处理的严苛要求。良好习惯与细致设计,是mmap高可靠使用的核心。
公众号 | FunIO
微信搜一搜 “funio”,发现更多精彩内容。
个人博客 | blog.boringhex.top

被折叠的 条评论
为什么被折叠?



