什么导致页面错误?

41

根据维基百科:

当程序访问一个在虚拟地址空间中映射但未加载到物理内存中的页面时,硬件引发软件陷阱,这就是页错误。(我强调)

好的,这很有道理。

但如果是这样的话,为什么每当Process Hacker中的进程信息刷新时,我都会看到大约15个页错误?

Screenshot

换句话说,为什么会有任何内存被分页出去呢?(我不知道是用户内存还是内核内存。)我没有页面文件,而且在干净重启后,RAM使用量约为4GB中的1.2GB。没有任何资源短缺,为什么会有东西被分页出去呢?

8个回答

64

(我是Process Hacker的作者。)

首先:

页面错误是由硬件引发的陷阱,当程序访问映射在虚拟地址空间中但未加载到物理内存中的页面时。

那并不完全正确,正如同一篇文章中稍后解释的那样(次要页面故障)。存在软页面错误,此时内核所需做的仅是将页面添加到进程的工作集中。以下是Windows Internals书中的一张表格(我排除了导致访问冲突的部分):

Page Fault原因 结果
访问未在内存中但在磁盘中的页面文件或映射文件上的页面 分配物理页面,并将所需页面从磁盘读入相关的工作集中
访问待机列表或修改列表上的页面 将页面转换至相关进程、会话或系统工作集
访问需求为零的页面 将一个填充为零的页面添加到相关的工作集
写入一个写时复制页面 生成进程专用(或会话专用)页面副本,并将原始页面替换为进程或系统的工作集中

如上所示,页面错误可能出现各种不同的原因。只有其中之一与从磁盘读取相关。如果尝试从堆中分配块并且堆管理器分配新页面,然后访问这些页面,则会发生需求为零的页面错误。如果尝试通过写入kernel32的页面来挂钩kernel32中的函数,则会发生写时复制错误,因为这些页面正在被默默地复制,以使更改不影响其他进程。

现在更具体地回答你的问题:当更新其服务信息时,Process Hacker似乎只会出现页面故障 - 也就是说,当它调用EnumServicesStatusEx时,它会远程过程调用到SCM(services.exe)。我猜测,在这个过程中,大量的内存被分配,导致需要零页故障(如果我没记错,服务信息需要几个页面来存储)。


1
啊,看来父亲最懂,哈哈。 :) (是的,我知道你是作者,我也是建议转移到MSVCRT的那个人。:P)嗯...所以这是由RPC引起的,是吗?有趣...所以如果你去掉这个调用,你就不会得到那些页面错误了? - user541686
如果你移除了那个调用,显然仍然会出现故障... 有什么想法吗? - user541686
@Mehrdad:我的观点是,页面错误发生有很多原因,如果你正在编写程序,它们并不是真正需要担心的事情。 - wj32
好的,我只是想知道这种情况的确切原因是什么,RPC 的事情肯定很有启发性,谢谢。 - user541686
1
在操作系统术语中,为了完整起见,除了次要(软)/主要(硬)之外,还有第三种页面错误:无效页面错误是指内核的页面错误处理程序决定进程甚至没有逻辑上映射该虚拟地址。例如,空指针引用-虚拟地址0未映射在硬件页表中(因此是#PF x86硬件异常),当内核检查如何纠正情况并重试故障指令时,它发现没有修复方法。(在Linux上,内核会发送一个SIGSEGV信号(段错误)或者杀死进程,如果没有处理程序的话。) - Peter Cordes

8
一个缓慢而稳定的页面错误来源是操作系统探测不经常访问的页面。 在这种情况下,操作系统将某些页面标记为不存在,但将它们保留在内存中。 如果应用程序访问该页面,则触发#PF陷阱,操作系统会简单地将该页面再次标记为存在而无需进一步操作。 如果"长时间"过去了而页面从未发生错误,那么操作系统就知道该页面是需要交换的好候选者,当需要时可以进行交换。即使没有资源压力,这种机制也可以主动运行。

听起来像是一个合理的原因,虽然不是非常有说服力。尽管如此,仍然是一个很好的潜在原因。+1 - user541686
1
@vladr - 我记得这至少是Xen的一种策略,但是一些简短的谷歌搜索未能找到任何证据。 - srking
操作系统难道不只是清除页面表项中的“已访问”位(https://wiki.osdev.org/Paging#Page_Directory),而不是使PTE无效吗?因此,如果需要该页面,就不会出现错误,只需微码辅助来原子更新PTE。(这是x86的工作方式,其他ISA可能没有“已访问”位?)如果嵌套页表不支持“已访问”位,或者在不支持嵌套页表的硬件上,嵌套页表正在使用客户操作系统,则我可以看到Xen会这样做。 - Peter Cordes
我认为错误推测的访问不会设置被访问位,因为在当前的CPU中需要微码协助才能实现。这基本上类似于一个故障处理程序,可能直到其被确定为非推测性,即直到加载或存储达到退休状态之前才触发。但如果我对此有误解,那可能会证明取消映射页面是合理的,因为错误推测实际上不会导致页面错误。 - Peter Cordes

4
“在虚拟地址空间中映射的页面,但未加载到物理内存中”并不意味着它以前曾经在物理内存中。假设您映射了一个文件?它仍然在磁盘上,还没有在内存中。

假设您映射了一个日志文件并不断添加内容。每次超过已提交内存的末尾时,都会发生一个页面错误,操作系统将为您提供一个新的空页面并调整文件长度。


它也可能是程序捕捉并处理的访问违规问题。
可能是程序使用的内存段超过了TLB(页表的高速缓存)的容量。当页面是连续的时,它们都可以由单个页表条目处理。但如果内存在物理地址空间中是碎片化的,则需要许多页表条目,并且它们可能不适合TLB。发生TLB未命中时,操作系统的页错误处理程序被调用并查找进程的页表中的映射。
在某些方面,这是 Dean's answer的变体:页面已经在物理RAM中,操作系统确实需要将这些映射加载到TLB中,但这不是因为IPC。
Brian指出x86(因此所有Win32系统)可以在不引起页面故障的情况下处理此问题。
另一个导致页面错误的原因是触发用于堆栈增长和写时复制的保护页,但通常情况下,这些不会没有边界而发生。我不确定它们是否会显示为访问违规,因为它们将被标记为MMU陷阱入口处的访问违规,但可能由操作系统页面故障处理程序处理,并且不会转换为用户模式(SEH)访问违规。

@Ben:这是一个很好的观点,但据我所知,这里没有日志记录。它应该每次读取相同的值。 - user541686
@Ben:关于访问违规的观点很有意思,让我在几分钟内检查一下... - user541686
@Ben:事实证明没有发生访问违规。 - user541686
@Ben:有趣的是你提到了翻译旁路缓存,我没有想到过。但我不认为这是原因,因为几乎所有其他进程的页面错误都保持完全恒定,即使它们正在执行某些工作。(例如,我在Chrome中移动鼠标,显然会收到“WM_MOUSEMOVE”消息,但增加的是Process Hacker的页面错误,而不是Chrome的。) - user541686
在x86上,TLB未命中是由硬件直接处理而不会陷入操作系统。只有在页表不包含适当的条目时才会调用操作系统。 - Brian
显示剩余5条评论

2
任何时候读取mmap'd(内存映射文件)部分都会生成页面错误,包括加载DLL。因此,加载DLL实际上并没有将整个DLL读入内存,只有在执行代码时才会导致它被故障引入。

1
@Mehrdad:没错,它每页会出现一次故障,之后该页面就存在于RAM中了。但他的想法是正确的,解释了在没有任何页面被分页的情况下发生页面故障的原因。 - Ben Voigt
@Mehrdad:当出现访问冲突时,每次访问的可能是同一页而不是新的不同页面(该页面仍然是“新的”,因为没有提交任何内容)。但由于您已经检查并未发现访问冲突,至少目前我无法解释。您能连接内核调试器吗? - Ben Voigt
@Ben:不是很确定...我从未进行过内核调试,而且这是我的唯一笔记本电脑,现在没有其他可用的工具。 :-) 访问冲突可能没有被记录下来吗?我使用Visual Studio附加到程序,并且它仅在启动时记录了两个访问违规,但之后没有记录任何违规。它们中的任何一个都可能没有显示出来(如果是这样,为什么)? - user541686
@Mehrdad:不,我认为任何被计算为页错误的访问违规都会在调试器中作为一次first-chance异常被捕获。但我有另一个可能的解释:页表太大而无法适应TLB——请参见我的答案。 - Ben Voigt
故障(fault in)-- 令人愉悦 :-) - Wolf
显示剩余4条评论

1

当内存在进程之间共享时,您会看到软页错误。基本上,如果您有一个在两个进程之间共享的内存映射文件,当第二个进程加载内存映射文件时,将生成软页错误 - 内存已经在物理RAM中,但操作系统需要修复内存管理器的表,以便您进程中的虚拟内存地址指向正确的物理页面。

特别是对于像Process Hacker这样的东西,它可能会向每个运行的进程注入代码(以收集信息),因此很可能会大量使用共享内存来进行IPC。


1
这应该仅在将新页面提交到映射中时发生,而不是在每次上下文切换时发生。 - Ben Voigt
第一段有趣的信息,听起来很合理。但是第二段,我不认为Process Hacker会在没有我的允许下注入任何代码;它所做的任何读取都应该来自内核内存,这是跨进程共享的。 - user541686

1
操作系统使用分页技术将应该放置在物理内存中的项目进行分组,并在物理内存和共享内存之间移动它们。大多数情况下,放置在单个页面中的数据项是相关的。 当页面中的数据项长时间未被使用时,操作系统将其移动到虚拟内存中以释放一些物理内存空间。然后,当需要访问虚拟内存中的页面时,操作系统将其从虚拟内存(硬盘)移动到物理内存中。这就是页面错误! 请记住,不同的操作系统在分页算法方面是不同的。 页面错误基础知识

@Mehrdad:只有在所有内存都被使用时,操作系统才会进行映射(即使是这种情况,也只有在操作系统感觉磁盘缓存太小的情况下才会进行映射)。 - Ben Voigt
@Ben:啊。但我不明白:在这个过程经历了成千上万次的刷新之后,为什么任何东西都不会被分页?每次刷新时它到底会分页哪些内容,而之前没有进行分页呢? - user541686
@Mehrdad:这就是为什么我在宣传访问冲突的解释。如果缓存管理器找不到要交换的数据,因为地址没有与任何文件映射相关联,或者源不可用(I/O错误,映射了不可用的网络文件等),那么故障的根本原因就没有得到解决,下一次访问该区域将会导致另一个陷阱。 - Ben Voigt
@Ben:我检查过了,没有发生访问违规。 - user541686
@Farzin:这没有意义。一切都应该已经分页了,因为没有新的东西每秒加载数百次。 - user541686
显示剩余4条评论

0

资源分配是在保持主存储器可用的同时尽可能减少需要使用辅助存储器之间的微妙平衡。如果一个进程试图分配内存并且无法分配,那通常是一个异常情况,有时甚至是致命的异常情况。

基本上,你不能将所有东西都保存在没有可用资源的RAM中,因为当程序启动或请求更多资源时,它会崩溃。


当然不是。这是因为操作系统正确地管理了每个进程。请记住,每个进程,就是每个进程,都有4GB的地址空间(64位系统更多)。您的截图中列出了10个进程。您有40GB的可用RAM吗?结论是页面错误是正常且预期的行为。 - Bacon Bits
你知道虚拟内存是如何工作的吗?即使由于内存管理不良而加载的所有内容都一次性放入了RAM中,它仍然无法达到4GB...更不用说40GB了。 - user541686

0
让我们了解一下内存的基本工作原理。内存有物理地址和逻辑地址。物理地址由称为帧的块组成。逻辑地址由称为页的块组成。CPU生成逻辑地址并将其分为两部分,即偏移量和页号。页表包含各个页号的索引及其与各个帧号的匹配关系。它将偏移量加到帧号上,最终给出物理内存中的地址。
表中所需的页号不存在会导致页面错误。页面错误的主要原因是物理内存(或主内存)中缺少一块内存,或者请求位置上的内存量不足。这意味着一块内存是程序的工作集的一部分,但系统无法在物理内存中找到它。这是计算机硬件生成的异常,告诉操作系统有关丢失引用的情况。
内存管理单元(MMU)处理此过程。现在,有两种类型的页面错误,即硬页面错误和软页面错误。
硬页错误 当所需页面不在主物理内存中时,系统必须从虚拟内存中获取它,这就是硬页错误。页面表中的条目与有效位相关联。如果某个条目的有效位为零,则表示该页面尚未分配任何帧,因此会发生页面错误。
软页错误 当要查找的页面在内存的其他位置找到时,就会发生软页错误。系统找到内存片段的实际位置可以是“缓存”。
现在让我们了解页面错误的不同原因。
当您希望访问的页面不是主存储器驻留的页面时,可能会发生页面错误。但是,要访问的内存片段位于虚拟内存中,需要从那里获取。
当您想要访问的页面属于处于“待机”模式类别的页面表的一部分时,可能会发生页面错误。这意味着页面处于修改后的页面表下,为了解决这个问题,页面必须被转移到正确的会话页面表或适当的工作集中。
页面错误的发生可能是由于访问需求为零的页面的做法。当您希望从堆内存分配一个块时,堆管理器提供新页面,并且当您尝试访问这些页面时,会发生需求为零的页面错误。
当您尝试通过写入其页面来挂钩kernel32中的函数时,会发生写时复制页面错误。这是因为这些页面会被静默地复制。因此,对这些页面的更改不会影响其他进程。
因此,页面错误的发生原因不止一种,如上所述。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接