设为首页 收藏本站
查看: 746|回复: 0

[经验分享] [Windows驱动开发](四)内存管理

[复制链接]

尚未签到

发表于 2017-6-29 08:01:10 | 显示全部楼层 |阅读模式
一、内存管理概念
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?

  • //  
  •   
  • #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")  
  •   
  • //   

//
#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?

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

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



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

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









[cpp] view plain copy


print?

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

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



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









[cpp] view plain copy


print?

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

//
#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?

  • //  
  •   
  • 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  
  •     );  
  •   
  • //  

//
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?

  • //  
  •   
  • NTKERNELAPI  
  • VOID  
  • ExFreePoolWithTag(  
  •     __in __drv_freesMem(Mem) PVOID P,   // 要释放的地址  
  •     __in ULONG Tag  
  •     );  
  •   
  • #define ExFreePool(a) ExFreePoolWithTag(a,0)  
  •   
  • //  

//
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?

  • // 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;  
  •   
  • //   

// 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?

  • //  
  •   
  • typedef struct _MYDATASTRUCT{  
  •     // List Entry要作为_MYDATASTRUCT结构体的一部分  
  •     LIST_ENTRY ListEntry;  
  •   
  •     // 自己定义的数据  
  •     ULONG x;  
  •     ULONG y;  
  • };  
  •   
  • //  

//
typedef struct _MYDATASTRUCT{
// List Entry要作为_MYDATASTRUCT结构体的一部分
LIST_ENTRY ListEntry;
// 自己定义的数据
ULONG x;
ULONG y;
};
//



3. 从首部插入链表

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









[cpp] view plain copy


print?

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

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



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



4. 从尾部插入链表

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









[cpp] view plain copy


print?

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

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



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



5. 从链表删除

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









[cpp] view plain copy


print?

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

//
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?

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

//
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?

  • // 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);  
  •   
  • //   

// 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?

  • //   
  •   
  • VOID   
  •   ExFreeToNPagedLookasideList(  
  •     IN PNPAGED_LOOKASIDE_LIST  Lookaside,  
  •     IN PVOID  Entry);  
  •   
  • VOID   
  •   ExFreeToPagedLookasideList(  
  •     IN PPAGED_LOOKASIDE_LIST  Lookaside,  
  •     IN PVOID  Entry);  
  •   
  • //   

//
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?

  • //   
  •   
  • VOID ExDeleteNPagedLookasideList(IN PNPAGED_LOOKASIDE_LIST  Lookaside);  
  •   
  • VOID ExDeletePagedLookasideList(IN PPAGED_LOOKASIDE_LIST  Lookaside);  
  •   
  • //   

//
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 = (PMYDATASTRUCT)ExAllocateFromPagedLookasideList(&Lookaside);

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

         {


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

         }

     }

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

     {

         ExFreeToPagedLookasideList(&Lookaside,pMyData);

         pMyData =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));

         }

        

     }

     else

     {

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

     }

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

  }

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-389173-1-1.html 上篇帖子: MyCat部署运行(Windows环境)与使用步骤详解 下篇帖子: Surface Dial 与 Windows Wheel UWP应用开发
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表