《Windows内存管理(一):Windows性能监视器(PerfMon)》
Windows内存管理是一个复杂的主题,涉及多个层次和组件。以下是一个分层的概述。
Windows使用虚拟内存来给每个进程提供一个看似连续的内存空间,实际上这些空间可能分散在物理内存和磁盘上。
每个Windows进程
都有自己的虚拟地址空间,32位
系统通常是4GB
,64位
系统则远大于此。
32位
还是64位
系统上,绝不是拍拍脑袋随便决定的。当虚拟内存空间大小成为项目瓶颈时,很显然选择64位
的操作系统,我们的项目也对应使用64位
编译工具链进行编译.每个进程都有自己的用户空间需要管理,当我们使用VirtualAlloc
等函数申请一块固定的地址空间时,首先需要确认这块空间是否被占用,如果该空间已被占用则申请失败 用户空间并非像内核空间一样通过一块链表去管理已占用的线性地址空间(效率低), 而是通过搜索二叉树 申请内存的两种方式:
通过VirtualAlloc/VirtualAllocEx
申请:Private Memory(独享物理页)
通过CreateFileMapping
进行映射:Mapped Memory(共享物理页)
当物理内存不足时,Windows
会使用页面文件(pagefile.sys
)作为虚拟内存的扩展。
VituaAlloc
LPVOID VituaAlloc{
LPVOID IpAddress, //要分配的内存区域的地址
DWORD dwSize, //分配的大小
DWORD fiAllocationType, //分配的类型
DWORD fiProtect //该内存的初始保护属性
};
#include <windows.h>
int main() {
// 分配虚拟内存
LPVOID lpvBase = VirtualAlloc(
NULL, // 系统选择地址
1024 * 1024, // 分配1MB空间
MEM_COMMIT | MEM_RESERVE, // 分配并提交
PAGE_READWRITE); // 可读写保护
if (lpvBase == NULL) {
// 处理错误
}
// 使用内存...
// 释放内存
if (!VirtualFree(lpvBase, 0, MEM_RELEASE)) {
// 处理错误
}
return 0;
}
#define MapFileName "共享内存"
//内核对象:1、物理页 2、文件
HANDLE g_hMapFile = CreateFileMapping(INVALID_HANDLE_VALUE,NULL,PAGE_READWRITE,0,BUFSIZ,MapFileName);
//将物理页与线性地址进行映射
LPTSTR g_IpBuff=(LPTSTR)MapViewOfFile(g_hMapFile,FILE_MAP_ALL_ACCESS,0,0,BUFSIZ);
*(PDWORD)g_IpBuff = 0x12345678;
printf("%p",g_IpBuff);
HANDLE g_hFile = CreateFile("C:\\NOTEPAD.EXE",GENERIC_READIGENERIC_WRITE,FILE_SHARE_READ,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
?
HANDLE g_hMapFile = CreateFileMapping(g_hFile,NULL,PAGE_READWRITE,O,BUFSIZ,NULL);
LPTSTR g_IpBuff=(LPTSTR)MapViewOfFile(g_hMapFile,FILE_MAP_ALL_ACCESS,0,0,BUFSIZ);
*(PDWORD)g_IpBuff = 0x12345678;
HMODULE hModule = ::LoadLibrary("C:INOTEPAD.EXE");
1、LoadLibrary就是通过内存映射的方式实现的
2、为了避免影响到别人,属性为:写拷贝
Windows通过内存管理器来管理物理内存,它负责页面的分配和回收。
工作集是指一个进程当前在物理内存中的页面集合。
#include <windows.h>
int main() {
// 分配内存
LPVOID lpvBase = VirtualAlloc(NULL, 1024 * 1024, MEM_COMMIT, PAGE_READWRITE);
// 锁定内存页
if (!VirtualLock(lpvBase, 1024 * 1024)) {
// 处理错误
}
// 使用内存...
// 解锁内存页
VirtualUnlock(lpvBase, 1024 * 1024);
// 释放内存
VirtualFree(lpvBase, 0, MEM_RELEASE);
return 0;
}
Windows使用分页系统来管理内存,非活跃的页面可以被移动到磁盘上的页面文件。
在Windows内核中,内存管理是通过两种主要类型的内存池来实现的:非分页池(Non-Paged Pool)和分页池(Paged Pool)。这些池是操作系统内核用来分配内存给内核模式下的驱动程序和内核系统的。
非分页池(Non-Paged Pool)
分页池(Paged Pool)
#include <ntddk.h>
void DriverEntry() {
// 分配非分页池内存
PVOID pNonPagedMemory = ExAllocatePoolWithTag(NonPagedPool, 1024, 'Tag1');
// 分配分页池内存
PVOID pPagedMemory = ExAllocatePoolWithTag(PagedPool, 1024, 'Tag2');
// 使用内存...
// 释放内存
ExFreePool(pNonPagedMemory);
ExFreePool(pPagedMemory);
}
在这个示例中,ExAllocatePoolWithTag函数用于分配内存,并且每个分配都有一个标签,这有助于调试和跟踪内存使用。
注意事项
理解和正确使用这两种内存池对于开发稳定的内核模式代码至关重要。
Windows
内存管理是一个多层次的架构,它涵盖了从用户空间的虚拟内存分配到内核空间的物理内存和内存池管理。理解这些层次和它们如何交互对于开发高效、稳定的Windows应用程序和驱动程序至关重要。
开发者可以通过各种API与这些层次交互,例如使用 VirtualAlloc
和 VirtualFree
在用户空间分配和释放内存,或者在内核模式下使用 ExAllocatePool
和 ExFreePool
。正确地管理内存不仅可以提高应用程序的性能,还可以避免内存泄漏和其他内存相关的错误。
此外,开发者还需要考虑到内存的访问模式,比如内存页的锁定(VirtualLock
)和解锁(VirtualUnlock
),以及对非分页池和分页池的使用,这些都会影响程序的性能和稳定性。
最后,高级内存管理还可能涉及到对页面文件的管理、理解工作集的概念、处理页面错误以及优化内存使用和访问模式。通过工具如Windows性能监视器(Performance Monitor
)和资源监视器(Resource Monitor
),开发者可以监控应用程序的内存使用情况,从而做出相应的优化。
那么,我们在这里拓展性的提几个问题:
32位
的.exe
应用程序可以在64位
的操作系统上运行,为何反过来却不行呢?答: 在Windows上,32位应用程序可以在64位操作系统上运行,这是因为64位Windows操作系统提供了一种名为WOW64(Windows 32-bit on Windows 64-bit)的兼容层。WOW64是一个兼容性环境,它模拟了32位Windows操作系统,允许32位应用程序在64位系统上运行,而无需任何修改。
WOW64实现了以下功能:
相反,64位程序不能在32位操作系统上运行,原因包括:
因此,由于硬件和软件架构的根本差异,64位应用程序无法在32位操作系统上直接运行。
在Windows
操作系统中,可以通过几种方法来设置进程可以分配的最大虚拟内存大小:
方法1. 系统属性设置
可以通过修改系统属性来设置所有进程可用的虚拟内存总量,即页面文件的大小。这不是针对单个进程,而是针对整个系统。
方法2. 通过程序设置
如果你正在开发软件,可以在程序中使用Windows API来限制特定进程的最大虚拟内存大小。例如,可以使用SetProcessWorkingSetSize
函数来设置进程的工作集大小,这影响了进程的物理内存使用量,间接影响虚拟内存的使用。
#include <windows.h>
BOOL SetMaxMemoryUsage(HANDLE hProcess, SIZE_T dwMinimumWorkingSetSize, SIZE_T dwMaximumWorkingSetSize) {
return SetProcessWorkingSetSize(hProcess, dwMinimumWorkingSetSize, dwMaximumWorkingSetSize);
}
在这个例子中,hProcess
是进程的句柄,dwMinimumWorkingSetSize
和dwMaximumWorkingSetSize
是你希望设置的工作集的最小和最大大小。
方法3:通过Job对象
Windows提供了Job对象,可以用来管理多个进程的资源使用。可以创建一个Job对象,将一个或多个进程与之关联,并设置资源限制。
#include <windows.h>
int main() {
// 创建Job对象
HANDLE hJob = CreateJobObject(NULL, NULL);
// 将进程关联到Job对象
AssignProcessToJobObject(hJob, GetCurrentProcess());
// 设置Job对象的内存限制
JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = {0};
jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_PROCESS_MEMORY;
jeli.ProcessMemoryLimit = sizeInBytes; // 设置进程内存限制
SetInformationJobObject(hJob, JobObjectExtendedLimitInformation, &jeli, sizeof(jeli));
// ... 运行进程 ...
// 关闭Job对象句柄
CloseHandle(hJob);
return 0;
}
在这个例子中,sizeInBytes是你希望限制的内存大小。
注意
在进行任何更改之前,请确保你了解这些设置的含义以及它们对系统的潜在影响。
new
或者 malloc
方法为对象分配的内存,都指的是虚拟内存么?new
或 malloc
分配内存时,你实际上是在分配虚拟内存。在Windows操作系统中,每个进程都有自己的虚拟地址空间,这个地址空间是由操作系统管理的,它将虚拟地址映射到物理内存上。new
或 malloc
请求内存时,操作系统会在进程的虚拟地址空间中找到一块足够大的未使用区域,并将其标记为已使用。然后,当你首次访问这块内存时,操作系统会将虚拟地址映射到物理内存上。这个过程通常被称为按需分页(demand paging)。其他补充:
Windows分页系统是操作系统内存管理的核心组成部分,它允许物理内存(RAM)的有效使用,并为每个进程提供一个大的、连续的虚拟地址空间。以下是Windows分页系统的详细解释:
虚拟内存和物理内存
Windows分页系统的设计允许多个进程共享有限的物理内存资源,同时提供了足够的虚拟内存空间,以便运行大型应用程序。通过需求分页、页面置换算法和内存压力管理,Windows能够在物理内存受限的情况下维持系统的稳定性和性能。
参考文献
1、https://zhuanlan.zhihu.com/p/670251533
2、https://zhuanlan.zhihu.com/p/34753439
3、https://learn.microsoft.com/en-us/sysinternals/downloads/vmmap