Подтвердить что ты не робот

Отображение несмежных блоков из файла в смежные адреса памяти

Меня интересует перспектива использования IO с отображением памяти, предпочтительно использование объектов в boost:: interprocess для кросс-платформенных поддержки, чтобы отображать несмежные блоки размера страницы в файле в файл в смежное адресное пространство в памяти.

Упрощенный конкретный сценарий:

У меня есть ряд структур "простой-старый", каждая из которых имеет фиксированную длину (меньше размера страницы системы). Эти структуры объединены в (очень длинный) поток с типом и расположением структур определяемые значениями тех структур, которые ведут их в поток. Я стараюсь минимизировать задержку и максимизировать пропускную способность в требуя одновременной среды.

Я могу очень хорошо прочесть эти данные путем сопоставления памяти в блоках как минимум в два раза больше размера страницы системы... и создания нового отображение, непосредственно прочитав структуру, выходящую за пределы предпоследняя системная страница-граница. Это позволяет взаимодействовать с кодом с явными структурами данных, чтобы блаженно не знать, что эти структуры отображены в память... и, например, можно сравнить два различные структуры, использующие memcmp() непосредственно, не заботясь о границах страниц.

Интересным становится интерес к обновлению данных потоки... пока они (одновременно) читаются. Стратегия, которую я бы как использовать, вдохновлен "Копировать на запись" на системном уровне страницы гранулярность... по существу, написание "накладных страниц", что позволяет процесс чтения старых данных, в то время как другой считывает обновленные данные.

Управление управляющими страницами, которые нужно использовать, и когда это не обязательно тривиально... это не моя главная забота. Моя главная проблема заключается в том, что я могу иметь структуру, охватывающую страницы 4 и 5, а затем обновить структура, полностью содержащаяся на странице 5... запись новой страницы в местоположение 6... оставляя страницу 5 для "сбора мусора", когда это которые, как считается, не достигают большего. Это означает, что если я сопоставляю страницу 4 в местоположение M, мне нужно отобразить страницу 6 в ячейку памяти M + page_size..., чтобы иметь возможность надежно обрабатывать структуры, которые границы перекрестных страниц с использованием существующих (не связанных с памятью) функций.

Я пытаюсь установить лучшую стратегию, и мне мешает документация. Я чувствую себя неполной. По сути, мне нужно отделить выделение адресного пространства из памяти в этот адрес пространство. С mmap() я знаю, что могу использовать MAP_FIXED - если я хочу явным образом контролирую местоположение отображения... но я не понимаю, как я необходимо зарезервировать адресное пространство, чтобы сделать это безопасно. Могу ли я отобразить /dev/zero для двух страниц без MAP_FIXED, затем дважды используйте MAP_FIXED сопоставить две страницы в выделенное пространство с явными адресами VM? Если так, следует ли мне также называть munmap() три раза? Будет ли он утечка ресурсов? и/или иметь любые другие неблагоприятные накладные расходы? Чтобы сделать проблему еще более сложный, мне бы хотелось, чтобы сопоставимое поведение в Windows... есть ли способ сделать это? Есть ли опрятные решения, если я буду компрометировать свои кросс-платформенные амбиции?

-

Спасибо за ваш ответ, Махмуд... Я читал и думаю, что понял этот код... Я скомпилировал его под Linux, и он ведет себя так, как вы предлагаете.

Моя основная проблема связана с линией 62 - с использованием MAP_FIXED. Он делает некоторые предположения о mmap, которые я не смог подтвердить, когда прочитал документацию, которую я могу найти. Вы сопоставляете страницу "обновление" в том же адресном пространстве, что и mmap(), возвращенном первоначально - я предполагаю, что это "правильно" - то есть не то, что просто происходит для работы в Linux? Я также должен предположить, что он работает кросс-платформенным для сопоставлений файлов, а также анонимных сопоставлений.

Образец определенно перемещает меня вперед... документирование того, что мне в конечном итоге необходимо, возможно, возможно, с mmap() на Linux - по крайней мере. Мне бы очень хотелось, указатель на документацию, показывающую, что строка MAP_FIXED будет работать, когда образец демонстрирует... и, что идеально, преобразование из Linux/Unix определенного mmap() в независимую от платформы (Boost:: interprocess).

4b9b3361

Ответ 1

Ваш вопрос немного запутан. Из того, что я понял, этот код будет делать то, что вам нужно:

#define PAGESIZE 4096

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <errno.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <assert.h>

struct StoredObject
{
    int IntVal;
    char StrVal[25];
};

int main(int argc, char **argv)
{
    int fd = open("mmapfile", O_RDWR | O_CREAT | O_TRUNC, (mode_t) 0600);
    //Set the file to the size of our data (2 pages)
    lseek(fd, PAGESIZE*2 - 1, SEEK_SET);
    write(fd, "", 1); //The final byte

    unsigned char *mapPtr = (unsigned char *) mmap(0, PAGESIZE * 2, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

    struct StoredObject controlObject;
    controlObject.IntVal = 12;
    strcpy(controlObject.StrVal, "Mary had a little lamb.\n");

    struct StoredObject *mary1;
    mary1 = (struct StoredObject *)(mapPtr + PAGESIZE - 4); //Will fall on the boundary between first and second page
    memcpy(mary1, &controlObject, sizeof(StoredObject));

    printf("%d, %s", mary1->IntVal, mary1->StrVal);
    //Should print "12, Mary had a little lamb.\n"

    struct StoredObject *john1;
    john1 = mary1 + 1; //Comes immediately after mary1 in memory; will start and end in the second page
    memcpy(john1, &controlObject, sizeof(StoredObject));

    john1->IntVal = 42;
    strcpy(john1->StrVal, "John had a little lamb.\n");

    printf("%d, %s", john1->IntVal, john1->StrVal);
    //Should print "12, Mary had a little lamb.\n"

    //Make sure the data on the disk, as this is the initial, "read-only" data
    msync(mapPtr, PAGESIZE * 2, MS_SYNC);

    //This is the inital data set, now in memory, loaded across two pages
    //At this point, someone could be reading from there. We don't know or care.
    //We want to modify john1, but don't want to write over the existing data
    //Easy as pie.

    //This is the shadow map. COW-like optimization will take place: 
    //we'll map the entire address space from the shared source, then overlap with a new map to modify
    //This is mapped anywhere, letting the system decide what address we'll be using for the new data pointer
    unsigned char *mapPtr2 = (unsigned char *) mmap(0, PAGESIZE * 2, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

    //Map the second page on top of the first mapping; this is the one that we're modifying. It is *not* backed by disk
    unsigned char *temp = (unsigned char *) mmap(mapPtr2 + PAGESIZE, PAGESIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED | MAP_ANON, 0, 0);
    if (temp == MAP_FAILED)
    {
        printf("Fixed map failed. %s", strerror(errno));
    }
    assert(temp == mapPtr2 + PAGESIZE);

    //Make a copy of the old data that will later be changed
    memcpy(mapPtr2 + PAGESIZE, mapPtr + PAGESIZE, PAGESIZE);

    //The two address spaces should still be identical until this point
    assert(memcmp(mapPtr, mapPtr2, PAGESIZE * 2) == 0);

    //We can now make our changes to the second page as needed
    struct StoredObject *mary2 = (struct StoredObject *)(((unsigned char *)mary1 - mapPtr) + mapPtr2);
    struct StoredObject *john2 = (struct StoredObject *)(((unsigned char *)john1 - mapPtr) + mapPtr2);

    john2->IntVal = 52;
    strcpy(john2->StrVal, "Mike had a little lamb.\n");

    //Test that everything worked OK
    assert(memcmp(mary1, mary2, sizeof(struct StoredObject)) == 0);
    printf("%d, %s", john2->IntVal, john2->StrVal);
    //Should print "52, Mike had a little lamb.\n"

    //Now assume our garbage collection routine has detected that no one is using the original copy of the data
    munmap(mapPtr, PAGESIZE * 2);

    mapPtr = mapPtr2;

    //Now we're done with all our work and want to completely clean up
    munmap(mapPtr2, PAGESIZE * 2);

    close(fd);

    return 0;
}

Мой измененный ответ должен касаться ваших проблем безопасности. Используйте только MAP_FIXED во втором вызове mmap (например, я выше). Замечательная вещь о MAP_FIXED заключается в том, что она позволяет вам перезаписать существующий раздел адреса mmap. Он выгрузит диапазон, который вы перекрываете, и замените его новым отображаемым контентом:

 MAP_FIXED
              [...] If the memory
              region specified by addr and len overlaps pages of any existing
              mapping(s), then the overlapped part of the existing mapping(s) will be
              discarded. [...]

Таким образом, вы позволяете ОС позаботиться о том, чтобы найти непрерывный блок памяти сотен мегабайт для вас (никогда не звоните MAP_FIXED по адресу, который вы не знаете наверняка, недоступен). Затем вы вызываете MAP_FIXED в подразделе того, что теперь отображено огромное пространство с данными, которые вы будете изменять. Тад.


В Windows что-то вроде этого должно работать (я нахожусь в Mac на данный момент, так не проверен):

int main(int argc, char **argv)
{
    HANDLE hFile = CreateFile(L"mmapfile", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    //Set the file to the size of our data (2 pages)
    SetFilePointer(hFile, PAGESIZE*2 - 1, 0, FILE_BEGIN);
    DWORD bytesWritten = -1;
    WriteFile(hFile, "", 1, &bytesWritten, NULL);

    HANDLE hMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, PAGESIZE * 2, NULL);
    unsigned char *mapPtr = (unsigned char *) MapViewOfFile(hMap, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, PAGESIZE * 2);

    struct StoredObject controlObject;
    controlObject.IntVal = 12;
    strcpy(controlObject.StrVal, "Mary had a little lamb.\n");

    struct StoredObject *mary1;
    mary1 = (struct StoredObject *)(mapPtr + PAGESIZE - 4); //Will fall on the boundary between first and second page
    memcpy(mary1, &controlObject, sizeof(StoredObject));

    printf("%d, %s", mary1->IntVal, mary1->StrVal);
    //Should print "12, Mary had a little lamb.\n"

    struct StoredObject *john1;
    john1 = mary1 + 1; //Comes immediately after mary1 in memory; will start and end in the second page
    memcpy(john1, &controlObject, sizeof(StoredObject));

    john1->IntVal = 42;
    strcpy(john1->StrVal, "John had a little lamb.\n");

    printf("%d, %s", john1->IntVal, john1->StrVal);
    //Should print "12, Mary had a little lamb.\n"

    //Make sure the data on the disk, as this is the initial, "read-only" data
    //msync(mapPtr, PAGESIZE * 2, MS_SYNC);

    //This is the inital data set, now in memory, loaded across two pages
    //At this point, someone could be reading from there. We don't know or care.
    //We want to modify john1, but don't want to write over the existing data
    //Easy as pie.

    //This is the shadow map. COW-like optimization will take place: 
    //we'll map the entire address space from the shared source, then overlap with a new map to modify
    //This is mapped anywhere, letting the system decide what address we'll be using for the new data pointer
    unsigned char *reservedMem = (unsigned char *) VirtualAlloc(NULL, PAGESIZE * 2, MEM_RESERVE, PAGE_READWRITE);
    HANDLE hMap2 = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, PAGESIZE, NULL);
    unsigned char *mapPtr2 = (unsigned char *) MapViewOfFileEx(hMap2, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, PAGESIZE, reservedMem);

    //Map the second page on top of the first mapping; this is the one that we're modifying. It is *not* backed by disk
    unsigned char *temp = (unsigned char *) MapViewOfFileEx(hMap2, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, PAGESIZE, reservedMem + PAGESIZE);
    if (temp == NULL)
    {
        printf("Fixed map failed. 0x%x\n", GetLastError());
        return -1;
    }
    assert(temp == mapPtr2 + PAGESIZE);

    //Make a copy of the old data that will later be changed
    memcpy(mapPtr2 + PAGESIZE, mapPtr + PAGESIZE, PAGESIZE);

    //The two address spaces should still be identical until this point
    assert(memcmp(mapPtr, mapPtr2, PAGESIZE * 2) == 0);

    //We can now make our changes to the second page as needed
    struct StoredObject *mary2 = (struct StoredObject *)(((unsigned char *)mary1 - mapPtr) + mapPtr2);
    struct StoredObject *john2 = (struct StoredObject *)(((unsigned char *)john1 - mapPtr) + mapPtr2);

    john2->IntVal = 52;
    strcpy(john2->StrVal, "Mike had a little lamb.\n");

    //Test that everything worked OK
    assert(memcmp(mary1, mary2, sizeof(struct StoredObject)) == 0);
    printf("%d, %s", john2->IntVal, john2->StrVal);
    //Should print "52, Mike had a little lamb.\n"

    //Now assume our garbage collection routine has detected that no one is using the original copy of the data
    //munmap(mapPtr, PAGESIZE * 2);

    mapPtr = mapPtr2;

    //Now we're done with all our work and want to completely clean up
    //munmap(mapPtr2, PAGESIZE * 2);

    //close(fd);

    return 0;
}

Ответ 2

но я не понимаю, как мне зарезервировать адресное пространство, чтобы сделать это безопасно

Это зависит от ОС, но немного копается в msdn для mmap (я начал с "xp mmap" в поиске msdn) показывает, что у Microsoft есть свои обычные VerboseAndHelpfullyCapitalizedNames для (многих) функций, реализующих куски mmap. Оба файла и анонимные пользователи могут обрабатывать запросы с фиксированным адресом так же, как любая система POSIX-2001, например, если что-то еще в вашем адресном пространстве разговаривает с ядром, вы можете разобраться с ним. Ни в коем случае я не буду трогать "безопасно", нет такой вещи, как "безопасно" с кодом, который вы хотите перенести на неуказанные платформы. Вам нужно будет создать свой собственный пул анонимной памяти с предварительно отображенной анонимной памятью, которую вы можете размонтировать и выложить позже под своим контролем.

Ответ 3

Я тестировал код Windows из @Mahmoud, ну, на самом деле, я протестировал следующий аналогичный код, и он не работает (код Linux работает.) Если вы раскомментируете VirtualFree, он будет работать. Как уже упоминалось в моем комментарии выше, в окнах вы можете зарезервировать адресное пространство с помощью VirtualAlloc, но вы не можете использовать MapViewOfFileEx с уже отображенным адресом, поэтому сначала вам нужно VirtualFree. Тогда есть условие гонки, когда другой поток может захватить адрес памяти, прежде чем вы это сделаете, поэтому вам нужно делать все в цикле, например. попробуйте до 1000 раз, а затем сдайтесь.

package main

import (
    "fmt"
    "os"
    "syscall"
)

func main() {
    const size = 1024 * 1024

    file, err := os.Create("foo.dat")
    if err != nil {
        panic(err)
    }

    if err := file.Truncate(size); err != nil {
        panic(err)
    }

    const MEM_COMMIT = 0x1000

    addr, err := virtualAlloc(0, size, MEM_COMMIT, protReadWrite)
    if err != nil {
        panic(err)
    }

    fd, err := syscall.CreateFileMapping(
        syscall.Handle(file.Fd()),
        nil,
        uint32(protReadWrite),
        0,
        uint32(size),
        nil,
    )

    //if err := virtualFree(addr); err != nil {
    //  panic(err)
    //}

    base, err := mapViewOfFileEx(fd, syscall.FILE_MAP_READ|syscall.FILE_MAP_WRITE, 0, 0, size, addr)
    if base == 0 {
        panic("mapViewOfFileEx returned 0")
    }
    if err != nil {
        panic(err)
    }

    fmt.Println("success!")
}

type memProtect uint32

const (
    protReadOnly  memProtect = 0x02
    protReadWrite memProtect = 0x04
    protExecute   memProtect = 0x20
    protAll       memProtect = 0x40
)

var (
    modkernel32         = syscall.MustLoadDLL("kernel32.dll")
    procMapViewOfFileEx = modkernel32.MustFindProc("MapViewOfFileEx")
    procVirtualAlloc    = modkernel32.MustFindProc("VirtualAlloc")
    procVirtualFree     = modkernel32.MustFindProc("VirtualFree")
    procVirtualProtect  = modkernel32.MustFindProc("VirtualProtect")
)

func mapViewOfFileEx(handle syscall.Handle, prot memProtect, offsetHigh uint32, offsetLow uint32, length uintptr, target uintptr) (addr uintptr, err error) {
    r0, _, e1 := syscall.Syscall6(procMapViewOfFileEx.Addr(), 6, uintptr(handle), uintptr(prot), uintptr(offsetHigh), uintptr(offsetLow), length, target)
    addr = uintptr(r0)
    if addr == 0 {
        if e1 != 0 {
            err = error(e1)
        } else {
            err = syscall.EINVAL
        }
    }
    return addr, nil
}

func virtualAlloc(addr, size uintptr, allocType uint32, prot memProtect) (mem uintptr, err error) {
    r0, _, e1 := syscall.Syscall6(procVirtualAlloc.Addr(), 4, addr, size, uintptr(allocType), uintptr(prot), 0, 0)
    mem = uintptr(r0)
    if e1 != 0 {
        return 0, error(e1)
    }
    return mem, nil
}

func virtualFree(addr uintptr) error {
    const MEM_RELEASE = 0x8000
    _, _, e1 := syscall.Syscall(procVirtualFree.Addr(), 3, addr, 0, MEM_RELEASE)
    if e1 != 0 {
        return error(e1)
    }
    return nil
}