VirtualAlloc Confusions - 仅用于页面吗?

时间:2014-03-04 21:16:59

标签: c++ windows memory memory-management

我需要使用VirtualAlloc为我的项目分配可执行内存,以便JIT将自定义脚本格式重新编译为x86 / etc.但是我觉得很困惑,其他人似乎都没有注意到,而且它的行为似乎明显缺乏细节。

我知道它分配'虚拟'内存意味着它可以是物理上的任何东西(RAM /磁盘),但在使用时它可以简单地被认为是'内存'。但是,例如,如果我做了类似的事情:

#define MB 1024*1024
auto pAddr = VirtualAlloc(NULL, 8*MB, MEM_RESERVE, PAGE_NOACCESS);
VirtualAlloc(pAddr + 4*MB, 1*MB, MEM_COMMIT, PAGE_EXECUTE_READWRITE);  // commit 1MB, 4MB's into the reserved memory

仅使用1MB,还是5MB?显然,我并不认为它是5MB - 我只是不知道在这个看似明显的情况下会发生什么。有效吗?可以自由地提交和取消保留内存中的任何范围吗?更重要的是,它可以无序使用还是应该以递增方式分配(通过MSDN文档,就像你可以用它做的一样)。或者,VirtualAlloc只对一次分配“页面”感到满意吗?

我发现的每个例子似乎都只对我展示如何分配页面感兴趣 - 这可能只是最基本的用法,但远非最实用的 - 但我想用它来分配脚本的编译代码,这可能是偶尔在执行期间重新编译。我需要尝试为这些分配创建一些接口,所以我可以简单地说“为这个脚本编译提供一些内存”,它会自动返回以前提交的未使用的空间或提交一些新空间 - 所以任何提示如何最好地从虚拟内存中分配(例如,最好不再提交可能会再次提交的内存?)也将不胜感激。

2 个答案:

答案 0 :(得分:4)

行;我认为我理解你所得到的东西,我希望这能澄清事情。

从概念上讲,VirtualAlloc适用于各个页面。

为简单起见,我们考虑一个32位x86进程。虚拟地址空间是从第0页到第1048575页的一系列页面。这些页面中的每一页可以保留也可以不保留;如果保留,可能会也可能不会提交。 (如果已提交,它也将具有零个或多个内存保护选项,并且页面可能存在各种其他状态,但我们现在几乎可以忽略所有这些状态。)

不能仅保留或提交部分页面,也不能让同一页面的两个部分具有不同的内存保护选项。相反,保留和/或提交的页面是否连续并不重要。

如果使用特定的起始地址和区域大小调用VirtualAlloc,则它将作用于指定的虚拟地址区域中包含一个或多个字节的每个页面。地址和大小仅使用 来计算要处理的页面。参数是地址而不是页码的唯一原因是为了简化程序员的工作。

从概念上讲,对VirtualAlloc的单个调用覆盖多个页面相当于为每个页面调用一次VirtualAlloc。唯一的区别(效率除外)是同时作用于多个页面是原子的,因此要么失败要么成功整个范围。

请特别注意,如果您成功多次调用VirtualAlloc覆盖连续页面,则无法告诉后面这些页面是单独分配的。操作系统只记得页面所处的状态,而不是它是如何到达的。 [ 附录: 哎呀;这是错的。 VirtualQuery的文档说它可以判断连续页面是否属于同一分配。也许他们被标记为唯一的分配ID或其他东西。我根本不相信这些信息实际上是由内存管理器使用的,但显然 是保留的。]

请记住,HeapCreate函数已经允许您创建一个堆,其内存块允许代码执行。除非您的应用程序有非常不寻常的需求,否则您不可能通过编写自己的堆管理器来获得任何收益。

答案 1 :(得分:2)

仅限页面。

VirtualAlloc(Windows API函数)的历史及其内存页面概念与英特尔x86处理器系列的发展密切相关。

使用最初的8086处理器(cirka 1979 IIRC,1980年第一台基于它的PC?)在机器代码级别的寻址是16位。本身会产生最大64K的地址范围,但处理器将每个地址仅视为偏移到64K 的内存中。它支持四个逻辑段,即代码,数据,堆栈和“额外”段,这些地址中的哪一个是依赖于上下文的偏移量。物理存储器中每个段的开始由16位段选择器寄存器确定,分别称为CS,DS,SS和ES。为了形成段的起始地址,处理器简单地将选择器向左移位4位,对应于乘以16.因此,* 物理地址对应于在上下文中使用的偏移O.段选择器是S,A = 16 * S + O,它产生大约20位的物理地址(但你可以在顶部稍微包裹一下)。

逻辑地址=(段偏移,段选择器)
计算(偏移,选择器)→物理地址

使用80286,段选择器通过表映射到物理地址,这允许更多的内存,并允许不同大小的段。我认为它有24位物理地址,不确定;它仍然是一个基本上是16位处理器,但支持(相对而言)大量内存。使用286,将偏移量称为逻辑地址是有意义的,因为进程不能只计算相应的物理地址。

逻辑地址=(段偏移,段选择器)
查找(偏移,选择器)→物理地址

80386最终为PC带来了32位编程。代码段可以是32位或16位,确定那里的机器代码的解释。此外,它支持透明的自动内存管理,虚拟内存(使用磁盘存储模拟真实内存)。为此,它根据固定大小页面添加了另一层地址转换。因此,程序的逻辑地址首先被视为段偏移,286样式,然后生成的中间地址被分解为内存页面选择器加页面偏移,每个内存页面具有固定大小。这个想法是固定大小的页面可以有效地交换到磁盘,然后回来。

逻辑地址=(段偏移,段选择器)
查找(soffset,sselector)→中间地址

中间地址=(页面偏移,页面选择器)
查找(poffset,pselector)→物理地址或“页面未映射”

为了使其有意义,您需要拥有比物理内存页更多的逻辑页。如果不是,那么每个逻辑页面都可以映射到它自己的物理页面,并且交换没有任何好处。这意味着提交逻辑页面,即将它们映射到物理页面,可能会失败。

这些是由VirtualAlloc和家人处理的网页。您可以分配逻辑页面。然后通过提交将它们映射到物理页面,由于整个方案的目的,这些页面必须在单个页面上完成:页面是硬件级别的内存管理单元。 / p>