Каков наиболее надежный / портативный способ выделения памяти по низким адресам в 64-битных системах?

мне нужно выделять большие блоки памяти (на пользовательский распределитель), которые попадают в первые 32 ГБ виртуального адресного пространства.

Я полагаю, что если бы мне понадобились, скажем, блоки 1 МБ, я мог бы повторить с помощью mmap и MAP_FIXED_NOREPLACE (или VirtualAlloc) от низких адресов вперед с шагом, скажем, 1 МБ, пока вызов не завершится успешно. Переходите от последнего успешного блока к следующему.

это звучит неуклюже, но, по крайней мере, это будет несколько надежно против ос изменяется макет адресного пространства и алгоритм ASLR. Из моего понимания текущих макетов ОС должно быть много памяти, доступной в первом 32GB таким образом, но, может быть, мне чего-то не хватает?

есть ли что-нибудь в Windows, Linux, OS X, iOS или Android, что бы победить эту схему? Есть ли лучший способ?

на всякий случай, если вам интересно, это для реализации виртуальной машины для языка программирования, где все указатели в 32-битном значении на 64-битная система может дать огромные преимущества использования памяти и даже набирает скорость. Поскольку все объекты выровнены по крайней мере на 8 байт, нижние 3 бита могут быть смещены, расширяя диапазон указателей с 4 ГБ до 32 ГБ.

1 ответов


для ограничения диапазона выделенной памяти в windows мы можем использовать NtAllocateVirtualMemory function - этот api доступен для использования как в режиме пользователя, так и в режиме ядра. в пользовательском режиме он экспортируется ntdll.dll файлы (использовать ntdll.Либ!--14--> или ntdllp.Либ!--14--> из wdk). в этом api существует параметр -ZeroBits -количество битов адреса высокого порядка, которые должны быть равны нулю в базовом адресе вида сечения. но в msdn ссылка следующие слова о ZeroBits некорректно. правильно:

ZeroBits

поставляет количество битов адреса высокого порядка которые должны быть нул внутри базовый адрес вида сечения. Значение этого аргумента должно быть меньше или равно максимальному числу нулевых битов и только используется, когда управление памятью определяет, где выделить представление (т. е. когда BaseAddress равен null).

если ZeroBits равно нулю, тогда нулевые битовые ограничения не применяются.

если ZeroBits больше чем 0 и чем 32, тогда оно количество ведущих нулевых битов из бита 31. Также требуются биты 63:32 быть нулем. Это сохраняет совместимость с 32-разрядными системами. Если ZeroBits больше 32, затем он рассматривается как маска, а затем отсчитывается число ведущих нулей в маске. Это тогда становится аргументом нулевых битов.

так что действительно мы можем использовать ZeroBits as mask-это наиболее энергопотребление. но можно использовать и как нулевой бит отсчет от 31 бита (в этом случае 63-32 бита всегда будут равны 0). поскольку гранулярность распределения (в настоящее время 64kb - 0x10000) - low 16 бит всегда равна 0. поэтому допустимое значение для ZeroBits в бит-номерном режиме - от 1 до 15 (=31-16). чтобы лучше понять, как работает этот параметр-посмотрите, например, код. для лучше демо-эффект я буду использовать

MEM_TOP_DOWN

указанный регион должен быть создан по самому высокому виртуальному адресу возможно на основе ZeroBits.

PVOID BaseAddress;
ULONG_PTR ZeroBits;
SIZE_T RegionSize = 1;
NTSTATUS status;

for (ZeroBits = 0xFFFFFFFFFFFFFFFF;;)
{
    if (0 <= (status = NtAllocateVirtualMemory(NtCurrentProcess(), &(BaseAddress = 0), 
        ZeroBits, &RegionSize, MEM_RESERVE|MEM_TOP_DOWN, PAGE_NOACCESS)))
    {
        DbgPrint("%p:%p\n", ZeroBits, BaseAddress);
        NtFreeVirtualMemory(NtCurrentProcess(), &BaseAddress, &RegionSize, MEM_RELEASE);

        ZeroBits >>= 1;
    }
    else
    {
        DbgPrint("%x\n", status);
        break;
    }
}

for(ZeroBits = 0;;) 
{
    if (0 <= (status = NtAllocateVirtualMemory(NtCurrentProcess(), &(BaseAddress = 0), 
        ZeroBits, &RegionSize, MEM_RESERVE|MEM_TOP_DOWN, PAGE_NOACCESS)))
    {
        DbgPrint("%x:%p\n", ZeroBits++, BaseAddress);
        NtFreeVirtualMemory(NtCurrentProcess(), &BaseAddress, &RegionSize, MEM_RELEASE);
    }
    else
    {
        DbgPrint("%x\n", status);
        break;
    }
}

и выход:

FFFFFFFFFFFFFFFF:00007FF735B40000
7FFFFFFFFFFFFFFF:00007FF735B40000
3FFFFFFFFFFFFFFF:00007FF735B40000
1FFFFFFFFFFFFFFF:00007FF735B40000
0FFFFFFFFFFFFFFF:00007FF735B40000
07FFFFFFFFFFFFFF:00007FF735B40000
03FFFFFFFFFFFFFF:00007FF735B40000
01FFFFFFFFFFFFFF:00007FF735B40000
00FFFFFFFFFFFFFF:00007FF735B40000
007FFFFFFFFFFFFF:00007FF735B40000
003FFFFFFFFFFFFF:00007FF735B40000
001FFFFFFFFFFFFF:00007FF735B40000
000FFFFFFFFFFFFF:00007FF735B40000
0007FFFFFFFFFFFF:00007FF735B40000
0003FFFFFFFFFFFF:00007FF735B40000
0001FFFFFFFFFFFF:00007FF735B40000
0000FFFFFFFFFFFF:00007FF735B40000
00007FFFFFFFFFFF:00007FF735B40000
00003FFFFFFFFFFF:00003FFFFFFF0000
00001FFFFFFFFFFF:00001FFFFFFF0000
00000FFFFFFFFFFF:00000FFFFFFF0000
000007FFFFFFFFFF:000007FFFFFF0000
000003FFFFFFFFFF:000003FFFFFF0000
000001FFFFFFFFFF:000001FFFFFF0000
000000FFFFFFFFFF:000000FFFFFF0000
0000007FFFFFFFFF:0000007FFFFF0000
0000003FFFFFFFFF:0000003FFFFF0000
0000001FFFFFFFFF:0000001FFFFF0000
0000000FFFFFFFFF:0000000FFFFF0000
00000007FFFFFFFF:00000007FFFF0000
00000003FFFFFFFF:00000003FFFF0000
00000001FFFFFFFF:00000001FFFF0000
00000000FFFFFFFF:00000000FFFF0000
000000007FFFFFFF:000000007FFF0000
000000003FFFFFFF:000000003FFF0000
000000001FFFFFFF:000000001FFF0000
000000000FFFFFFF:000000000FFF0000
0000000007FFFFFF:0000000007FF0000
0000000003FFFFFF:0000000003FF0000
0000000001FFFFFF:0000000001FF0000
0000000000FFFFFF:0000000000FF0000
00000000007FFFFF:00000000007F0000
00000000003FFFFF:00000000003F0000
00000000001FFFFF:00000000001F0000
00000000000FFFFF:00000000000F0000
000000000007FFFF:0000000000070000
000000000003FFFF:0000000000030000
000000000001FFFF:0000000000010000
c0000017
0:00007FF735B40000
1:000000007FFF0000
2:000000003FFF0000
3:000000001FFF0000
4:000000000FFF0000
5:0000000007FF0000
6:0000000003FF0000
7:0000000001FF0000
8:0000000000FF0000
9:00000000007F0000
a:00000000003F0000
b:00000000001F0000
c:00000000000F0000
d:0000000000070000
e:0000000000030000
f:0000000000010000
c0000017

поэтому, если мы скажем, что хотим ограничить выделение памяти 32Gb(0x800000000) - мы можем использовать ZeroBits = 0x800000000 - 1:

NtAllocateVirtualMemory(NtCurrentProcess(), &(BaseAddress = 0), 
            0x800000000 - 1, &RegionSize, MEM_RESERVE|MEM_TOP_DOWN, PAGE_NOACCESS)

в результате память будет выделена в диапазоне [0, 7FFFFFFFF] (на самом деле [0, 7FFFF0000] поскольку гранулярность распределения низких 16 бит адреса всегда равна 0)


чем вы можете создать кучу RtlCreateHeap на выделенный диапазон области и выделить память из этой кучи (Примечание-это также пользовательский режим api-use ntdll[p].Либ!--14--> для ввода компоновщик)

PVOID BaseAddress = 0;
SIZE_T RegionSize = 0x10000000;// reserve 256Mb
if (0 <= NtAllocateVirtualMemory(NtCurrentProcess(), &BaseAddress, 
    0x800000000 - 1, &RegionSize, MEM_RESERVE, PAGE_READWRITE))
{
    if (PVOID hHeap = RtlCreateHeap(0, BaseAddress, RegionSize, 0, 0, 0))
    {
        HeapAlloc(hHeap, 0, <somesize>);
        RtlDestroyHeap(hHeap);
    }

    VirtualFree(BaseAddress, 0, MEM_RELEASE);
}