Go 通过 syscall 包和 golang.org/x/sys/unix 提供了原生的 mmap 支持,同时社区有多个成熟的封装库实现跨平台兼容。
import "syscall"
// 基本的 mmap 使用
data, err := syscall.Mmap(
int(file.Fd()), // 文件描述符
0, // 偏移量
size, // 映射大小
syscall.PROT_READ|syscall.PROT_WRITE, // 保护标志
syscall.MAP_SHARED, // 映射标志
)
if err != nil {
panic(err)
}
// 使用 data []byte 直接读写
// ...
// 解除映射
syscall.Munmap(data)
⚠️ 注意: syscall 包已标记为弃用,推荐使用 golang.org/x/sys 包。
更底层、更强大的控制:
import "golang.org/x/sys/unix"
// 基本 mmap
b, err := unix.Mmap(
int(file.Fd()),
0,
size,
unix.PROT_READ|unix.PROT_WRITE,
unix.MAP_SHARED,
)
// 指定地址的 mmap (用于共享内存等场景)
var addr = unsafe.Pointer(uintptr(0x088800000000))
s, err := unix.MmapPtr(
int(file.Fd()), // 文件描述符
0, // 文件偏移量
addr, // 指定内存地址
4096, // 大小
unix.PROT_READ|unix.PROT_WRITE,
unix.MAP_PRIVATE|unix.MAP_FIXED,
)
// 匿名映射 (用于进程间共享内存)
data, err := unix.Mmap(
-1, // fd = -1 表示匿名
0,
size,
unix.PROT_READ|unix.PROT_WRITE,
unix.MAP_SHARED|unix.MAP_ANONYMOUS,
)
go get github.com/edsrzf/mmap-go
import "github.com/edsrzf/mmap-go"
// 映射整个文件
f, _ := os.OpenFile("data.txt", os.O_RDWR, 0644)
defer f.Close()
mmap, err := mmap.Map(f, mmap.RDWR, 0)
if err != nil {
log.Fatal(err)
}
defer mmap.Unmap()
// 直接像操作 []byte 一样操作文件
fmt.Println(string(mmap))
mmap[0] = 'X'
mmap.Flush() // 强制刷盘
特点:
go get go.dw1.io/mmapfile
import "go.dw1.io/mmapfile"
// 像使用 *os.File 一样使用
f, err := mmapfile.Open("data.txt")
if err != nil {
log.Fatal(err)
}
defer f.Close()
// 标准 io 接口
buf := make([]byte, 100)
n, _ := f.Read(buf)
// 或直接使用内存访问 (零拷贝)
data := f.Bytes() // 直接返回 []byte 视图
特点:
io 接口 (Reader, Writer, Seeker, ReaderAt, WriterAt)go get github.com/blevesearch/mmap-go
与 edsrzf/mmap-go 类似,但维护更活跃。
| 平台 | syscall.Mmap | golang.org/x/sys/unix | mmap-go | mmapfile |
|---|---|---|---|---|
| Linux | ✅ | ✅ | ✅ | ✅ |
| macOS | ✅ | ✅ | ✅ | ✅ |
| Windows | ❌ | ✅ (x/sys/windows) | ✅ | ✅ |
| FreeBSD | ✅ | ✅ | ✅ | ⚠️ |
Windows 特殊处理:
Windows 没有 mmap 系统调用,使用 MapViewOfFile API。第三方库会自动处理差异。
package main
import (
"fmt"
"log"
"os"
"github.com/edsrzf/mmap-go"
)
func main() {
f, err := os.Open("huge_file.bin")
if err != nil {
log.Fatal(err)
}
defer f.Close()
// 映射整个文件到内存
data, err := mmap.Map(f, mmap.RDONLY, 0)
if err != nil {
log.Fatal(err)
}
defer data.Unmap()
// 随机访问任意位置 (零拷贝)
fmt.Printf("Byte at offset 1M: %d
", data[1024*1024])
// 处理完成后自动解除映射
}
package main
import (
"fmt"
"golang.org/x/sys/unix"
"os"
)
const shmSize = 4096
func main() {
// 创建匿名共享内存
data, err := unix.Mmap(
-1, // fd = -1 表示不关联文件
0,
shmSize,
unix.PROT_READ|unix.PROT_WRITE,
unix.MAP_SHARED|unix.MAP_ANONYMOUS,
)
if err != nil {
panic(err)
}
// 写入数据
copy(data, []byte("Hello from mmap!"))
// fork 子进程会继承这段内存
pid, _ := unix.ForkExec("./child", nil, &unix.ProcAttr{})
// 子进程可以读取 data 中的内容
unix.Munmap(data)
}
type MmapDB struct {
file *os.File
data mmap.MMap
dataPtr *[maxMapSize]byte
}
func OpenDB(path string, size int) (*MmapDB, error) {
f, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, 0644)
if err != nil {
return nil, err
}
// 扩展文件到指定大小
if err := f.Truncate(int64(size)); err != nil {
return nil, err
}
// 内存映射
data, err := mmap.Map(f, mmap.RDWR, 0)
if err != nil {
return nil, err
}
return &MmapDB{
file: f,
data: data,
dataPtr: (*[maxMapSize]byte)(unsafe.Pointer(&data[0])),
}, nil
}
func (db *MmapDB) WriteAt(offset int, data []byte) {
copy(db.dataPtr[offset:], data)
}
func (db *MmapDB) ReadAt(offset, length int) []byte {
return db.data[offset : offset+length]
}
func (db *MmapDB) Close() error {
db.data.Flush()
db.data.Unmap()
return db.file.Close()
}
data, _ := syscall.Mmap(...)
defer syscall.Munmap(data) // 必须!否则内存泄漏
// 错误:写入超出文件大小的位置
data[1000000] = 1 // 如果文件只有 1KB,这会崩溃!
// 正确:先扩展文件
file.Truncate(newSize)
// MmapPtr 的 offset 必须是页大小的整数倍
pageSize := os.Getpagesize() // 通常是 4096
offset := (offset + pageSize - 1) &^ (pageSize - 1) // 对齐
// 注意:mmap 返回的内存不受 Go GC 管理
// 需要手动 Unmap
// 如果需要让 GC 追踪,可以包装在结构中
type MmappedFile struct {
data []byte
}
func (m *MmappedFile) Close() error {
return syscall.Munmap(m.data)
}
// 使用 madvise 提示访问模式
syscall.Syscall(syscall.SYS_MADVISE,
uintptr(unsafe.Pointer(&b[0])),
uintptr(len(b)),
uintptr(syscall.MADV_RANDOM), // 或 MADV_SEQUENTIAL
)
| 操作 | 标准 io | mmap |
|---|---|---|
| 小文件读取 (1KB) | 基准 | 50x 更快 |
| 大文件顺序读取 | 基准 | 3x 更快 |
| 随机访问 | 基准 | 25x 更快 |
| 并行读取 | 基准 | 10-12x 更快 |
| 需求 | 推荐方案 |
|---|---|
| 简单文件映射 | github.com/edsrzf/mmap-go |
| 高性能文件访问 | go.dw1.io/mmapfile |
| 底层控制 | golang.org/x/sys/unix |
| 进程间共享内存 | golang.org/x/sys/unix + MAP_ANONYMOUS |
| 跨平台兼容 | github.com/edsrzf/mmap-go |
核心要点:
研究时间: 2026-03-07
标签: #Go #mmap #内存映射 #IPC #性能优化
还没有人回复