推荐使用 github.com/edsrzf/mmap-go 实现跨平台文件映射,它屏蔽了 linux/macos/windows 差异,自动处理页对齐、map_populate、msync 兼容性及 windows 的 mapviewoffile 模拟,避免 syscall.mmap 导致的 sigbus 或 panic。

用 mmap-go 实现跨平台文件虚拟映射
直接调用 syscall.Mmap 在 Go 1.17+ 已被弃用,Windows 上会 panic,Linux/macOS 下极易因页对齐、PROT_WRITE 与只读文件冲突、或切片 len/cap 错误触发 SIGBUS。必须用封装好的库。
推荐唯一靠谱选择:github.com/edsrzf/mmap-go。它被 etcd 和 bolt 长期验证,内部处理了:MAP_POPULATE、长度向上对齐到页边界、msync 兼容性、Windows 上 MapViewOfFile 模拟等全部细节。
-
mmap.Open("data.bin", os.O_RDWR, 0)是最安全的入口,自动适配平台和 flag - 不要用
os.OpenFile(..., os.O_CREATE)后传给mmap.Map()—— 文件长度为 0 时Map()会报invalid argument - 若需映射空文件,先
ftruncate(用file.Truncate(size)),再映射
MAP_SHARED vs MAP_PRIVATE:写操作是否落盘的关键开关
这是最容易被忽略的语义陷阱。默认行为不等于“改内存就改文件”——取决于你用的是哪个 flag。
-
MAP_PRIVATE(mmap.RDWR默认):写操作触发写时复制(COW),只改进程私有页;msync无效;程序退出即丢数据 -
MAP_SHARED(需显式传mmap.RDWR | mmap.SHARED):写操作直接脏页,内核按需刷盘;mm.Flush()或msync(MS_SYNC)可强制同步 -
MAP_SYNC仅 XFS/Btrfs 支持,mmap-go 当前未暴露该 flag;强一致性场景慎用
典型错误:以为 data[0] = 1 就已落盘,结果断电后文件没变——大概率是用了 MAP_PRIVATE。
获取可读写切片的唯一安全方式
系统 mmap 返回原始内存地址,Go 运行时不感知长度。任何手动构造切片的行为都高危。
- ✅ 唯一正确方式:
data := mm.Bytes()—— 它内部校验了 offset + length ≤ 映射区,返回的[]byte可直接读写 - ❌ 禁止
unsafe.Slice(unsafe.Pointer(&data[0]), n):若n超出映射长度,访问必SIGBUS - ❌ 禁止用
reflect.SliceHeader强转:Go 1.21+ 已禁止未对齐指针转换,且运行时无法防护越界 - 如只需某段数据,用
data[offset:offset+size]切片,不是重新 unsafe 构造
映射后文件被截断或磁盘写满会怎样
mmap 不做运行时长度防护。一旦底层文件被外部修改(如 truncate -s 0 data.bin),后续访问原 data 切片仍会 SIGBUS。
- 这不是 bug,是 mmap 的设计特性:映射建立后,内核只管页表,不管文件元数据是否变更
- 无法通过
stat检查实时长度来规避——访问时才触发缺页异常,此时已晚 - 生产环境务必确保映射期间文件不被外部工具修改;写关键数据前建议先
mm.Flush() - Windows 下 mmap-go 用
MapViewOfFile模拟,但也不支持MAP_SYNC,强一致性要求高的场景应避免
最麻烦的点不在 API 怎么写,而在于 mmap 是个“静默失效”的机制:错一次,就是 SIGBUS crash,没有中间态可 debug。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











