【问题标题】:What is the most reliable / portable way to allocate memory at low addresses on 64-bit systems?在 64 位系统的低地址分配内存的最可靠/可移植的方法是什么?
【发布时间】:2018-10-29 23:02:49
【问题描述】:

我需要分配位于前 32GB 虚拟地址空间内的大块内存(供我的自定义分配器使用)。

我想如果我需要 1MB 块,我可以使用 mmapMAP_FIXED_NOREPLACE(或 VirtualAlloc)从低地址开始以 1MB 的增量进行迭代,直到调用成功。从上一个成功的区块继续下一个。

这听起来很笨拙,但至少它对操作系统地址空间布局更改和 ASLR 算法更改具有一定的鲁棒性。根据我对当前操作系统布局的理解,这样前 32GB 中应该有足够的可用内存,但也许我遗漏了什么?

在 Windows、Linux、OS X、iOS 或 Android 中有什么东西可以打败这个计划吗?有没有更好的办法?

以防万一您想知道,这是为一种编程语言实现 VM,其中在 64 位系统上将所有指针拟合为 32 位值可以带来巨大的内存使用优势甚至速度提升。由于所有对象至少 8 字节对齐,因此可以将低 3 位移出,将指针范围从 4GB 扩展到 32GB。

【问题讨论】:

  • MS-Window 你可以查询页表,这可能需要一些 VirtualAlloc 的试验和错误参见:msdn.microsoft.com/en-us/library/windows/desktop/…
  • 语言是否有 charchar * 的等价物?如果是这样,是否所有 char 对象也是 8 字节对齐的?
  • 您愿意接受避免虚拟地址限制的替代解决方案吗?
  • @RichardCritten:谢谢!这样可以加快速度。
  • @Angew:它有 8 字节对齐的字符串,没有裸字符 * 子字符串

标签: c++ memory-management operating-system mmap vm-implementation


【解决方案1】:

为了限制 Windows 中分配的内存范围,我们可以使用NtAllocateVirtualMemory 函数 - 此 api 可用于用户模式和内核模式。在用户模式下,它由 ntdll.dll 导出(使用 wdk 中的 ntdll.libntdllp.lib)。在这个 api 中存在参数 - ZeroBits - 截面视图的基地址中必须为零的高位地址位数。但在 msdn 链接中,关于 ZeroBits 的下一个词不正确。正确的是:

ZeroBits

提供必须为 0 的高位地址位数 截面视图的基地址。该参数的值必须 小于或等于最大零位数,并且仅 当内存管理确定分配视图的位置时使用 (即当 BaseAddress 为空时)。

如果 ZeroBits 为零,则不应用零位约束。

如果 ZeroBits 大于 0 且小于 32,那么它是 来自第 31 位的前导零位的数量。还需要位 63:32 为零。这保留了与 32 位系统的兼容性。 如果 ZeroBits 大于 32,则将其视为掩码,然后计算前导零的个数 在面具中。这将成为零位参数。

所以我们真的可以使用 ZeroBits 作为掩码 - 这是最耗电的。但可以用作 31 位的零位计数(在这种情况下,63-32 位将始终等于 0)。因为分配粒度(当前为 64kb - 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[p].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);
}

【讨论】:

  • 谢谢,对于完全符合我要求的 Windows 案例来说,这是一个绝妙的答案。我很欣赏示例代码。不过,也希望获得一些关于如何在其他操作系统上实现相同功能的提示。
  • @Aardappel - 不幸的是,我的回答仅适用于 Windows 案例。我对另一个操作系统一无所知。
  • 好的,我会在几天内接受你的回答,除非有人能做得更好。
猜你喜欢
  • 1970-01-01
  • 2018-03-19
  • 1970-01-01
  • 2013-11-25
  • 1970-01-01
  • 2021-04-10
  • 1970-01-01
  • 1970-01-01
  • 2013-07-30
相关资源
最近更新 更多