Вызов mremap в Go не работает, но не выдает ошибок

#linux #go #unix #system-calls #mmap

#linux #Вперед #unix #системные вызовы #mmap

Вопрос:

Я пытаюсь получить mremap файл из Go, но размер файла, похоже, не меняется, несмотря на возвращенный errno of 0 . Это приводит к segfault, когда я пытаюсь получить доступ к отображенной памяти.

Я включил приведенный ниже код. Реализация аналогична mmap реализации в sys пакете, поэтому я не уверен, что здесь происходит не так:

 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(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("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: % vn", 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`.
}
  

Я пытался искать другой код Go, который использует mremap , но, похоже, я не могу его найти. Я был бы признателен за любой вклад!

Комментарии:

1. Я не знаю go, но IIRC в C вам нужно будет увеличить базовый файл ftruncate() или что-то еще, прежде чем расширять отображение памяти.

2. Моей первой мыслью было strace бы убедиться mremap , что вызов выдан и интерпретирован правильно

3. Это mremap работает для меня (Linux 4.19.0, amd64), но я согласен с @Shawn в этом: сначала вам нужно увеличить файл , а затем изменить его отображение, чтобы охватить добавление, а не наоборот. Только что проверил, что добавление file.Truncate(2 * 4096) before mremap устраняет проблему.

4. Вызов truncate перед mremap исправил это. Спасибо!

5. @EarlyEarthworm, подумайте о том, чтобы ответить на принятие вашего собственного вопроса: это абсолютно нормально для SO (и на самом деле поощряется, поскольку пользователи могут специально искать вопросы с принятыми ответами).

Ответ №1:

Как упоминалось в комментариях @kostix, mmap используется для отображения обычного файла в память. Причина, по которой доступ к буферу приводит к segfault, заключается в том, что сам базовый файл недостаточно велик. Решение состоит в том, чтобы обрезать файл до желаемой длины перед вызовом mremap :

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