您的位置:首页 > 其它

地址转译的相关问题(五)

2017-03-28 09:43 176 查看
PFN数据库的概念
前面我们看到,有效的PTE 中有一个页面帧编号,此页面帧编号为20 位,指向一个物理内存中的页面,而且我们在前面的介绍中看到了利用MiRemoveAnyPage 或 MiRemoveZeroPage 函数申请物理内存页面的用法,其返回值也是一个页面帧。

 

所谓PFN 数据库,就是一个数组,每一项都描述了一个物理页面的状态,大小为8字节*6。

如下:

 
typedef
struct _MMPFN {
   union {
       PFN_NUMBER Flink;
       WSLE_NUMBER WsIndex;
       PKEVENT Event;
       NTSTATUS ReadStatus;
 
       //
       // Note: NextStackPfn is actually used asSLIST_ENTRY, however
       // because of its alignmentcharacteristics, using that type would
       // unnecessarily add padding to thisstructure.
       //
 
       SINGLE_LIST_ENTRY NextStackPfn;
   } u1;
   PMMPTE PteAddress;
   union {
       PFN_NUMBER Blink;
 
       //
       // ShareCount transitions are protectedby the PFN lock.
       //
 
       ULONG_PTR ShareCount;
   } u2;
   union {
 
       //
       // ReferenceCount transitions aregenerally done with InterlockedXxxPfn
       // sequences, and only the 0->1 and1->0 transitions are protected
       // by the PFN lock.  Note that a *VERY* intricate synchronization
       // scheme is being used to maximizescalability.
       //
 
       struct {
            USHORT ReferenceCount;
            MMPFNENTRY e1;
       };
       struct {
            USHORT ReferenceCount;
            USHORT ShortFlags;
       } e2;
   } u3;
#if defined (_WIN64)
   ULONG UsedPageTableEntries;
#endif
   union {
       MMPTE OriginalPte;
       LONG AweReferenceCount;
   };
   union {
       ULONG_PTR EntireFrame;
       struct {
#if defined (_WIN64)
            ULONG_PTR PteFrame: 57;
#else
            ULONG_PTR PteFrame: 25;
#endif
            ULONG_PTR InPageError : 1;
            ULONG_PTR VerifierAllocation : 1;
            ULONG_PTR AweAllocation : 1;
            ULONG_PTR Priority : MI_PFN_PRIORITY_BITS;
            ULONG_PTR MustBeCached : 1;
       };
   } u4;
 
} MMPFN, *PMMPFN;

 

 
extern
PMMPFN MmPfnDatabase;
 
#define MI_PFN_ELEMENT(index) (&MmPfnDatabase[index])

 

可以看出,MmPfnDatabase数组是以页帧编号为索引的,因此,要查看一个物理页面的状态,只需直接以页帧编号为索引,就可以获取到该页面的PFN 项。

 

一个物理页面可能的状态:

活动(active valid)

页面处于活动状态是指正在被某个 进程使用,或者被用于系统空间(非换页内存池,或者在系统工作集)。对应有一个 有效的PTE指向该页面。

备用状态(standby)

这种页面原来属于某个进程或系统工作集,但现在已经从工 作集中移除。这种页面包含的数据对于原来的工作集仍然是有效的,也就是说,在原 来工作集中,页面的内容尚未被修改。原来工作集中的PTE仍然指向该页面,但是 已被标记成正在转移的无效PTE。这种页面处于被回收状态,既可以被系统回收以作 他用,也可以被原来的工作集回收而继续留用。

已修改状态(modified)

类似于备用状态,已经从原来的工作集中移除,但是,页 面包含的内容已经被修改过。原来工作集中的PTE仍然指向物理页面,但已被标记 成正在转移的无效PTB。如果系统要把这种页面回收作他用,则必须将其中的内容写 到磁盘上。

已修改但不写出(modified no-write)。

类似于上一种状态,但区别在于,内存管理 器不会将它的内容写到磁盘上。

转移状态

说明一个页面正处于I/O操作进行中,当两个线程并发地在同一个页面上 引发页面错误时,页面错误处理例程通过这一状态可以判断出冲突的页面错误的情 形,从而正确地处理。在4.4.3节中我们看到过这种页面错误冲突的处理过程。值得 一提的是,这里的转移状态是针对一个物理页面中的内容,而无效PTE的转移状态 则是针对它所指的页面已被转移到备用链表或已修改链表中。两者含义大不相同。

空闲状态。

页面是空闲的,不属于任何一个工作集,它们包含了不确定的数据,内存 管理器在重新使用这些页面以前,应根据需要(尤其是出于安全考虑)清除脏数据。

零化状态

页面是空闲的,不属于任何一个工作集,其中的内容已经被全部清零。

坏状态

页面产生硬件错误。系统不再使用这样的页面。

 

其中 OriginalPte 包含了指向此页面的PTE 的原始内容,当一个物理页面分配给一个PTE ,它记录了原来的PTE,之后当该物理页面不再为它所用,可以恢复原来的PTE,

 

活动状态的页面不存在链表,而备用页面,修改页面,已修改但不写出页面,零化页面或空闲页面都组织成一个链表。

 

对于正在转移状态的PFN,第一项要么指向一个同步事件(I/O正在进行),要么是一个I/O 错误码(页面换入过程中产生错误),转移状态的PFN 的用途是用于识别和消除冲突的页面冲突。

 

贴几张PFN 数据库相关的图

 










遗留的几个函数的分析

首先了解一个结构体

typedef
struct _MMINPAGE_SUPPORT {
    KEVENT Event;
    IO_STATUS_BLOCKIoStatus;
    LARGE_INTEGERReadOffset;
    LONG WaitCount;
#if defined (_WIN64)
    ULONGUsedPageTableEntries;
#endif
    PETHREADThread;
    PFILE_OBJECTFilePointer;
    PMMPTE BasePte;
    PMMPFN Pfn;
    union {
        MMINPAGE_FLAGS e1;
        ULONG_PTRLongFlags;
        PMDLPrefetchMdl;       // Only used under _PREFETCH_
    } u1;
    MDL Mdl;
    PFN_NUMBERPage[MM_MAXIMUM_READ_CLUSTER_SIZE + 1];
   SINGLE_LIST_ENTRY ListEntry;
} MMINPAGE_SUPPORT, *PMMINPAGE_SUPPORT;
 

 

访问一个驻留在页面文件中的页面

NTSTATUS
MiResolvePageFileFault (
   IN PVOID FaultingAddress,
   IN PMMPTE PointerPte,
   OUT PMMPTE CapturedPteContents,
   OUT PMMINPAGE_SUPPORT *ReadBlock,
   IN PEPROCESS Process,
   IN KIRQL OldIrql
)
函数建立一个MDL
和相关的结构体,然后读取页面文件以解决页面错误
 
申请一个读块,计算周边可以一起读取的页面的大小
如果一次读取的页面只有一个
申请一个内存页,然后调用MiInitializeReadInProgressSinglePfn初始化PFN
元素为转译/正在读入的状态。初始一个ReadBlockLocal的一个事件,当I/O
操作完成,该事件被设置触发。
 
如果有多个页面需要读取
申请多个内存页,构建一个MDL
描述这些页面,然后调用MiInitializeReadInProgressPfn,设置所有PTE为转移状态,且无效,每次内部循环都增加页表页的共享计数,即PTE
个数。
 
// PageFileNumber为页面文件号,默认只有一个页面文件,最多16个
然后设置ReadBlockLocal
的文件对象为MmPagingFile[PageFileNumber]->File;
 
函数的最后判断,如果一次读取的页面只有一个,为其建立一个MDL。
 
 
NTSTATUS
IoPageRead(
    IN PFILE_OBJECTFileObject,
    IN PMDLMemoryDescriptorList,
    IN PLARGE_INTEGERStartingOffset,
    IN PKEVENT Event,
    OUT PIO_STATUS_BLOCKIoStatusBlock
    )
 
所有正在执行的I/O
操作,被设置为IRP_PAGING_IO,读页操作被标识为使用IRP IRP_INPUT_OPERATION
MDL 标识读写操作的内存页面,大小,如果MDL
的低字节被设置,为异步操作,否则为同步
 
函数内部得到文件对象关联的设备对象,然后申请IRP
,和IRP
堆栈,设置IRP读写方式(同步/异步)及其它成员。
然后设置IRPSPà主功能码为读,设置文件对象,长度和偏移,并将其IRP设置为刚申请的IRP。然后IoCallDriver(deviceObject,irp),调用驱动程序,进行读操作。
 
 

访问一个要求零PTE

 

首先通过MiRemoveZeroPage 或MiRemoveAnyPage 或MiRemoveZeroPageIfAny 申请一个物理页面,得到 PageFrameIndex,然后调用 MiInitializePfn 函数初始化PFN项,该函数设置PFN指向PTE地址,以及保留PTE 的值。最后如果是用户空间的PTE,设置PTE 中的有效位,访问位,PFN 域,以及保护属性。如果是系统空间的PTE,只设置PFN,保护属性,还有一个检查判断全局访问标识的位

 

NTSTATUS
MiResolveProtoPteFault (
   IN ULONG_PTR StoreInstruction,
   IN PVOID FaultingAddress,
   IN PMMPTE PointerPte,
   IN PMMPTE PointerProtoPte,
   IN OUT PMMPFN *LockedProtoPfn,
   OUT PMMINPAGE_SUPPORT *ReadBlock,
   OUT PMMPTE CapturedPteContents,
   IN PEPROCESS Process,
   IN KIRQL OldIrql,
   IN PVOID TrapInformation
   )
如果指令尝试修改错误的地址(比如修改,访问请求),非空
LockedProtoPfn 指向原型PTE
的PFN
的地址,如果锁定了PFN
非空,否则为空,如果此函数解锁PFN,也应该清除这个指针。
ReadBlock 已经了解过了。CapturedPteContents---捕获的PTE
内容,以便比较PTE
是否改变。当且仅当调用者要处理I/O
的情况(此函数返回STATUS_ISSUE_PAGING_IO)
 
首先,如果原型PTE
有效,直接调用MiCompleteProtoPteFault完成函数即可,该函数增加包含PTE
的页表页面的共享计数,如果是修改指令,且非拷贝写,设置PFN
修改位,PTE
脏位。如果LockedProtoPfn不为空,释放锁,清除指针。然后利用原型PTE
设置PTE
有效即可
 
如果需要重新检查访问权限,检查访问权限。然后判断是否为拷贝写。
 
如果原型PTE
要求零页面,且页面属性为拷贝写,让这个页面变为私有的需要零的页面,并申请一个零页面并返回。
 
然后就是分别解决原型PTE
的几个页面错误问题:页面文件,转移,需要零页面,映射文件错误。
 
其中的三个,页面文件,转移,要求零页面已经介绍过,下面主要看映射文件错误:
 
NTSTATUS
MiResolveMappedFileFault (
    IN PMMPTEPointerPte,
    OUTPMMINPAGE_SUPPORT *ReadBlock,
    IN PEPROCESSProcess,
    IN KIRQLOldIrql
    )
函数建立MDL
和其它需要的结构以处理页面错误
首先得到PTE
对应的子内存区对象和子内存区对象对应的控制域。然后申请ReadBlock
以在之后的读磁盘操作中使用。
然后建立MDL,尽量增加一次读取磁盘的数据大小。
然后计算要读取的文件内偏移,通过函数:MiStartingOffset
得到Subsection
和 PTE
指定的文件的偏移,镜像文件是512
字节对齐的,而数据文件是4KB
对齐的。SubsectionBase 为第一个PTE 地址,当前PTE 地址-第一个pte地址之后得到的是页面文件的数量,然后*页面大小4KB,然后+subsection 中的startsector 指定的开始簇*(镜像文件/数据文件对应的对齐大小,即可得到文件偏移)。
offset = base + (thispte-basepte)<< PAGE_SHIFT)
 
后面申请内存页面,初始化MDL
和 ReadBlock
之后返回一个STATUS_ISSUE_PAGING_IO。
 

 

访问一个转移状态的页面

转换PTE是在空闲或修改的列表上,如果不在两个联表上,则是由于它的ReferenceCount或当前正在从磁盘读入(正在读取)。如果正在读取该页面,则这是一个冲突的访问请求,应该做相应处理

 

首先获得PTE
对应的PFN Index
和 PFN
 
如果当前页面得到一个读页错误,证明有其它线程正在冲突访问当前页面,延缓操作,并让其它线程完成并返回。
在释放锁定之前捕捉相关的pfn字段,因为页面可能会立即重新使用,返回一个I/O status
 
如果当前操作的页面正在读----冲突的页面错误,首先增加pfn
的引用计数,这样在所有的冲突的页面错误完成之前,这个页面不会被复用。
 
设置InPageBlock
的地址,此函数的调用者必须释放这个块。
然后调用函数MiWaitForInPageComplete 该函数内部进行的操作,如上一篇“冲突的页面错误”锁描述。
 
如果是普通的转移状态的PTE,直接设置PTE
的状态???
 
 
 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: