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

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_18218335/article/details/67632084

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 的状态???

 

 

 

展开阅读全文

没有更多推荐了,返回首页