关键的Windows内核数据结构一览(下)

Posted 看雪学院

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关键的Windows内核数据结构一览(下)相关的知识,希望对你有一定的参考价值。

译者注:本文延续上一篇。由于下篇中的某些数据结构译者本人并不熟悉,所以译文可能会有一些蹩脚的地方。如果你发现了错误,烦请及时指出,玉涵百拜。



I/O管理器


nt!_IRP


IRP表示一个I/O请求包结构体,它用来封装执行一个特定I/O操作所需要的所有参数以及I/O操作的状态。IRP的表现也类似于一个线程独立调用栈因此它可以从一个线程传递到另一个线程,也可以通过驱动实现的队列传递给一个DPC例程。IRPs是Windows异步I/O处理模型的关键所在,应用程序可以发出一连串的I/O请求并继续执行其他代码,而与此同时I/O请求会被驱动或硬件设备处理。这种异步模型允许相当大的吞吐量且可以实现最佳资源利用。IRPs通过I/O管理器组件分配,由Win32 I/O APIs进行操纵。


在内核中IRPs也可以通过设备驱动为I/O请求而分配。IRPs流穿过驱动的栈,在这里驱动在传递IRP给下方的驱动之前会先增加它自身的值到IRP上,这是通过调用IoCallDriver()完成的。一般来说在最下方的驱动栈上会通过调用IoCompleteRequest()来完成该IRP,该调用会引起栈上的每个驱动被通知I/O已完成并给这些驱动一个在此IRP上执行后向处理操作的机会。

 

IRPs包含一个固定大小的头部,即IRP数据结构和一个可变的堆栈位置,存储在StackCount字段,其中每个堆栈位置都通过IO_STACK_LOCATION结构来表示。IRPs必须包含足够多的堆栈位置,该数量不应少于设备栈上互相叠罗汉的设备对象个数,这将用于处理IRP。驱动栈上的设备对象数量存储在DEVICE_OBJECT.StackSize字段。IRPs一般由支持固定大小分配器的前向链表分配。因此,由I/O管理器分配的IRPs有10或4个I/O堆栈位置,这取决于IRP目标所在的堆栈上的设备对象数量。

 

一些IRPs通过ThreadListEntry字段被链入到线程中,链表头为ETHREAD.IrpList

 

Tail.Overlay.ListEntry字段被驱动用来保存内部队列的IRP,典型的固定在一个有LIST_ENTRY结构的设备扩展结构体中,它由驱动创建DEVICE_OBJECT时创建。

 

Tail.CompletionKey在IRP链入到一个I/O完成端口时被使用。

 

调试器命令!irp将显示一个IRP的细节描述。!irpfind命令可以通过扫描non-paged pool来找到系统中所有的或者特定的IRPs集合。

 


APIs:

  • IoAllocateIrp()

  • IoBuildDeviceIoControlRequest()

  • IoFreeIrp()

  • IoCallDriver()

  • IoCompleteRequest()

  • IoBuildAsynchronousFsdRequest()

  • IoBuildSynchronousFsdRequest()

  • IoCancelIrp()

  • IoForwardAndCatchIrp()

  • IoForwardIrpSynchronously()

  • IoIsOperationSynchronous()

  • IoMarkIrpPending()


nt!_IO_STACK_LOCATION


IO_STACK_LOCATION包含有关一个I/O操作的信息,该操作要求在一组驱动中执行一个特定的驱动程序。IRP包含多个内嵌的I/O堆栈位置,它们都是在IRP被分配时分配的。设备栈上有多少个驱动IRP中就至少有多少个I/O堆栈位置。I/O堆栈位置归设备所有,它们逆序出现在设备堆栈上,也就是说堆栈上最顶层的设备拥有最底层的堆栈位置,反之亦然。I/O管理器负责填充最顶层设备的I/O堆栈位置,每个设备都负责填充链中下一个设备的I/O堆栈位置。

 

Parameters字段是一个多重结构体的共用体,每个结构代表了一个对应驱动必须执行的I/O操作。共用体Parameters中特定结构的选择依赖于MajorFunction字段。该字段可用的值由IRP_MJ_XXX在wdm.h中定义。具体的主函数(major function)还拥有和它们相关联的副函数(minor function)。这些副函数的数量存储在MinorFunction字段中。例如IRP_MN_START_DEVICE是一个和主函数IRP_MJ_PNP相关的副函数码。

 

APIs:

  • IoGetCurrentIrpStackLocation()

  • IoGetNextIrpStackLocation()

  • IoCopyCurrentIrpStackLocationToNext()

  • ioskipCurrentIrpStackLocation()

  • IoMarkIrpPending()


nt!_DRIVER_OBJECT


DRIVER_OBJECT表示一个加载到内存中的驱动映像。I/O管理器会在驱动加载到内存前创建驱动对象,此后DriverEntry()例程会收到一个指向该驱动对象的指针。驱动被卸载出内存后驱动对象的释放与此相似。

 

MajorFunction字段是个数组,每个元素指向一个由驱动所知的分发入口指针提供的函数。这些入口指针被I/O管理器用来分发IRPs给驱动处理。

 

DriverName字段包含对象管理器命名空间内驱动对象的名称。

 

 

FastIoDispatch指向了FAST_IO_DISPATCH类型的结构,它包含文件系统驱动提供的例程指针。

 

DriverSection指向一个LDR_DATA_TABLE_ENTRY类型的数据结构,供加载器在内存中找到驱动映像。

 

调试器命令!drvobj显示了一个驱动对象的信息。

 

APIs:

  • IoAllocateDriverObjectExtension()

  • IoGetDriverObjectExtension()


nt!_DEVICE_OBJECT


DEVICE_OBJECT用于表示一个系统中的逻辑或物理设备。和驱动对象不同的是,驱动对象在驱动加载前由I/O管理器创建,而设备对象则是由驱动本身来创建。I/O请求的目标总是设备对象而不是驱动对象。一个指向设备对象的指针会传递给驱动的分发例程以识别哪个设备对象是此I/O请求的目标。

 

驱动对象在创建时怀有一个设备链表。该链表固定在DRIVER_OBJECT.DeviceObject中,使用NextDevice字段来把所有的设备驱动链在一起。设备对象,同样的,有一个DriverObject字段用以指回拥有它的驱动对象。尽管设备对象是系统定义的数据结构它们也可以拥有驱动特定的扩展结构。该扩展数据结构也一起放在设备对象中,它在non-paged pool中分配,大小是一个特定的声明的尺寸,而用于指向扩展结构的指针就是设备对象的DeviceExtension字段。

 

设备对象可以互相叠罗汉来形成一组设备对象。StackSize字段标志了有多少个设备对象在该设备对象的下方。该字段也被I/O管理器用来为IRPs分配合适数量的I/O堆栈位置。CurrentIrp和DeviceQueue字段只有当驱动为设备对象使用系统管理的I/O时才有用,这非常罕见,因此CurrentIrp字段在大多数情况下常常被设置成NULL。AttachedDevice指向了下一个设备栈上更高层的设备对象。

 

调试器命令!devobj可以显示一个设备对象的信息。

 

APIs:

  • IoCreateDevice()

  • IoDeleteDevice()

  • IoCreateDeviceSecure()

  • IoCreateSymbolicLink()

  • IoCallDriver()

  • IoCreateFileSpecifyDeviceObjectHint()

  • IoAttachDevice()

  • IoAttachDeviceToDeviceStack()

  • IoDetachDevice()

  • IoGetAttachedDevice()

  • IoGetAttachedDeviceReference()

  • IoGetLowerDeviceObject()

  • IoGetDeviceObjectPointer()

  • IoGetDeviceNumaNode()


nt!_DEVICE_NODE


DEVICE_NODE用于表示一个已经被PnP管理器枚举到的物理或逻辑设备。设备节点(nodes)是电源管理和PnP操作的目标。系统中完整的硬件设备树源于具有层级结构的设备节点。设备节点有一个父子与兄弟的关系网。用户模式在CfgMgr32.h/CfgMgr32.lib定义的配置管理器APIs用于处理这些设备节点。

 

DEVICE_NODE结构体的Child,Sibling,Parent以及LastChild字段用于存放系统中所有的设备节点,形成一个层级结构。设备节点包含至少一个由PhysicalDeviceObject字段指向的物理设备对象(PDO)和一个功能设备对象(FDO),以及一到多个过滤设备对象(FDOs)。ServiceName字段指向了一个字符串,标志了创建FDO并驱动该设备的功能驱动。InstancePath指向了一个字符串,该字符串唯一的特定标志一个设备实例,系统中可以有多个相同的设备实例。State,PreviousState,StateHistory字段的组合用来确定设备节点在其当前状态设定之前所经历的状态。

 

调试器命令!devnode可以显示DEVICE_NODE结构体的信息。!devstack命令显示了所有的设备对象,它们是单一devnode的一部分。

nt!_FILE_OBJECT

FILE_OBJECT表示了一个打开的设备对象实例。文件对象会在一个用户模式进程调用CreateFile()或本地(native)APINtCreateFile()或内核模式驱动调用ZwCreateFile()时被创建。多个文件对象可以指向一个设备对象除非该设备被标记被设置为排他属性,这是通过设置DEVICE_OBJECT标记中的DO_EXCLUSIVE位实现的。

 

DeviceObject字段指向了打开该文件对象实例的设备对象。Event字段包含一个内嵌的事件结构体,它用于阻塞一个在设备对象上已经请求过异步I/O操作的线程以便于其所有的驱动程序执行异步IO。

 

FsContext和FsContext2被文件系统驱动(FSDs)所使用,它们保存文件对象特定的上下文信息。当被一个文件系统驱动使用时,FsContext字段指向了一个类型为FSRTL_COMMON_FCB_HEADER或FSRTL_ADVANCED_FCB_HEADER的结构体,它包含一个文件或流的信息。多重FILE_OBJECT的FsContext字段表示相同的文件(或流)的打开实例指向同一个文件控制块(FCB)。FsContext2字段指向了一个缓存控制块,FSD用它来存储有关文件或流的特定实例信息。

 

CompletionContext,IrpList以及IrpListLock用于文件对象和I/O完成端口相关联的场景。CompletionContext字段由NtSetInformationFile()初始化,调用参数为信息类FileCompletionInformation。CompletionContext.Port字段指向一个KQUEUE类型的结构体,它包含一个已完成并等待被取回的IRP链表。IoCompleteRequest()通过字段IRP.Tail.Overlay.ListEntry查询该链表中的IRP。

 

调试命令!fileobj显示了一个文件对象的信息。

 

APIs:

  • IoCreateFile()

  • IoCreateFileEx()

  • IoCreateFileSpecifyDeviceObjectHint()

  • IoCreateStreamFileObject()

  • IoCreateStreamFileObjectEx()

  • ZwCreateFile()

  • ZwReadFile()

  • ZwWriteFile()

  • ZwFsControlFile()

  • ZwDeleteFile()

  • ZwDeviceIoControlFile()

  • ZwFlushBuffersFile()

  • ZwOpenFile()

  • ZwFsControlFile()

  • ZwLockFile()

  • ZwQueryDirectoryFile()

  • ZwQueryEaFile()

  • ZwCancelIoFile()

  • ZwQueryFullAttributesFile()

  • ZwQueryInformationFile()

  • ZwQueryVolumeInformationFile()

  • ZwSetEaFile()

  • ZwSetInformationFile()

  • ZwSetQuotaInformationFile()

  • ZwSetVolumeInformationFile()

  • ZwUnlockFile()

  • ZwWriteFile()



对象和句柄


nt!_OBJECT_HEADER


Windows内核中的对象(Object)数据结构用来表示通用的设施比如文件、注册表键、进程、线程、设备等等。它由对象管理器管理,对象管理器是Windows内核的一个组件。所有这样的对象内部都有一个先驱的OBJECT_HEADER结构体,它包含对象相关的信息并用来维护对象的生命周期,允许对象有一个专有名字,通过应用访问控制来保护对象,调用对象特定类型方法以及追踪分配器的配额使用。

 

对象中部署在OBJECT_HEADER后方的数据结构与OBJECT_HEADER存在着部分的重叠。实际上对象的数据存放位置是从OBJECT_HEADER的Body字段开始而不是从其尾部开始。

 

对象头部包含了引用计数HandleCount和PointerCount,它们被对象管理器用来保存对象直到没有外部的引用指向该对象。HandleCount是句柄的数量,PointerCount是句柄和内核模式对象引用的数量。

 

对象头可以由一个可选的对象头引导,类似OBJECT_HEADER_PROCESS_INFO,OBJECT_HEADER_QUOTA_INFO,OBJECT_HEADER_HANDLE_INFO,OBJECT_HEADER_NAME_INFO以及OBJECT_HEADER_CREATOR_INFO结构,它们描述了关于对象额外的属性。InfoMask字段是一个位掩码,它决定了当前是哪一种前面描述的可选头结构。

 

SecurityDescriptor字段指向一个类型为SECURITY_DESCRRIPTOR的结构体,它包含了任意访问控制列表(DACL)和系统访问控制列表(SACL)。DACL用于检查进程的tokens是否有访问对象的权限。SACL用于审计访问对象的权限。

 

内核不能在IRQL高于PASSIVE_LEVEL的级别上删除对象。ObpDeferObjectDeletion()把对象链在一起,其删除操作需要被延迟到一个链表中,该链表在内核变量ObpRemoveObjectList中。NextToFree字段为此而生。

 

QuotaBlockCharged字段指向了EPROCESS.QuotaBlock的EPROCESS_QUOTA_BLOCK结构体,它被PsChargeSharedPoolQuota()和PsReturnSharedPoolQuota()函数用来追踪一个使用Non-Paged Pool和Paged Pool的特定进程。分配对象时配额总是会被分配。

 

调试器命令!object显示了存储在对象头的信息。!obtrace命令显示了根据对象引用trace到的数据。如果对象引用追踪在一个对象上可用,!obja命令可以显示任何对象的属性信息。

 

APIs:

  • ObReferenceObject()

  • ObReferenceObjectByPointer()

  • ObDereferenceObject()


nt!_OBJECT_TYPE


对对象管理器管理的每种类型的对象来说,都有一个“类型对象”结构体用于存储该类型对象的通用属性。这种“类型对象”结构体由OBJECT_TYPE表示。Windows 7上有大概42种不同的对象类型结构体。内核变量ObTypeIndexTable是一个指针数组,它的每个成员都指向一种对象类型的OBJECT_TYPE结构体。对于每种对象类型内核也会保存一个指向相关对象类型结构体的全局变量。例如,变量nt!IoDeviceObjectType指向了DEVICE_OBJECTS的OBJECT_TYPE结构体。

 

OBJECT_TYPE的TypeInfo字段指向了一个OBJECT_TYPE_INITIALIZER结构体,该结构体包含了对象类型特定的函数,它们被对象管理器用来在对象上执行各种各样的操作。

 

CallbackList字段一个特定对象类型的驱动安装的回调函数列表的头。当前只有进程和线程对象支持回调函数,它们由TypeInfo.SupportsObjectCallbacks字段指定。

 

关键字段包含了池标签(pool tag),它用于分配该类型的对象。

 

调试器命令!object ObjectTypes用于显示系统中所有的类型对象。

nt!_HANDLE_TABLE_ENTRY

 

Object字段指向了一个对象结构体,比如File, Key, Kevent等,它们的句柄均已创建。

 

GrantedAccess字段是一个类型为ACCESS_MASK的位掩码,它决定了对象上特定句柄所允许的操作集合。该字段的值由SeAccessCheck()计算,基于调用者(可信访问)的访问请求以及对象安全描述符的DACL中的ACEs。

 

调试命令!handle可以用来查看任何进程的句柄表。!htrace命令可以用来显示堆栈上跟踪句柄的数据,如果句柄跟踪可用的话。

 

APIs:

  • ObReferenceObjectByHandle()

  • ObReferenceObjectByHandleWithTag()



内存管理


nt!_MDL


MDL表示一个内存描述符列表结构,它描述那些已被锁定的用户或内核模式内存。它由一个固定长度的头和可变的页帧数(PFNs)组成。每一页都由MDL描述。

 

 

某些类型的驱动程序,例如网络栈,在Windows支持的链式MDLs,多个MDL中描述了实质上分散的缓冲区,它们由Next字段链在一起。

 

 

 

Size字段包含MDL数据结构以及MDL后面跟随的整个PFN数组的尺寸。

 

 

ByteCount字段描述了MDL锁住的缓冲区尺寸。

 

APIs:

  • IoAllocateMdl()

  • IoBuildPartialMdl()

  • IoFreeMdl()

  • MmInitializeMdl()

  • MmSizeOfMdl()

  • MmPrepareMdlForReuse()

  • MmGetMdlByteCount()

  • MmGetMdlByteOffset()

  • MmGetMdlVirtualAddress()

  • MmGetSystemAddressForMdl()

  • MmGetSystemAddressForMdlSafe()

  • MmGetMdlPfnArray()

  • MmBuildMdlForNonPagedPool()

  • MmProbeAndLockPages()

  • MmUnlockPages()

  • MmMapLockedPages()

  • MmMapLockedPagesSpecifyCache()

  • MmUnmapLockedPages()

  • MmAllocatePagesForMdl()

  • MmAllocatePagesForMdlEx()

  • MmFreePagesFromMdl()

  • MmMapLockedPagesWithReservedMapping()

  • MmUnmapReservedMapping()

  • MmAllocateMappingAddress()

  • MmFreeMappingAddress()


nt!_MMPTE


 

 

 

当物理页的内容被保存在页文件中时,Windows内存管理器会修改PTE来指向页文件中的页位置,此时使用的是u.Soft子结构。u.Soft.PageFileLow字段决定了Windows支持的16个页文件中哪一个包含了该页,而u.Soft.PageFileHigh则包含了在该页文件中页的索引。

 

nt!_MMPFN

Windows内存管理持有系统中每个物理页的信息,放置在一个叫PFN数据库的数组中。MMPFN结构体表示了该数据库中的每一个独立条目,它包含了单一物理页的信息。

 

变量nt!MmPfnDatabase指向了MMPN结构体数组,它们组成了PFN数据库。PFN数据库的条目数量为nt!MmPfnSize,这其中有一些额外的条目来处理热插拔内存。为了保存内存,MMPFN结构被塞得很满。每个字段的解析都是不同的,而这取决于每个页的状态。

 

物理页的状态存储在u3.e1.PageLocation,由枚举类型nt!_MMLISTS中的一个条目标识。

 

u2.ShareCount字段包含进程指向该页的PTE数量,如果存在共享页则应该比1大。

 

u3.e2.ReferenceCount包含该页的引用数量,如果页被锁住则也包含锁的数量。该引用计数会在u2.ShareCount变成0时递减。

 

调试命令!pfn可以显示给定物理页的MMPFN结构体的所有内容。

nt!_MMPFNLIST

内存管理器持有处于相同状态的链在一起的物理页。这可以加速查找某种给定状态的一个或多个页的过程,例如,查找空闲页或被零化换出(zeroed out)的页。MMPFNLIST结构体持有这些链表的头。系统中有多个MMPFNLIST结构体,它们中的每个都包含一个特定状态的页并被存储在内核变量nt!Mm<PageState>ListHead中,这里的PageState可以表示待命(Standby)、已修改(Modified)、无写修改(ModifiedNoWrite),空闲(Free),只读(Rom),损坏(Bad),零化(Zeroed)。活跃态的页面例如当前属于一个进程工作集的页面不会再任何一个链表中。

 

在Windows的新版本中,nt!MmStandbyPageListHead不再使用,取而代之的是一个具有优先级的8链表集合,存在nt!MmStandbyPageListByPriority。nt!MmFreePageListHead和nt!MmModifiedPageListHead也不再被使用,转而使用的是nt!MmFreePagesByColor和nt!MmModifiedProcessListByColor或nt!MmModifiedPageListByColor。

 

MMPFN.u1.Flink和MMPFN.u2.Blink字段对一个特定页来说,用于保存该页到一个双向链表中。这些链表的头存储在对应的MMPFNLIST结构体的Flink和Blink字段。

 

ListName字段是枚举类型MMLISTS中的一个,它标识了该链表中页的类型。

 

调试器命令!memusage 8命令显示了某个特定状态的页的数量。

nt!_MMWSL

Windows中每个进程都有一个和它关联的工作集,它由那些进程不会触发页错误的页组成。工作集整理者(WST),是内存管理器的一个组件,它运行在KeBalanceSetManager()线程的上下文,尽力去移除进程不再使用的页并重新分配它们到有需求的其他进程。为了执行这一任务,WST需要存储关于系统中每个进程的工作集信息。该信息被保存在MMWSL结构体。每个进程的EPROCESS.Vm.VmWorkingSetList都指向了它的MMWSL结构。

 

 

MMWSL结构体的Wsle字段指向进程工作集列表项数组的基址。数组中条目的有效值为EPROCESS.Vm.WorkingSetSize。


nt!_MMWSLE


MMWSLE数据结构表示一个进程工作集中单一页面的工作集列表,因此每一个在进程工作集中的页都有一个MMWSLE结构。该结构被工作集整理者用来判定该特定页是否是一个潜在的可整理候选页,也就是说从进程的工作集中移除。

 

当一个进程试图访问一个在工作集中的页面时,CPU内存管理单元MMU会在该页对应的PTE中设置MMPTE.Hard.Accessed位。工作集整理者有规律的唤醒并扫描一个进程的WSLEs。在扫描期间他会检查从上一次来是否已访问过一个特定的页,这是通过检查PTE的访问位来实现的。如果该页从上次扫描以来从未被访问过,该页就通过递增u1.e1.Age字段逐渐老化。如果页被访问过,那么u1.e1.Age字段就被重置为0。当u1.e1.Age涨到7时,该页就被认为是一个可以被换出的候选页。

 

 

调试命令!wsle可以显示一个特定进程的工作集列表条目。


nt!_POOL_HEADER


内核中动态内存分配是由非页池、分页池和会话池组成的。根据请求分配的大小,池分配分为小池分配(尺寸少于4K)和大池分配(尺寸大于等于4k)。池分配大小在x86系统上总是向上取整到8字节,在x64系统上向上取整到16字节。

 

每个小池分配都由一个池头、数据区域组成。数据区域用于存储数据,里面还有一些用于对其的padding。池头用POOL_HEADER结构表示,该结构包含了关于后面跟随的数据区的信息。

 

 

大池分配没有内嵌的POOL_HEADER,取而代之的是,池头部存储了一个分离的叫做nt!PoolBigTable的表。因为大池分配需要在一个页边界(4K)对齐,所以这是有必要的。

 

 

APIs:

  • ExAllocatePoolWithTag()

  • ExAllocatePoolWithQuotaTag()

  • ExFreePool()


nt!_MMVAD


 

 

调试器命令!vad显示了进程的VAD结构体信息。

 

APIs:

  • ZwAllocateVirtualMemory()

  • ZwMappedViewOfSection()

  • MmMapLockedPagesSpecifyCache()



缓存管理器


nt!_VACB


 

内核全局变量CcNumberOfFreeVacbs和CcNumberOfFreeHighPriorityVacbs一起决定了可以分配的VACB的数量。所有这样的VACB都被保存在一个CcVacbFreeList或CcVacbFreeHighPriorityList链表中。这一链接字段就是为此而准备的。

 

 

SharedCacheMap字段指向了共享缓存映射结构,它包含该VACB并描述了VACB映射到该视图中的文件映射的区段。

 

ArrayHead字段指向VACB_ARRAY_HEADER结构体,它包含了VACB。

 

调试器命令!filecache显示了使用中的VACB数据结构信息。


nt!_VACB_ARRAY_HEADER


4095个VACB块是一起被分配的,同时分配了一个头VACB_ARRAY_HEADER,它用于管理VACB。VACB_ARRAY_HEADER结构附着在VACB数组的后面。

 

 

内核变量CcVacbArrays指向了一个指针数组,这些指针指向VACB_ARRAY_HEADER结构体。VacbArrayIndex字段是特定VACB_ARRAY_HEADER结构体在数组中的索引值。变量CcVacvArraysHighestUsedIndex包含数组中上一次使用的那个索引值。该数组受CcVacbSpinLock这个队列自旋锁(queued spinlock)保护。

 

VACB_ARRAY_HEADER头结构的数量被存储在全局变量CcVacbArraysAllocated中,它包含了当前整个系统分配的头结构,它们由CcVacbArrays指向。


nt!_SHARED_CACHE_MAP


 

通过同一个SHARED_CACHE_MAP结构体可以访问相同文件流的所有映射的VACB。共享缓存映射结构保证了该文件的特定区段永远不会在缓存中除此之外的映射。

 

全局变量CcDirtySharedCacheMapList包含了所有的SHARED_CACHE_MAP结构体链表,它们包含携带脏数据的视图。链表中有一个特殊的项—全局变量CcLazyWriterCursor,以它开始的SHARED_CACHE_MAP结构体的子链都是懒回写(lazy written)。在每个懒回写完成后,CcLazyWriterCursor都在CcDirtySharedCacheMapList中移动。

 

包含了没有任何脏页的视图的SHARED_CACHE_MAP结构体被保存在全局链表CcCleanSharedCacheMapList中。SharedCacheMapLinks字段用于组织这两种队列(dirty or clean)。

 

SectionSize字段决定了通过SHARED_CACHE_MAP映射的区段的大小。

 

InitialVacbs字段是一个内建的4 VACB指针数组,它用于映射那些小于1MB的文件区段。如果区段大小超过了1MB,一个128 VACB的指针数组会被分配并存储在Vacbs字段中,它指向了当前可以描述文件到32MB(也就是128*256K)大小的VACBs。


如果区段尺寸超过32MB,则指针数组的128个指针都用来指向另一个128 VACB指针数组。这种额外的层级设计使得区段大小可以达到4GB(128*128*256K)。一共可以有7层VACB,尺寸也就是(128^7*256K),这将远高于缓存管理器提供的最大区段尺寸(2^63)。

 

函数CcCreateVacbArray()层级了VACB数组,所有的VACB数组都由一个推锁(VacbLock字段)保护。

 

PrivateList是一个链表的头,它用于保存和每个文件打开实例,也就是FILE_OBJECT相关联的PRIVATE_CACHE_MAP结构体。PRIVATE_CACHE_MAP.PrivateLinks字段用于形成链表。

 

调试命令!fileobj可以显示关于SECTION_OBJECT_POINTER结构体的信息,它包含一个指向SHARED_CACHE_MAP的指针。


nt!_PRIVATE_CACHE_MAP


缓存管理器执行一个文件的智能预阅读来提升性能。这些预阅读在每个特定文件打开的实例上都独立执行。和每个文件打开实例相关联的PRIVATE_CACHE_MAP结构体,保存了一个上次文件阅读操作的历史记录,它被缓存管理器用于执行智能预阅读操作。

 

FILE_OBJECT.PrivateCacheMap指向了和文件打开实例相关联的PRIVATE_CACHE_MAP结构体。当该文件缓存被激活时该字段由CcInitializeCacheMap()初始化,同样当缓存被清掉时则通过CcUninitializeCacheMap()完成。

 

FileObject字段,指向了和PRIVATE_CACHE_MAP相关联的FILE_OBJECT。预阅读操作仅在FILE_OBJECT.Flags字段的FO_RANDOM_ACCESS位未置位时才会执行。

 

SHARED_CACHE_MAP.PrivateList指向所有打开的特定文件的实例的PRIVATE_CACHE_MAP结构体。特定文件的打开实例的PRIVATE_CACHE_MAP结构体通过PrivateLinks字段链在一起。

 

FileOffset1,FileOffset2,BeyondLastBye,BeyondLastByte2四个字段一起用于决定在特定FILE_OBJECT对应的文件上阅读的模式。缓存管理器函数CcUpdateReadHistory()用来更新这些读操作的计数。

 

Flags.ReadAheadEnabled字段决定了预读操作对该文件打开实例来说是否是需要的。Flags.ReadAheadActive字段由CcScheduleReadAhead()设置,标志了ReadAheadWorkItem字段的读工作例程当前是否是活跃的(active)。

 

调试命令!fileobj显示了关于PRIVATE_CACHE_MAP的信息。

 

APIs:

  • CcInitializeCacheMap()

  • CcUninitializeCacheMap()

  • CcIsFileCached()


nt!_SECTION_OBJECT_POINTERS


SECTION_OBJECT_POINTERS和文件对象相关联,它指向了文件映射以及该文件的缓存相关信息。一个单一文件可以有2个分离的映射,一个为可执行文件,另一个为数据文件。

 

DataSectionObject字段指向了控制区域,它是一种服务于内存管理器和文件系统之间的一个链接,它用于内存映射数据文件。

 

ImageSectionObject字段指向另一个控制区域结构,它用于内存映射可执行文件。一个数据映射由单一连续的具有相同保护属性的VA组成,一个映像映射则由可执行体不同的区段映射的多个不同保护属性的区域组成。

 

SharedCacheMap字段指向了文件的SHARED_CACHE_MAP结构,它描述了该文件被缓存在那里,缓存了哪些部分。

 

上面提到的SECTION_OBJECT_POINTERS结构体中的字段,是由内存管理器和文件系统驱动配合该文件对象的SECTION_OBJECT_POINTERS设置的。

 

APIs:

  • CcFlushCache()

  • CcPurgeCacheSection()

  • CcCoherencyFlushAndPurgeCache()

  • MmFlushImageSection()

  • MmForceSectionClosed()

  • MmCanFileBeTruncated()

  • MmDoesFileHaveUserWritableReferences()

  • CcGetFileObjectFromSectionPtrsRef()

  • CcSetFileSizes()



关键的Windows内核数据结构一览(下)


关键的Windows内核数据结构一览(下)

看雪ID:玉涵                                 

https://bbs.pediy.com/user-530966.htm



本文由看雪论坛 玉涵 原创

转载请注明来自看雪社区


关键的Windows内核数据结构一览(下)


关键的Windows内核数据结构一览(下)



好书推荐: