【问题标题】:How to resize regions allocated by VirtualAlloc?如何调整 VirtualAlloc 分配的区域大小?
【发布时间】:2011-09-21 18:04:17
【问题描述】:

我想调整由 MS 窗口的 VirtualAlloc 分配的内存区域的大小。查看 VirtualFree 文档,可以仅部分解除区域的提交,但无法部分释放它。也就是说,可以释放部分物理内存,但不能释放部分虚拟内存。

我知道在这种情况下可能需要重新分配区域。但是,复制整个区域的效率会相当低。有没有办法让窗口分配一个不同大小的新区域,指向相同的内存?

【问题讨论】:

  • 虚拟内存的重点是将这些东西抽象出来,让内核处理内存放置。
  • 你的应用程序是否需要释放虚拟内存页;你的虚拟地址空间用完了吗?
  • @DanielTrebbien:情况如下..我需要一个新的区域,所以我尝试用VirtualAlloc分配它。但是,VirtualAlloc 会引发内存不足。同时,由于我自己保留了一些元数据,我知道某些区域只有半满。我现在想廉价地缩小或重新分配这些区域,直到我再次拥有内存。
  • 你无法完成这项工作。只有使用可以跟踪指针并更新它们的垃圾收集器才能移动分配。取消提交区域不会缩小分配,它只是取消映射它们。您正在了解为什么 malloc() 会以它的方式工作。
  • 没有引用页面的未经检查的指针。如果VMem地址改变了就可以了。也就是说,只要速度快,获得类似 realloc 的东西就足够了。

标签: c winapi


【解决方案1】:

正如您所提到的,似乎不可能部分释放一系列保留页面,因为VirtualFree() documentation 声明:

如果 dwFreeType 参数是 MEM_RELEASE,[lpAddress] 必须是当页面区域 [被] 保留时由 VirtualAlloc 函数返回的基地址。

还有:

如果 dwFreeType 参数为 MEM_RELEASE,[dwSize] 必须为 0(零)。

VirtualFree() 本身是内核函数NtFreeVirtualMemory() 的薄包装器。 Its documentation page(与ZwFreeVirtualMemory()相同)也有这种写法。

一种可能的解决方法是将一个大型预订与多个较小的预订分开。例如,假设您通常一次保留 8 MiB 的虚拟地址空间。您可以尝试在 32 个连续的 256 KiB 保留中保留该范围。第一个 256 KiB 预留将包含一个 32 位无符号位字段,如果 ith 获得了 256 KiB 预留:

#define NOMINMAX
#include <windows.h>
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

#define RESERVATION_SIZE (256*1024)

typedef struct st_first_reservation {
    size_t reservation_size;
    uint32_t rfield;
    char premaining[0];
} st_first_reservation;

int main()
{
    SYSTEM_INFO sys_info = { 0 };
    GetSystemInfo(&sys_info);

    assert((RESERVATION_SIZE % sys_info.dwPageSize) == 0);

    void *vp = VirtualAlloc(NULL, 32*RESERVATION_SIZE, MEM_RESERVE, PAGE_NOACCESS);
    if (VirtualFree(vp, 0, MEM_RELEASE) == 0) {
        fprintf(stderr, "Error: VirtualFree() failed.\n");
        return EXIT_FAILURE;
    }

    st_first_reservation *pfirst_reservation = (st_first_reservation *) VirtualAlloc(vp, RESERVATION_SIZE, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
    if (pfirst_reservation == NULL) {
        pfirst_reservation = (st_first_reservation *) VirtualAlloc(NULL, RESERVATION_SIZE, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
        if (pfirst_reservation == NULL) {
            fprintf(stderr, "Error: VirtualAlloc() failed.\n");
            return EXIT_FAILURE;
        }
    }

    fprintf(stderr, "pfirst_reservation = 0x%p\n", (void *) pfirst_reservation);

    pfirst_reservation->reservation_size = RESERVATION_SIZE;
    pfirst_reservation->rfield = 1LU;

    char *p = (char *) pfirst_reservation;
    unsigned i = 1;
    for (; i < 32; ++i) {
        vp = VirtualAlloc(p += RESERVATION_SIZE, RESERVATION_SIZE, MEM_RESERVE, PAGE_NOACCESS);
        if (vp != NULL) {
            assert(((void *) vp) == p);
            pfirst_reservation->rfield |= 1LU << i;
            fprintf(stderr, "Obtained reservation #%u\n", i + 1);
        } else {
            fprintf(stderr, "Failed to obtain reservation #%u\n", i + 1);
        }
    }

    fprintf(stderr, "pfirst_reservation->rfield = 0x%08x\n", pfirst_reservation->rfield);

    return EXIT_SUCCESS;
}

样本输出:

pfirst_reservation = 0x009A0000 获得预订#2 获得预订#3 获得预订#4 获得预订#5 获得预订#6 获得预订#7 获得预订#8 获得预订#9 获得预订#10 获得预订#11 获得预订#12 获得预订#13 获得预订#14 获得预订#15 获得预订#16 获得预订#17 获得预订#18 获得预订#19 获得预订#20 获得预订#21 获得预订#22 获得预订#23 获得预订#24 获得预订#25 获得预订#26 获得预订#27 获得预订#28 获得预订#29 获得预订#30 获得预订#31 获得预订#32 pfirst_reservation->rfield = 0xffffffff

编辑:我发现最好一次免费“预先保留”三十二个 256 KiB 范围,然后尝试重新保留尽可能多的可以。

我更新了上面的代码和示例输出。

在多线程环境中,代码可能会回退到第一次保留的“任何地方”分配。尝试在 32*RESERVATION_SIZE 字节的保留然后释放范围内保留 RESERVATION_SIZE 字节五次左右,最终回到“任何地方”分配是一个好主意。

【讨论】:

  • 有趣,我也有同样的想法,但我不确定它是否有效或如何详细完成。事实上,如果我考虑一下,甚至不需要位图,因为我只会缩小或扩大“区域”。因此,当前分配页面的计数器就足够了。无论如何,非常感谢代码,可惜我不能多次投票。
  • @cib:我不确定您是否可以在没有位图的情况下使用。例如,可能会发生无法获得第 32 次预订的情况。即使您使用互斥锁保护对VirtualAlloc() 的所有调用,另一个进程也有可能通过VirtualAllocEx() 在您的进程地址空间中分配虚拟地址空间。
  • 我认为一旦我无法提供连续的内存块,我必须重新分配或引发错误。如果我的“区域”中有漏洞,我不能将其用作单个内存块,而必须将其视为它的本来面目,即一组单独的页面。另外,虽然这有点离题,但您能解释一下为什么其他进程可能想要在我的进程中分配内存吗?如果没有适当的进程间权限系统,这听起来无论如何都不安全。
  • @cib:我只使用了一次VirtualAllocEx() 来在explorer.exe 中分配一些内存。 IIRC,我需要为资源管理器地址空间中的特定结构分配空间,以便我可以向资源管理器的一个窗口(对应于开始栏的“通知区域”)发送消息。该窗口将用信息填充结构。然后我打电话给ReadProcessMemory() 来阅读它写的内容,然后打电话给VirtualFree(),这样我就不会在资源管理器中泄漏内存。这是类似的东西:autohotkey.com/forum/topic5825.html
  • 啊。好吧,我不打算使用这些功能,所以如果有一个程序尝试这样的事情并且它破坏了事情,那对我来说是可以接受的。我认为这里更大的问题是我自己的进程中使用常规 malloc 的线程。
【解决方案2】:

不是答案,但我必须问:

考虑到您的痛苦、VirtualAlloc( ) 的性能损失以及代码的不可移植性;与 VIrtualAlloc( ) 给出的任何值相比,您是否可以考虑使用 malloc( ) 和朋友来代替? IOW,VirtualAlloc() 有什么真正的优势吗?

在我看来(也许只是我认为),malloc() 的功能和通用性超过了 VirtualAlloc() 所承诺的任何吸引力。它会让你更直接地处理你的地区。

很抱歉没有回答。我讨厌人们问“谁曾经会认为做那个?”但当然,当我是问“为什么”的人时,一切都不同了:-)

【讨论】:

  • 简单,我正在编写自己的 malloc 级内存管理器因为 malloc 对我正在做的事情效率不高
  • @cib,在大多数情况下,即使直接使用 malloc 效率低下,间接使用 malloc(即 malloc 以获得您进一步细分的大块)通常也可以...跨度>
  • @bdonlan:可能是这种情况,但听起来充其量只是一个 hack。使用 malloc,您几乎无法控制分配的位置和方式。例如,我的一些要求可能包括页面对齐和某个地址范围内的地址。当然,一些 malloc 实现可能会奏效,但由于 malloc 是抽象的,你永远无法确定。
  • @cib -- 使 malloc( ) 返回的地址在页面或其他任何地方对齐是很容易的。毕竟,VirtualAlloc() 正在这样做;为什么不是你?我之所以依赖这一点,是因为,说实话,当您调用专有功能时,您是在钉牢可移植性的棺材。您可能会说,“好吧,它只会在 Windows 下使用,老天爷!”是的,我已经说过很多次了,只是为了以后踢自己。
【解决方案3】:

如果要缩小分配,可以在分配的子范围上使用 VirtualFree 和 MEM_DECOMMIT。请注意,这不会释放 地址空间;只有物理内存。如果你想扩大它,你可以尝试VirtualAlloc在你现有的分配之后立即传递一个地址。当然,这可能会失败,此时您需要复制内存。

您也可以尝试将GlobalAlloc 与GMEM_MOVEABLE 和GlobalReAlloc(或等效的Heap* 函数)一起使用。

如果您需要释放地址空间,您可能想尝试使用匿名内存映射对象,并在运行时更改它们的映射窗口 - 或者干脆使用 64 位来获得额外的地址空间。

【讨论】:

  • 没有帮助,他的地址空间不足。 VirtualAlloc/Free 与按需分页虚拟内存操作系统上的 RAM 无关。
  • @HansPassant,看看我的最后一个建议——他可以使用匿名内存映射对象,并从中映射窗口。或者直接转到 64 位。
猜你喜欢
  • 2013-02-10
  • 2011-08-30
  • 2013-07-18
  • 2020-07-23
相关资源
最近更新 更多