应优先使用 github.com/edsrzf/mmap-go 而非 syscall.Mmap,因其自动处理页对齐、跨平台差异、错误转换、len/cap 校验及并发安全,避免 invalid argument panic、SIGBUS 和 Windows 崩溃;syscall.Mmap 需手动处理对齐、权限匹配、文件大小校验及 msync 刷盘,极易出错。

别用 syscall.Mmap 手写,直接上 github.com/edsrzf/mmap-go —— 它自动处理页对齐、平台差异、错误转换和切片长度校验,否则你大概率会遇到 invalid argument panic 或 Windows 上直接崩溃。
为什么 syscall.Mmap 在 Go 里几乎没法安全用
它不校验参数合法性:传进去的 offset 不是页对齐(比如不是 4096 的整数倍)、length 超过文件真实大小、prot 和文件打开模式不匹配(如只读句柄配 PROT_WRITE),都会直接 panic 或返回 EINVAL。Windows 下它甚至只是个 stub,调用必失败,返回 ENOSYS。
-
syscall.Getpagesize()得自己算对齐,漏掉就崩 - 返回的
[]byte的len和cap默认为 0,必须手动用unsafe.Slice构造,一错就 SIGBUS - 忘记
defer syscall.Munmap()会导致映射泄漏,进程重启前无法复用地址空间 - 跨 goroutine 调
syscall.Munmap多次会 crash,而mmap-go内部已加锁防护
用 mmap-go 打开只读超大文件的最小安全写法
适用于日志行号跳转、二进制索引定位、只读配置文件加载等场景——核心是让内核按需加载页,避免一次性拷贝整块数据到用户态 buffer。
- 用
mmap.Open("data.bin"),它自动调f.Stat().Size()并做页对齐,返回的mmap.Map可直接当[]byte用,len(mm)准确 - 不要把
mm传给bufio.Scanner—— 它内部会 append,而 mmap 区域不可扩容,必然 SIGBUS - 需要解析文本时,用
bytes.IndexByte(mm[off:], '\n')手动找换行符,或用unsafe.Slice(unsafe.Add(unsafe.Pointer(&mm[0]), offset), length)(Go 1.21+)构造子视图 - 访问前仍要检查
off + size ,因为文件可能被外部截断
写入后数据没落盘?那是你没调 Flush()
mmap-go 默认用 MAP_SHARED,改内存等于改 page cache,但不会自动刷到磁盘。程序退出、崩溃或断电,未刷的数据就丢了——这不是 bug,是 mmap 的 POSIX 语义。
立即学习“go语言免费学习笔记(深入)”;
- 写操作后必须显式调
mm.Flush(),它等价于msync(MS_SYNC) -
defer mm.Close()只触发munmap,不刷盘,不能替代Flush() - 如果只是临时缓存(如解压中间态),需
MAP_PRIVATE,但mmap-go不暴露 flag 控制,此时得退回裸syscall.Mmap自行构造 - 多进程同时写同一文件,必须加
flock或syscall.Flock,否则数据错乱
最易被忽略的点:映射超大文件(>100GB)时,Linux 内核默认 /proc/sys/vm/max_map_count 通常只有 65536,容易报 cannot allocate memory;得提前 sysctl -w vm.max_map_count=262144。还有,顺序遍历整个大文件时,mmap 因缺页中断密集反而比 bufio.NewReader(f).Read 更慢——它只在随机访问或重复扫描时才真正省事。


















