转载请标明是引用于 http://blog.csdn.net/chenyujing1234
欢迎大家拍砖!
参考书籍<<Windows驱动开发技术详解>>
在驱动程序编写中,分配和管理内存不能使用熟知的Win32 API函数,取而代之的是DDK提供的高效的内核函数。程序员
必须小心地使用这些内存相关的内核函数,因为在内核模式下,OS不会检查内存使用的合法性。
1、 内存管理概念
1、1 物理内存概念(Physical Memory Address)
PC上有三条总线,分别是数据总线,地址总线和控制总线。32位的CPU的寻址能力为4G(2的32次方)个字节。用户最多可以使用4G的真实的物理内存。
PC中会拥有很多设备,其中很多设备都提供了自己的设备内存。例如,显卡就会提供自己的显存。这部分内在会映射到PC的物理内存上,
也就是读写这段物理地址,其实会读写的设备内存地址,而是不会写物理内存地址。
下图是我PC中显卡的显存地址,一个设备可以有好几块设备内存映射到物理内存上:
1、2 虚拟内存地址概念(Virtual Memory Address)
虽然可以寻址4G的内存,而在PC 往往没有如此多的真实物理内存。操作系统和硬件(这里指的是CPU中的内存管理单元MMU)为使用者提供了虚拟内在的根据。
Windows的所有程序(包括Ring0层和Ring3层的程序)可以操作的都是虚拟内存。之所以称为虚拟内存,是因为对它的所有操作,最终会变成一系列对真实内存的操作。
转换的过程是,在CPU中有一个重要的寄存器CR0,它是32位的寄存器,其中一个位(PG位)是负责告诉系统是否分页的。
Windows在启动前会将它的PG位置置1,即Windows允许分页。DDK中有个宏PAGE_SIZE记录着分页的大小,一般为4KB.
其中有一部分单元会和物理内存对应起来,即虚拟内存中第N个分页单元对应着物理内存第M个分页单元。这种对应不是一一对应,而是多对一映射的,多个虚拟内存页
可以映射到同一个物理内存页。
还有一部分单元会被映射成磁盘上的文件,并标记为脏的(Dirty)。读取这段虚拟内存时,系统会发出一个异常,此时会触发异常处理函数,异常处理函数会将
这个分页的磁盘文件读入内存,并将标记设置为不脏。当让经常不读写的内存页,可以交替(Swap)成文件,并将此页设置为脏。
还有一部分单元什么也没有对应,即空的。
1、3 用户模式地址和内核模式地址
虚拟地址在0-0x7FFFFFFF范围内的虚拟内存,即低2GB的虚拟地址,被称为用户棋式地址,而0x80000000-0xFFFFFFFF范围内的虚拟内存,即高2G的虚拟内存,
被称为内核模式地址。Windows规定运行在用户态(Ring3层)的程序,只能访问用户模式地址,而运行在核心态(Ring0层)的程序,可以访问整个4G的虚拟内存,即
用户模式地址和内核模式地址。
Windows的核心代码和Windows的驱动程序加载的位置都是在高2G的内核地址里,所以一般应用程序是不能访问到这些核心代和重要数据的。这大大提高了系统的稳健性。
同时Windows操作系统在进程切换时,保持内核模式的地址是完全相同的,也就是说,所有进程的内核地址映射完全一致,进程切换时,只改变用户模式地址的映射。
1、4 Windows驱动程序和进程的关系
驱动程序可以看成是一个特殊的DLL,文件被应用程序加载到虚拟内存中,只不过加载地址是内核模式地址,而不是用户模式地址。
它能访问的只是这个进程的虚拟内存,而不能是其他进程的虚拟地址。
Window驱动程序里的不同例程运行在不同的进程中。
DriverEntry例程和AddDevice例程是运行在系统(System)进程中的,这个进程是Window中非常重要的进程,也是Windos第一个运行的进程。
当需要加载的时候,这个进程中会有一个线程将驱动程序加载到内核模式地址空间内,并调用DriverEntry例程。
而其它一些例程,如IRP_MJ_READ和IRP_MJ_WRITE的派遣函数会运行于应用程序的“上下文”中,所谓运行在进程的“上下文”,指的是运行于某个进程的环境中,
所能访问的虚拟地址是这个进程的虚拟地址。
有个技巧可以看出代码是运行于某个进程的上下文中,在代码中打印一行log信息,这行信息打印出当前进程的进程名。如果当前进程是发起I/O请求的进程,
则说明在进程的“上下文”中。下面给出的函数可以显示出当前进程的进程名,我们可以在任意的例程中调用这个函数,然后用DebugView软件查看LOG信息。
VOID DisplayItsProcessName(){ // 得到当前进程 PEPROCESS pEProcess = PsGetCurrentProcess(); // 得到当前进程名称 PTSTR ProcessName = (PTSTR)((ULONG)pEProcess + 0x174); KdPrint(("%s\n", ProcessName));}
其中,PsGetCurrentProcess函数是得到当前运行的进程,它是EPROCESS的结构体,然而EPROCESS这个结构体是微软没有公开的结构体,其中0x174偏移的位置,
记录一个字符串指针。有兴趣的,可以利用WinDbg工具,查看该结构体的内容。和版本Windows的该结构,可能有些差别,下面是在Winows XP SP2的机器上用
WindDbg查看该结构的结果。如下:
1、5 分页和非分页内存
Windows规定有些虚拟内存页面是可以交换到文件中的,这类内存被称为分页内存;
而有些虚拟内存页永远不会交换到文件中,这些内存被称为非分页内存。
当程序的中断请求级在DISPATCH_LEVEL之上时(包括DISPATCH_LEVEL层),程序只能使用非分页内存,否则会导致蓝屏死机。
在编译DDK提供的例程时,可以指定某个例程和某个全局变量是载入分页内存还是非分页内存,需要自己做如下定义:
#define PAGEDCODE code_seg("PAGE")#define LOCKEDCODE code_seg()#define INITCODE code_seg("INIT")#define PAGEDDATA data_seg("PAGE")#define LOCKEDDATA data_seg()#define INITDATA data_seg("INIT")
如果将某个函数载入到分页内存中,我们需要在函数的实现中加入以下代码:
#pragma PAGEDCODEVOID SomeFunction(){ PAGED_CODE();}
其中,PAGED_CODE是DDK提供的宏,它只在check版本中生效,它会检查这个函数是否运行低于DISPATCH_LEVEL的中断请求级,如果
等于或高于这个中断请求级,将产生一个断言。
#if DBG#define PAGED_CODE() \ { if (KeGetCurrentIrql() > APC_LEVEL) { \ KdPrint(( "EX: Pageable code called at IRQL %d\n", KeGetCurrentIrql() )); \ ASSERT(FALSE); \ } \ }#else#define PAGED_CODE() NOP_FUNCTION;#endif
如果让函数加载到非分页内存中,需要在函数的实现加如下代码:
#pragma LOCKEDCODEVOID SomeFunction(){ // 做一些其他事情}
还有一种特殊情况,就是某个例程需要在初始化的时候载入内存,然后就可以从内存中卸载掉。这种情况指出现在DriverEntry情况下;
尤其是NT式的驱动,DriverEntry会很长,占据很大的空间,为了节省内存,需要及时从内存中卸载掉。代码如下:
#pragma INITCODEextern "C" NTSTATUS DriverEntry ( IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING pRegistryPath ) { // 做一些事情}
1、6 分配内核内存
Windows驱动程序使用的内存资源非常珍贵,分配内存时要尽量节约。和应用程序一样,局部变量是在存放在(Stack)空间中的,但栈空间不会像应用程序那么大,
所以驱动程序不适合递归调用或局部变量是大型结构体。如果需要大型结构体,请在堆(Heap)中申请。
堆中申请内存的函数有以下几个,原型如下:
NTKERNELAPIPVOIDExAllocatePool( IN POOL_TYPE PoolType, IN SIZE_T NumberOfBytes );NTKERNELAPIPVOIDNTAPIExAllocatePoolWithTag( IN POOL_TYPE PoolType, IN SIZE_T NumberOfBytes, IN ULONG Tag );NTKERNELAPIPVOIDNTAPIExAllocatePoolWithQuota( IN POOL_TYPE PoolType, IN SIZE_T NumberOfBytes, );NTKERNELAPIPVOIDExAllocatePoolWithQuotaTag( IN POOL_TYPE PoolType, IN SIZE_T NumberOfBytes, IN ULONG Tag );
** PoolType是个枚举变量,如果此值为NonPagedPool,则分配非分页内存,如果此值为PagedPool,则分配内存为分页内存、、、
typedef enum _POOL_TYPE { NonPagedPool, PagedPool, NonPagedPoolMustSucceed, DontUseThisType, NonPagedPoolCacheAligned, PagedPoolCacheAligned, NonPagedPoolCacheAlignedMustS, MaxPoolType
** NumberOfBytes是分配内存的大小,注意最好是4的倍数。
** 返回值分配的内存地址,一定是内核模式地址,如果返回0,则代表分配失败。
以上四个函数功能类似,函数以WithQuota结尾的代码分配时按配额分配;
函数以WithTag结尾的函数,和ExtAllocatePool功能类似,唯一不同的是多了一个Tag参数,系统在要求的内存外又额外多分配了4个字节的标签。在调试时,可以找到是否有标有这个标签的内存没有被释放。
将分配的内存,进行回收的函数是ExtFreePool()和ExtFreePoolWithTag。
2、在驱动中使用链表
在驱动程序开发中,经常使用链表这种数据结构,DDK为用户提供两种链表的数据结构,简化了对链表的操作。
链表中可以记录将整型、浮点、字符型或者程序员自定义的数据结构。链表通过指针将这些数据结构组成一个“链”,链中每个元素对应着记录的数据。
对于单向链表,每个元素中有一个Next指针指向一下元素。对于双向链表,每个元素有两个指针:BLINK指针指向前一个元素,FLINK指向向一个元素。
2、1 链表结构
DDK提供了标准的双向链表,双向链表可以将链表形成一个环。BLINK指针指向前一个元素,FLINK指向向一个元素。
以下是DDK提供的双向链表的数据结构。读者会奇怪这个数据只有指向前后的指针,而没有数据,这个问题在下一节介绍。
typedef struct _LIST_ENTRY { struct _LIST_ENTRY *Flink; struct _LIST_ENTRY *Blink;} LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;
2、2 链表初始化
每个双向链表都是以一个链表头作为链表的第一个元素。初次使用链表需要初始化,主要将链表头FLink和BLink两个指针指向自己,
这意味着链表空。如下所示。
如何判断链表是否为空,可以判断链表头的FLink和Blink是否指向自己。DDK为程序员提供一个宏简化这种检查,这就是IsListEmpty。
#define IsListEmpty(ListHead) \ ((ListHead)->Flink == (ListHead))
程序员需要自己定义链表中每个元素的数据类型,并将LIST_ENTRY结构作为自定义结构的一个子域。LIST_ENTRY的作用是将自定义的
数据结构串成一个链表。
typedef struct _WORK_QUEUE_ITEM { LIST_ENTRY List; PWORKER_THREAD_ROUTINE WorkerRoutine; PVOID Parameter;} WORK_QUEUE_ITEM, *PWORK_QUEUE_ITEM;
2、3 从首部插入链表
对链表的插入有两种方式,一种是在链表的头部插入,一种是在链表的尾部插入。
在头部使用的语句是InsertHeadList,用法如下:
InsertHeadList(&targetPortGroup->TargetPortList, &listEntry->ListEntry);
其中targetPortGroup->TargetPortList是LIST_ENTRY结构的链表头,listEntry是用户定义的数据结构,而它的子域ListEntry是包含其中的LIST_ENTRY数据结构。
假设链表中已经有一个元素Entry1,如下图,现在将另外一个元素Entry2插入链表,InsertHeadList插入后的结果如下:
2、4 从尾部插入链表
使用语句InsertTailList,
2、5 从链表删除
和插入链表一样,删除也有两种对应的方法,一种是从链表头删除,一种是从链表尾部删除。分别对应RemoveHeadList和RemoveTailList函数。用法如下:
Entry = RemoveHeadList( &VolDo->OverflowQueue );
RemoveTailList( &InstCtx->QueueHead );
其中VolDo->OverflowQueue是链表头,Entry是从链表删除下来的元素中的ListEntry,这里有一个问题,
就是如何从pEntry得到用户自定义的数据结构的指针,有以下两种情况。
(1)当自定义数据结构的第一个字段是LIST_ENTRY时,如:
typedef struct _WORK_QUEUE_ITEM { LIST_ENTRY List; PWORKER_THREAD_ROUTINE WorkerRoutine; PVOID Parameter;} WORK_QUEUE_ITEM, *PWORK_QUEUE_ITEM;
此时,RemoveHeadList 返回的指针可以当做用户自定义的指针,即:
PLIST_ENTRY pEntry = RemoveHeadList(&head);PWORK_QUEUE_ITEM pMyData = (PWORK_QUEUE_ITEM)pEntry;
(2)当自定义的数据结构第一个字段不是LIST_ENTRY时,如:
typedef struct _IRP_CONTEXT_LITE { // Type and size of this record (must be CDFS_NTC_IRP_CONTEXT_LITE) NODE_TYPE_CODE NodeTypeCode; NODE_BYTE_SIZE NodeByteSize; // Fcb for the file object being closed. PFCB Fcb; // List entry to attach to delayed close queue. LIST_ENTRY DelayedCloseLinks; // User reference count for the file object being closed. ULONG UserReference; // Real device object. This represents the physical device closest to the media. PDEVICE_OBJECT RealDevice;} IRP_CONTEXT_LITE;
此时,RemoveHeadList 返回的指针不能当做用户的自定义的指针。
此时需要通过返回指针的地址逆向算出自定义数据的指针。一般通过RemoveHeadList返回指针在自定义数据中的偏移量,
用返回指针减去这个偏移量,就会得到用户自定义结构的指针的地址。
如下图,指针A是自定义的数据地址,指针B是自定义数据结构中的pEntry 。
为了简化这个操作,DDK为程序员提供了宏CONTAINING_RECORD,其用法如下:
PIRP_CONTEXT_LITE NextIrpContextLite = CONTAINING_RECORD( Entry, IRP_CONTEXT_LITE, DelayedCloseLinks );
CONTAINING_RECORD的第一参数是相当于上图中的指针B,第二个参数是数据结构的名字,第三个参数是数据结构中的字段,返回相当于上图中的A。
2、6 实验
#pragma INITCODEVOID LinkListTest() { LIST_ENTRY linkListHead; //初始化链表 InitializeListHead(&linkListHead); PMYDATASTRUCT pData; ULONG i = 0; //在链表中插入10个元素 KdPrint(("Begin insert to link list")); for (i=0 ; i<10 ; i++) { pData = (PMYDATASTRUCT) ExAllocatePool(PagedPool,sizeof(MYDATASTRUCT)); pData->number = i; InsertHeadList(&linkListHead,&pData->ListEntry); } //从链表中取出,并显示 KdPrint(("Begin remove from link list\n")); while(!IsListEmpty(&linkListHead)) { PLIST_ENTRY pEntry = RemoveTailList(&linkListHead); pData = CONTAINING_RECORD(pEntry, MYDATASTRUCT, ListEntry); KdPrint(("%d\n",pData->number)); ExFreePool(pData); } }
3 、Lookaside结构
频繁申请和回收内存,会导致在内存上产生大量的内存“空洞”,从而导致最终无法申请内存。DDK为程序员提供了Lookaside结构来解决这个问题。
3、1 频繁申请内存的弊端
如下图显示,在内存中先后申请三块内存。最开始可用的内存是连续的。当某个时刻内存块2被回收后,如果系统想分配一块略大于原先的块2的内存,这时原先的内存
2就不能被申请成功,因此频繁地申请、回收内存会导致在内存上产生大量的内存空洞。
3、2 使用Lookaside
如果驱动程序频繁地从内存中申请、回收固定大小的内存,DDK提供了一种机制来解决这个问题,就是使用Lookaside 对象。
可将Lookaside对象想象成一个内存容器。在初始时,它先向Windows申请了一块比较大的内存,以后程序员每次申请内存时,不是直接向Windows申请内存,
而是向Lookaside对象申请内存。Lookaside对象会智能地避免产生内存“空洞”。
如果Lookaside对象内部的内存不够时,它会向操作系统申请更多的内存。当Lookaside对象内部有大量未使用的内存时,它会让Winodws回收一部分内存。
总之,Lookaside是一个自动的内存分配容器。通过对Lookaside对象申请内存,效率高于直接向Windows申请内存。
Loodaside一般会在以下情况下使用:
(1)程序员每次申请固定大小的内存;
(2)申请和回收操作十分频繁。
如果程序员遇到上述两种情况,可以考虑使用Lookaside对象,驱动程序中的运行效率是程序员必须考虑的问题。如果驱动程序效率低,会严重影响到OS的性能。
使用Lookaside,首先要初始化Lookaside对象,有以下两个函数可用:
NTKERNELAPIVOIDExInitializeNPagedLookasideList ( IN PNPAGED_LOOKASIDE_LIST Lookaside, IN PALLOCATE_FUNCTION Allocate, IN PFREE_FUNCTION Free, IN ULONG Flags, IN SIZE_T Size, IN ULONG Tag, IN USHORT Depth );
NTKERNELAPIVOIDExInitializePagedLookasideList ( IN PPAGED_LOOKASIDE_LIST Lookaside, IN PALLOCATE_FUNCTION Allocate, IN PFREE_FUNCTION Free, IN ULONG Flags, IN SIZE_T Size, IN ULONG Tag, IN USHORT Depth );
这两个函数分别是对非分页和分页Loodaside对象进行初始化。
在初始化完成Lookaside对象后,可以进行申请内存操作了,有以下两个函数:
__inlinePVOIDExAllocateFromNPagedLookasideList( IN PNPAGED_LOOKASIDE_LIST Lookaside )
__inlinePVOIDExAllocateFromPagedLookasideList( IN PPAGED_LOOKASIDE_LIST Lookaside )
这两个函数分别是对非分页和分页的内存的申请。
对Lookaside对象回收内存操作,有以下函数:
__inlineVOIDExFreeToNPagedLookasideList( IN PPAGED_LOOKASIDE_LIST Lookaside, IN PVOID Entry )
__inlineVOIDExFreeToPagedLookasideList( IN PPAGED_LOOKASIDE_LIST Lookaside, IN PVOID Entry )
使用完Lookaside对象后,需要删除Lookaside对象,有以下函数:
NTKERNELAPIVOIDExDeleteNPagedLookasideList ( IN PPAGED_LOOKASIDE_LIST Lookaside );
NTKERNELAPIVOIDExDeletePagedLookasideList ( IN PPAGED_LOOKASIDE_LIST Lookaside );
3、3 实验
typedef struct _MYDATASTRUCT { CHAR buffer[64];} MYDATASTRUCT, *PMYDATASTRUCT;#pragma INITCODEVOID LookasideTest() { //初始化Lookaside对象 PAGED_LOOKASIDE_LIST pageList; ExInitializePagedLookasideList(&pageList,NULL,NULL,0,sizeof(MYDATASTRUCT),'1234',0);#define ARRAY_NUMBER 50 PMYDATASTRUCT MyObjectArray[ARRAY_NUMBER]; //模拟频繁申请内存 for (int i=0;i<ARRAY_NUMBER;i++) { MyObjectArray[i] = (PMYDATASTRUCT)ExAllocateFromPagedLookasideList(&pageList); } //模拟频繁回收内存 for (i=0;i<ARRAY_NUMBER;i++) { ExFreeToPagedLookasideList(&pageList,MyObjectArray[i]); MyObjectArray[i] = NULL; } ExDeletePagedLookasideList(&pageList); //删除Lookaside对象}
4、运行时函数
4、1 内存间复制(非重叠)
在驱动程序开发中,经常会用到内存的复制。例如,将需要显示的内容,从缓冲区复制到显卡内存中。
DDK为程序员提供以下函数。
NTSYSAPIVOIDNTAPIRtlCopyMemory ( VOID UNALIGNED *Destination, CONST VOID UNALIGNED *Source, SIZE_T Length );
Destination:表示要复制内存的目的地址;
Source: 表示要复制内存的源地址;
Length: 表示要复制的内存的长度,单位是字节。
和RtlCopyMemory功能类似的函数有RtlCopyBytes,这两个函数的参数全部一样,功能也完全一样,只是在不同的平台下有不同的实现。
在Inter 32位的CPU平台下,这两个函数其实是两个宏。
#define RtlCopyBytes RtlCopyMemory
另外Windows XP DDK又添加了一个新的函数RtlCopyMemory32,这个函数不是依靠memcpy实现的,因为是针对32位搬移,速度做了优化。
NTSYSAPIVOIDNTAPIRtlCopyMemory32 ( VOID UNALIGNED *Destination, CONST VOID UNALIGNED *Source, ULONG Length );
4、2 内存间复制(可重叠)
用RtlCopyMemory可以复制内存,但其内部没有考虑内存重叠的情况。如下图所示,有三段等长的内存,ABCD分别代表三段内存的起始地址和终止地址。
如果需要将A到C段的内存复制到B到D这段上,这时B到C的内存是重叠的部分。
RtlCopyMemory函数的内部实现是依靠memcpy函数实现的。依据C99定义,memcpy没有考虑重叠部分,因此它不能保证重叠部分是否被复制。
为了保证重叠部分也能被正确复制,C99规定memmove函数完成这个任务,这个函数对两个内存内容是否重叠进行了判断,这种判断牺牲了速度。
如果程序员能确保复制的内存没重叠,请选择使用memcpy 函数。
DDK用宏对memmove进行了封装,名称变为RtlMoveMemory。
NTSYSAPIVOIDNTAPIRtlMoveMemory ( VOID UNALIGNED *Destination, CONST VOID UNALIGNED *Source, SIZE_T Length );
4、3 填充内存
在驱动开发中,还经常用到对某段内存区域用固定字节填充。DDK为程序员提供了函数RtlFillMemory,它在32平台下是个宏,实际函数是memset函数。
NTSYSAPIVOIDNTAPIRtlFillMemory ( VOID UNALIGNED *Destination, SIZE_T Length, UCHAR Fill );
Fill: 需要填充的字节
在驱动程序开发中,还经常对某段内存填零,DDK提供的宏是RtlZeroBytes和RtlZeroMemory。
NTSYSAPIVOIDNTAPIRtlZeroMemory ( VOID UNALIGNED *Destination, SIZE_T Length );
4、4 内存比较
在驱动开发中,还会用到比较两块内存是否是一致的,该函数是RtlCompareMemory。
NTSYSAPISIZE_TNTAPIRtlCompareMemory ( const VOID *Source1, const VOID *Source2, SIZE_T Length );
Source1:比较的第一个内存地址;
Source2:比较的第二个内存地址;
Length : 比较的长度;
4、5 关于运行时函数的注意事项
DDK提供的标准的运行时函数名都是RtlXXX形式的,其中,大部分以宏的形式给出的,例如RtlCopyMemory就是一个宏,定义为:
#define RtlEqualMemory(Destination,Source,Length) (!memcmp((Destination),(Source),(Length)))#define RtlMoveMemory(Destination,Source,Length) memmove((Destination),(Source),(Length))#define RtlCopyMemory(Destination,Source,Length) memcpy((Destination),(Source),(Length))#define RtlFillMemory(Destination,Length,Fill) memset((Destination),(Fill),(Length))#define RtlZeroMemory(Destination,Length) memset((Destination),0,(Length))
memcpy这些函数是由ntoskrnl.exe导出的函数。
下面通过VC提供的工具Depends查看这些导出函数。
用Depends工具打开编译的DDK驱动程序,显示出以下4部信息:
(1)标号为1的窗口,可以看出驱动程序HelloDDK.sys依赖的动态链接库(ntoskrnl.exe)。一般的动态链接库都是以DLL文件存在的,而ntoskrnl.exe却是以EXE文件形式
存在的。但不管什么,它都是PE格式文件。ntoskrnl.exe同时依赖于三个动态链接库。
(2)标号为2的窗口,列出了HelloDDK.sys中使用的ntoskrnl.exe的导出函数。例如,这个驱动程序中用到了RtlCopyMemory宏,而这个宏的定义是memcopy函数,
因此在导出函数中可以找到memcopy函数。
4、6 实现
#pragma INITCODEVOID RtlTest() { PUCHAR pBuffer = (PUCHAR)ExAllocatePool(PagedPool,BUFFER_SIZE); //用零填充内存 RtlZeroMemory(pBuffer,BUFFER_SIZE); PUCHAR pBuffer2 = (PUCHAR)ExAllocatePool(PagedPool,BUFFER_SIZE); //用固定字节填充内存 RtlFillMemory(pBuffer2,BUFFER_SIZE,0xAA); //内存拷贝 RtlCopyMemory(pBuffer,pBuffer2,BUFFER_SIZE); //判断内存是否一致 ULONG ulRet = RtlCompareMemory(pBuffer,pBuffer2,BUFFER_SIZE); if (ulRet==BUFFER_SIZE) { KdPrint(("The two blocks are same.\n")); }}
5、使用C++特性分配内存
在C++语言中分配内存时,可以使用new操作符,回收内存时使用delete操作符。但是在驱动程序中,
使用new和delete操作符,将得到错误的链接提示。如:
PCHAR p = new CHAR[1024];
编译时,会得到以下错误:
1>Driver.obj : error LNK2019: 无法解析的外部符号 "void * __cdecl operator new(unsigned int)" ([email protected]@Z),该符号在函数 "void __stdcall RtlTest(void)" (?RtlTest@@YGXXZ) 中被引用1>MyDriver_Check/HelloDDK.sys : fatal error LNK1120: 1 个无法解析的外部命令
如果希望在驱动程序中使用new和delete操作符,应该对new和delete操作符进行重载。
new和delete操作符可以通过内核函数ExAllocatePool 和回收函数ExFreePool实现。同时还可通过new操作符指定颁布内存还是非分布内存。
以下给出示例代码:
//全局new操作符void * __cdecl operator new(size_t size,POOL_TYPE PoolType=PagedPool){ KdPrint(("global operator new\n")); KdPrint(("Allocate size :%d\n",size)); return ExAllocatePool(PagedPool,size);}//全局delete操作符void __cdecl operator delete(void* pointer){ KdPrint(("Global delete operator\n")); ExFreePool(pointer);}class TestClass{public: //构造函数 TestClass() { KdPrint(("TestClass::TestClass()\n")); } //析构函数 ~TestClass() { KdPrint(("TestClass::~TestClass()\n")); } //类中的new操作符 void* operator new(size_t size,POOL_TYPE PoolType=PagedPool) { KdPrint(("TestClass::new\n")); KdPrint(("Allocate size :%d\n",size)); return ExAllocatePool(PoolType,size); } //类中的delete操作符 void operator delete(void* pointer) { KdPrint(("TestClass::delete\n")); ExFreePool(pointer); }private: char buffer[1024];};void TestNewOperator(){ TestClass* pTestClass = new TestClass; delete pTestClass; pTestClass = new(NonPagedPool) TestClass; delete pTestClass; char *pBuffer = new(PagedPool) char[100]; delete []pBuffer; pBuffer = new(NonPagedPool) char[100]; delete []pBuffer;}