[Windows驱动开发](四)内存管理

一、内存管理概念

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

PC上有三条总线,分别是数据总线、地址总线和控制总线。32位CPU的寻址能力为4GB(2的32次方)个字节。用户最多可以使用4GB的真实物理内存。PC中很多设备都提供了自己的设备内存。这部分内存会映射到PC的物理内存上,也就是读写这段物理地址,其实读写的是设备内存地址,而不是物理内存地址。

2. 虚拟内存概念

虽然可以寻址4GB的内存,但是PC中往往没有如此多的真实物理内存。操作系统和硬件(主要是CPU中的内存管理单元MMU)为使用者提供了虚拟内存的概念。Windows的所有程序可以操作的都是虚拟内存。对虚拟内存的所有操作最终都会被转换成对真实物理内存的操作。

CPU中有一个重要的寄存器CR0,它是一个32位寄存器,其中的PG位负责标记是否分页。Windows在启动前会将它设置为1,即允许分页。WDK中有一个宏PAGE_SIZE记录分页大小,一般为4KB。4GB的虚拟内存会被分割成1M个分页单元。

其中,有一部分单元会和物理内存对应起来,即虚拟内存中第N个分页单元对应着物理内存的第M个分页单元。这种对应不是一一对应,而是多对一的映射,多个虚拟内存页可以映射同一个物理内存页。还有一部分单元会被映射成磁盘上的一个文件,并被标记为“脏的(Dirty)”。读取这段虚拟内存的时候,系统会发出一个异常,此时会触发异常处理函数,异常处理函数会将这个页的磁盘文件读入内存,并将其标记设置为“不脏”。让经常不读写的内存页交换(Swap)成文件,并将此页设置为“脏”。还有一部分单元什么也没有对应,为空。

Windows如此设计是因为以下两种原因:

a. 虚拟的增加了内存的大小。

b. 使不同进程的虚拟内存互不干扰。

3. 用户态地址和内核态地址

虚拟地址在0~0x7fffffff范围内的虚拟内存,即低2GB的虚拟地址,被称为用户态地址。而0x80000000~0xffffffff范围内的虚拟内存,即高2GB的虚拟内存,被称为内核态地址。Windows规定运行在用户态(Ring3层)的程序只能访问用户态地址,而运行在内核态(Ring0层)的程序可以访问整个4GB的虚拟内存。

Windows的核心代码和Windows的驱动程序加载的位置都是在高2GB的内核地址中。Windows操作系统在进程切换时,保持内核态地址是完全相同的,即所有进程的内核地址映射完全一致,进程切换时只改变用户模式地址的映射。

4. Windows驱动程序和进程的关系

驱动程序类似于一个DLL,被应用程序加载到虚拟内存中,只不过加载地址是内核地址。它能访问的只是这个进程的虚拟内存,不能访问其他进程的虚拟地址。Windows驱动程序里的不同例程运行在不同的进程中。DriverEntry例程和AddDevice例程是运行在系统(System)进程中的。这个进程是Windows第一个运行的进程。当需要加载的时候,这个进程中会有一个线程将驱动程序加载到内核模式地址空间内,并调用DriverEntry例程。

其他的例程,如IRP的派遣函数会运行于应用程序的“上下文”中。“上下文”是指运行于某个进程的环境中,所能访问的虚拟地址是这个进程的虚拟地址。

在内核态通过调用PsGetCurrentProcess()函数得到当前IO活动的进程,它是EPROCESS的结构体,其中包含了进程的相关信息。由于微软没有公开EPROCESS结构体,所以不同的系统需要使用Windbg查看其具体的值。在Win XP SP2中这个结构的0x174偏移处记录了一个字符串指针,表示的是进程的映像名称。

5. 分页与非分页内存

Windows规定有些虚拟内存页面是可以交换到文件中的,这类内存被称为分页内存。而有些虚拟内存页永远也不会交换到文件中,这些内存被称为非分页内存。

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

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

[cpp] view plain copy

print?

  1. //
  2. #define PAGEDCODE code_seg("PAGE")
  3. #define LOCKEDCODE code_seg()
  4. #define INITCODE code_seg("INIT")
  5. #define PAGEDDATA code_seg("PAGE")
  6. #define LOCKEDDATA code_seg()
  7. #define INITDATA code_seg("INIT")
  8. //
//

#define PAGEDCODE code_seg("PAGE")
#define LOCKEDCODE code_seg()
#define INITCODE code_seg("INIT")

#define PAGEDDATA code_seg("PAGE")
#define LOCKEDDATA code_seg()
#define INITDATA code_seg("INIT")

// 

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

[cpp] view plain copy

print?

  1. //
  2. #pragma PAGEDCODE
  3. VOID SomeFunction()
  4. {
  5. PAGED_CODE();
  6. // Do any other things ....
  7. }
  8. //
//

#pragma PAGEDCODE
VOID SomeFunction()
{
    PAGED_CODE();
    // Do any other things ....
}

//

其中,PAGED_CODE()是WDK提供的宏,只在check版本中生效。他会检测这个函数是否运行低于DISPATCH_LEVEL的中断请求级,如果等于或高于这个中断请求级,将产生一个断言。

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

[cpp] view plain copy

print?

  1. //
  2. #pragma LOCKEDCODE
  3. VOID SomeFunction()
  4. {
  5. // Do any other things ....
  6. }
  7. //
//

#pragma LOCKEDCODE
VOID SomeFunction()
{
    // Do any other things ....
}

//

还有一些特殊的情况,当某个例程在初始化的时候载入内存,然后就可以从内存中卸载掉。这种情况特指在调用DriverEntry的时候。尤其是NT式驱动,它会很长,占用很大的空间,为了节省内存,需要及时的从内存中卸载掉。代码如下:

[cpp] view plain copy

print?

  1. //
  2. #pragma INITCODE
  3. extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING pRegistryPath)
  4. {
  5. // Do any other things ....
  6. }
  7. //
//

#pragma INITCODE
extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING pRegistryPath)
{
    // Do any other things ....
}

//

6. 分配内核内存

Windows驱动程序使用的内存资源非常珍贵,分配内存时要尽量节约。和应用程序一样,局部变量是存放在栈(Stack)空间中的。但是栈空间不会像应用程序那么大,所以驱动程序不适合递归调用或者局部变量是大型结构体。如果需要大型结构体,需要在堆(Heap)中申请。

堆中申请内存的函数有以下几个:

[cpp] view plain copy

print?

  1. //
  2. NTKERNELAPI
  3. PVOID
  4. ExAllocatePool(
  5. __drv_strictTypeMatch(__drv_typeExpr) __in POOL_TYPE PoolType,
  6. __in SIZE_T NumberOfBytes
  7. );
  8. NTKERNELAPI
  9. PVOID
  10. NTAPI
  11. ExAllocatePoolWithTag(
  12. __in __drv_strictTypeMatch(__drv_typeExpr) POOL_TYPE PoolType,
  13. __in SIZE_T NumberOfBytes,
  14. __in ULONG Tag
  15. );
  16. NTKERNELAPI
  17. PVOID
  18. ExAllocatePoolWithQuota(
  19. __drv_strictTypeMatch(__drv_typeExpr) __in POOL_TYPE PoolType,
  20. __in SIZE_T NumberOfBytes
  21. );
  22. NTKERNELAPI
  23. PVOID
  24. ExAllocatePoolWithQuotaTag(
  25. __in __drv_strictTypeMatch(__drv_typeExpr) POOL_TYPE PoolType,
  26. __in SIZE_T NumberOfBytes,
  27. __in ULONG Tag
  28. );
  29. //
//

NTKERNELAPI
PVOID
ExAllocatePool(
    __drv_strictTypeMatch(__drv_typeExpr) __in POOL_TYPE PoolType,
    __in SIZE_T NumberOfBytes
    );

NTKERNELAPI
PVOID
NTAPI
ExAllocatePoolWithTag(
    __in __drv_strictTypeMatch(__drv_typeExpr) POOL_TYPE PoolType,
    __in SIZE_T NumberOfBytes,
    __in ULONG Tag
    );

NTKERNELAPI
PVOID
ExAllocatePoolWithQuota(
    __drv_strictTypeMatch(__drv_typeExpr) __in POOL_TYPE PoolType,
    __in SIZE_T NumberOfBytes
    );

NTKERNELAPI
PVOID
ExAllocatePoolWithQuotaTag(
    __in __drv_strictTypeMatch(__drv_typeExpr) POOL_TYPE PoolType,
    __in SIZE_T NumberOfBytes,
    __in ULONG Tag
    );

//

● PoolType:枚举变量。如果为NonPagedPool,则分配非分页内存。如果为PagedPool,则分配分页内存。

● NumberOfBytes:分配内存的大小。注:最好是4的倍数。

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

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

以上4个函数都需要指定PoolType,分别可以指定如下几种:

● NonPagedPool:指定要求分配非分页内存。

● PagedPool:指定要求分配分页内存。

● NonPagedPoolMustSucceed:指定分配非分页内存,必须成功。

● DontUseThisType:未指定。

● NonPagedPoolCacheAligned:指定要求分配非分页内存,而且必须内存对齐。

● PagedPoolCacheAligned:指定分配分页内存,而且必须内存对齐。

● NonPagedPoolCacheAlignedMustS:指定分配非分页内存,而且必须对齐,且必须成功。

将分配的内存进行回收的函数是ExFreePool和ExFreePoolWithTag,他们的原型是:

[cpp] view plain copy

print?

  1. //
  2. NTKERNELAPI
  3. VOID
  4. ExFreePoolWithTag(
  5. __in __drv_freesMem(Mem) PVOID P,   // 要释放的地址
  6. __in ULONG Tag
  7. );
  8. #define ExFreePool(a) ExFreePoolWithTag(a,0)
  9. //
//

NTKERNELAPI
VOID
ExFreePoolWithTag(
    __in __drv_freesMem(Mem) PVOID P,   // 要释放的地址
    __in ULONG Tag
    );

#define ExFreePool(a) ExFreePoolWithTag(a,0)

//

二、在驱动中使用链表

WDK提供了两种链表:单向链表、双向链表。

单项链表每个元素有一个Next指针指向下一个元素。双向链表每隔元素有两个指::BLINK指向前一个元素,FLINK指向下一个元素。

1. 链表结构

[cpp] view plain copy

print?

  1. // WDK中定义的双向链表数据结构
  2. //
  3. //  Doubly linked list structure.  Can be used as either a list head, or
  4. //  as link words.
  5. //
  6. typedef struct _LIST_ENTRY {
  7. struct _LIST_ENTRY *Flink;
  8. struct _LIST_ENTRY *Blink;
  9. } LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;
  10. //
  11. //  Singly linked list structure. Can be used as either a list head, or
  12. //  as link words.
  13. //
  14. typedef struct _SINGLE_LIST_ENTRY {
  15. struct _SINGLE_LIST_ENTRY *Next;
  16. } SINGLE_LIST_ENTRY, *PSINGLE_LIST_ENTRY;
  17. //
// WDK中定义的双向链表数据结构

//
//  Doubly linked list structure.  Can be used as either a list head, or
//  as link words.
//

typedef struct _LIST_ENTRY {
   struct _LIST_ENTRY *Flink;
   struct _LIST_ENTRY *Blink;
} LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;

//
//  Singly linked list structure. Can be used as either a list head, or
//  as link words.
//

typedef struct _SINGLE_LIST_ENTRY {
    struct _SINGLE_LIST_ENTRY *Next;
} SINGLE_LIST_ENTRY, *PSINGLE_LIST_ENTRY;

// 

2. 链表初始化

初始化链表头用InitializeListHead宏实现。让双向链表的两个指针都指向自己。

判断链表是否为空,只用判断链表指针是否指向自己即可。WDK提供了一个IsListEmpty。

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

[cpp] view plain copy

print?

  1. //
  2. typedef struct _MYDATASTRUCT{
  3. // List Entry要作为_MYDATASTRUCT结构体的一部分
  4. LIST_ENTRY ListEntry;
  5. // 自己定义的数据
  6. ULONG x;
  7. ULONG y;
  8. };
  9. //
//

typedef struct _MYDATASTRUCT{
    // List Entry要作为_MYDATASTRUCT结构体的一部分
    LIST_ENTRY ListEntry;

    // 自己定义的数据
    ULONG x;
    ULONG y;
};

//

3. 从首部插入链表

在头部插入链表使用语句InsertHeadList。

[cpp] view plain copy

print?

  1. //
  2. InsertHeadList(&head, &mydata->ListEntry);
  3. //
//

InsertHeadList(&head, &mydata->ListEntry);

//

head是LIST_ENTRY结构的链表头,mydata是用户定义的数据结构,它的子域ListEntry是包含其中的LIST_ENTRY数据结构。

4. 从尾部插入链表

在尾部插入链表使用语句InsertTailList。

[cpp] view plain copy

print?

  1. //
  2. InsertTailList(&head, &mydata->ListEntry);
  3. //
//

InsertTailList(&head, &mydata->ListEntry);

//

head是LIST_ENTRY结构的链表头,mydata是用户定义的数据结构,它的子域ListEntry是包含其中的LIST_ENTRY数据结构。

5. 从链表删除

从链表删除元素也是分两种。一种是从链表头部删除,一种是从链表尾部删除。分别队形RemoveHeadList和RemoveTailList函数。

[cpp] view plain copy

print?

  1. //
  2. PLIST_ENTRY pEntry = RemoveHeadList(&head);
  3. PLIST_ENTRY pEntry = RemoveTailList(&tail);
  4. //
//

PLIST_ENTRY pEntry = RemoveHeadList(&head);
PLIST_ENTRY pEntry = RemoveTailList(&tail);

//

head是链表头,pEntry是从链表删除下来的元素中的ListEntry。

如果用户自定义的数据结构第一个字段是LIST_ENTRY时,返回的指针可以强制转换为用户的数据结构指针。

如果第一个字段不是LIST_ENTRY时,需要减去偏移量。为了简化操作WDK提供了宏CONTAINING_RECORD,其用法如下:

[cpp] view plain copy

print?

  1. //
  2. PLIST_ENTRY pEntry = RemoveHeadList(&head);
  3. PIRP pIrp = CONTAINING_RECORD(pEntry, MYDATASTRUCT, ListEntry);
  4. //
//

PLIST_ENTRY pEntry = RemoveHeadList(&head);
PIRP pIrp = CONTAINING_RECORD(pEntry, MYDATASTRUCT, ListEntry);

//

ListEntry为自定义的数据结构指针。


三、 Lookaside结构

频繁申请和回收内存,会导致在内存上产生大量内存“空洞”,导致无法申请新的内存。WDK为程序员提供了Lookaside结构来解决此问题。

1. 频繁申请内存的弊端

频繁的申请与释放内存,会导致内存产生大量碎片。即使内存中有大量的可用内存,也会导致没有足够的连续内存空间而导致申请内存失败。在操作系统空闲的时候,系统会整理内存中的碎片,将碎片合并。

2. 使用Lookaside

Lookaside对象可以理解成一个内存容器。在初始的时候,它先向Windows申请量一块比较大的内存。以后程序员每次申请的时候就不直接向Windows申请内存了,而是直接向Lookaside对象申请呢村。Lookaside对象智能的避免产生内存碎片。

如果Lookaside内部内存不够用时它会向操作系统申请更多的内存。当Lookaside有大量内存未被使用时,它会让Windows回收部分内存。使用Lookaside申请内存效率要高于直接向Windows申请内存。

Lookaside一般在以下情况使用:

a. 程序员每次申请固定大小的内存;

b. 申请和回收操作非常频繁。

使用Lookaside对象,首先要进行初始化:

[cpp] view plain copy

print?

  1. // WDK提供的Lookaside初始化函数
  2. VOID ExInitializeNPagedLookasideList(
  3. IN PNPAGED_LOOKASIDE_LIST  Lookaside,
  4. IN PALLOCATE_FUNCTION  Allocate  OPTIONAL,
  5. IN PFREE_FUNCTION  Free  OPTIONAL,
  6. IN ULONG  Flags,
  7. IN SIZE_T  Size,
  8. IN ULONG  Tag,
  9. IN USHORT  Depth);
  10. VOID ExInitializePagedLookasideList(
  11. IN PPAGED_LOOKASIDE_LIST  Lookaside,
  12. IN PALLOCATE_FUNCTION  Allocate  OPTIONAL,
  13. IN PFREE_FUNCTION  Free  OPTIONAL,
  14. IN ULONG  Flags,
  15. IN SIZE_T  Size,
  16. IN ULONG  Tag,
  17. IN USHORT  Depth);
  18. //
// WDK提供的Lookaside初始化函数

VOID ExInitializeNPagedLookasideList(
    IN PNPAGED_LOOKASIDE_LIST  Lookaside,
    IN PALLOCATE_FUNCTION  Allocate  OPTIONAL,
    IN PFREE_FUNCTION  Free  OPTIONAL,
    IN ULONG  Flags,
    IN SIZE_T  Size,
    IN ULONG  Tag,
    IN USHORT  Depth);

VOID ExInitializePagedLookasideList(
    IN PPAGED_LOOKASIDE_LIST  Lookaside,
    IN PALLOCATE_FUNCTION  Allocate  OPTIONAL,
    IN PFREE_FUNCTION  Free  OPTIONAL,
    IN ULONG  Flags,
    IN SIZE_T  Size,
    IN ULONG  Tag,
    IN USHORT  Depth);

// 

这两个函数分别是对非分页内存和分页内存的申请。内存回收可用以下函数

[cpp] view plain copy

print?

  1. //
  2. VOID
  3. ExFreeToNPagedLookasideList(
  4. IN PNPAGED_LOOKASIDE_LIST  Lookaside,
  5. IN PVOID  Entry);
  6. VOID
  7. ExFreeToPagedLookasideList(
  8. IN PPAGED_LOOKASIDE_LIST  Lookaside,
  9. IN PVOID  Entry);
  10. //
// 

VOID
  ExFreeToNPagedLookasideList(
    IN PNPAGED_LOOKASIDE_LIST  Lookaside,
    IN PVOID  Entry);

VOID
  ExFreeToPagedLookasideList(
    IN PPAGED_LOOKASIDE_LIST  Lookaside,
    IN PVOID  Entry);

// 

它们是用于回收非分页内存与分页内存。

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

[cpp] view plain copy

print?

  1. //
  2. VOID ExDeleteNPagedLookasideList(IN PNPAGED_LOOKASIDE_LIST  Lookaside);
  3. VOID ExDeletePagedLookasideList(IN PPAGED_LOOKASIDE_LIST  Lookaside);
  4. //
// 

VOID ExDeleteNPagedLookasideList(IN PNPAGED_LOOKASIDE_LIST  Lookaside);

VOID ExDeletePagedLookasideList(IN PPAGED_LOOKASIDE_LIST  Lookaside);

// 

这两个函数分别删除非分页与分页的Lookaside对象。

Lookaside结构

频繁的申请和回收内存,会导致在内存上产生大量的内存“空洞”,从而导致最终无法申请内存。DDK为程序员提供了Lookaside结构来解决这个问题。

我们可以将Lookaside对象看成是一个内存容器。在初始化的时候,它先向Windows申请了一块比较大的内存。以后程序员每次申请内存的时候,不是直接向Windows申请内存,而是想Lookaside对象申请内存。Looaside会智能的避免产生内存“空洞”。如果Lookaside对象内部内存不够用时,它会向操作系统申请更多的内存。

Lookaside一般会在以下情况下使用:

1.       程序员每次申请固定大小的内存。

2.       申请和回收的操作十分频繁。

要使用Looaside对象,首先要初始化Lookaside对象,有以下两个函数可以使用:

(1)VOID    ExInitializeNPagedLookasideList(     IN PNPAGED_LOOKASIDE_LIST  Lookaside,     IN PALLOCATE_FUNCTION  Allocate  OPTIONAL,     IN PFREE_FUNCTION  Free  OPTIONAL,     IN ULONG  Flags,     IN SIZE_T  Size,     IN ULONG  Tag,     IN USHORT  Depth     );

(2)VOID    ExInitializePagedLookasideList(     IN PPAGED_LOOKASIDE_LIST  Lookaside,     IN PALLOCATE_FUNCTION  Allocate  OPTIONAL,     IN PFREE_FUNCTION  Free  OPTIONAL,     IN ULONG  Flags,     IN SIZE_T  Size,     IN ULONG  Tag,     IN USHORT  Depth     );

 

初始化玩Lookaside对象后,可以进行申请内存的操作了:

(1)PVOID      ExAllocateFromNPagedLookasideList(     IN PNPAGED_LOOKASIDE_LIST  Lookaside     );

 

(2)PVOID    ExAllocateFromPagedLookasideList(     IN PPAGED_LOOKASIDE_LIST  Lookaside     );

 

对Lookaside对象回收内存:

(1)VOID    ExFreeToNPagedLookasideList(     IN PNPAGED_LOOKASIDE_LIST  Lookaside,     IN PVOID  Entry     );

 

(2)VOID    ExFreeToPagedLookasideList(     IN PPAGED_LOOKASIDE_LIST  Lookaside,     IN PVOID  Entry     );

 

在使用完Lookaside对象后,要删除Lookaside对象:

(1)VOID    ExDeleteNPagedLookasideList(     IN PNPAGED_LOOKASIDE_LIST  Lookaside     );

 

(2) VOID    ExDeletePagedLookasideList(     IN PPAGED_LOOKASIDE_LIST  Lookaside     );

 

测试代码:

#pragma INITCODE

VOID LookasideTets()

{

KdPrint(("进入LookasideTest函数!\n"));

PAGED_LOOKASIDE_LIST  Lookaside;

ExInitializePagedLookasideList(&Lookaside,NULL, NULL, 0, sizeof(MYDATASTRUCT), ‘abcd‘, 0);

PMYDATASTRUCT pMyData[50];

for (inti=0; i<50; i++)

{

pMyData[i] = (PMYDATASTRUCT)ExAllocateFromPagedLookasideList(&Lookaside);

if ((i+1)%10 == 0)

{

KdPrint(("申请了 %d 个数据了!\n", ++i));

}

}

for (inti=0; i<50; i++)

{

ExFreeToPagedLookasideList(&Lookaside,pMyData[i]);

pMyData[i] =NULL;

if ((i+1)%10 == 0)

{

KdPrint(("释放了 %d 个数据的内存了!\n", ++i));

}

}

ExDeletePagedLookasideList(&Lookaside);

}

2.运行时函数

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

VOID    RtlCopyMemory(     IN VOID UNALIGNED  *Destination,     IN CONST VOID UNALIGNED  *Source,     IN SIZE_T  Length     );

 

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

VOID    RtlMoveMemory(     IN VOID UNALIGNED  *Destination,     IN CONST VOID UNALIGNED  *Source,     IN SIZE_T  Length     );

 

(3)填充内存

VOID    RtlFillMemory(     IN VOID UNALIGNED  *Destination,     IN SIZE_T  Length,     IN UCHAR  Fill     );

 

VOID    RtlZeroMemory(     IN VOID UNALIGNED  *Destination,     IN SIZE_T  Length     );

 

(4)内存比较

SIZE_T    RtlCompareMemory(     IN CONST VOID  *Source1,     IN CONST VOID  *Source2,     IN SIZE_T  Length     );

ULONG   RtlEqualMemory(      CONST VOID  *Source1,      CONST VOID  *Source2,      SIZE_T  Length      );

测试代码:

#define BUFFER_SIZE 1024

#pragma INITCODE

VOID RtlTest()

{

KdPrint(("进入RtlTest函数!\n"));

PUCHAR pBuffer1 = (PUCHAR)ExAllocatePool(PagedPool,BUFFER_SIZE);

RtlZeroMemory(pBuffer1,BUFFER_SIZE);

PUCHAR pBuffer2 = (PUCHAR)ExAllocatePool(PagedPool,BUFFER_SIZE);

RtlFillMemory(pBuffer2,BUFFER_SIZE, 0xAA);

RtlCopyMemory(pBuffer1,pBuffer2, BUFFER_SIZE);

if (RtlEqualMemory(pBuffer1,pBuffer2, BUFFER_SIZE))

{

KdPrint(("两块内存块数据一样!\n"));

for(inti=0; i<BUFFER_SIZE;i++)

{

KdPrint(("%02X", pBuffer1[i]));

}

}

else

{

KdPrint(("两块内存块数据不一样!\n"));

}

KdPrint(("离开RtlTest函数!\n"));

}

时间: 2024-08-28 02:21:14

[Windows驱动开发](四)内存管理的相关文章

Windows驱动开发基础(八)内存管理

Windows驱动开发基础系列,转载请标明出处:http://blog.csdn.net/ikerpeng/article/details/38826159 就32位的计算机来说,他有4G的真实的物理内存.但是这样是不够的,于是引入了虚拟内存的概念.使得每一个进程都有4G的虚拟内存. 虚拟内存实际上就是采用了一种映射的方式.4G的内存实际上被分页.一般来说一个页的大小是4K.也是说它被分为了1M个页.在这么多的页里面,有一部分是对应于物理内存的(可以是多对一的):有一部分是对应于磁盘上的空间,但

Windows驱动开发(中间层)

Windows驱动开发 一.前言 依据<Windows内核安全与驱动开发>及MSDN等网络质料进行学习开发. 二.初步环境 1.下载安装WDK7.1.0(WinDDK\7600.16385.1) 地址:https://msdn.microsoft.com/en-us/windows/hardware/hh852365 2.下载InstDrv软件(用于安装.启动.停止.卸载驱动) 界面如下: 注:srvinstw.exe 也可以安装.卸载sys文件,但需要手动开启.关闭,即在cmd命令窗口下执行

Windows驱动开发基础(五)驱动程序的数据结构

Windows驱动开发基础:驱动程序的数据结构.转载请标明出处:http://blog.csdn.net/ikerpeng/article/details/38794405 I/O管理器定义了一些数据结构,这些很重要. 1. 驱动对象(DRIVER_OBJECT) 通过一个typedef 定义的以一个struct: <span style="font-family:Microsoft YaHei;">typedef struct { PDEVICE_OBJECT Devic

【转】Windows驱动开发如何入门

1.http://blog.csdn.net/charlessimonyi/article/details/50904854 (2016年03月16日 14:55:36) 2. 搞Windows驱动开发是一件痛苦的事情,特别是初学Windows驱动开发.有的人觉得Windows驱动开发就是把开发包WDK下载下来,然后只要掌握了C/C++语言,接下来无非就是类库调来调去,像调用MFC.QT之类的库那样,看着书和MSDN上的文档来就行了.等真正接触以后才发现根本不是那么一回事,痛苦源于以下几点: 痛

[转载]对iOS开发中内存管理的一点总结与理解

对iOS开发中内存管理的一点总结与理解 做iOS开发也已经有两年的时间,觉得有必要沉下心去整理一些东西了,特别是一些基础的东西,虽然现在有ARC这种东西,但是我一直也没有去用过,个人觉得对内存操作的理解是衡量一个程序员成熟与否的一个标准.好了,闲话不说,下面进入正题. 众所周知,ObjectiveC的内存管理引用的一种叫做“引用计数“ (Reference Count)的操作方式,简单的理解就是系统为每一个创建出来的对象,(这里要注意,只是对象,NSObject的子类,基本类型没有‘引用计数’)

Windows 驱动开发 - 1

上篇<Windows 驱动开发 - 基础2>已经介绍了windows大概的框架.那么使用WDF有那些的不同呢? 我们知道在WDF中,KMDF是必须的.而KMDF是架构在WDM之上的.  WDM驱动程序模型 在 WDM 驱动程序模型中,每个硬件设备至少有两个驱动程序.其中一个驱动程序我们称为功能(function)驱动程序,通常它就是你认为的那个硬件设备驱动程序.它了解使硬件工作的所有细节,负责初始化 I/O 操作,有责任处理 I/O 操作完成时所带来的中断事件,有责任为用户提供一种设备适合的控

YJX_Driver_033_驱动中的内存管理

1. 驱动中的内存管理 A. 物理内存 B. 虚拟内存 C. Ring0地址和Ring3地址 D. 驱动程序和进程的关系 E. 分页和非分页内存 F. 分配内核内存 [120]了解两个概念:物理内存  虚拟内存 [140]以下概念针对 32位Windows操作系统(32位及以上的CPU)(32位 / 64位 CPU) [210]64位下的这些概念 略有区别 [240] “ A.物理内存:(Physical Memory Address) 目前主流的操作系统还是32位的XP,而32位的系统提供的寻

Windows 驱动开发 - 6

上篇<Windows 驱动开发 - 5>我们设置了回调事件EvtIoDeviceControl,我们在此篇来实现他. 我们知道在进行读写之前我们要进行一定约定,比如同步. 在WDF中控制同步约定的方法为:WdfUsbTargetDeviceSendControlTransferSynchronously NTSTATUS WdfUsbTargetDeviceSendControlTransferSynchronously( [in] WDFUSBDEVICE UsbDevice, [in, o

Windows 驱动开发 - 2

上一篇<Windows 驱动开发 - 1>我们已经实现一个WDF"骨架".但是,他什么也没有干... WDF对象分为3类:方法.事件和属性. 在上节中WDF_DRIVER_CONFIG_INIT(&config, EvtDeviceAdd); WDF通知驱动程序执行EvtDeviceAdd事件. 接下来我们需要通知计算机准备硬件! 我们知道大部分IRP都是通过PnP/电源管理.I/O和WMI来实现功能. 参考文献: 1. Architecture of the Ke

Windows驱动开发(二)

本节主要介绍驱动开发的一些基础知识. 1. 驱动程序的基本组成 1.1. 最经常见到的数据结构 a. DRIVER_OBJECT驱动对象 [cpp] view plaincopy // WDK中对驱动对象的定义 // 每个驱动程序都会有一个唯一的驱动对象与之对应 // 它是在驱动加载时被内核对象管理程序创建的 typedef struct _DRIVER_OBJECT { CSHORT Type; CSHORT Size; // // The following links all of the