驱动程序内存分配

转载 2007年09月29日 09:17:00
 出处:http://blog.csdn.net/lbird/archive/2007/02/12/1508603.aspx

何谓可分页和非分页内存


      
默认情况下,内核加载器会加载所有的代码部分和全局数据到非分页内存中。而且,加载器是一次加载整个驱动的可执行文件,包括相关的DLL。加载后,内核加载器关闭驱动程序文件,甚至你可以删除当前正在执行的驱动文件。
但是,你可以告诉加载器你希望驱动的哪部分是可分页,所谓可分页,就是可能会被换页出内存(Page out)。可以使用下面的指令来实现:
#define ALLOC_PRAGMA
#pragma alloc_text(PAGE, function_name1)
#pragma alloc_text(PAGE, function_name2)

#endif
      

       function_namex 指定的函数代码将被放置于可分页内存中。
使数据段可分页,使用下面的编译指令:
#ifdef ALLOC_PRAGMA
#pragma data_seg(PAGE)

//
define your pageeble data section module here.
#pragma data_seg()

要注意,绝不能让可能在高的IRQL级别被调用的例程被换出页面。

      
可以调用MmLockPageableCodeSection MmLockPageableCodeSection-
ByHandle
来锁定被标志为可分页的代码段。
可以调用MmLockPageableDataSection MmLockPageableDataSectionB-
yHandle
来锁定被标志为可分页的数据段
可以调用MmUnlockPageableImageSection 来解除被上面列出的函数锁定的代码
或数据段。
可以调用MmPageEntireDriver 使整个驱动程序可分页,覆盖使用编译指令修饰的段的页面属性。
可以调用MmResetDriverPaging 把页面属性重设回最初描述的属性。

      
最后,把那些驱动初始化后不再需要的代码自动丢弃可以使用这些编译指令:
#ifdef ALLOC_PRAGMA
#pragma alloc_text(INIT, DriverEntry)

#pragma alloc_text(INIT, function_name) // function called by driverEntry

#endif
驱动程序在执行时可能需要动态分配内存空间,这时你要决定需要的是可分页还是不可分页的内存。如果你的驱动在运行中访问内存的时候能够经受页错误,那么尽量使用可分页内存。

注意:大多数低层磁盘和网络驱动通常不能使用可分页内存,因为他们的代码常常在较高的IRQL等级执行而不允许页错误。但是,文件系统(通常比磁盘驱动占用更大,更多资源)有时候可从可分页池中分配一些内存。

 

       非分页内存在整个系统中是一个有限的资源,其数量依赖于系统使用的类型,和系统可用的物理内存。NT提供下面的例程给内核驱动来分配内存:

ExAllocatePool

ExAllocatePoolWithQuota

ExAllocatePoolWithTag

ExAllocatePoolWithQuotaTag

调用这些函数来请求内存时,必须要指定请求的内存的类型:

NonPagedPool    请求分配一个不可分页的内存

PagedPool         请求分配一个可分页的内存

          如果你在分配的内存里有任何同步结构的话,决不要分配分页内存。

          当你的应用访问内存时候可以处理页错误的时候,应该指定这个类型。

NonPagedPoolMustSucceed

             在其它方式都失败时,而你又必须立即得到内存的时候可以使用这个标志类型。注意这种类型的内存是极度缺乏的资源,可能不足16K。注意,只有在其它途径都失败的时候才使用,如果分配失败,将会导致系统的bugcheck,错误代码是 MUST_SUCCEED_POOL_EMPTY

 

NonPagedPoolCacheAligned

          这个标志分配使用数据缓存线的尺寸来在CPU特定的边界对齐的非分页内存。注意这个操作默认是在Intel平台上的 NonPagedPool 分配类型。

 

PagedPoolCacheAligned

          这个标志分配使用数据缓存线的尺寸来在CPU特定的边界对齐的分页内存。

 

NonPagedPoolCacheAlignedMustSucceed

          参考NonPagedPoolMustSucceed NonPagedPoolCacheAligned

 

 

       内存池分配器初始化了一些列表,每个列表包含一种固定大小的块。当你使用上面的函数请求内存时,例程试图分配一个和你请求数量相近的或更大一点的固定大小的块。但是,如果你要求的数量超过一页时,或者超过列表中最大块的大小时,又或者在预先分配的列表中没有可用的块的时候,VMM就会从任何适当类型的系统可用的内存中分配你请求的数量内存给你。

       当预先分配的列表空了的时候,VMM会分配至少一页的内存,切分,然后把剩下的数据放进适当的块列表中。但是,当你请求的非分页内存的数量超过PAGE_SIZE时候,内存池分配例程不会切分未使用的部分,这会浪费宝贵的非分页内存。

也可以使用 MmAllocateNonCachedMemory MmAllocateContiguousMemory

来分配非分页或物理连续内存。它们通常不使用在文件系统或者过滤驱动中,而是用于执行池例程或者其它结构。

       内核驱动如果重复的分配和释放小块的内存(小于一个PAGE_SIZE, 可能导致系统的可用物理内存碎片化。这会给系统带来各种问题,包括降低系统的性能等。有一个方法可以避免系统碎片化,就是预先分配一块合理大小的内存,然后自已管理,在这个预先分配的块中分配和释放小块的内存,但这种方法有可能会浪费核心内存。

 

用池来管理内存

       上面提到用预先分配一块合理大小的内存来自已管理,可以避免系统内存碎片。我们可以用池来管理这块预先分配的内存。必须再次强调,预先分配的内存大小必须足够准确,太大会浪费宝贵的资源。

       调用 ExAllocatePool 来分配池使用的内存,你要选择从分页或者非分页的池中分配,注意你的内存片基址必须在8字节的边界对齐。

       还要分配和初始化一个自旋锁或者使用其它的同步机制来保护对内存块列表的修改。注意不要在比 DISPATCH_LEVEL 更高的 IRQL 等级使用池操作例程,因为在更高的 IRQL等级不能使用同步结构。

       然后定义一个ZONE_HEADER结构的全局变量,用来作为这个池的控制结构,并调用ExInitializeZone来初始化池头部。然后,就可以通过调用ExAllocateFromZone

ExInterlockedAllocateFromZone 来分配自已管理的内存块。这两个函数的差别在于后者使用了自旋锁用于操作同步。调用ExFreeToZone ExInterlockedFreeToZone来释放分配的内存。

       虽然池帮助减少系统内存的碎片,但池还是有一些不足:

1、 驱动程序必须预先为池分配内存,这些内存可能会闲置很久造成内存浪费

2、 你对需要的内存的数量必须相当的精确,在很多时候这个很难做到。

3、 当内存需求增大时,可以扩大池的尺寸,但是却不能减小池的尺寸,直到重启系统

 

lookaside lists

lookaside lists NT4.0里新的特性,它突破了池的限制。

       当你调用 ExInitializeNPagedLookasideList ExInitializePagedlookasideList初始化 lookaside lists 时不用预先分配内存,相反,只有当你有真正需要内存的时候才分配。

在初始化时,你必须指定列表的深度,表示尺寸的最大值。相关的函数有ExAllocateFromN-

PagedLookasideList ExAllocateFromPagedLookasideList。我们用一个 NPAGED_

LOOKASIDE_LIST PAGED_LOOKASIDE_LIST结构变量来保存lookaside lists的状态,注意这结构一定要从非分页内存中分配。

 参考 《NT文件系统内幕》

驱动程序内存分配

 何谓可分页和非分页内存       默认情况下,内核加载器会加载所有的代码部分和全局数据到非分页内存中。而且,加载器是一次加载整个驱动的可执行文件,包括相关的DLL。加载后,内核加载器关闭驱动程序文...
  • lbird
  • lbird
  • 2007年02月12日 17:36
  • 6066

《Linux设备驱动程序》——分配内存

一、kmalloc函数的内幕
  • Explorer_day
  • Explorer_day
  • 2014年11月17日 11:13
  • 1240

解析STL中典型的内存分配

1 vector 在C++中使用vector应该是非常频繁的,但是你是否知道vector在计算内存分配是如何么?        在c++中vector是非常类似数组,但是他比数组更加灵活,这就表...
  • wallwind
  • wallwind
  • 2012年03月23日 02:26
  • 3816

嵌入式操作系统内核原理和开发(固定内存分配算法)

【 声明:版权所有,欢迎转载,请勿用于商业用途。  联系信箱:feixiaoxing @163.com】      固定内存方式是最简单的方法,也是最容易想到的方法。所谓的固定内存,就是所有分配的内存...
  • feixiaoxing
  • feixiaoxing
  • 2012年06月24日 22:39
  • 3991

Linux内存布局、内存分配原理

Linux的虚拟内存管理有几个关键概念 1.每个进程有独立的虚拟地址空间,进程访问的虚拟地址并不是真正的物理地址 2.虚拟地址可通过每个进程的页表与物理地址进行映射,获得真正物理地址 3....
  • b2222505
  • b2222505
  • 2017年05月21日 22:09
  • 2328

内存分配算法

(1)首次适应算法。使用该算法进行内存分配时,从空闲分区链首开始查找,直至找到一个能满足其大小要求的空闲分区为止。然后再按照作业的大小,从该分区中划出一块内存分配给请求者,余下的空闲分区仍留在空闲分区...
  • u014427196
  • u014427196
  • 2016年09月09日 11:14
  • 545

《深入理解Java虚拟机》读书笔记——内存分配与回收策略

概述JVM采用分代的垃圾回收策略:不同对象的生命周期是不一样的。目前JVM分代主要是分三个年代: 新生代:所有新创建的对象都首先在新生代进行内存分配。新生代具体又分为3个区,一个Eden区、一个Fro...
  • zinss26914
  • zinss26914
  • 2015年08月17日 11:21
  • 1376

Windows内存分配(转)

堆和栈的区别 一、预备知识—程序的内存分配 一个由C/C++编译的程序占用的内存分为以下几个部分 1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类...
  • wudijunjun
  • wudijunjun
  • 2011年01月15日 06:15
  • 2727

python源码分析----内存分配(2)

上一篇说到了,在python的内存分配中两个非常重要的方法:PyObject_Malloc和PyObject_Free 在具体的来这两个方法之前,先要看看别的一些东西...
  • fjs_cloud
  • fjs_cloud
  • 2015年05月24日 20:36
  • 1002

Redis内存分配简单分析

Redis内存管理 1.Redis内存申请内存方式有三种: (1)系统自带的malloc/free方式进行申请/释放。 (2)使用tcmal...
  • innobase
  • innobase
  • 2016年05月03日 09:44
  • 312
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:驱动程序内存分配
举报原因:
原因补充:

(最多只允许输入30个字)