Windows基础知识
+ -

Windows内存类型介绍

2021-07-01 107 0

Windows上的内存有好几种说法,比如工作集(Working Set)、提交大小(Private Bytes)、虚拟大小(Virtual Size)。究竟这几种说法有什么区别?每种内存到底指的是什么?哪种内存才能真正反映程序使用的内存情况?虽然做Windows开发这么久,一直对这些只有模糊的认识。如果你去网上找寻答案,可能会找到一些错误的或者矛盾的说法,这更加让人感到迷惑。最近要处理Chromium内核一些因此内存不足而导致的崩溃问题,所以才深入去研究一下这个问题。

Windows上的程序都有自己专有的虚拟地址空间,比如32位的程序地址空间大小是4GB,这是因为32位指针可以表示从0x00000000到0xFFFFFFFF之间的任一值。每个进程的地址空间是专有的,当前进程只能访问到该进程的内存,看不到其他进程的内存。不同进程虽然访问同一个内存地址,实际上它们得到的数据是不同的。

虚拟地址空间不是真正的内存,不能够直接读写。当程序需要读写的时候,操作系统会在背后把物理存储器分配或者映射到相应的地址空间,否则将会导致访问违例(access violation)。

我们可以通过VirtualAlloc API分配一块地址空间的区域,这个操作被称之为预订(reserving)。当程序预订地址空间区域时,操作系统会确保区域的起始地址正好是分配粒度的整数倍。分配粒度会根据不同的CPU平台而有所不同。目前Windows分配粒度是64KB大小。页面是一个内存单元,系统通过它来管理内存。与分配粒度类似,页面大小会根据不同CPU而有所不同,x86和x64页面大小是4KB,而IA-64系统页面大小是8KB。

为了使用预订的地址空间区域,我们还必须分配物理存储器,并将存储器映射到所预订的的区域,这个过程被称之为调拨(committing)物理存储器。物理存储器始终都以页面为单位来调拨。

当我们调拨物理存储器给区域时,并不需要给整个区域都调拨物理存储器。当程序不再需要访问所预订的区域中已调拨的物理存储器时,应该释放物理存储器。这个过程被称之为撤销调拨(decommitting)物理存储器。

当今操作系统能让磁盘空间看起来像内存一样。磁盘上的文件被称之为页交换文件(paging file)。页交换文件以一种透明的方式增大了程序可用的内存(或存储器)的总量。如果一台电脑装备了1GB的内存条,硬盘上还有1GB的页交换文件,那么应用程序会认为可用的内存总量为2GB。

我写了一个inspect_memory的程序来验证各种分配内存方式对不同内存指标的影响:

通过new分配内存10MB,工作集(Working Set)增加10MB,提交大小(Private Bytes)增加10MB,虚拟大小(Virtual Size)增加10MB。
通过VirtualAlloc预订(reserving)内存10MB,工作集(Working Set)大小不变,提交大小(Private Bytes)大小不变,虚拟大小(Virtual Size)增加10MB。
通过VirtualAlloc预订(reserving)并调拨(committing)内存10MB,工作集(Working Set)增加10MB,提交大小(Private Bytes)增加10MB,虚拟大小(Virtual Size)增加10MB。
运行多个inspect_memory实例。在每个实例上通过new分配了很多内存,比如工作集(Working Set)、(Private Bytes)、虚拟大小(Virtual Size)都接近2GB停止。任务管理性能tab上显示的可用的物理内存很少时,发现先前实例的工作集(Working Set)在慢慢减少,而(Private Bytes)、虚拟大小(Virtual Size)保持不变。
通过上述实验,参考https://stackoverflow.com/questions/1984186/what-is-private-bytes-virtual-bytes-working-set可以得出结论:

提交大小(Private Bytes)是进程分配的内存大小。这些内存可能存在于RAM中,也可以存在于页交换文件(paging file)中。
工作集(Working Set)是进程存在于RAM中内存的大小,即不引起page fault异常就能够访问的内存。它通常包括一些可与其他进程共享的内存,比如内存映射文件。
虚拟大小(Virtual Size)是进程地址空间预订的区域大小。
操作系统会自动调度程序工作集(Working Set)内存的使用。
因此我觉得用提交大小(Private Bytes)来衡量程序使用内存的情况比较恰当,虽然提交大小(Private Bytes)不一定真正的占用RAM内存,但是提交大小(Private Bytes)一直增加,那么程序很可能存在内存使用不当的情况。
除了操作系统去调度程序工作集(Working Set)内存的大小,我们也可以通过SetProcessWorkingSetSize,、SetProcessWorkingSetSizeEx、EmptyWorkingSet等API去手动设置工作集(Working Set)的大小。360安全卫士的内存优化功能大概就是通过这些API去实现的。使用这些API减少工作集Working Set)的大小有点像掩耳盗铃,它是把进程存在于RAM中的内存交换到页交换文件(paging file)里,表面上是减少了物理内存的使用,实际上程序真正需要访问这些数据的时候,又需要操作系统把页交换文件(paging file)里数据交换到RAM内存中,增加了不必要的调度,反而影响了程序的运行性能,我们未必有操作系统的工作集(Working Set)调度做的好。
这些内存数据我们可以通过系统提供的API获得:

GetProcessMemoryInfo获得PROCESS_MEMORY_COUNTERS_EX数据,WorkingSetSize字段表示进程的工作集(Working Set)大小。
GetProcessMemoryInfo获得PROCESS_MEMORY_COUNTERS_EX数据,PrivateUsage字段表示进程的提交大小(Private Bytes)大小。
GlobalMemoryStatusEx获得MEMORYSTATUSEX数据,ullTotalVirtual减去ullAvailVirtual就是虚拟大小(Virtual Size)大小。
更多的数据信息可以查阅相关的MSDN文档。

参考:

https://bbs.pediy.com/thread-156036.htm
https://stackoverflow.com/questions/1984186/what-is-private-bytes-virtual-bytes-working-set
https://docs.microsoft.com/zh-cn/windows/desktop/Memory/working-set

0 篇笔记 写笔记

memcpy,memmove重叠拷贝
内存的复制一般使用memmove和memcopy这两种函数,当内存不重叠时,这两个函数可以说是没有区别的。但当出现内存重叠时,就会出现不同的结果。内存的复制函数一般定义如下:void *fun(void *dest, const void *src, size_t count)函数将从原一地址复......
Windows应用与内核内存共享
我们知道,进程之间如果要进行数据,是可以通过共享内存来实现的。其原理就是将同一片物理内存,分别进行不同的线性映射,这样就可以得到2个线性地址,而这两个线地址址对应的空间就是物理内存的空间。这样,如果我们在各自的进程分别读写内存,另一个进程中对应的线性地址空间的数据同时也会变化。原理如下图:相同的......
Windbg 内存属性(!address)
!address!address 扩展显示目标进程或目标机使用的内存信息在调试时, 对象和栈都包含了大量的指针, 我们无法很快地猜测出他们所表示的数据. 虽然我们可以很容易地将内核空间的地址与用户态空间的地址分开, 但要把一个表示栈的地址和一个表示堆的地址区分开却不容易. 我们可以使用一个很有用的......
Windows内核内存申请和释放
内存泄漏是C语言中一个臭名昭著的问题。但是作为内核开发者,读者将有必要自己来面对它。在传统的C语言中,分配内存常常使用的函数是malloc。这个函数的使用非常简单,传入长度参数就得到内存空间。在驱动中使用内存分配,这个函数不再有效。驱动中分配内存,最常用的是调用ExAllocatePoolWithT......
Windows内存类型介绍
Windows上的内存有好几种说法,比如工作集(Working Set)、提交大小(Private Bytes)、虚拟大小(Virtual Size)。究竟这几种说法有什么区别?每种内存到底指的是什么?哪种内存才能真正反映程序使用的内存情况?虽然做Windows开发这么久,一直对这些只有模糊的认识。......
内核中读取指定进程的内存空间
Windows驱动运行于内核中,应用层的所有进程共享。在应用层中,windows提供了如通过系统调用进入到内核中。在应用层不能直接引用或使用内核的地址。但在内核中,是可以使用应用层的地址的。但是由于进程的调度,当我们在内核中操作应用层的地址时,可能随时发生进程切换,这时我们使用的内存地址可能因为进程......
获取或修改指定进程ID的内存数据
在进程ID已知的前提下,如何获取该进程在内存中的数据,下面提供一种方法。void GetProcessMemory(ULONG PID,,PVOID PVOID pRaddAddr){ PROCESS_INFORMATION pi; ZeroMemory(&pi, si......
内存数据转存成文件SaveBuffToFile
需要引入头文件这样通过多字节和UNICODE支持多语言编码编程。BOOL SaveBuffToFile(PCHAR pFileName, PVOID pBuff, ULONG len){ FILE* fp = NULL; fopen_s(&f......
文件数据读取内存GetBufferFromFile
使用std::string存储从文件中读取的二进制数。BOOL GetBufferFromFile(TCHAR* pFileName, std::string& strFileStream){ HANDLE hFile = INVALID_HANDLE_VALUE; B......
DevCon Resources命令
DevCon Resources列出设备占用的系统资源。这些资源可能是可分配的和可寻址的总线路径,如 DMA 通道、I/O端口、IRQ 和内存地址。 在本地和远程计算机上有效。在指定的远程计算机上运行命令。 必须使用反斜杠。注意 若要在远程计算机上运行 DevCon 命令,组策略设置必须允许即插......
COM库的内存管理
由于COM组件程序和客户程序是通过二进制级标准建立连接的,所以在COM应用程序中凡涉及客户、COM库和组件三者之间内存交互(分配和释放不在同一个模块中)的操作必须使用一致的内存管理器。COM提供的内存管理标准,实际上是一个IMalloc接口:IID_IMalloc ={00000002-0000-......
COM 内存分配和释放
于动态内存的申请和释放,一定要遵守“谁申请,谁释放”的原则。为解决这个问题,COM函数内部根据实际需要动态申请内存,而调用者负责释放。虽违背了上述原则,但COM从方便性和效率出发,确实是这么设计的。C语言C++语言Win32平台COMIMalloc接口BSTR申请m......
Windows内存检查分类查看工具poolmon
挺好用的一个工具poolmon.exe可以查看Windows系统内存的所有信息。内存泄露,内存检查,内使例用实时显示工具。......
在驱动程序和应用程序间共享内存
译自:The NT Insider November-December 2007 Volume 14 Issue 4译者:lioqio[编辑提示:《The NT Insider》将陆续对以前文章进行维护修正,以便确认这些文章针对Windows的大多数当前版本是正确的,并且反映了最好的工程实践。这篇文......
Windows应用层创建共享内存,内核层使用ZwOpenSection打开
应用层创建共享内存,并使用RefreshBuffer通知内核层刷新数据:typedef struct __SHAREDBUFFER_HEADER { DWORD dwFrameType; /* compression hex (Data1 part......
作者信息
我爱内核
Windows驱动开发,网站开发
好好学习,天天向上。
取消
感谢您的支持,我会继续努力的!
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

您的支持,是我们前进的动力!