windows虚拟内存机制
在 Windows 系统中,每个进程都拥有自己独立的虚拟地址空间(virtual address space)。虚拟内存机制使得应用程序认为它拥有连续可用的内存(一个连续完整的地址空间),而实际上,它通常被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上。
以下是 Windows 内存分配过程的三个要点:
- 保留一段虚拟内存地址空间:使用带
mem_reserve参数的VirtualAlloc函数从进程的 4GB 空间中保留一段地址空间。起始地址必须是系统分配粒度的整数倍(通常为 64KB),大小必须是系统页面大小的整数倍(通常为 4KB)。 - 提交一段虚拟内存地址空间:通过带
mem_commit参数的VirtualAlloc函数,将进程已保留的一段地址空间映射到机器的虚拟内存上。起始地址和大小同样都必须是页面大小的整数倍(4KB)。 - 将虚拟内存地址空间映射到物理内存页(RAM):当访问进程提交的页面时,如果该页面不在物理内存页中,将产生一个缺页中断(又名页缺失、页面错误,page fault),系统会通过该机制真正分配物理内存页,同时修改对应页面的地址空间映射关系。
进程地址空间分布(以常见的 2GB 为例):Windows 系统在进程空间中专门划出一块 0x70000000–0x80000000(共 256MB)区域,用于映射常用的系统 DLL(如 kernel32.dll、ntdll.dll 等)。并且,系统会对系统 DLL 的默认基地址进行调整,防止加载时冲突,触发重定基地址(rebasing)。需要注意的是,基地址必须对齐到分配粒度(64KB)。
另外,在生成 EXE 和 DLL 模块时,可以使用
/dynamicbase链接参数启用动态基地址(Address Space Layout Randomization,ASLR),它可以实现地址空间布局随机化,防范恶意程序对已知地址进行攻击。Windows 虚拟内存机制涉及的一些内存指标概念如下:
- 虚拟内存:
private bytes:进程已提交(committed)的虚拟内存字节数,对应vmmap的private、Win7 任务管理器中的【提交大小】、资源管理器中的【提交】。peak private bytes:进程已提交的虚拟内存的最高峰字节数。virtual size:进程保留(reserved)的虚拟地址空间字节数。page faults:发生过的缺页中断次数,对应 Win7 任务管理器中的【页面错误】。
- 物理内存:
working set = ws private + ws shareable:进程占用物理内存的总字节数,对应 Win7 任务管理器中的【工作设置 (内存)】、资源管理器中的【工作集】。ws private:进程独享的物理内存字节数,例如堆内存、栈内存、通过写时复制(copy-on-write,COW)机制创建的内存等,对应 Win7 任务管理器中的【内存 (专用工作集)】、资源管理器中的【专用】。ws shareable:进程可与其他进程共享的物理内存字节数,例如 EXE 及 DLL 代码段、数据段等,对应 Win7 资源管理器中的【可共享】。ws shared:进程已与其他进程共享的物理内存字节数,且ws shared <= ws shareable。若只启动一个 EXE 实例,那么 EXE 的代码段、数据段等不会被共享,因而就不统计在ws shared中。peak working set:物理内存的最高峰字节数,对应 Win7 任务管理器中的【峰值工作设置 (内存)】。
无论是虚拟内存还是物理内存下的各个指标,通常都是通过统计用户态的那部分占用情况得出的。
为了扩大地址空间、对特定的内存地址提供写保护和公平分配内存等,Windows 系统采用了虚拟内存技术。但该技术也存在一些局限性,例如可能会浪费内存、增加指令的执行时间等。
页交换文件(pagefile)一般被用作可写物理内存页的后备存储器,在 Windows 系统中该文件名为 pagefile.sys,位于各盘的根目录中。当物理内存不够时,系统会进行页出(pageout)操作,将一些不经常使用且有后备的物理内存页释放,并根据情况将虚拟地址映射关系指向后备:
- 以页交换文件(如堆、栈等)为后备:在页交换文件中分配空间,并拷贝内容到其中后再释放。
- 以内存映射文件(如 EXE、DLL 等)为后备:直接释放。
而当系统读取某个虚拟内存地址,而该地址所在的页不在物理内存页中时,将产生一个缺页中断,触发页入(pagein)操作,告诉系统从页交换文件或者内存映射文件中取回包含该地址的虚拟内存页(即将内容拷回到物理内存页,并建立新的虚拟地址映射到物理内存页上,然后释放页交换文件中对应部分的空间)。
此外,系统在映射 EXE 或 DLL 文件时会把数据页指定为
page_writecopy属性,把代码页指定为page_execute_writecopy属性,利用写时复制机制节省物理内存和页交换文件的占用。当进程对具有writecopy属性的内存页面执行修改操作时,系统会找一个闲置的物理内存页,并拷贝所有内容到新页上,然后标记新页的后备存储器为页交换文件,最后将进程的虚拟内存页指向新的物理内存页。经过上述步骤,进程就可以使用新的页面进行操作。在 32 位 Windows 系统中,程序最多能使用 2GB 空间(0x00010000-0x7ffeffff)。若要获得 3GB 的地址空间,可以参考以下方法:
- 操作系统方面:
- 32 位 Windows XP:无具体方法。
- 32 位 Win7:管理员权限执行命令
bcdedit /set increaseuserva 3072来开启。 - 64 位 Win7:对 32 位程序默认开启 3GB,无需额外设置。
- 应用程序方面:无论是 32 位还是 64 位 Windows,若要让 32 位程序能使用 3GB 内存,必须在链接时加上参数
/largeaddressaware。
