我们都知道,地址的转译需要如下的PTE 结构
第一篇文章我们看到了正常的地址转译过程v 位总是为1,即PTE 有效,但是,虚拟地址到物理地址的转译并不总是这样的正常流程。当产生页面错误异常(14号),异常的陷阱处理器(_KiTrap0E函数)首先构造一个陷阱帧,然后把控制权交给内存管理器的MmAccessFault 函数。如果MmAccessFault 函数能能够解决此错误,则陷阱处理器正常返回;否则,_KiTrap0E 进行异常处理,具体的异常处理过程,暂不讨论。
NTSTATUS
MmAccessFault (
IN ULONG_PTR FaultStatus, // 发生错误时候的状态信息位
IN PVOID VirtualAddress, // 发生页面错误的虚拟地址
IN KPROCESSOR_MODE PreviousMode, //此页面是发生在内核模式还是用户模式
IN PVOID TrapInformation // 页面错误陷阱帧的地址
)
上面我们可以看到MmAccessFault 函数的主要的工作流程,他的主要的功能就是进行各种检查,以及处理一些MiCopyOnWrite和 MiResolveDemandZeroFault 函数来解决页面错误。而其它需要解决的页面错误,绝大多数是调用了MiDispatchFault函数来完成的。
NTSTATUS
MiDispatchFault (
IN ULONG_PTR FaultStatus,
IN PVOID VirtualAddress,
IN PMMPTE PointerPte,
IN PMMPTE PointerProtoPte,
IN LOGICAL RecheckAccess,
IN PEPROCESS Process,
IN PVOID TrapInformation,
IN PMMVAD Vad
)
当为了满足一个页面错误而必须发出一个读文件(页面文件或映射文件)操作时,页面换入I/O就会发生。而且,因为页表本身也是可换页的,所以,在处理一个页面错误时,若系统还需要加载页表的页面(该页面包含了原始被引用页面所对应的PTE或者原型PTE),则可能会招致额外的页面错误。
页面换入I/O操作是同步的,也就是说,线程等待一个事件,直至I/O完成,异步过程调用(APC)的交付是不会中断这种操作的。换页器在I/O请求函数中使用一个特殊的修饰符来指明这是一个换页I/O。当换页I/O完成时,I/O系统触发一个事件,该事件唤醒换页器,以便让它继续处理页面换入过程。
当换页I/O操作正在进行时,导致页面错误的那个线程并不拥有任何关键的内存管理同步对象。在换页I/O发生的时候,该进程内的其他线程仍然可以使用虚拟内存的功能,以及处理页面错误。
换页器I/O 完成时应该认识到的问题:
■同一个进程中或者不同进程中的另一个线程可以在同—个页面上产生页面错误(称为冲突 的页面错误,下一小节讲述这种情形);
■ 页面己经被从虚拟地址空间中删掉(和重新映射)了;
■ 页面上的保护域己经改变了;
■ 页面错误可能是针对原型PTE的,并且映射此原型PTE的那个贞面可能己经不在工作集中了。
换页器在换页I/O请求以前,在线程的内核栈中保存了足够的状态,以便来处理这些条件。
因此,当换页I/O请求完成时,换贞器可以检测到这些条件,如果有必要的话,它可以不将页面变成有效的,而直接解除此页面错误。若导致页面错误的指令被再次激发,则换页器再次被调用,然后根据新的状态再来评估此PTE。
一个页面正在被换入,另一个线程或者进程也激发了该页面的页面错误,这种情形称为冲突的页面错误(collided page fault).
如果另一个线程或进程在同一个页面上出错,则换页器检测到冲突的页面错误,注意到该页面正在转移过程中(in transition),以及一个读操作正在进行之中(PFN 数据库保留了这些信息,接下来将详细介绍),这种情况下,换页器发出一个等待操作,在该PFN数据库项中指定的事件上等待。此事件由第一个为了消除该页面错误而激发I/O请求的那个线程来初始化。
I/O 操作完成时,所有在该事件上等待的线程都满足了等待的条件。第一个获得PFN数据库锁的线程负责执行页面换入完成操作,这些操作包括:检查I/O状态确保I/O操作已经完成,清除PFN数据库中的“正在读取过程中”位,以及更新PFN。
随后的线程获取到PFN 数据库锁来清除冲突的页面错误时,换页器看到“正在读取过程中”位已经清除了,知道初始的更新已经执行过了。检查PFN 数据库项中的“页面换入错误(in-page error)标志,确保页面换入I/O 成功完成,如果”页面换入错误“标志已经被置上,该PTE 不被更新,且在导致页面错误的线程内引发一个页面换入错误异常。
下一篇讲授最后的几个函数,以及地址转译以及页面错误的整体描述和理解。