看懂任务管理器的内存页

随笔8个月前发布 張國榮
77 0 0

作者:木头龙
链接:https://www.zhihu.com/question/43714216/answer/1164044678
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

先说一下计算机里面,现代操作系统是如何管理内存的。物理内存按照字节编号,这个编号称之为地址(Address),地址从0开始顺序排序。但除了操作系统内核本身(驱动程序算内核的一部分),其它程序不能直接使用物理地址访问数据。操作系统会向应用程序提供一个连续的虚拟的内存地址空间,当应用程序要访问某个内存数据时,通过这个虚拟的地址访问,而操作系统内核会把这个虚拟地址转换为物理地址后,再进行相应操作。这种内存管理模式叫保护模式,与之相对的则是早期的简单操作系统如DOS,应用程序直接使用物理地址直接访问内存数据,称之为实模式。实模式的问题在于,如果一个程序发生错误,使用了一个错误的地址并改写了其中的数据,如果这个地址是其它程序使用的,会导致其它程序出错;更严重的情况是这个地址是操作系统使用的,可能会导致系统崩溃。甚至某些恶意程序如病毒木马,可以访问并窃取其它程序的机密数据。

在保护模式中,操作系统向应用程序提供的虚拟地址是连续的,但对应的物理地址则通常是随机并且错乱的。例如一个虚拟内存地址空间大小为M,内核保留地址空间大小为N的操作系统,使用容量为X的物理内存,运行一个应用程序A:

看懂任务管理器的内存页

图中“应用程序”用“进程”(Process)来表示。因为一个应用程序运行的时候可能会有多个进程,每个进程都有自己独立的虚拟地址空间。

补充一下,上图中的M和N对于特定的操作系统是固定的,例如32位Windows,M就是2³²=4GiB,N是M的一半,2GiB(通过特殊的启动参数可以改成3GiB);64位Windows,M是2⁶⁴=16EiB,N是8TiB[1]

接着运行另外一个进程B,而且进程A没有退出释放内存,可能会变成这样:

看懂任务管理器的内存页

可以看到,两个进程都有自己独立的虚拟地址空间,相同地址的虚拟内存会被映射到不同的物理内存上,互不干扰。但内核使用的物理内存只有一份,映射到不同进程的虚拟地址空间中,因此称之为“池”(Pool)。

我们需要一张映射表来记录虚拟内存和物理内存的映射关系(图中的箭头)。如果每个内存地址都需要一条记录,显然很浪费也没必要,现代PC中以页(Page)为单位,每一页大小为4096字节(4KiB),映射表只记录虚拟内存页到物理内存页的映射关系。

PS:并非所有内存都可以分页的,操作系统内核以及驱动程序使用的一部分内存是不能分页的(例如处理分页错误有关的代码),这部分内存的大小就是任务管理器中的“非分页池”(Non-Paged Pool,不清楚中文版的Windows为什么加入“缓冲”叫“非页面缓冲池”)。一般非分页池只会使用几百兆左右。

内核使用的其它内存,则可以分页使用,就是“分页池”(Paged Pool,中文版Windows界面显示的“页面缓冲池”)[2]

有了映射表,就可以实现一种特殊的内存使用方式——页面交换。假设继续运行另外一个进程C,进程C需要使用大量的内存。这个时候就会变成这样:

看懂任务管理器的内存页

操作系统会在硬盘上使用一个(也可以是多个)特殊的文件,称之为交换文件(Swap file)或者分页文件(Paging file),把暂时不用的物理内存页面中的数据写入这个文件,释放出来的页面用来存放进程C的数据。上图中,原来进程A使用的页面6被交换到地址X,进程B使用的页面5被交换到地址X+1。页面5、6用于存放进程C的数据,页面X、X+1为交换文件中的页面。

很显然,对于操作系统来说,可用的内存页面数量为X+1,总容量超过了物理内存大小。也就是说,操作系统可以使用比物理内存容量更大的内存。代价则是当进程A需要访问虚拟地址空间中第3个页面中的数据时,需要从硬盘上的交换文件,把页面X交换回物理内存中,这个时间比直接访问内存慢很多。例如今天的主流电脑访问内存延迟一般是50~100纳秒,访问固态硬盘的延迟一般是数十微秒——慢几百上千倍,访问机械硬盘的延迟一般是十多毫秒——慢几十万倍。

在第三张图中,可用内存容量为X+2,其中X为物理内存容量,2为交换文件大小。任务管理器中“已提交内存”后面的数字就是可用内存容量。顺道说一下,上面提到的“非页面缓冲池”所使用的内存,因为不能分页,所以是不可以交换到页面文件上的;而“页面缓冲池”使用的内存,可以和普通程序的页面一样,在长时间不使用时交换到页面文件上,腾出内存空间给其它应用使用。

当进程向操作系统请求使用一段内存的时候(例如C语言中调用malloc函数),就Windows来说,是调用VitualAlloc API。根据调用的参数不同,会有两种处理方式[3]

  • MEM_RESERVE,请求并保留。Windows会在虚拟内存中按照请求的内存地址和数量分配页面,并保证这部分虚拟内存在将来可用,不会被其它操作占用。但对物理内存、交换文件都没有任何影响。
  • MEM_COMMIT,请求并提交。Windows会根据请求的内存数量相应增大已提交内存数量,保证将来进程使用这部分内存的时候有足够的内存资源分配给进程。但直到进程真正访问对应的内存时,操作系统才会在物理内存中给应用程序分配内存页面并且初始化。此外,请求的虚拟内存起始地址以及数量所描述的内存段必须先通过MEM_RESERVE方式的VirtualAlloc调用所保留,否则会报错。

可以把两个参数进行OR运算后传入,一步调用就保留并提交。

任务管理器中“已提交”的两个数字,前面的数字,就是所有进程请求并提交的内存数量总和。这个值不能超过可提交内存上限,也就是后面的数字。可提交内存上限是物理内存容量+交换文件大小-内核保留内存。

如果进程试图向未提交的虚拟内存地址写入数据,会引发异常,例如这样:

看懂任务管理器的内存页

如果进程申请提交的内存数量加上已提交的内存数量超过了可提交内存上限,则会引发虚拟内存不足异常。不过Windows默认由操作系统管理分页文件(这里用Windows界面上的用词)大小,如果可提交内存上限不足,Windows会自动扩大分页文件大小,增大可提交内存上限。所以除非硬盘也满了,否则很少会出现这个错误,但如果有的朋友自己设置了分页文件的最大值而且设置的值不够大,则有可能碰到这个错误。此外,扩大分页文件是磁盘操作,相对内存操作来说速度很慢,有可能因为超时导致应用程序出错。其它操作系统的交换文件大小一般是固定的(例如Linux),需要手动调整。

前面说了使用分页文件很慢,那是否物理内存足够大我们就可以禁用分页文件呢?也不是。因为很多程序会提交一大段内存,但仅仅使用了其中一部分。像虚拟机、Ramdisk这类软件,根据设置可能动则申请数GB内存,但实际只用了其中几百兆。假设我们运行一台内存设置为8GB的虚拟机,但实际上虚拟机只使用了500MB内存,如果禁用了交换文件,那么操作系统就必须在物理内存中保留8GB空间给虚拟机进程;如果分页文件设置为2GB,操作系统就必须在物理内存中保留最少6GB空间;如果分页文件设置为8GB,那么只有虚拟机进程使用的500MB是真正占用了物理内存的。剩余的7.5GB只使用了页面文件所提供的可提交内存容量。

下图中,已提交比已使用内存要大6.8G,就是这部分被占用但未使用的内存(运行着一台2GB内存的虚拟机,一个2GB大小的Ramdisk)。

看懂任务管理器的内存页

如果要自行设置分页文件大小的话,建议先正常使用一段时间,在任务管理器中观察“已使用”和“已提交”的差值,多观察几次后,把分页文件的最小值设置为比这个差值常见大小稍大一点,这样可以最有效的利用内存和硬盘容量——毕竟现在的固态硬盘也不便宜,分页文件的最小值设置太大也是一种浪费;反过来,内存价格更贵,页面文件设置太小导致内存容量白白被占着不用更浪费


参考

  1. ^Pushing the Limits of Windows: Virtual Memory https://techcommunity.microsoft.com/t5/windows-blog-archive/pushing-the-limits-of-windows-virtual-memory/ba-p/723750
  2. ^Pushing the Limits of Windows: Paged and Nonpaged Pool https://techcommunity.microsoft.com/t5/windows-blog-archive/pushing-the-limits-of-windows-paged-and-nonpaged-pool/ba-p/723789
  3. ^VirtualAlloc function https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc?redirectedfrom=MSDN
© 版权声明

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...