英文:
Calling mremap in Go doesn't work, but gives no error
问题
我正在尝试从Go中使用mremap
重新映射文件,但是文件的大小似乎没有改变,尽管返回的errno
为0
。这导致当我尝试访问映射的内存时出现段错误。
我在下面包含了代码。这个实现类似于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 (
"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`.
}
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)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论