1 mmap 简介
In computing, mmap is a POSIX-compliant Unix system call that maps files or devices into memory. It is a method of memory-mapped file I/O.
-- mmap - wikipedia.org
简单理解,mmap 是一种将文件/设备映射到内存的方法,实现文件的磁盘地址和进程虚拟地址空间中的一段虚拟地址的一一映射关系。也就是说,可以在某个进程中通过操作这一段映射的内存,实现对文件的读写等操作。修改了这一段内存的内容,文件对应位置的内容也会同步修改,而读取这一段内存的内容,相当于读取文件对应位置的内容。
mmap 另一个非常重要的特性是:减少内存的拷贝次数。在 linux 系统中,文件的读写操作通常通过 read 和 write 这两个系统调用来实现,这个过程会产生频繁的内存拷贝。比如 read 函数就涉及了 2 次内存拷贝:
而mmap 只需要一次拷贝。即操作系统读取磁盘文件到页缓存,进程内部直接通过指针方式修改映射的内存。因此 mmap 特别适合读写频繁的场景,既减少了内存拷贝次数,提高效率,又简化了操作。KV数据库 bbolt 就使用了这个方法持久化数据。
2 标准库 mmap
Go 语言标准库 golang.org/x/exp/mmap 仅实现了 read 操作,后续能否支持 write 操作未知。使用场景非常有限。看一个简单的例子:
从第4个byte开始,读取 tmp.txt 2个byte的内容。
package main
import (
"fmt"
"golang.org/x/exp/mmap"
)
func main() {
at, _ := mmap.Open("./tmp.txt")
buff := make([]byte, 2)
_, _ = at.ReadAt(buff, 4)
_ = at.Close()
fmt.Println(string(buff))
}
$ echo "abcdefg" > tmp.txt
$ go run .
ef
如果使用 os.File
操作,代码几乎是一样的,os.File
还支持写操作 WriteAt
:
package main
import (
"fmt"
"os"
)
func main() {
f, _ := os.OpenFile("tmp.txt", os.O_CREATE|os.O_RDWR, 0644)
_, _ = f.WriteAt([]byte("abcdefg"), 0)
buff := make([]byte, 2)
_, _ = f.ReadAt(buff, 4)
_ = f.Close()
fmt.Println(string(buff))
}
3 mmap(linux)
如果要支持 write 操作,那么就需要直接调用 mmap 的系统调用来实现了。Linux 和 Windows 都支持 mmap,但接口有所不同。对于 linux 系统,mmap 方法定义如下:
func Mmap(fd int, offset int64, length int, prot int, flags int) (data []byte, err error)
每个参数的含义分别是:
- fd:待映射的文件描述符。
- offset:映射到内存区域的起始位置,0 表示由内核指定内存地址。
- length:要映射的内存区域的大小。
- prot:内存保护标志位,可以通过或运算符`|`组合
- PROT_EXEC // 页内容可以被执行
- PROT_READ // 页内容可以被读取
- PROT_WRITE // 页可以被写入
- PROT_NONE // 页不可访问
- flags:映射对象的类型,常用的是以下两类
- MAP_SHARED // 共享映射,写入数据会复制回文件, 与映射该文件的其他进程共享。
- MAP_PRIVATE // 建立一个写入时拷贝的私有映射,写入数据不影响原文件。
首先定义2个常量和数据类型Demo:
const defaultMaxFileSize = 1