Мне нужно выделить большие блоки памяти (которые будут использоваться моим пользовательским распределителем), которые попадают в первые 32 ГБ виртуального адресного пространства.
Я полагаю, что если бы мне понадобились, скажем, блоки 1 МБ, я мог бы выполнить mmap
а также MAP_FIXED_NOREPLACE
(или VirtualAlloc) начиная с младших адресов с шагом, скажем, 1 МБ, до тех пор, пока вызов не будет успешным. Продолжите с последнего успешного блока для следующего.
Это звучит неуклюже, но, по крайней мере, это будет несколько устойчиво к изменениям компоновки адресного пространства ОС и алгоритму ASLR. Исходя из моего понимания текущих макетов ОС, в первых 32 ГБ должно быть достаточно памяти, но, может быть, я что-то упустил?
Есть ли в Windows, Linux, OS X, iOS или Android что-нибудь, что могло бы победить эту схему? Есть ли способ лучше?
На всякий случай, если вам интересно, это для реализации виртуальной машины для языка программирования, где размещение всех указателей в 32-битном значении в 64-битной системе может дать огромные преимущества использования памяти и даже выигрыш в скорости. Поскольку все объекты выровнены по крайней мере на 8 байтов, младшие 3 бита могут быть сдвинуты, увеличив диапазон указателей с 4 до 32 ГБ.
для ограничения выделенного диапазона памяти в окнах мы можем использовать NtAllocateVirtualMemory
Функция — этот API доступен для использования как в режиме пользователя, так и в режиме ядра. в пользовательском режиме экспортируется ntdll.dll (использование ntdll.lib или же ntdllp.lib из ВДК). в этом API существует параметр — ZeroBits — Количество старших битов адреса, которые должны быть равны нулю в базовом адресе разреза. но в MSDN ссылку на следующие слова о ZeroBits это неверно. правильно это:
ZeroBits
Предоставляет количество битов адреса старшего разряда, которые должны быть равны нулю в
Базовый адрес вида в разрезе. Значение этого аргумента должно
быть меньше или равно максимальному количеству нулевых битов и только
используется, когда управление памятью определяет, где разместить представление
(т.е. когда BaseAddress равен нулю).Если ZeroBits равен нулю, то ограничения нулевого бита не применяются.
Если ZeroBits больше 0 и меньше 32, то это
количество старших нулевых битов из бита 31. Требуются также биты 63:32
быть нулем. Это сохраняет совместимость с 32-битными системами.
Если ZeroBits больше 32, то считается маской, а затем отсчитывается число начальных нулей
в маске. Это тогда становится аргументом нулевых битов.
так что на самом деле мы можем использовать ZeroBits в качестве маски — это наиболее энергопотребление. но можно использовать и в качестве отсчета нулевого бита из 31 бита (в этом случае 63-32 бита всегда будут равны 0). потому что гранулярность распределения (в настоящее время 64 КБ — 0x10000) — 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 — использовать Ntdll [р] .lib для ввода компоновщика)
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);
}
Других решений пока нет …