【问题标题】:Calling mremap in Go doesn't work, but gives no error在 Go 中调用 mremap 不起作用,但没有错误
【发布时间】:2021-02-21 16:05:47
【问题描述】:

我正在尝试从 Go 中 mremap 一个文件,但文件的大小似乎没有改变,尽管返回了 errno 的 0。当我尝试访问映射的内存时,这会导致段错误。

我已包含以下代码。该实现类似于sys 包中的mmap 实现,所以我不确定这里出了什么问题:

package main

import (
    "fmt"
    "io/ioutil"
    "log"
    "os"
    "reflect"
    "unsafe"

    "golang.org/x/sys/unix"
)

// taken from <https://github.com/torvalds/linux/blob/f8394f232b1eab649ce2df5c5f15b0e528c92091/include/uapi/linux/mman.h#L8>
const (
    MREMAP_MAYMOVE = 0x1
    // MREMAP_FIXED     = 0x2
    // MREMAP_DONTUNMAP = 0x4
)

func mremap(data []byte, size int) ([]byte, error) {
    header := (*reflect.SliceHeader)(unsafe.Pointer(&data))
    mmapAddr, mmapSize, errno := unix.Syscall6(
        unix.SYS_MREMAP,
        header.Data,
        uintptr(header.Len),
        uintptr(size),
        uintptr(MREMAP_MAYMOVE),
        0,
        0,
    )
    if errno != 0 {
        return nil, fmt.Errorf("mremap failed with errno: %s", errno)
    }
    if mmapSize != uintptr(size) {
        return nil, fmt.Errorf("mremap size mismatch: requested: %d got: %d", size, mmapSize)
    }

    header.Data = mmapAddr
    header.Cap = size
    header.Len = size
    return data, nil
}

func main() {
    log.SetFlags(log.LstdFlags | log.Lshortfile)
    const mmPath = "/tmp/mm_test"

    // create a file for mmap with 1 byte of data.
    // this should take up 1 block on disk (4096 bytes).
    err := ioutil.WriteFile(mmPath, []byte{0x1}, 0755)
    if err != nil {
        log.Fatal(err)
    }

    // open and stat the file.
    file, err := os.OpenFile(mmPath, os.O_RDWR, 0)
    if err != nil {
        log.Fatal(err)
    }
    defer file.Close()
    stat, err := file.Stat()
    if err != nil {
        log.Fatal(err)
    }

    // mmap the file and print the contents.
    // this should print only one byte of data.
    data, err := unix.Mmap(int(file.Fd()), 0, int(stat.Size()), unix.PROT_READ|unix.PROT_WRITE, unix.MAP_SHARED)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("mmap data: %+v\n", data)

    // mremap the file to a size of 2 blocks.
    data, err = mremap(data, 2*4096)
    if err != nil {
        log.Fatal(err)
    }

    // access the mremapped data.
    fmt.Println(data[:4096]) // accessing the first block works.
    fmt.Println(data[:4097]) // accessing the second block fails with `SIGBUS: unexpected fault address`.
}

我尝试寻找其他使用 mremap 的 Go 代码,但我似乎找不到任何代码。如有任何意见,我将不胜感激!

【问题讨论】:

  • 我不知道去,但是在 C 中的 IIRC,你必须在扩展内存映射之前使用 ftruncate() 或其他任何东西来增长底层文件。
  • 我的第一个想法是 strace 它以确保发出并正确解释 mremap 调用
  • mremap 为我工作(Linux 4.19.0,amd64),但我和@Shawn 合作:you first need to increase the file 然后调整其映射大小以覆盖添加,而不是其他方式圆形的。刚刚验证在mremap 之前添加file.Truncate(2 * 4096) 可以解决问题。
  • 在 mremap 修复它之前调用 truncate。谢谢!

标签: linux go unix system-calls mmap


【解决方案1】:

正如@kostix 在 cmets 中提到的,mmap 用于将常规文件映射到内存中。访问缓冲区导致段错误的原因是底层文件本身不够大。解决方法是在调用mremap之前将文件截断到想要的长度:

if err := file.Truncate(2*4096); err != nil {
  log.Fatal(err)
}
data, err = mremap(data, 2*4096)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-10-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-25
    相关资源
    最近更新 更多