golang 中 mmap 的使用
-----------------------
https://www.jianshu.com/p/964b887da04c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | package main import ( "fmt" "os" "syscall" ) const maxMapSize = 0x8000000000 const maxMmapStep = 1 << 30 // 1GB func main() { file, err := os.OpenFile( "my.db" , os.O_RDWR|os.O_CREATE, 0644) if err != nil { panic(err) } defer file.Close() stat, err := os.Stat( "my.db" ) if err != nil { panic(err) } size, err := mmapSize(int(stat.Size())) if err != nil { panic(err) } fmt.Println( "size:" , size) //// 没有下面这句,将会报错 //err = syscall.Ftruncate(int(file.Fd()), 2) //第一种改法, truncate() 这里的2,表示会将参数fd指定的文件大小改为参数length指定的大小 //if err != nil { // panic(err) //} // 第二种改法,对文件执行一下write , 或者WriteAt file.Write(make([]byte, 10)) // 这里的10, 表示文件大小限定为10了, 后面超多10的写不进(打开文件时,字符e没有)。 b, err := syscall.Mmap(int(file.Fd()), 0, 100, syscall.PROT_WRITE|syscall.PROT_READ, syscall.MAP_SHARED) if err != nil { panic(err) } for index, bb := range []byte( "Hello abcde" ) { b[index] = bb } err = syscall.Munmap(b) if err != nil { panic(err) } } func mmapSize(size int) (int, error) { // Double the size from 32KB until 1GB. for i := uint(15); i <= 30; i++ { if size <= 1<<i { return 1 << i, nil } } // Verify the requested size is not above the maximum allowed. if size > maxMapSize { return 0, fmt.Errorf( "mmap too large" ) } // If larger than 1GB then grow by 1GB at a time. sz := int64(size) if remainder := sz % int64(maxMmapStep); remainder > 0 { sz += int64(maxMmapStep) - remainder } // Ensure that the mmap size is a multiple of the page size. // This should always be true since we're incrementing in MBs. pageSize := int64(os.Getpagesize()) if (sz % pageSize) != 0 { sz = ((sz / pageSize) + 1) * pageSize } // If we've exceeded the max size then only grow up to the max size. if sz > maxMapSize { sz = maxMapSize } return int(sz), nil } |
另外一个例子:https://gist.github.com/suyash/a19b7f91000b24fde4bc4a015680c611
write.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | package main import ( "os" "syscall" ) func main() { filename, offset, length := "testgo.out" , 10, 20 f, err := os.Create(filename) if err != nil { panic(err) } f.Write(make([]byte, length)) fd := int(f.Fd()) b, err := syscall.Mmap(fd, 0, length, syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED) if err != nil { panic(err) } b[offset-1] = 'x' err = syscall.Munmap(b) if err != nil { panic(err) } } |
reader.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | package main import ( "fmt" "os" "syscall" ) func main() { filename, offset := "main.c" , 10 f, err := os.Open(filename) if err != nil { panic(err) } fd := int(f.Fd()) stat, err := f.Stat() if err != nil { panic(err) } size := int(stat.Size()) b, err := syscall.Mmap(fd, 0, size, syscall.PROT_READ, syscall.MAP_SHARED) if err != nil { panic(err) } fmt.Printf( "%c\n" , b[offset-1]) err = syscall.Munmap(b) if err != nil { panic(err) } } |
reader.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/mman.h> int main () { const char* filename = "main.c" ; int offset = 10; int fd = open(filename, O_RDONLY); if (fd == -1) { printf( "cannot open %s\n" , filename); return -1; } struct stat sbuf; if (stat(filename, &sbuf) == -1) { printf( "cannot stat %s\n" , filename); return -1; } int size = sbuf.st_size; char* data = mmap((caddr_t)0, size, PROT_READ, MAP_SHARED, fd, 0); if (data == -1) { printf( "cannot mmap %s\n" , filename); return -1; } printf( "byte at offset %d is %c\n" , offset, data[offset - 1]); int err = munmap(data, size); if (err == -1) { printf( "cannot munmap %s\n" , filename); return -1; } return 0; } |
----------------------
最近工作中在研究Hyperledger Fabric区块链开源项目,其中区块链peer节点底层使用了leveldb作为State Database的存储介质,出于好奇,决定对一些常用的KV存储做一些研究。
这一次我主要对2种KV存储的源码做了分析,一个是BoltDB,这是LMDB的Go语言版本,另一个就是goleveldb。
在阅读BoltDB项目源码的过程中,我发现它将持久化文件以只读模式通过mmap映射到内存空间中,然后通过索引找到内存中key对应的value所指向的空间,然后将这段内存返回给用户。之前虽然也听说过内存映射文件技术,但一直没有实际使用过,因此这一次我决定对mmap做一些尝试,以下是这次尝试的过程。
文件写入
内存映射文件(Memory-mapped file)将一段虚拟内存逐字节对应于一个文件或类文件的资源,使得应用程序处理映射部分如同访问主存,用于增加I/O性能。Mmap函数存在于Go's syscall package中,它接收一个文件描述符,需要映射的大小(返回的切片容量)以及需要的内存保护和映射类型。
package main
import (
"fmt"
"os"
"syscall"
)
const maxMapSize = 0x8000000000
const maxMmapStep = 1 << 30 // 1GB
func main() {
file, err := os.OpenFile("my.db", os.O_RDWR|os.O_CREATE, 0644)
if err != nil {
panic(err)
}
defer file.Close()
stat, err := os.Stat("my.db")
if err != nil {
panic(err)
}
size, err := mmapSize(int(stat.Size()))
if err != nil {
panic(err)
}
b, err := syscall.Mmap(int(file.Fd()), 0, size, syscall.PROT_WRITE|syscall.PROT_READ, syscall.MAP_SHARED)
if err != nil {
panic(err)
}
for index, bb := range []byte("Hello world") {
b[index] = bb
}
err = syscall.Munmap(b)
if err != nil {
panic(err)
}
}
func mmapSize(size int) (int, error) {
// Double the size from 32KB until 1GB.
for i := uint(15); i <= 30; i++ {
if size <= 1<<i {
return 1 << i, nil
}
}
// Verify the requested size is not above the maximum allowed.
if size > maxMapSize {
return 0, fmt.Errorf("mmap too large")
}
// If larger than 1GB then grow by 1GB at a time.
sz := int64(size)
if remainder := sz % int64(maxMmapStep); remainder > 0 {
sz += int64(maxMmapStep) - remainder
}
// Ensure that the mmap size is a multiple of the page size.
// This should always be true since we're incrementing in MBs.
pageSize := int64(os.Getpagesize())
if (sz % pageSize) != 0 {
sz = ((sz / pageSize) + 1) * pageSize
}
// If we've exceeded the max size then only grow up to the max size.
if sz > maxMapSize {
sz = maxMapSize
}
return int(sz), nil
}
如果你直接运行这个程序,那么将会发生错误(内存地址会不一样)
unexpected fault address 0x13ac000
fatal error: fault
[signal SIGBUS: bus error code=0x2 addr=0x13ac000 pc=0x10c1375]
SIGBUS
信号意味着你在文件的地址以外写入内容,根据Linux man pages mmap(2)的描述:
SIGBUS Attempted access to a portion of the buffer that does not correspond to the file (for example, beyond the end of the file, including the case where another process has truncated the file).
在创建新文件时,它最初为空,即大小为0字节,您需要使用ftruncate
调整其大小,至少足以包含写入的地址(可能四舍五入到页面大小)。修改main
函数:
func main() {
file, err := os.OpenFile("my.db", os.O_RDWR|os.O_CREATE, 0644)
if err != nil {
panic(err)
}
defer file.Close()
stat, err := os.Stat("my.db")
if err != nil {
panic(err)
}
size, err := mmapSize(int(stat.Size()))
if err != nil {
panic(err)
}
err = syscall.Ftruncate(int(file.Fd()), int64(size))
if err != nil {
panic(err)
}
b, err := syscall.Mmap(int(file.Fd()), 0, size, syscall.PROT_WRITE|syscall.PROT_READ, syscall.MAP_SHARED)
if err != nil {
panic(err)
}
for index, bb := range []byte("Hello world") {
b[index] = bb
}
err = syscall.Munmap(b)
if err != nil {
panic(err)
}
}
再次运行程序,可正常写入。
读取文件
读取文件的方式更加简单,直接以只读方式将文件映射到主存中即可:
func main() {
file, err := os.OpenFile("my.db", os.O_RDONLY, 0600)
if err != nil {
panic(err)
}
defer file.Close()
stat, err := os.Stat("my.db")
if err != nil {
panic(err)
}
b, err := syscall.Mmap(int(file.Fd()), 0, int(stat.Size()), syscall.PROT_READ, syscall.MAP_SHARED)
if err != nil {
panic(err)
}
defer syscall.Munmap(b)
fmt.Println(string(b))
}
运行程序,即可打印出文件内容
作者:绝望的祖父
链接:https://www.jianshu.com/p/964b887da04c
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 25岁的心里话
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
2018-04-08 vim 调到闭合的{