<svg xmlns="http://www.w3.org/2000/svg" style="display: none;"> <path stroke-linecap="round" d="M5,0 0,2.5 5,5z" id="raphael-marker-block" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></path> </svg>
讯享网
讯享网
这个函数是由底层的异常响应程序调用的,异常响应程序的作用类似于中断响应程序。当发生某种异常时(页面异常是14号常),CPU首先进入相应的异常响应程序,在那里再根据具体的情况调用不同的处理程序。
参数 Storelnstruction是个表示异常原因的布尔量,为0表示缺页,非0表示越权。该信息来自发生异常时CPU自动压入堆栈的出错代码。发生异常时,CPU一方面根据异常的种类产生中断1异常向量,使控制转入相应的异常响应程序入口,另一方面在保存返回断点的同时将一个进一步说明异常原因的出错代码压入堆栈。
般而言,页面异常多半发生于用户空间,发生在系统空间的可能性也有,因为Windows允许倒出系统空间的部分页面。其中有一种情况,表面看来是个问题,实际上却不是。我们知道,当内核的映射出现变动时,该变动首先反映在全局的内核映射表中,然后才反映到当前进程的映射表中。如果在这中间访问了刚发生变动而尚未来得及反映到当前进程的页面映射表中的页面,就可能发生异常。但是此时只要依据全局的内核映射表更新当前进程的映射表,就可以重试引起异常的访问了这里的 Mmi386MakeKernelPageTableGlobal()就用来检查是否属于这种情况,并(就所涉及的页面)更新当前进程的映射表。这个函数返回TRUE表示天下本无事,所以这里就直接成功返回了,这是一种优化。
发生页面异常的原因大体上可以分成两大类。一类是由于违反了页面的保护模式,这是因访问权限不足而引起的:另一类是因缺页而引起的。这里分别提供了两个函数加以处理,我们只看其中之一MmNotPresentFault(),这是对于缺页的处理。
参数Address表明引起异常的内存单元地址(而不是引起异常的指令所在的地址),如果这个(虚存)地址在系统空间,就通过MmGetKernelAddressSpace()获取代表系统空间的数据结构(指针)在用户空间则从当前进程的数据结构获取其用户空间的结构指针。
然后,通过 MmLocateMemoryAreaByAddress()在该空间中寻找所属的虚存区间。如果找不到,就说明这个地址根本就不在任何已经分配的区间之内,所以这是一次属于越界访问的“硬伤”。
而如果找到了这个地址所属的区间,那就要看该区间的类型了。对于一般的虚存区间,即类型为MEMORY_AREA_VIRTUAL_MEMORY的区间,所做的反应是通过MmNotPresentFaultVirtualMemory()加以处理。
讯享网
在同一个内存区间之内,还可以有多个不同的区块,其中有的可能已经交割兑现了,有的可能还只是保留了但未交割,所以这里还要通过 MmFindRegion()从中找到具体的区块。显然,只有已经交割的区块才可以被访问。所以,如果具体区块的类型(其实是状态)是MEM_RESERVE或者PAGE_NOACCESS,就没有什么办法可以补救了,所以直接失败返回,由系统的出错处理机制即“结构化异常处理”机制去采取进一步的措施。
参数 Address已与页面边界对齐,因为前面的实参是PAGE_ROUND_DOWN(Address)。所以这儿的参数 Address 是页面的起始地址。
内核中有一组杂凑(Hash)的页面操作(请求)队列,每当要进行页面操作时就创建一个MM_PAGEOP数据结构并将其挂入某个杂凑队列,表示此项操作正在进行之中。所以,如果根据杂凑值在队列中找到了特征相符的数据结构,就说明己有针对同一个页面的操作在进行中,需要等待其完成,所以返回指向这个数据结构的指针。要是找不到,就分配一个MM_PAGEOP数据结构,加以初始化之后将其挂入杂凑队列,同样也返回指向这个数据结构的指针。
回到 MmNotPresentFaultVirtualMemory()的代码。如果从 MmGetPageOp()返回的结构指针PageOp表明该页面操作的启动者并非当前线程,就说明别的线程已经启动了针对同一个页面的操作,所以通过KeWaitForSingleObject()等待该页面操作完成。但是,针对同一页面的操作未必就是同样的操作。如果所完成的操作不是MM_PAGEOP_PAGEIN,就说明这里所需的操作尚未完成,同志仍需努力。怎么努力呢?就是出错返回,返回出错代码STATUS_MM_RESTART_OPERATION。这样,上一层函数MmNotPresentFault()中的 do{}while()循环就会以相同的参数再次调用MmNotPresentFaultVirtualMemory(),这一次可能就不存在由别的线程启动的针对同一个页面的操作了。如果还存在也不要紧,只不过是又一轮循环而己。反之,如果刚完成的这个操作恰好也是MM_PAGEOP_PAGEIN,那就实际上合二为一了,目的已经达到,可以成功返回了。
如果没有别的线程在进行针对同一页面的操作,则当前线程就是该页面操作的启动者,所以当前线程承担着完成此项操作的责任,我们继续往下看:
讯享网
内核函数 MmRequestPageMemoryConsumer()的作用是分配一个物理页面,如果内核中已经没有空闲的物理页面,就得把已经有较长时间没有得到访问的页面换出到页面倒换文件,腾出一些物理页面,然后再来分配。这个函数的第二个参数为真表示可以等待、即等待换出页面以腾出一些空闲的物理页面。这里的第一次 MmRequestPageMemoryConsumer()是不等待的,如果失败就再来一次,但这一次只好等待了。
获得了空闲的物理页面之后,当然需要在虚存页面与物理页面之间建立起映射,但是这里又有两种不同的情况。一种情况是,在发生本次页面异常之前相应的页面映射表项是0,说明这是全新的映射,此时所需的是一个空白的物理页面,所以只需建立映射就行了。另一种情况是相应的页面映射表项非0,说明原来是有物理页面的,只是其内容已经倒换出去,此时需要先从倒换文件中读入该页面的映像,然后才能建立映射。函数MmIsPageSwapEntry()就是用于这个判断的,这个函数返回非0表示已经有页面映像在倒换文件中
对于已被倒换出去的页面,代码中先通过 MmDeletePageFileMapping()从相应的页面映射表项中获取“倒换描述项”,即倒换页面所在的文件和位置。如前所述,当页面不在内存中时,相应页面表项 PTE的最低位为0,而其余31位就用来描述作为后备的倒换页面所在的文件和位置。获得了这些信息以后,就通过MmReadFromSwapPage()从页面倒换文件读入相应的页面映像。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/143808.html