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

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

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

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

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

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

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

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

--

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

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

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


person aSteve    schedule 04.05.2012    source источник


Ответы (3)


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

#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;
}
person Mahmoud Al-Qudsi    schedule 07.05.2012
comment
Спасибо, это полезно. В идеале мне нужен указатель на документацию, в которой говорится о том, что использование MAP_FIXED таким образом безопасно / целесообразно, а также для определения возможности кроссплатформенного подхода с использованием ускорения. - person aSteve; 07.05.2012
comment
Вот чего я не понял в вашем вопросе. Это то, для чего MAP_FIXED сделан. Теперь правильный способ его использования, если вы говорите о сотнях МиБ адресного пространства, - это использовать фиксированную адресацию для всех сопоставлений, в противном случае позволяя системе выбирать случайный расположение достаточно хорошее. - person Mahmoud Al-Qudsi; 08.05.2012
comment
Я знал, что MAP_FIXED имеет значение (в Linux, MapViewOfFileEx в Windows), хотя меня это расстраивало. Поскольку требование фиксированного адреса для сопоставления менее переносимо, использование этой опции не рекомендуется. в моей man-странице linux mmap. Я действительно ожидаю отображения сотен MiB (по крайней мере), но я не понимаю, как лучше всего зарезервировать адресное пространство, чтобы выделить его самому - если не через mmap (). - person aSteve; 11.05.2012
comment
Это код на языке C; не C ++. Вы должны использовать RAII (например, scopeGuard), чтобы обеспечить очистку. - person Jonathan Leonard; 11.05.2012
comment
Спасибо за обновление ... Для Unix / Linux мне нравится этот подход ... хотя он намекает, что мне нужно написать свою собственную библиотеку отображения памяти, а не использовать ту, что в Boost :: interprocess. Я могу это сделать - если нужно. Если мне нужно написать свою собственную платформо-независимую библиотеку сопоставления, я бы хотел, чтобы она учитывала как Windows, так и Unix. Насколько я могу судить, это желаемое поведение mmap / MAP_FIXED не имитируется эквивалентными вызовами Windows для MapViewOfFileEx () ... хотя, возможно, VirtualAlloc () с MEM_RESERVE может предоставить аналогичные возможности? - person aSteve; 13.05.2012
comment
Да, вы должны заменить первый вызов mmap во второй половине кода на VirtualAlloc(MEM_RESERVE), который сообщит ОС найти вам непрерывный блок из x байтов, который она отложит в сторону , но не будет физически выделять в этот момент. Затем вы можете MapViewOfFileEx поверх возвращенного адреса, используя последнюю подсказку параметра lpBaseAddress. Хотя MS утверждает, что это не гарантирует, что это будет работать само по себе, с VirtualAlloc это становится гарантированным (они дополняют друг друга). - person Mahmoud Al-Qudsi; 13.05.2012
comment
@ MahmoudAl-Qudsi «Образец кода» можно легко скопировать / вставить в «библиотеку» или код приложения. Вопрос помечен как c ++, поэтому можно ожидать, что в ответах будут использоваться лучшие практики для этого языка. - person Jonathan Leonard; 13.05.2012
comment
@Jonathan Я не собираюсь превращать каждый ответ здесь в полностью документированную, протестированную и отлично написанную библиотеку с открытым исходным кодом. - person Mahmoud Al-Qudsi; 14.05.2012
comment
Библиотеки @Mahmoud предназначены для вызова / использования и поэтому могут быть закодированы так, как вы хотите [при условии правильности]. Однако пример кода предназначен для чтения и имитации читателями (и многие из них просто копируют / вставляют весь блок). Кроме того, я бы сказал, что писать в стиле C ++ должно быть вашим первым инстинктом; не второй или ниже. Гораздо проще написать качественный код с первого раза, чем переводить его с C. - person Jonathan Leonard; 14.05.2012
comment
Фактически, я ответил на него на C (и скомпилировал его как таковой), поскольку я использовал Posix вместо ускорения, чтобы предоставить решение. Тем не менее, я думаю, что требование абсолютно похожих на корабль ответов приведет к тому, что никто не попытается ответить, если только у них не будет пары часов, чтобы потратить на составление ответа на каждый вопрос. В конце концов, это не моя проблема, если люди копируют и вставляют код вслепую, не глядя. - person Mahmoud Al-Qudsi; 14.05.2012
comment
Хотя я все еще надеюсь найти решение, ориентированное на Boost, его поиск кажется все более маловероятным - по крайней мере, в краткосрочной перспективе. За пределами этой области, с моей точки зрения, следующим лучшим решением было создание подходящих вызовов API ОС. - person aSteve; 14.05.2012
comment
Потратив некоторое время на изучение других проблем, я вернулся к этой - и, поняв, что мне нужно «свернуть свое собственное» отображение памяти ... Я попробовал пример Windows выше ... и обнаружил, что он не работает .. Адрес, возвращаемый VirtualAlloc (), не принимается MapViewOfFileEx (). Эта страница MSDN, похоже, предполагает, что этот подход недействителен. msdn.microsoft.com/ en-us / library / windows / desktop / - person aSteve; 25.08.2012
comment
@aSteve ты нашел решение? Как минимум, вы можете использовать виртуальное выделение, чтобы зарезервировать адрес, немедленно освободить его и попытаться сопоставить. Там есть небольшое окно для состояния гонки, где другой поток может захватить это адресное пространство, и в этом случае вы можете просто попробовать все шаги еще раз, пока в конечном итоге это не увенчается успехом. Мое чтение документов совпадает с вашим: никакое другое выделение памяти не может иметь место в области, которая используется для сопоставления, включая использование функции VirtualAlloc или VirtualAllocEx для резервирования памяти. - person Eloff; 08.07.2015
comment
Не нашел удовлетворительного решения, которым остался доволен ... Под Linux я подошел ближе, чем под Windows. То, что я узнал из этого исследования, повлияло на мою общую стратегию дизайна, сделав этот вопрос сегодня для меня одним только академическим. - person aSteve; 11.07.2015

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

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

person jthill    schedule 13.05.2012
comment
Я нашел VirtualAlloc () с MEM_RESERVE в Windows, хотя я не экспериментировал с этим широко. Любая конкретная документация, подробно описывающая, как зарезервировать память для отображения на явные адреса, была бы полезна. Я согласен с тем, что большая проблема здесь - безопасность и портативность. Это то, что привлекло меня к Boost :: interprocess - который имеет объект, который позволяет мне указывать явные адреса ... но не документирует, как устанавливать действительные явные адреса ... Не работает - ни в Linux, ни в Windows. - person aSteve; 13.05.2012
comment
(Уточнение: стратегия Махмуда работает в соответствии с его образцом, но этот метод не работает при использовании только переносимой библиотеки boost :: interprocess.) - person aSteve; 13.05.2012
comment
@aSteve, но не документирует, как установить действительные явные адреса ... потому что это зависит от того, что еще происходит в вашем адресном пространстве, есть ли какие-либо административные ограничения на размер адресного пространства (ulimits), ... Начать просто. mmap огромную полосу MAP_ANONYMOUS | MAP_NORESERVE, а затем, когда вы хотите ее кусок, отмените отображение всего этого, MAP_FIXED кусок, который вы хотите, и повторно M_A | M_N остальное. - person jthill; 13.05.2012

Я тестировал код Windows от @Mahmoud, ну, на самом деле я тестировал следующий похожий код, и он не работает (код Linux работает). Если вы раскомментируете VirtualFree, он будет работать. Как упоминалось в моем комментарии выше, в Windows вы можете зарезервировать адресное пространство с помощью 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
}
person Eloff    schedule 14.07.2015