zoukankan      html  css  js  c++  java
  • golang 中 mmap 的使用

    -----------------------

    https://www.jianshu.com/p/964b887da04c

    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

    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

    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
    ", b[offset-1])
    
    	err = syscall.Munmap(b)
    	if err != nil {
    		panic(err)
    	}
    }
    

      reader.c

    #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
    ", filename);
        return -1;
      }
    
      struct stat sbuf;
      if (stat(filename, &sbuf) == -1) {
        printf("cannot stat %s
    ", 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
    ", filename);
        return -1;
      }
    
      printf("byte at offset %d is %c
    ", offset, data[offset - 1]);
    
      int err = munmap(data, size);
      if (err == -1) {
        printf("cannot munmap %s
    ", 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
    来源:简书
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 相关阅读:
    ✨Synchronized底层实现---偏向锁
    🌞LCP 13. 寻宝
    ✨Synchronized底层实现---概述
    ⛅104. 二叉树的最大深度
    c++多线程之顺序调用类成员函数
    C++ STL实现总结
    C#小知识
    C#中HashTable和Dictionary的区别
    WPF的静态资源(StaticResource)和动态资源(DynamicResource)
    WPF之再谈MVVM
  • 原文地址:https://www.cnblogs.com/oxspirt/p/14633182.html
Copyright © 2011-2022 走看看