Window XP驱动开发(二十)Window驱动的内存管理

 

Window XP驱动开发(二十)Window驱动的内存管理


 

转载:  http://blog.csdn.net/chenyujing1234/article/details/7710627

 

 在驱动程序编写中,分配和管理内存不能使用熟知的Win32 API函数,取而代之的是DDK提供的高效的内核函数。程序员
必须小心地使用这些内存相关的内核函数,因为在内核模式下,OS不会检查内存使用的合法性。

 

1、 内存管理概念

1、1  物理内存概念(Physical Memory Address)

PC上有三条总线,分别是数据总线地址总线控制总线。32位的CPU的寻址能力为4G(2的32次方)个字节。用户最多可以使用4G的真实的物理内存。(所以在一般的XP电脑中只能使用4G的内存条,而在64位的电脑中却可以用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信息。

[cpp]  view plain copy
  1. VOID DisplayItsProcessName()  
  2. {  
  3.     // 得到当前进程  
  4.     PEPROCESS pEProcess = PsGetCurrentProcess();  
  5.     // 得到当前进程名称  
  6.     PTSTR ProcessName = (PTSTR)((ULONG)pEProcess + 0x174);  
  7.     KdPrint(("%s\n", ProcessName));  
  8. }  


其中,PsGetCurrentProcess函数是得到当前运行的进程,它是EPROCESS的结构体,然而EPROCESS这个结构体是微软没有公开的结构体,其中0x174偏移的位置,

记录一个字符串指针。有兴趣的,可以利用WinDbg工具,查看该结构体的内容。和版本Windows的该结构,可能有些差别,下面是在Winows XP SP2的机器上用

WindDbg查看该结构的结果。如下:

 

1、5  分页和非分页内存

 Windows规定有些虚拟内存页面是可以交换到文件中的,这类内存被称为分页内存;

而有些虚拟内存页永远不会交换到文件中,这些内存被称为非分页内存

当程序的中断请求级在DISPATCH_LEVEL之上时(包括DISPATCH_LEVEL层),程序只能使用非分页内存,否则会导致蓝屏死机。

在编译DDK提供的例程时,可以指定某个例程和某个全局变量是载入分页内存还是非分页内存,需要自己做如下定义:

[cpp]  view plain copy
  1. #define PAGEDCODE code_seg("PAGE")  
  2. #define LOCKEDCODE code_seg()  
  3. #define INITCODE code_seg("INIT")  
  4.   
  5. #define PAGEDDATA data_seg("PAGE")  
  6. #define LOCKEDDATA data_seg()  
  7. #define INITDATA data_seg("INIT")  

如果将某个函数载入到分页内存中,我们需要在函数的实现中加入以下代码:

[cpp]  view plain copy
  1. #pragma PAGEDCODE  
  2. VOID SomeFunction()  
  3. {  
  4.     PAGED_CODE();  
  5. }  

其中,PAGED_CODE是DDK提供的宏,它只在check版本中生效,它会检查这个函数是否运行低于DISPATCH_LEVEL的中断请求级,如果

等于或高于这个中断请求级,将产生一个断言。

[cpp]  view plain copy
  1. #if DBG  
  2. #define PAGED_CODE() \  
  3.     { if (KeGetCurrentIrql() > APC_LEVEL) { \  
  4.           KdPrint(( "EX: Pageable code called at IRQL %d\n", KeGetCurrentIrql() )); \  
  5.           ASSERT(FALSE); \  
  6.        } \  
  7.     }  
  8. #else  
  9. #define PAGED_CODE() NOP_FUNCTION;  
  10. #endif  

如果让函数加载到非分页内存中,需要在函数的实现加如下代码:

[cpp]  view plain copy
  1. #pragma LOCKEDCODE  
  2. VOID SomeFunction()  
  3. {  
  4.     // 做一些其他事情  
  5. }  

还有一种特殊情况,就是某个例程需要在初始化的时候载入内存,然后就可以从内存中卸载掉。这种情况指出现在DriverEntry情况下;

尤其是NT式的驱动,DriverEntry会很长,占据很大的空间,为了节省内存,需要及时从内存中卸载掉。代码如下:

[cpp]  view plain copy
  1. #pragma INITCODE  
  2. extern "C" NTSTATUS DriverEntry (  
  3.             IN PDRIVER_OBJECT pDriverObject,  
  4.             IN PUNICODE_STRING pRegistryPath    )   
  5. {  
  6.     // 做一些事情  
  7. }  

1、6    分配内核内存

Windows驱动程序使用的内存资源非常珍贵,分配内存时要尽量节约。和应用程序一样,局部变量是在存放在(Stack)空间中的,但栈空间不会像应用程序那么大,

所以驱动程序不适合递归调用或局部变量是大型结构体。如果需要大型结构体,请在堆(Heap)中申请。

堆中申请内存的函数有以下几个,原型如下:

 

[cpp]  view plain copy
  1. NTKERNELAPI  
  2. PVOID  
  3. ExAllocatePool(  
  4.     IN POOL_TYPE PoolType,  
  5.     IN SIZE_T NumberOfBytes  
  6.     );  
  7.   
  8. NTKERNELAPI  
  9. PVOID  
  10. NTAPI  
  11. ExAllocatePoolWithTag(  
  12.     IN POOL_TYPE PoolType,  
  13.     IN SIZE_T NumberOfBytes,  
  14.     IN ULONG Tag  
  15.     );  
  16.   
  17. NTKERNELAPI  
  18. PVOID  
  19. NTAPI  
  20. ExAllocatePoolWithQuota(  
  21.     IN POOL_TYPE PoolType,  
  22.     IN SIZE_T NumberOfBytes,  
  23.     );  
  24. NTKERNELAPI  
  25. PVOID  
  26. ExAllocatePoolWithQuotaTag(  
  27.     IN POOL_TYPE PoolType,  
  28.     IN SIZE_T NumberOfBytes,  
  29.     IN ULONG Tag  
  30.     );  



**  PoolType是个枚举变量,如果此值为NonPagedPool,则分配非分页内存,如果此值为PagedPool,则分配内存为分页内存、、、

[cpp]  view plain copy
  1. typedef enum _POOL_TYPE {  
  2.     NonPagedPool,  
  3.     PagedPool,  
  4.     NonPagedPoolMustSucceed,  
  5.     DontUseThisType,  
  6.     NonPagedPoolCacheAligned,  
  7.     PagedPoolCacheAligned,  
  8.     NonPagedPoolCacheAlignedMustS,  
  9.     MaxPoolType  


 

** NumberOfBytes是分配内存的大小,注意最好是4的倍数

** 返回值分配的内存地址,一定是内核模式地址,如果返回0,则代表分配失败。

以上四个函数功能类似,函数以WithQuota结尾的代码分配时按配额分配

函数以WithTag结尾的函数,和ExtAllocatePool功能类似,唯一不同的是多了一个Tag参数,系统在要求的内存外又额外多分配了4个字节的标签。在调试时,可以找到是否有标有这个标签的内存没有被释放。

将分配的内存,进行回收的函数是ExtFreePool()和ExtFreePoolWithTag

2、在驱动中使用链表

在驱动程序开发中,经常使用链表这种数据结构,DDK为用户提供两种链表的数据结构,简化了对链表的操作。

链表中可以记录将整型、浮点、字符型或者程序员自定义的数据结构。链表通过指针将这些数据结构组成一个“链”,链中每个元素对应着记录的数据。

对于单向链表,每个元素中有一个Next指针指向一下元素。对于双向链表,每个元素有两个指针:BLINK指针指向前一个元素,FLINK指向后一个元素。

 

2、1  链表结构

DDK提供了标准的双向链表,双向链表可以将链表形成一个环。BLINK指针指向前一个元素,FLINK指向后一个元素。

以下是DDK提供的双向链表的数据结构。读者会奇怪这个数据只有指向前后的指针,而没有数据,这个问题在下一节介绍。

[cpp]  view plain copy
  1. typedef struct _LIST_ENTRY {  
  2.    struct _LIST_ENTRY *Flink;  
  3.    struct _LIST_ENTRY *Blink;  
  4. } LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;  

 

2、2 链表初始化

 每个双向链表都是以一个链表头作为链表的第一个元素。初次使用链表需要初始化,主要将链表头FLink和BLink两个指针指向自己,这意味着链表空。如下所示。

如何判断链表是否为空,可以判断链表头的FLink和Blink是否指向自己。DDK为程序员提供一个宏简化这种检查,这就是IsListEmpty。

[cpp]  view plain copy
  1. #define IsListEmpty(ListHead) \  
  2.     ((ListHead)->Flink == (ListHead))  


程序员需要自己定义链表中每个元素的数据类型,并将LIST_ENTRY结构作为自定义结构的一个子域。LIST_ENTRY的作用是将自定义的

数据结构串成一个链表。

[cpp]  view plain copy
  1. typedef struct _WORK_QUEUE_ITEM {  
  2.     LIST_ENTRY List;  
  3.     PWORKER_THREAD_ROUTINE WorkerRoutine;  
  4.     PVOID Parameter;  
  5. } WORK_QUEUE_ITEM, *PWORK_QUEUE_ITEM;  

2、3 从首部插入链表

 对链表的插入有两种方式,一种是在链表的头部插入,一种是在链表的尾部插入。

在头部使用的语句是InsertHeadList,用法如下:

[cpp]  view plain copy
  1. InsertHeadList(&targetPortGroup->TargetPortList, &listEntry->ListEntry);  

其中targetPortGroup->TargetPortList是LIST_ENTRY结构的链表头,listEntry是用户定义的数据结构,而它的子域ListEntry是包含其中的LIST_ENTRY数据结构

假设链表中已经有一个元素Entry1,如下图,现在将另外一个元素Entry2插入链表,InsertHeadList插入后的结果如下:


 

 2、4 从尾部插入链表

使用语句InsertTailList,

2、5 从链表删除

和插入链表一样,删除也有两种对应的方法,一种是从链表头删除,一种是从链表尾部删除。分别对应RemoveHeadList和RemoveTailList函数。用法如下:

[cpp]  view plain copy
  1. Entry = RemoveHeadList( &VolDo->OverflowQueue );  
[cpp]  view plain copy
  1. RemoveTailList( &InstCtx->QueueHead );  

其中VolDo->OverflowQueue是链表头,Entry是从链表删除下来的元素中的ListEntry,这里有一个问题,就是如何从pEntry得到用户自定义的数据结构的指针,有以下两种情况。

(1)当自定义数据结构的第一个字段是LIST_ENTRY时,如:

[cpp]  view plain copy
  1. typedef struct _WORK_QUEUE_ITEM {  
  2.     LIST_ENTRY List;  
  3.     PWORKER_THREAD_ROUTINE WorkerRoutine;  
  4.     PVOID Parameter;  
  5. } WORK_QUEUE_ITEM, *PWORK_QUEUE_ITEM;  

 

此时,RemoveHeadList 返回的指针可以当做用户自定义的指针,即:

[cpp]  view plain copy
  1. PLIST_ENTRY pEntry = RemoveHeadList(&head);  
  2. PWORK_QUEUE_ITEM pMyData = (PWORK_QUEUE_ITEM)pEntry;   


(2)当自定义的数据结构第一个字段不是LIST_ENTRY时,如:

[cpp]  view plain copy
  1. typedef struct _IRP_CONTEXT_LITE {  
  2.   
  3.     //  Type and size of this record (must be CDFS_NTC_IRP_CONTEXT_LITE)  
  4.     NODE_TYPE_CODE NodeTypeCode;  
  5.     NODE_BYTE_SIZE NodeByteSize;  
  6.   
  7.     //  Fcb for the file object being closed.  
  8.     PFCB Fcb;  
  9.   
  10.     //  List entry to attach to delayed close queue.  
  11.     LIST_ENTRY DelayedCloseLinks;  
  12.   
  13.     //  User reference count for the file object being closed.  
  14.     ULONG UserReference;  
  15.   
  16.     //  Real device object.  This represents the physical device closest to the media.  
  17.     PDEVICE_OBJECT RealDevice;  
  18.   
  19. } IRP_CONTEXT_LITE;  



此时,RemoveHeadList 返回的指针不能当做用户的自定义的指针。此时需要通过返回指针的地址逆向算出自定义数据的指针。一般通过RemoveHeadList返回指针在自定义数据中的偏移量,用返回指针减去这个偏移量,就会得到用户自定义结构的指针的地址。

如下图,指针A是自定义的数据地址,指针B是自定义数据结构中的pEntry 。

为了简化这个操作,DDK为程序员提供了宏CONTAINING_RECORD,其用法如下:

[cpp]  view plain copy
  1. PIRP_CONTEXT_LITE NextIrpContextLite = CONTAINING_RECORD( Entry,  
  2.                                                    IRP_CONTEXT_LITE,  
  3.                                                    DelayedCloseLinks );  

CONTAINING_RECORD的第一参数是相当于上图中的指针B,第二个参数是数据结构的名字,第三个参数是数据结构中的字段,返回相当于上图中的A。


2、6 实验
[cpp]  view plain copy
  1. #pragma INITCODE  
  2. VOID LinkListTest()   
  3. {  
  4.     LIST_ENTRY linkListHead;  
  5.     //初始化链表  
  6.     InitializeListHead(&linkListHead);  
  7.   
  8.     PMYDATASTRUCT pData;  
  9.     ULONG i = 0;  
  10.     //在链表中插入10个元素  
  11.     KdPrint(("Begin insert to link list"));  
  12.     for (i=0 ; i<10 ; i++)  
  13.     {  
  14.         pData = (PMYDATASTRUCT)  
  15.             ExAllocatePool(PagedPool,sizeof(MYDATASTRUCT));  
  16.         pData->number = i;  
  17.         InsertHeadList(&linkListHead,&pData->ListEntry);  
  18.     }  
  19.   
  20.     //从链表中取出,并显示  
  21.     KdPrint(("Begin remove from link list\n"));  
  22.     while(!IsListEmpty(&linkListHead))  
  23.     {  
  24.         PLIST_ENTRY pEntry = RemoveTailList(&linkListHead);  
  25.         pData = CONTAINING_RECORD(pEntry,  
  26.                               MYDATASTRUCT,  
  27.                               ListEntry);  
  28.         KdPrint(("%d\n",pData->number));  
  29.         ExFreePool(pData);  
  30.     }  
  31.    
  32. }  

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对象,有以下两个函数可用:

[cpp]  view plain copy
  1. NTKERNELAPI  
  2. VOID  
  3. ExInitializeNPagedLookasideList (  
  4.     IN PNPAGED_LOOKASIDE_LIST Lookaside,  
  5.     IN PALLOCATE_FUNCTION Allocate,  
  6.     IN PFREE_FUNCTION Free,  
  7.     IN ULONG Flags,  
  8.     IN SIZE_T Size,  
  9.     IN ULONG Tag,  
  10.     IN USHORT Depth  
  11.     );  


 

[cpp]  view plain copy
  1. NTKERNELAPI  
  2. VOID  
  3. ExInitializePagedLookasideList (  
  4.     IN PPAGED_LOOKASIDE_LIST Lookaside,  
  5.     IN PALLOCATE_FUNCTION Allocate,  
  6.     IN PFREE_FUNCTION Free,  
  7.     IN ULONG Flags,  
  8.     IN SIZE_T Size,  
  9.     IN ULONG Tag,  
  10.     IN USHORT Depth  
  11.     );  


这两个函数分别是对非分页和分页Loodaside对象进行初始化。

在初始化完成Lookaside对象后,可以进行申请内存操作了,有以下两个函数:

[cpp]  view plain copy
  1. __inline  
  2. PVOID  
  3. ExAllocateFromNPagedLookasideList(  
  4.     IN PNPAGED_LOOKASIDE_LIST Lookaside  
  5.     )  

 

[cpp]  view plain copy
  1. __inline  
  2. PVOID  
  3. ExAllocateFromPagedLookasideList(  
  4.     IN PPAGED_LOOKASIDE_LIST Lookaside  
  5.     )  


这两个函数分别是对非分页和分页的内存的申请。

对Lookaside对象回收内存操作,有以下函数:

[cpp]  view plain copy
  1. __inline  
  2. VOID  
  3. ExFreeToNPagedLookasideList(  
  4.     IN PPAGED_LOOKASIDE_LIST Lookaside,  
  5.     IN PVOID Entry  
  6.     )  


 

 

[cpp]  view plain copy
  1. __inline  
  2. VOID  
  3. ExFreeToPagedLookasideList(  
  4.     IN PPAGED_LOOKASIDE_LIST Lookaside,  
  5.     IN PVOID Entry  
  6.     )  


 

使用完Lookaside对象后,需要删除Lookaside对象,有以下函数:

[cpp]  view plain copy
  1. NTKERNELAPI  
  2. VOID  
  3. ExDeleteNPagedLookasideList (  
  4.     IN PPAGED_LOOKASIDE_LIST Lookaside  
  5.     );  

 

[cpp]  view plain copy
  1. NTKERNELAPI  
  2. VOID  
  3. ExDeletePagedLookasideList (  
  4.     IN PPAGED_LOOKASIDE_LIST Lookaside  
  5.     );  

3、3 实验
[cpp]  view plain copy
  1. typedef struct _MYDATASTRUCT   
  2. {  
  3.     CHAR buffer[64];  
  4. } MYDATASTRUCT, *PMYDATASTRUCT;  
  5.   
  6. #pragma INITCODE  
  7. VOID LookasideTest()   
  8. {  
  9.     //初始化Lookaside对象  
  10.     PAGED_LOOKASIDE_LIST pageList;  
  11.     ExInitializePagedLookasideList(&pageList,NULL,NULL,0,sizeof(MYDATASTRUCT),'1234',0);  
  12.   
  13. #define ARRAY_NUMBER 50  
  14.     PMYDATASTRUCT MyObjectArray[ARRAY_NUMBER];  
  15.     //模拟频繁申请内存  
  16.     for (int i=0;i<ARRAY_NUMBER;i++)  
  17.     {  
  18.         MyObjectArray[i] = (PMYDATASTRUCT)ExAllocateFromPagedLookasideList(&pageList);  
  19.     }  
  20.   
  21.     //模拟频繁回收内存  
  22.     for (i=0;i<ARRAY_NUMBER;i++)  
  23.     {  
  24.         ExFreeToPagedLookasideList(&pageList,MyObjectArray[i]);  
  25.         MyObjectArray[i] = NULL;  
  26.     }  
  27.   
  28.     ExDeletePagedLookasideList(&pageList);  
  29.     //删除Lookaside对象  
  30. }  


4、运行时函数

4、1 内存间复制(非重叠)

在驱动程序开发中,经常会用到内存的复制。例如,将需要显示的内容,从缓冲区复制到显卡内存中。

DDK为程序员提供以下函数:

[cpp]  view plain copy
  1. NTSYSAPI  
  2. VOID  
  3. NTAPI  
  4. RtlCopyMemory (  
  5.    VOID UNALIGNED *Destination,  
  6.    CONST VOID UNALIGNED *Source,  
  7.    SIZE_T Length  
  8.    );  


Destination:表示要复制内存的目的地址;

Source: 表示要复制内存的源地址;

Length: 表示要复制的内存的长度,单位是字节。

和RtlCopyMemory功能类似的函数有RtlCopyBytes,这两个函数的参数全部一样,功能也完全一样,只是在不同的平台下有不同的实现。

在Inter 32位的CPU平台下,这两个函数其实是两个宏。

[cpp]  view plain copy
  1. #define RtlCopyBytes RtlCopyMemory  

另外Windows XP DDK又添加了一个新的函数RtlCopyMemory32,这个函数不是依靠memcpy实现的,因为是针对32位搬移,速度做了优化

[cpp]  view plain copy
  1. NTSYSAPI  
  2. VOID  
  3. NTAPI  
  4. RtlCopyMemory32 (  
  5.    VOID UNALIGNED *Destination,  
  6.    CONST VOID UNALIGNED *Source,  
  7.    ULONG Length  
  8.    );  

4、2 内存间复制(可重叠)

用RtlCopyMemory可以复制内存,但其内部没有考虑内存重叠的情况。如下图所示,有三段等长的内存,ABCD分别代表三段内存的起始地址和终止地址。

如果需要将A到C段的内存复制到B到D这段上,这时B到C的内存是重叠的部分。

RtlCopyMemory函数的内部实现是依靠memcpy函数实现的。依据C99定义,memcpy没有考虑重叠部分,因此它不能保证重叠部分是否被复制。

为了保证重叠部分也能被正确复制,C99规定memmove函数完成这个任务,这个函数对两个内存内容是否重叠进行了判断,这种判断牺牲了速度

如果程序员能确保复制的内存没重叠,请选择使用memcpy 函数。DDK用宏对memmove进行了封装,名称变为RtlMoveMemory。

[cpp]  view plain copy
  1. NTSYSAPI  
  2. VOID  
  3. NTAPI  
  4. RtlMoveMemory (  
  5.    VOID UNALIGNED *Destination,  
  6.    CONST VOID UNALIGNED *Source,  
  7.    SIZE_T Length  
  8.    );  
4、3 填充内存

在驱动开发中,还经常用到对某段内存区域用固定字节填充。DDK为程序员提供了函数RtlFillMemory,它在32平台下是个宏,实际函数是memset函数。

[cpp]  view plain copy
  1. NTSYSAPI  
  2. VOID  
  3. NTAPI  
  4. RtlFillMemory (  
  5.    VOID UNALIGNED *Destination,  
  6.    SIZE_T Length,  
  7.    UCHAR Fill  
  8.    );  


Fill: 需要填充的字节

在驱动程序开发中,还经常对某段内存填零,DDK提供的宏是RtlZeroBytes和RtlZeroMemory。

[cpp]  view plain copy
  1. NTSYSAPI  
  2. VOID  
  3. NTAPI  
  4. RtlZeroMemory (  
  5.    VOID UNALIGNED *Destination,  
  6.    SIZE_T Length  
  7.    );  
4、4 内存比较

在驱动开发中,还会用到比较两块内存是否是一致的,该函数是RtlCompareMemory。

[cpp]  view plain copy
  1. NTSYSAPI  
  2. SIZE_T  
  3. NTAPI  
  4. RtlCompareMemory (  
  5.     const VOID *Source1,  
  6.     const VOID *Source2,  
  7.     SIZE_T Length  
  8.     );  

Source1:比较的第一个内存地址;

Source2:比较的第二个内存地址;

Length : 比较的长度;

4、5 关于运行时函数的注意事项

DDK提供的标准的运行时函数名都是RtlXXX形式的,其中,大部分以宏的形式给出的,例如RtlCopyMemory就是一个宏,定义为:

[cpp]  view plain copy
  1. #define RtlEqualMemory(Destination,Source,Length) (!memcmp((Destination),(Source),(Length)))  
  2. #define RtlMoveMemory(Destination,Source,Length) memmove((Destination),(Source),(Length))  
  3. #define RtlCopyMemory(Destination,Source,Length) memcpy((Destination),(Source),(Length))  
  4. #define RtlFillMemory(Destination,Length,Fill) memset((Destination),(Fill),(Length))  
  5. #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 实现
[cpp]  view plain copy
  1. #pragma INITCODE  
  2. VOID RtlTest()   
  3. {  
  4.     PUCHAR pBuffer = (PUCHAR)ExAllocatePool(PagedPool,BUFFER_SIZE);  
  5.     //用零填充内存  
  6.     RtlZeroMemory(pBuffer,BUFFER_SIZE);  
  7.   
  8.     PUCHAR pBuffer2 = (PUCHAR)ExAllocatePool(PagedPool,BUFFER_SIZE);  
  9.     //用固定字节填充内存  
  10.     RtlFillMemory(pBuffer2,BUFFER_SIZE,0xAA);  
  11.   
  12.     //内存拷贝  
  13.     RtlCopyMemory(pBuffer,pBuffer2,BUFFER_SIZE);  
  14.   
  15.     //判断内存是否一致  
  16.     ULONG ulRet = RtlCompareMemory(pBuffer,pBuffer2,BUFFER_SIZE);  
  17.     if (ulRet==BUFFER_SIZE)  
  18.     {  
  19.         KdPrint(("The two blocks are same.\n"));  
  20.     }  
  21. }  

 

5、使用C++特性分配内存

在C++语言中分配内存时,可以使用new操作符,回收内存时使用delete操作符。但是在驱动程序中,使用new和delete操作符,将得到错误的链接提示。如:

[cpp]  view plain copy
  1. PCHAR  p = new CHAR[1024];  

 

编译时,会得到以下错误:

[plain]  view plain copy
  1. 1>Driver.obj : error LNK2019: 无法解析的外部符号 "void * __cdecl operator new(unsigned int)" (??2@YAPAXI@Z),该符号在函数 "void __stdcall RtlTest(void)" (?RtlTest@@YGXXZ) 中被引用  
  2. 1>MyDriver_Check/HelloDDK.sys : fatal error LNK1120: 1 个无法解析的外部命令  

如果希望在驱动程序中使用new和delete操作符,应该对new和delete操作符进行重载

new和delete操作符可以通过内核函数ExAllocatePool  和回收函数ExFreePool实现。同时还可通过new操作符指定颁布内存还是非分布内存。

以下给出示例代码:

[cpp]  view plain copy
  1. //全局new操作符  
  2. void * __cdecl operator new(size_t size,POOL_TYPE PoolType=PagedPool)  
  3. {  
  4.     KdPrint(("global operator new\n"));  
  5.     KdPrint(("Allocate size :%d\n",size));  
  6.     return ExAllocatePool(PagedPool,size);  
  7. }  
  8. //全局delete操作符  
  9. void __cdecl operator delete(void* pointer)  
  10. {  
  11.     KdPrint(("Global delete operator\n"));  
  12.     ExFreePool(pointer);  
  13. }  
  14.   
  15. class TestClass  
  16. {  
  17. public:  
  18.     //构造函数  
  19.     TestClass()  
  20.     {  
  21.         KdPrint(("TestClass::TestClass()\n"));  
  22.     }  
  23.   
  24.     //析构函数  
  25.     ~TestClass()  
  26.     {  
  27.         KdPrint(("TestClass::~TestClass()\n"));  
  28.     }  
  29.   
  30.     //类中的new操作符  
  31.     void* operator new(size_t size,POOL_TYPE PoolType=PagedPool)  
  32.     {  
  33.         KdPrint(("TestClass::new\n"));  
  34.         KdPrint(("Allocate size :%d\n",size));  
  35.         return ExAllocatePool(PoolType,size);  
  36.     }  
  37.   
  38.     //类中的delete操作符  
  39.     void operator delete(void* pointer)  
  40.     {  
  41.         KdPrint(("TestClass::delete\n"));  
  42.         ExFreePool(pointer);  
  43.     }  
  44. private:  
  45.     char buffer[1024];  
  46. };  
  47.   
  48.   
  49. void TestNewOperator()  
  50. {  
  51.     TestClass* pTestClass = new TestClass;  
  52.     delete pTestClass;  
  53.   
  54.     pTestClass = new(NonPagedPool) TestClass;  
  55.     delete pTestClass;  
  56.   
  57.     char *pBuffer = new(PagedPool) char[100];  
  58.     delete []pBuffer;  
  59.   
  60.     pBuffer = new(NonPagedPool) char[100];  
  61.     delete []pBuffer;  
  62. }  


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值