Windows API 之 VirtualAlloc

Reserves, commits, or changes the state of a region of pages in the virtual address space of the calling process. Memory allocated by this function is automatically initialized to zero.

LPVOID WINAPI VirtualAlloc(
  _In_opt_ LPVOID lpAddress,
  _In_     SIZE_T dwSize,
  _In_     DWORD  flAllocationType,
  _In_     DWORD  flProtect
);
  • 虚拟内存技术原理:

Windows的内存结构是深入理解Windows操作系统如何运作的最关键之所在,通过对内存结构的认识可清楚地了解诸如进程间数据的共享、对内存进行有效的管理等问题,从而能够在程序设计时使程序以更加有效的方 式运行。Windows操作系统对内存的管理可采取多种不同的方式,其中虚拟内存的管理方式可用来管理大型的对象和结构数组。

在Windows系统中,任何一个进程都被赋予其自己的虚拟地址空间,该虚拟地址空间覆盖了一个相当大的范围,对于32位进程,其地址空间为 2^32=4,294,967,296 Byte,这使得一个指针可以使用从0x00000000到0xFFFFFFFF的4GB范围之内的任何一个值。虽然每一个32位进程可使用4GB的地址 空间,但并不意味着每一个进程实际拥有4GB的物理地址空间,该地址空间仅仅是一个虚拟地址空间,此虚拟地址空间只是内存地址的一个范围。进程实际可以得到的物理内存要远小于其虚拟地址空间。进程的虚拟地址空间是为每个进程所私有的,在进程内运行的线程对内存空间的访问都被限制在调用进程之内,而不能访问属于其他进程的内存空间。这样,在不同的进程中可以使用相同地址的指针来指向属于各自调用进程的内容而不会由此引起混乱。

在进程创建之初并被赋予地址空间时,其虚拟地址空间尚未分配,处于空闲状态。这时地址空间内的内存是不能使用的,必须首先通过VirtualAlloc()函数来分配其内的各个区域,对其进行保留

其参数lpAddress包含一个内存地址,用于定义待分配区域的首地址。通常可将此参数设置为NULL,由系统通过搜索地址空间来决定满足条件的未保留 地址空间。这时系统可从地址空间的任意位置处开始保留一个区域,而且还可以通过向参数flAllocationType设置MEM_TOP_DOWN标志 来指明在尽可能高的地址上分配内存。如果不希望由系统自动完成对内存区域的分配而为lpAddress设定了内存地址(必须确保其始终位于进程的用户模式分区中,否则将会导致分配的失败), 那么系统将在进行分配之前首先检查在该内存地址上是否存在足够大的未保留空间,如果存在一个足够大的空闲区域,那么系统将会保留此区域并返回此保留区域的虚拟地址,否则将导致分配的失败而返回NULL。这里需要特别指出的是,在指定lpAddress的内存地址时,必须确保是从一个分配粒度的边界处开始。

  • 内存的“保留”与“提交”:

Win32为系统中的每一个应用程序(进程)提供一个独立的、2GB的用户地址空间。对于应用程序来说,好象是有2GB的可用内存(实际上在Windows95/98,NT,Win2000   Advanced   Server/Enterprise   Server   上的内存分配略有不同),而不用考虑实际可用的物理内存的量。如果某个应用程序要求的内存比可用的内存更多时,Win32是这样满足这种要求的,它从这个和/或其他的进程把非关键内存分页(paging)到一个页文件,并且释放这些物理内存页

在任意给定的时间,进程中每个地址都可以被当作是自由的、保留的或已提交的进程开始时,所有地址的都是自由的,意味着它们都是自由空间并且可以被提交到 内存,或者为将来使用而保留起来,但是它们不能存取(read/write)。在任何自由的地址能够被使用前,它必须首先被分配为保留的或已提交的。  

当在一个进程中保留地址时,没有物理内存页被提交(没有给这个保留的地址空间分配实际的物理内存),并且,也许更为重要的是,在页文件中没有为备份该内存而保留空间。而且,保留一个地址范围将不会保证将来会有可用的物理内存来提交给这些地址。实际上,它只是保存了一个指定的自由地址,一直到需要使用它时,而阻止了其它分配对该段地址的请求如果没有 这种类型的保护,那么例程操作(routine   operations),例如加载一个DLL或者资源,可能会占有指定的地址,并且危害以后对它的使用。  要使用保留的地址,内存首先必须被提交给该地址。  

当内存被提交时,内存物理页被分配,并且该段空间被保留在在一个页文件中。也就是说,已提交的内存页总是以物理内存页或者在已经被分页的磁盘上的页文件的 形式存在。当提交一个大块内存时,在初始阶段,其部分或者全部内存没有驻留在物理内存中也是有可能的。某些内存页一开始驻留在页文件中,直到它被访问。在系统中,一旦内存页已提交,虚拟内存管理器象对待所有其它的内存页一样对待它们。   
  在Win32虚拟内存系统中,使用了页表(page   tables)来访问物理内存页。每个页表本身也是一个内存页,象已提交的页一样。偶而,当提交内存时,同时还必须对页表分配附加的页。所以,提交一页内 存的请求可能需要为页表分配一页,为请求的页分配一页,并且在页文件中需要两页空间来备份这些页中的每一页。因此,VirtualAlloc完成一个内存 提交请求所需要的时间变化很大,它取决于系统的状态以及请求的空间大小。

上边的描述有些抽象,参考下这篇文章

  •  《Windows任务管理器中的几个内存的概念》

进程的内存

对于系统中的每一个进程而言, 都有 4GB 的 "内存空间". 也就是每个进程都认为自己有 4GB 的内存可以使用.

系统将每个进程的 4GB 地址空间, 从逻辑上划分为两大部分:

  a) 蓝色的是用户空间, 此空间是被用户程序所使用的. 比如我在代码中写 "分配 100MB 内存", 其实占用的就是这一部分.

  b) 红色的是内核空间, 此空间是被用作操作系统执行必要的线程切换以及从用户态函数进入内核态执行功能所保留的内存地址. 应用程序无法操作此区域.

Intel x86 体系内存管理

Intel 规定, 一个在计算机内部, 可以使用 "分页机制" 对硬件内存进行 "虚拟化". 其核心技术如下图:

首先, 在程序中的一个地址 0x1234, 5678 被计算机的页部件(硬件)经过 1,2,3 步, 从线性地址(程序中的地址) 转变为真正机器上的物理地址(即实际内存的硬件地址). 每个线性地址都被分成 "页目录索引(PDE, 10-bit)", "页表索引(PTE, 10-bit)", "页内偏移(offset, 12-bit)" 三部分.

  1) 在页目录中根据 PDE 找到页表的位置, 即通过 0x48 找到 0xa000, 0000.(PDT的地址保存在CR3中)

  2) 根据页表中的 PTE 找到页地址, 即通过 0x345 找到 0x4000, 0000.

  3) 根据偏移, 在页中找到我们要的具体地址, 即已知页位于 0x4000, 0000, 我们需要存取其 0x678 偏移处的数据, 则我们所需要操作的真是物理地址就是 0x4000, 0678.

基于 x86 的 Windows 内存管理

  • 举例:

当我们调用

时,pBase返回0x00080000,我们观察地址范围0x00080000-0x0009b000(dwSize就是分配的Size,其时大小为0x1b000)的内存:

已经被VirtualAlloc初始化为0.

参考:

https://msdn.microsoft.com/en-us/library/windows/desktop/aa366887%28v=vs.85%29.aspx

http://www.yesky.com/67/1753067.shtml

http://super-man-woman.blog.163.com/blog/static/3789803820098532317144/

http://www.cnblogs.com/walfud/articles/3256233.html 

理解paging:http://www.mouseos.com/arch/paging.html

Pushing the limits of windows:Virtual Memory:http://blogs.technet.com/b/markrussinovich/archive/2008/11/17/3155406.aspx

原文地址:https://www.cnblogs.com/adylee/p/9935038.html