<学习笔记>Windows驱动开发技术详解__Windows内存管理
2016-03-31 20:29
501 查看
作为开发Windows驱动程序的程序员,需要比普通程序员更多了解Windows内部的内存管理机制,并在驱动程序中有效地使用内存。在驱动程序的编写中,分配和管理内存不能使用熟知的Win32 API函数,取而代之的是DDK提供的高效内核函数。C语言和C++中大多数关于内存操作的运行时函数,大多在内核模式下是无法使用的。例如,C语言中的malloc函数和C++中的new操作符等。
内存管理的概念:
1.物理内存:
PC上有三条总线,分别是数据总线,地址总线和控制总线。32位的CPU寻址能力是4GB。用户最多可以使用4GB的真实物理内存。PC中会拥有许多设备,其中很多设备都拥有自己的设备内存,这部分的设备内存会映射到PC机的物理内存上,读写这段物理地址其实会读写设备内存地址。
2虚拟内存:
.虽然可以寻址4GB的内存,而PC里往往没有如此多的真实物理内存。操作系统和硬件为使用者提供了虚拟内存的概念。虚拟内存和物理内存之间的转换暂不讨论。
3.用户模式地址和内核模式地址:
虚拟地址在0~0X7FFFFFFF范围内的虚拟内存,即低2GB的虚拟地址,被称为用户模式地址。而0X80000000~0XFFFFFFFF范围内的虚拟内存,即高2GB的虚拟内存,被称为内核模式地址。
4.Windows驱动程序和进程的关系:
驱动程序可以看成一个特殊的DLL文件被应用程序加载到虚拟内存中,只不过加载地址是内核模式地址,而不是用户模式地址。
5.分页和非分页内存:
Windows规定有些虚拟内存页面是可以交换到文件中的,这类内存成为分页内存。而有些虚拟内存页永远不会交换到文件中,这些内存被称为非分页内存。
当程序的中断请求级在DISPATCH_LEVEL之上时,程序只能使用非分页内存,否则会导致蓝屏死机。
在编译DDK提供的例程时,可以指定某个例程和某个全局变量是载入分页内存还是非分页内存,需要做如下定义:
[cpp] view
plain copy
#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")
如果将某个函数在入到分页内存,我们需要在函数的实现中加入如下代码:
[cpp] view
plain copy
#pragma INITCODE
[cpp] view
plain copy
VOID SomeFunction()
{
PAGED_CODE();
//do something
}
如果要让程序加载到非分页内存,需要在函数的实现中加入如下代码:
[cpp] view
plain copy
#pragma LOCKEDCODE
VOID SomeFunction()
{
//do something
}
还有一种特殊情况,就是某个例程初始化的时候载入内存,然后就可以从内存中卸载掉,例如DriverEntry
[cpp] view
plain copy
#pragma INITCODE
extern "C" NTSTATUS DriverEntry(
IN PDRIVER_OBJECT pDriverObject,
IN PUNICODE_STRING pRegistryPath
)
{
}
6.内存的分配:
Windows驱动程序使用的内存资源非常珍贵,局部变量是在栈(stack)空间中,但是驱动程序的栈空间不会像应用程序那么大,所以不适合进行递归调用或者局部变量是大型的结构体,否则请在堆(Heap)中申请。
堆中申请内存的函数有以下几个:
[cpp] view
plain copy
PVOID
ExAllocatePool(
IN POOL_TYPE PoolType,
IN SIZE_T NumberOfBytes
);
PVOID
ExAllocatePoolWithTag(
IN POOL_TYPE PoolType,
IN SIZE_T NumberOfBytes,
IN ULONG Tag
);
PVOID
ExAllocatePoolWithQuota(
IN POOL_TYPE PoolType,
IN SIZE_T NumberOfBytes
);
PVOID
ExAllocatePoolWithQuotaTag(
IN POOL_TYPE PoolType,
IN SIZE_T NumberOfBytes,
IN ULONG Tag
);
其中有些重要的参数:
PoolType: 是个枚举变量,如果此值为NonPagedPool,则分配非分页内存。如果次值为PagedPool,则分配内存分页内存。
NumberOfBytes:是分配内存的大小,注意最好是4的倍数。
以上四个函数功能类似,函数以WithQuota结尾的代表分配的时候按额分配。以WithTag结尾的函数和ExAllocatePool功能类似,唯一不同的是多了一个Tag参数,
系统要求的内存外又额外地多分配4个字节的标签。在调试的时候,可以找出是否有标有这个标签的内存没有被释放。
将分配的内存,进行回收的函数原型如下:
[cpp] view
plain copy
VOID
ExFreePool(
IN PVOID P
);
NTKERNELAPI
VOID
ExFreePoolWithTag(
IN PVOID P,
IN ULONG Tag
);
参数P就是要释放的内存。
在内存中使用链表:
链表中可以记录整形,浮点型,字符型或者程序员自定义的数据结构。对于单链表,元素中有一个Next指针指向下一个元素。对于双链表,每个元素有两个指针:BLINK指向前一个元素,FLINK指向下一个元素。
1.链表的结构:
DDK提供了标准的双向链表。双向链表可以将链表形成一个环。以下是DDK提供的双向链表的数据结构:
[cpp] view
plain copy
typedef struct _LIST_ENTRY {
struct _LIST_ENTRY *Flink;
struct _LIST_ENTRY *Blink;
} LIST_ENTRY, *PLIST_ENTRY
这个结构体只有指针没有数据。
2.链表的初始化:
每个双向链表都是以链表头作为链表的第一个元素。初次使用链表头需要进行初始化,主要将链表头的Flink和Blink两个指针都指向自己。初始化链表头用InitiallizeListHead宏实现。判断链表头是否为空,DDK提供了一个宏简化这种检查,这就是IsListEmpty。
[cpp] view
plain copy
IsListEmpty(&head);
程序员需要自己定义链表中每个元素的数据类型,并将LIST_ENTRY结构作为自定义结构的一个子域。例如:
[cpp] view
plain copy
typedef struct _MYDATASTRUCT
{
LIST_ENTRY ListEntry;
ULONG x;
ULONG y;
}MYDATASTRUCT,*PMYDATASTRCUT;
3.插入链表:
对链表的插入有两种方式,一种是在链表的头部插入,一种是在链表的尾部插入。
在头部插入链表使用语句InsertHeadList,用法如下:
[cpp] view
plain copy
InsertHeadList(&head,&mydata->ListEntry);
在尾部插入链表使用语句InsertTailList,用法如下:
[cpp] view
plain copy
InsertTailList(&head,&mydata->ListEntry);
4.链表的删除:
和插入链表一样,删除链表也有两种方法。一种从头部删除,一种从尾部删除。分别对应RemoveHeadList和RemoveTailList函数。其使用方法如下:
[cpp] view
plain copy
PLIST_ENTRY = RemoveHeadList(&head);
PLIST_ENTRY = RemoveTailList(&head);
下面代码完整演示向链表进行插入,删除等操作,其主要代码如下:
[cpp] view
plain copy
typedef struct _MYDATASTRUCT
{
ULONG number;
LIST_ENTRY ListEntry;
}MYDATASTRUCT,*PMYDATASTRCUT;
#pragma INITCODE
VOID LinkListTest()
{
LIST_ENTRY linkListHead;
//初始化链表
InitializeListHead(&linkListHead);
PMYDATASTRCUT pData;
ULONG i = 0;
//在链表中插入10个元素
KdPrint(("Begain insert to link list"));
for (i = 0 ; i < 10 ; i++)
{
pData = (PMYDATASTRCUT)
ExAllocatePool(PagedPool,sizeof(MYDATASTRUCT));
pData->number = i;
InsertHeadList(&linkListHead,&pData->ListEntry);
}
//从链表中取出,并显示
KdPrint(("Begain 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);
}
}
在DebugView中打印log信息:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202003/14/c86831a6af73a0bc6d5935947201628a.gif)
Lookaside结构:
频繁申请和回收内存,会导致在内存上产生大量的内存“空洞”,从而导致最终无法申请内存。DDK为程序员提供了Lookaside结构来解决这个问题。
1.使用Lookaside:
Lookaside一般会在一下情况使用:
(1)程序员每次申请固定大小的内存
(2)申请和回收的操作十分频繁
使用Lookaside对象,首先要初始化Lookaside对象,有以下两个函数可以使用:
[cpp] view
plain copy
VOID
ExInitializeNPagedLookasideList(
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
);
VOID
ExInitializePagedLookasideList(
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
);
这两个函数分别对非分页和分页Lookaside对象进行初始化。
在初始化完Lookaside对象后,可以进行申请内存的操作了,有以下两个函数:
[cpp] view
plain copy
PVOID
ExAllocateFromNPagedLookasideList(
IN PNPAGED_LOOKASIDE_LIST Lookaside
);
PVOID
ExAllocateFromPagedLookasideList(
IN PPAGED_LOOKASIDE_LIST Lookaside
);
这两个函数分别是对非分页和分页内存的申请。
对Lookaside对象进行回收内存的操作,有以下两个函数:
[cpp] view
plain copy
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
VOID
ExDeleteNPagedLookasideList(
IN PNPAGED_LOOKASIDE_LIST Lookaside
);
VOID
ExDeletePagedLookasideList(
IN PPAGED_LOOKASIDE_LIST Lookaside
);
下面代码完整展示Lookaside对象的使用:
[cpp] view
plain copy
#pragma INITCODE
VOID 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;
}
//删除Lookaside对象
ExDeletePagedLookasideList(&pageList);
}
运行时函数:
1.内存间复制(非重叠)
在驱动程序开发中,经常用到内存的复制。DDK为程序员提供了以下函数:
[cpp] view
plain copy
VOID
RtlCopyMemory(
IN VOID UNALIGNED *Destination,
IN CONST VOID UNALIGNED *Source,
IN SIZE_T Length
);
Destination:表示要复制内存的目的地址
Source:表示要复制内存的源地址
Length:表示要复制内存的长度,单位是字节
2.内存间的复制(可重叠)
函数原型:
[cpp] view
plain copy
VOID
RtlMoveMemory(
IN VOID UNALIGNED *Destination,
IN CONST VOID UNALIGNED *Source,
IN SIZE_T Length
);
Destination:表示要复制内存的目的地址
Source:表示要复制内存的源地址
Length:表示要复制内存的长度,单位是字节
3.内存填充:
驱动程序开发中,还经常用到对某段内存区域用固定字节填充。DDK为程序员提供了函数RtlFillMemory。它在IA32平台下也是个宏,实际是memset函数。
[cpp] view
plain copy
VOID
RtlFillMemory(
IN VOID UNALIGNED *Destination,
IN SIZE_T Length,
IN UCHAR Fill
);
Destination:目的地址
Length:长度
Fill:需要填充的字节
在驱动开发中,还经常需要对某段内存填零,DDK提供的宏是RtZeroBytes和RtZeroMemory。
[cpp] view
plain copy
VOID
RtlZeroMemory(
IN VOID UNALIGNED *Destination,
in SIZE_T Length
);
Destination:目的地址
Length:长度
4.内存比较:
驱动开发中,还会用到比较两块内存是否一致。该函数是RtlCompareMemory,其申明是:
[cpp] view
plain copy
ULONG
RtlEqualMemory(
CONST VOID *Source1,
CONST VOID *Source2
SIZE_T Length
);
Source1:比较的第一个内存地址
Source2:比较的第二个内存地址
Length:比较的长度,单位为字节
将这些运行时函数统一做一个实验,代码如下:
[cpp] view
plain copy
#define BUFFER_SIZE 1024
#pragma INITCODE
VOID RtTest()
{
PUCHAR pBuffer = (PUCHAR)ExAllocatePool(PagedPool,BUFFER_SIZE);
//用零填充内存
RtlZeroMemory(pBuffer,BUFFER_SIZE);
PUCHAR pBuffer2 = (PUCHAR)ExAllocatePool(PagedPool,BUFFER_SIZE);
//用固定字节填充内存
RtlFillMemory(pBuffer,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"));
}
}
内存管理的概念:
1.物理内存:
PC上有三条总线,分别是数据总线,地址总线和控制总线。32位的CPU寻址能力是4GB。用户最多可以使用4GB的真实物理内存。PC中会拥有许多设备,其中很多设备都拥有自己的设备内存,这部分的设备内存会映射到PC机的物理内存上,读写这段物理地址其实会读写设备内存地址。
2虚拟内存:
.虽然可以寻址4GB的内存,而PC里往往没有如此多的真实物理内存。操作系统和硬件为使用者提供了虚拟内存的概念。虚拟内存和物理内存之间的转换暂不讨论。
3.用户模式地址和内核模式地址:
虚拟地址在0~0X7FFFFFFF范围内的虚拟内存,即低2GB的虚拟地址,被称为用户模式地址。而0X80000000~0XFFFFFFFF范围内的虚拟内存,即高2GB的虚拟内存,被称为内核模式地址。
4.Windows驱动程序和进程的关系:
驱动程序可以看成一个特殊的DLL文件被应用程序加载到虚拟内存中,只不过加载地址是内核模式地址,而不是用户模式地址。
5.分页和非分页内存:
Windows规定有些虚拟内存页面是可以交换到文件中的,这类内存成为分页内存。而有些虚拟内存页永远不会交换到文件中,这些内存被称为非分页内存。
当程序的中断请求级在DISPATCH_LEVEL之上时,程序只能使用非分页内存,否则会导致蓝屏死机。
在编译DDK提供的例程时,可以指定某个例程和某个全局变量是载入分页内存还是非分页内存,需要做如下定义:
[cpp] view
plain copy
#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")
如果将某个函数在入到分页内存,我们需要在函数的实现中加入如下代码:
[cpp] view
plain copy
#pragma INITCODE
[cpp] view
plain copy
VOID SomeFunction()
{
PAGED_CODE();
//do something
}
如果要让程序加载到非分页内存,需要在函数的实现中加入如下代码:
[cpp] view
plain copy
#pragma LOCKEDCODE
VOID SomeFunction()
{
//do something
}
还有一种特殊情况,就是某个例程初始化的时候载入内存,然后就可以从内存中卸载掉,例如DriverEntry
[cpp] view
plain copy
#pragma INITCODE
extern "C" NTSTATUS DriverEntry(
IN PDRIVER_OBJECT pDriverObject,
IN PUNICODE_STRING pRegistryPath
)
{
}
6.内存的分配:
Windows驱动程序使用的内存资源非常珍贵,局部变量是在栈(stack)空间中,但是驱动程序的栈空间不会像应用程序那么大,所以不适合进行递归调用或者局部变量是大型的结构体,否则请在堆(Heap)中申请。
堆中申请内存的函数有以下几个:
[cpp] view
plain copy
PVOID
ExAllocatePool(
IN POOL_TYPE PoolType,
IN SIZE_T NumberOfBytes
);
PVOID
ExAllocatePoolWithTag(
IN POOL_TYPE PoolType,
IN SIZE_T NumberOfBytes,
IN ULONG Tag
);
PVOID
ExAllocatePoolWithQuota(
IN POOL_TYPE PoolType,
IN SIZE_T NumberOfBytes
);
PVOID
ExAllocatePoolWithQuotaTag(
IN POOL_TYPE PoolType,
IN SIZE_T NumberOfBytes,
IN ULONG Tag
);
其中有些重要的参数:
PoolType: 是个枚举变量,如果此值为NonPagedPool,则分配非分页内存。如果次值为PagedPool,则分配内存分页内存。
NumberOfBytes:是分配内存的大小,注意最好是4的倍数。
以上四个函数功能类似,函数以WithQuota结尾的代表分配的时候按额分配。以WithTag结尾的函数和ExAllocatePool功能类似,唯一不同的是多了一个Tag参数,
系统要求的内存外又额外地多分配4个字节的标签。在调试的时候,可以找出是否有标有这个标签的内存没有被释放。
将分配的内存,进行回收的函数原型如下:
[cpp] view
plain copy
VOID
ExFreePool(
IN PVOID P
);
NTKERNELAPI
VOID
ExFreePoolWithTag(
IN PVOID P,
IN ULONG Tag
);
参数P就是要释放的内存。
在内存中使用链表:
链表中可以记录整形,浮点型,字符型或者程序员自定义的数据结构。对于单链表,元素中有一个Next指针指向下一个元素。对于双链表,每个元素有两个指针:BLINK指向前一个元素,FLINK指向下一个元素。
1.链表的结构:
DDK提供了标准的双向链表。双向链表可以将链表形成一个环。以下是DDK提供的双向链表的数据结构:
[cpp] view
plain copy
typedef struct _LIST_ENTRY {
struct _LIST_ENTRY *Flink;
struct _LIST_ENTRY *Blink;
} LIST_ENTRY, *PLIST_ENTRY
这个结构体只有指针没有数据。
2.链表的初始化:
每个双向链表都是以链表头作为链表的第一个元素。初次使用链表头需要进行初始化,主要将链表头的Flink和Blink两个指针都指向自己。初始化链表头用InitiallizeListHead宏实现。判断链表头是否为空,DDK提供了一个宏简化这种检查,这就是IsListEmpty。
[cpp] view
plain copy
IsListEmpty(&head);
程序员需要自己定义链表中每个元素的数据类型,并将LIST_ENTRY结构作为自定义结构的一个子域。例如:
[cpp] view
plain copy
typedef struct _MYDATASTRUCT
{
LIST_ENTRY ListEntry;
ULONG x;
ULONG y;
}MYDATASTRUCT,*PMYDATASTRCUT;
3.插入链表:
对链表的插入有两种方式,一种是在链表的头部插入,一种是在链表的尾部插入。
在头部插入链表使用语句InsertHeadList,用法如下:
[cpp] view
plain copy
InsertHeadList(&head,&mydata->ListEntry);
在尾部插入链表使用语句InsertTailList,用法如下:
[cpp] view
plain copy
InsertTailList(&head,&mydata->ListEntry);
4.链表的删除:
和插入链表一样,删除链表也有两种方法。一种从头部删除,一种从尾部删除。分别对应RemoveHeadList和RemoveTailList函数。其使用方法如下:
[cpp] view
plain copy
PLIST_ENTRY = RemoveHeadList(&head);
PLIST_ENTRY = RemoveTailList(&head);
下面代码完整演示向链表进行插入,删除等操作,其主要代码如下:
[cpp] view
plain copy
typedef struct _MYDATASTRUCT
{
ULONG number;
LIST_ENTRY ListEntry;
}MYDATASTRUCT,*PMYDATASTRCUT;
#pragma INITCODE
VOID LinkListTest()
{
LIST_ENTRY linkListHead;
//初始化链表
InitializeListHead(&linkListHead);
PMYDATASTRCUT pData;
ULONG i = 0;
//在链表中插入10个元素
KdPrint(("Begain insert to link list"));
for (i = 0 ; i < 10 ; i++)
{
pData = (PMYDATASTRCUT)
ExAllocatePool(PagedPool,sizeof(MYDATASTRUCT));
pData->number = i;
InsertHeadList(&linkListHead,&pData->ListEntry);
}
//从链表中取出,并显示
KdPrint(("Begain 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);
}
}
在DebugView中打印log信息:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202003/14/c86831a6af73a0bc6d5935947201628a.gif)
Lookaside结构:
频繁申请和回收内存,会导致在内存上产生大量的内存“空洞”,从而导致最终无法申请内存。DDK为程序员提供了Lookaside结构来解决这个问题。
1.使用Lookaside:
Lookaside一般会在一下情况使用:
(1)程序员每次申请固定大小的内存
(2)申请和回收的操作十分频繁
使用Lookaside对象,首先要初始化Lookaside对象,有以下两个函数可以使用:
[cpp] view
plain copy
VOID
ExInitializeNPagedLookasideList(
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
);
VOID
ExInitializePagedLookasideList(
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
);
这两个函数分别对非分页和分页Lookaside对象进行初始化。
在初始化完Lookaside对象后,可以进行申请内存的操作了,有以下两个函数:
[cpp] view
plain copy
PVOID
ExAllocateFromNPagedLookasideList(
IN PNPAGED_LOOKASIDE_LIST Lookaside
);
PVOID
ExAllocateFromPagedLookasideList(
IN PPAGED_LOOKASIDE_LIST Lookaside
);
这两个函数分别是对非分页和分页内存的申请。
对Lookaside对象进行回收内存的操作,有以下两个函数:
[cpp] view
plain copy
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
VOID
ExDeleteNPagedLookasideList(
IN PNPAGED_LOOKASIDE_LIST Lookaside
);
VOID
ExDeletePagedLookasideList(
IN PPAGED_LOOKASIDE_LIST Lookaside
);
下面代码完整展示Lookaside对象的使用:
[cpp] view
plain copy
#pragma INITCODE
VOID 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;
}
//删除Lookaside对象
ExDeletePagedLookasideList(&pageList);
}
运行时函数:
1.内存间复制(非重叠)
在驱动程序开发中,经常用到内存的复制。DDK为程序员提供了以下函数:
[cpp] view
plain copy
VOID
RtlCopyMemory(
IN VOID UNALIGNED *Destination,
IN CONST VOID UNALIGNED *Source,
IN SIZE_T Length
);
Destination:表示要复制内存的目的地址
Source:表示要复制内存的源地址
Length:表示要复制内存的长度,单位是字节
2.内存间的复制(可重叠)
函数原型:
[cpp] view
plain copy
VOID
RtlMoveMemory(
IN VOID UNALIGNED *Destination,
IN CONST VOID UNALIGNED *Source,
IN SIZE_T Length
);
Destination:表示要复制内存的目的地址
Source:表示要复制内存的源地址
Length:表示要复制内存的长度,单位是字节
3.内存填充:
驱动程序开发中,还经常用到对某段内存区域用固定字节填充。DDK为程序员提供了函数RtlFillMemory。它在IA32平台下也是个宏,实际是memset函数。
[cpp] view
plain copy
VOID
RtlFillMemory(
IN VOID UNALIGNED *Destination,
IN SIZE_T Length,
IN UCHAR Fill
);
Destination:目的地址
Length:长度
Fill:需要填充的字节
在驱动开发中,还经常需要对某段内存填零,DDK提供的宏是RtZeroBytes和RtZeroMemory。
[cpp] view
plain copy
VOID
RtlZeroMemory(
IN VOID UNALIGNED *Destination,
in SIZE_T Length
);
Destination:目的地址
Length:长度
4.内存比较:
驱动开发中,还会用到比较两块内存是否一致。该函数是RtlCompareMemory,其申明是:
[cpp] view
plain copy
ULONG
RtlEqualMemory(
CONST VOID *Source1,
CONST VOID *Source2
SIZE_T Length
);
Source1:比较的第一个内存地址
Source2:比较的第二个内存地址
Length:比较的长度,单位为字节
将这些运行时函数统一做一个实验,代码如下:
[cpp] view
plain copy
#define BUFFER_SIZE 1024
#pragma INITCODE
VOID RtTest()
{
PUCHAR pBuffer = (PUCHAR)ExAllocatePool(PagedPool,BUFFER_SIZE);
//用零填充内存
RtlZeroMemory(pBuffer,BUFFER_SIZE);
PUCHAR pBuffer2 = (PUCHAR)ExAllocatePool(PagedPool,BUFFER_SIZE);
//用固定字节填充内存
RtlFillMemory(pBuffer,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"));
}
}
相关文章推荐
- Linux下Postfix的配置和使用
- 算法 - 求和为n的连续正整数序列(C++)
- WINDOWS动态链接库--MFC规则动态链接库
- 自言自语的第一篇博文
- Git使用笔记01
- IOS学习之——SDWebImage的使用
- Ajax Loading —— spin.js
- 作文网高并发环境下如何使用MySQL
- C++ 几种智能指针的简单实现
- 循环队列
- oc内存管理
- 智能企业与信息化之一
- 基于任务的异步编程模式,Task-based Asynchronous Pattern
- php学习中的session和cookie的初步认识及其他
- Qt实现Ribbon效果
- 关于角色攻击范围判定和攻击判定
- leetcode 275. H-Index II
- 英语总结
- java集合,ArrayList,LinkedList知识点总结
- view 中函数的调用顺序,以xib自定义view为例