2012-05-04 2 views
18

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

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

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

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

Когда все становится интересным, это касается обновления данных потоков ... пока они (одновременно) прочитаны. Стратегия, которую я хотел бы использовать , похожа на использование «Copy On Write» на системной странице размером детализации ... по существу, пишет «накладные страницы», что позволяет одному процессу читать старые данные, в то время как другой читает обновленные данные.

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

Я пытаюсь установить лучшую стратегию, и мне мешает документация . Я чувствую себя неполной. По сути, мне нужно отделить выделение адресного пространства от картографирования памяти по этому адресу . С 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) подход.

ответ

7

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

#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's 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's 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; 
} 
+0

Спасибо, это полезно. В идеале я хотел бы указать указатель на документацию, которая предполагает, что это безопасно/целесообразно использовать MAP_FIXED таким образом, а также установить, возможен ли кросс-платформенный подход с использованием boost. – aSteve

+0

Вот что я не понял о вашем вопросе. Это то, что «MAP_FIXED» * сделано * для. Теперь правильный способ его использования * если * вы говорите о сотнях MiB адресного пространства, было бы использовать фиксированную адресацию для * all * сопоставлений, в противном случае система выбора случайного местоположения будет достаточно хорошей. –

+0

Я знал, что MAP_FIXED имеет отношение (в Linux, MapViewOfFileEx в Windows), хотя меня раздражало: «Поскольку требуемый фиксированный адрес для сопоставления менее переносимый, использование этого параметра не рекомендуется». в моей manux странице linux mmap. Я ожидаю, что буду отображать сотни MiB (по крайней мере), но я не знаю, как лучше всего зарезервировать адресное пространство, чтобы выделить его самостоятельно - если не через mmap(). – aSteve

3

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

это собирается меняться в зависимости от операционной системы, но немного рытья на MSDN для ттаров (я начал с «XP ттаром» по поиску MSDN) показывает Microsoft имеет свои обычные VerboseAndHelpfullyCapitalizedN ames для (многих) функций, реализующих куски mmap. Оба файла и анонимные пользователи могут обрабатывать запросы с фиксированным адресом так же, как любая система POSIX-2001, например, если что-то еще в вашем адресном пространстве разговаривает с ядром, вы можете разобраться с ним. Ни в коем случае я не буду трогать «безопасно», нет такой вещи, как «безопасно» с кодом, который вы хотите переносить на неуказанные платформы. Вам нужно будет создать свой собственный пул анонимной памяти с предварительно отображенной анонимной памятью, которую вы можете размонтировать и выложить позже под своим контролем.

+0

Я нашел VirtualAlloc() с MEM_RESERVE на Windows - хотя я не экспериментировал с ним широко. Любая конкретная документация, в которой подробно описывается, как резервировать память для сопоставления явным адресам, была бы полезной. Я согласен с тем, что большая проблема здесь - безопасность и мобильность. Это то, что привлекло меня к Boost :: interprocess - который имеет объект, который позволяет мне указывать явные адреса ... но не документирует, как устанавливать действительные явные адреса ... и стратегия «наложения сопоставлений» из Mahmoud doesn работайте в Linux или Windows. – aSteve

+0

(Уточнение: стратегия Махмуда работает по его образцу, но техника не работает при использовании только портативной библиотеки boost :: interprocess.) – aSteve

+0

@aSteve ", но не документирует, как установить действительные явные адреса" .. это потому, что это зависит от того, что еще происходит в вашем адресном пространстве, есть ли какие-либо административные ограничения на размер адресного пространства ('ulimit's), ... Начать просто. mmap огромный swathe MAP_ANONYMOUS | MAP_NORESERVE, а затем, когда вы захотите его кусочек, отмените все это, MAP_FIXED, который вы хотите, и повторите M_A | M_N. – jthill

1

Я проверил код 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 
} 
Смежные вопросы