在Go中调用mremap函数不起作用,但没有报错。

huangapple go评论83阅读模式
英文:

Calling mremap in Go doesn't work, but gives no error

问题

我正在尝试从Go中使用mremap重新映射文件,但是文件的大小似乎没有改变,尽管返回的errno0。这导致当我尝试访问映射的内存时出现段错误。

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

package main

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

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

// 取自 <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"

	// 创建一个包含1字节数据的mmap文件。
	// 这应该占用磁盘上的1个块(4096字节)。
	err := ioutil.WriteFile(mmPath, []byte{0x1}, 0755)
	if err != nil {
		log.Fatal(err)
	}

	// 打开并获取文件状态。
	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文件并打印内容。
	// 这应该只打印一个字节的数据。
	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)

	// 将文件的大小重新映射为2个块。
	data, err = mremap(data, 2*4096)
	if err != nil {
		log.Fatal(err)
	}

	// 访问重新映射的数据。
	fmt.Println(data[:4096]) // 访问第一个块成功。
	fmt.Println(data[:4097]) // 访问第二个块失败,出现`SIGBUS: unexpected fault address`。
}

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

英文:

I'm trying to mremap a file from Go, but the size of the file doesn't seem to be changing, despite the returned errno of 0. This results in a segfault when I try to access the mapped memory.

I've included the code below. The implementation is similar to the mmap implementation in the sys package, so I'm not sure what's going wrong here:

package main

import (
	&quot;fmt&quot;
	&quot;io/ioutil&quot;
	&quot;log&quot;
	&quot;os&quot;
	&quot;reflect&quot;
	&quot;unsafe&quot;

	&quot;golang.org/x/sys/unix&quot;
)

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

func mremap(data []byte, size int) ([]byte, error) {
	header := (*reflect.SliceHeader)(unsafe.Pointer(&amp;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(&quot;mremap failed with errno: %s&quot;, errno)
	}
	if mmapSize != uintptr(size) {
		return nil, fmt.Errorf(&quot;mremap size mismatch: requested: %d got: %d&quot;, size, mmapSize)
	}

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

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

	// 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(&quot;mmap data: %+v\n&quot;, 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`.
}

I tried looking for other Go code that uses mremap, but I can't seem to find any. I would appreciate any input!

答案1

得分: 1

正如@kostix在评论中提到的那样,mmap被用于将一个普通文件映射到内存中。访问缓冲区导致段错误的原因是底层文件本身不够大。解决方法是在调用mremap之前将文件截断为所需的长度:

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

As @kostix mentioned in the comments, mmap is being used to map a regular file into memory. The reason that accessing the buffer results in a segfault is that the underlying file itself is not large enough. The solution is to truncate the file to the desired length before calling mremap:

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

huangapple
  • 本文由 发表于 2020年11月10日 01:49:38
  • 转载请务必保留本文链接:https://go.coder-hub.com/64756694.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定