应使用 github.com/edsrzf/mmap-go 而非 syscall.Mmap,因其自动处理页对齐、平台差异与错误转换;映射后必须显式调用 mm.Flush() 才能持久化写入,defer mm.Close() 仅释放映射不刷盘。
直接结论:别手写 syscall.mmap,用 github.com/edsrzf/mmap-go 或 golang.org/x/exp/mmap;映射后必须显式 flush() 才能落盘,否则改了也白改。
为什么 syscall.Mmap 在 Go 里容易 panic
不是代码写错,而是参数和文件状态不匹配。最常踩的坑有三个:
- 传给
Mmap的length超过文件当前大小——必须先调f.Stat()拿真实Size(),别硬写1024 * 1024 * 1024 - 只读映射却用
os.OpenFile(f, os.O_RDWR)+PROT_WRITE,或写入映射却用os.Open(只读句柄) - Windows 下直接调
syscall.Mmap必然失败,它只是 stub,返回ENOSYS;得切到golang.org/x/sys/windows
mmap-go 怎么开箱即用且不丢数据
它自动处理页对齐、平台差异和错误转换,但默认行为有陷阱:
- 用
mmap.Open("file.dat", mmap.RDWR)才支持写入,RDONLY更安全且某些系统有优化 -
mm.Bytes()返回的切片已正确设置len/cap,可直接下标访问,但不能append或重切(会 panic) - 修改内存后,
mm.Flush()必须手动调——它等价于msync(MS_SYNC);defer mm.Close()只做munmap,不刷盘 - 若只是临时缓存(如解压中间态),需私有映射(
MAP_PRIVATE),但mmap-go不暴露 flag 控制,得退回裸syscall.Mmap
随机读 vs 顺序扫描:mmap 真的更快吗
不是所有大文件都适合 mmap,性能取决于访问模式:
- 适合:频繁跳转访问固定偏移(如日志按行号查、数据库索引定位)——内核按需加载页,省去整块拷贝
- 不适合:顺序遍历整个 50GB 文件——缺页中断密集,TLB 压力大,
bufio.NewReader(f).Read更稳 - 小文件(
- 超大映射(>100GB)可能撞
/proc/sys/vm/max_map_count,报cannot allocate memory,需提前sysctl -w vm.max_map_count=262144
写入后 SIGBUS 怎么定位和修复
SIGBUS 几乎只发生在访问非法地址,常见原因很具体:
- 用
unsafe.Slice计算偏移时越界:确保offset + length ,Go 1.21+ 推荐用 <code>unsafe.Slice(unsafe.Add(unsafe.Pointer(&data[0]), offset), length) - 映射后文件被其他进程
truncate或unlink,导致部分映射区域“幽灵化” - Windows 下文件大小不是 64KB 整数倍,
CreateFileMapping报The parameter is incorrect;得用os.Truncate补零 - 多进程共享写时没加锁,或未用
MAP_SHARED,另一进程读不到最新内容
真正麻烦的是:mmap 的生命周期和 GC 完全无关,Unmap 必须显式调,且只能对原始切片变量操作——对子切片或 copy 出来的副本调 Unmap 会 panic;多次 Unmap 同一块内存会 crash。


















