《分页和非分页内存》由会员分享,可在线阅读,更多相关《分页和非分页内存(4页珍藏版)》请在金锄头文库上搜索。
1、何谓可分页和非分页内存默认情况下,内核加载器会加载所有的代码部分和全局数据到非分页内存中。而且,加载器 是一次加载整个驱动的可执行文件,包括相关的DLL。加载后,内核加载器关闭驱动程序文件, 甚至你可以删除当前正在执行的驱动文件。但是,你可以告诉加载器你希望驱动的哪部分是可分页,所谓可分页,就是可能会被换页出内存 (Page out)。可以使用下面的指令来实现:#define ALLOC_PRAGMA#pragma alloc_text(PAGE, function_name1)#pragma alloc_text(PAGE, function_name2)#endif由function_na
2、mex指定的函数代码将被放置于可分页内存中。使数据段可分页,使用下面的编译指令:#ifdef ALLOC_PRAGMA#pragma data_seg(PAGE) / define your pageeble data section module here.#pragma data_seg()要注意,绝不能让可能在高的IRQL级别被调用的例程被换出页面。可以调用 MmLockPageableCodeSection 和 MmLockPageableCodeSection- ByHandle来锁定被标志为可分页的代码段。可以调用 MmLockPageableDataSection 和 MmLoc
3、kPageableDataSectionB - yHandle来锁定被标志为可分页的数据段可以调用MmUnlockPageablelmageSection来解除被上面列出的函数锁定的代码 或数据段。可以调用MmPageEntireDriver使整个驱动程序可分页,覆盖使用编译指令修饰的段的页面属 性。可以调用MmResetDriverPaging把页面属性重设回最初描述的属性。最后,把那些驱动初始化后不再需要的代码自动丢弃可以使用这些编译指令:#ifdef ALLOC_PRAGMA#pragma alloc_text(INIT, DriverEntry)#pragma alloc_text(I
4、NIT, function_name) / function called by driverEntry#endif驱动程序在执行时可能需要动态分配内存空间,这时你要决定需要的是可分页还是不可分页的内 存。如果你的驱动在运行中访问内存的时候能够经受页错误,那么尽量使用可分页内存。注意:大多数低层磁盘和网络驱动通常不能使用可分页内存,因为他们的代码常常在较高的IRQL 等级执行而不允许页错误。但是,文件系统(通常比磁盘驱动占用更大,更多资源)有时候可从 可分页池中分配一些内存。非分页内存在整个系统中是一个有限的资源,其数量依赖于系统使用的类型,和系统可用的 物理内存。NT提供下面的例程给内核驱动
5、来分配内存:ExAllocatePoolExAllocatePoolWithQuotaExAllocatePoolWithTag ExAllocatePoolWithQuotaTag调用这些函数来请求内存时,必须要指定请求的内存的类型:NonPagedPool 请求分配一个不可分页的内存 PagedPool请求分配一个可分页的内存如果你在分配的内存里有任何同步结构的话,决不要分配分页内存。 当你的应用访问内存时候可以处理页错误的时候,应该指定这个类型。NonPagedPoolMustSucceed在其它方式都失败时,而你又必须立即得到内存的时候可以使用这个标志类型。注意这 种类型的内存是极度缺
6、乏的资源,可能不足16K。注意,只有在其它途径都失败的时候才使用, 如果分配失败,将会导致系统的bugcheck,错误代码是MUST_SUCCEED_POOL_EMPTY。NonPagedPoolCacheAligned这个标志分配使用数据缓存线的尺寸来在CPU特定的边界对齐的非分页内存。注意这 个操作默认是在Intel平台上的NonPagedPool分配类型。PagedPoolCacheAligned这个标志分配使用数据缓存线的尺寸来在CPU特定的边界对齐的分页内存。NonPagedPoolCacheAlignedMustSucceed参考 NonPagedPoolMustSucceed 和
7、 NonPagedPoolCacheAligned内存池分配器初始化了一些列表,每个列表包含一种固定大小的块。当你使用上面的函数请 求内存时,例程试图分配一个和你请求数量相近的或更大一点的固定大小的块。但是,如果你要 求的数量超过一页时,或者超过列表中最大块的大小时,又或者在预先分配的列表中没有可用的 块的时候,VMM就会从任何适当类型的系统可用的内存中分配你请求的数量内存给你。当预先分配的列表空了的时候,VMM会分配至少一页的内存,切分,然后把剩下的数据放 进适当的块列表中。但是,当你请求的非分页内存的数量超过PAGE_SIZE时候,内存池分配 例程不会切分未使用的部分,这会浪费宝贵的非分页
8、内存。也可以使用 MmAllocateNonCachedMemory 或 MmAllocateContiguousMemory来分配非分页或物理连续内存。它们通常不使用在文件系统或者过滤驱动中,而是用于执行池例 程或者其它结构。内核驱动如果重复的分配和释放小块的内存(小于一个PAGE_SIZE),可能导致系统的可 用物理内存碎片化。这会给系统带来各种问题,包括降低系统的性能等。有一个方法可以避免系 统碎片化,就是预先分配一块合理大小的内存,然后自已管理,在这个预先分配的块中分配和释 放小块的内存,但这种方法有可能会浪费核心内存。用池来管理内存上面提到用预先分配一块合理大小的内存来自已管理,可以
9、避免系统内存碎片。我们可以用 池来管理这块预先分配的内存。必须再次强调,预先分配的内存大小必须足够准确,太大会浪费 宝贵的资源。调用ExAllocatePool来分配池使用的内存,你要选择从分页或者非分页的池中分配,注意 你的内存片基址必须在8字节的边界对齐。还要分配和初始化一个自旋锁或者使用其它的同步机制来保护对内存块列表的修改。注意不 要在比DISPATCH_LEVEL更高的IRQL等级使用池操作例程,因为在更高的IRQL等级不 能使用同步结构。然后定义一个ZONE_HEADER结构的全局变量,用来作为这个池的控制结构,并调用ExInitializeZone来初始化池头部。然后,就可以通过
10、调用ExAllocateFromZone和 ExInterlockedAllocateFromZone来分配自已管理的内存块。这两个函数的差别在于后者使用了 自旋锁用于操作同步。调用ExFreeToZone和ExInterlockedFreeToZone来释放分配的内存。虽然池帮助减少系统内存的碎片,但池还是有一些不足:1、驱动程序必须预先为池分配内存,这些内存可能会闲置很久造成内存浪费2、你对需要的内存的数量必须相当的精确,在很多时候这个很难做到。3、当内存需求增大时,可以扩大池的尺寸,但是却不能减小池的尺寸,直到重启系统。lookaside listslookaside lists是NT4
11、.0里新的特性,它突破了池的限制。当你调用 ExInitializeNPagedLookasideList 和 ExInitializePagedlookasideList 初始化lookaside lists时不用预先分配内存,相反,只有当你有真正需要内存的时候才分配。在初始化时,你必须指定列表的深度,表示尺寸的最大值。相关的函数有ExAllocateFromNPagedLookasideList 和 ExAllocateFromPagedLookasideList。我们用一个 NPAGED_LOOKASIDE_LIST 或 PAGED_LOOKASIDE_LIST 结构变量来保存 lookaside lists 的状态,注意这结构一定要从非分页内存中分配。参考NT文件系统内幕