06-驱动中的内存管理

常规内存分配
WDK提供了一系列内存分配函数,其中最基本的是ExAllocatePoolWithTag,函数原型如下:
PVOID ExAllocatePoolWithTag (POOL_TYPE PoolType, SIZE_T NumberOfBytes, ULONG Tag);
PoolType表示需要申请何种类型的内存,PoolType为POOL_TYPE枚举类型,常用的值是NonPagedPool与PagedPool;其中NonPagedPool表示非分页内存,PagedPool表示分页内存;这里重新强调一下,非分页内存是指这块内存的内容不会被置换到磁盘上,非分页内存非常宝贵,一般用于高IRQL的代码中。当然,非分页内存也可以用于低IRQL的代码中。
NumberOfBytes表示分配内存的大小,单位是字节。
Tag 一个标记,用于识别内存(自己随便起,释放时会用到)
如果不想使用标记,可以选择ExAllocatePool函数。不过这个函数我在编译时会出现警告,并且编译失败,应该是微软有意淘汰这个函数,所有还是尽量使用安全的函数吧,如果非要使用不可,可以使用#pragma warning(disable: 4996)来消除警告,真心不推荐
除了NonPagedPool与PagedPool这两种内存,WDK还定义了其他一系列的内存类型:
typedef Enum_is_bitflag enum _POOL_TYPE {
NonPagedPool,
NonPagedPoolExecute = NonPagedPool,
PagedPool,
NonPagedPoolMustSucceed = NonPagedPool + 2,
DontUseThisType,
NonPagedPoolCacheAligned = NonPagedPool + 4,
PagedPoolCacheAligned,
NonPagedPoolCacheAlignedMustS = NonPagedPool + 6,
MaxPoolType,
NonPagedPoolBase = 0,
NonPagedPoolBaseMustSucceed = NonPagedPoolBase + 2,
NonPagedPoolBaseCacheAligned = NonPagedPoolBase + 4,
NonPagedPoolBaseCacheAlignedMustS = NonPagedPoolBase + 6,
NonPagedPoolSession = 32,
PagedPoolSession = NonPagedPoolSession + 1,
NonPagedPoolMustSucceedSession = PagedPoolSession + 1,
DontUseThisTypeSession = NonPagedPoolMustSucceedSession + 1,
NonPagedPoolCacheAlignedSession = DontUseThisTypeSession + 1,
PagedPoolCacheAlignedSession = NonPagedPoolCacheAlignedSession + 1,
NonPagedPoolCacheAlignedMustSSession = PagedPoolCacheAlignedSession + 1,
NonPagedPoolNx = 512,
NonPagedPoolNxCacheAligned = NonPagedPoolNx + 4,
NonPagedPoolSessionNx = NonPagedPoolNx + 32,
} Enum_is_bitflag POOL_TYPE;
从上面的定义可以看到,POOL_TYPE的枚举值较多,但很多是保留给系统使用的,除了上面介绍的NonPagedPool与PagedPool,还有两个重要的类型是NonPagedPoolExecute与NonPagedPoolNx。
NonPagedPool类型的内存属性为“可执行”。意思是这个内存中如过存入的可执行二进制数据那么这里可能会成为被攻击的对象。
所有就有了NonPagedPoolNx类型,它具备不可执行属性。
NonPagedPoolExecute类型与NonPagedPool类型等价。
ExAllocatePoolWithTag函数成功执行后返回分配内存的首地址,失败返回NULL。
在使用分配的内存前,一定要对返回值进行判断。因为在驱动中任何一个错误都即易引起蓝屏,特别是内存方面。
有申请就一定要释放,
释放使用ExFreePoolWithTag函数,ExFreePoolWithTag函数原型如下:
VOID ExFreePoolWithTag (PVOID P, ULONG Tag);
P表示需要释放的内存地址,也就是申请时的返回值。
Tag申请内存时自己做的标记。搞错了会引起内存释放失败。
如果分配内存使用的是ExAllocatePool函数,释放时请使用相应的ExFreePool函数。

内存管理池:
首先声明:内存管理池不是书面说法,只是我自己方便理解,书面叫 快查表 或 旁视列表。
在高频率是申请释放同样大小的内存时,如果使用普通的内存申请,会造成内存碎片,内存碎片过多会影响程序运行。
内存管理池的一般操作顺序是:开发者首先初始化一个内存管理池对象,在初始化时,需要设置内存管理池中内存块的大小,如每次需要从内存管理池对象中申请1024字节大小的内存,那么在初始化内存管理池时可以指定大小为1024;接着在需要使用内存的时候,直接向内存管理池对象申请内存,在内存使用完毕后,需要通过内存管理池对象来回收这些内存;最后,当不再需要使用内存管理池对象时将其删除。
内存管理池对象内部会维护内存的使用状态,一块内存使用结束后,会释放回内存管理池对象内,但这块内存不会马上被释放到操作系统中。如果这个时候开发者向内存管理池对象申请内存,内存管理池对象会把刚才回收的内存块返回给申请者。通过这种类似“缓存”的机制,内存管理池对象对内存进行了二次管理,减少了向系统申请或释放的次数,提高了性能。
先初始化一个内存管理池对象,初始化通过ExInitializeNPagedLookasideList函数实现,原型如下:VOID ExInitializeNPagedLookasideList (
Out PNPAGED_LOOKASIDE_LIST Lookaside,
In_opt PALLOCATE_FUNCTION Allocate,
In_opt PFREE_FUNCTION Free,
In ULONG Flags,
In SIZE_T Size,
In ULONG Tag,
In USHORT Depth );
参数Lookaside表示被初始化的内存管理池对象的指针,ExInitializeNPagedLookasideList执行后,Lookaside会被初始化。
参数Allocate是一个函数指针,当开发者需要从已初始化的内存管理池对象分配内存时,系统会调用开发者设置的Allocate函数,Allocate函数的原型如下:
PVOID ALLOCATE_FUNCTION (
In POOL_TYPE PoolType,
In SIZE_T NumberOfBytes,
In ULONG Tag);
typedef ALLOCATE_FUNCTION *PALLOCATE_FUNCTION;
从上面原型可以看出和ExAllocatePoolWithTag函数原型一致,可以自己实现一个Allocate函数,也可以使用系统默认的函数,系统默认填NULL即可。
参数Free也是一个函数指针,删除从内存管理池对象中申请出来的内存块时,系统就会调用Free参数指向的函数。Free函数原型如下:
VOID FREE_FUNCTION (In __drv_freesMem(Mem) PVOID Buffer);
typedef FREE_FUNCTION *PFREE_FUNCTION;
没有特殊的释放要求,可以把Free参数设置为NULL,在这种情况下,系统使用默认的释放函数。
参数Flags控制内存管理池对象的内存分配行为,WIN7之前的系统这个参数没有意义,可以设置的值有:
POOL_NX_ALLOCATION:表示分配的非分页内存的属性为不可执行,类似NonPagedPoolNx标志。
POOL_RAISE_IF_ALLOCATION_FAILURE:表示如果内存失败,将抛出一个异常。
如果没有特殊要求,可以把Flags参数设置为0。
Size参数表示每次从内存管理池对象中申请内存的固定大小,单位是字节,在64位系统下,这个值不能小于8。
Tag参数表示分配内存时所使用的标记,与ExAllocatePoolWithTag函数中的Tag参数函数一样。
Depth参数是一个保留参数,没有意义,传递0即可;
内存管理池对象初始化后,就可以从这个对象中申请内存,申请内存的函数为ExAllocateFromNPagedLookasideList,该函数的原型非常简单:
PVOID ExAllocateFromNPagedLookasideList ( Inout PNPAGED_LOOKASIDE_LIST Lookaside)
只需把内存管理池对象的地址(就是初始化函数的参数1)传入到上面的函数,即可分配内存,内存大小为ExInitializeNPagedLookasideList函数所指定的Size。如果ExAllocateFromNPagedLookasideList函数执行成功则返回相应的内存块,否则返回NULL。
ExAllocateFromNPagedLookasideList函数返回的内存块,需要使用ExFreeToNPagedLookasideList函数对其进行释放。ExFreeToNPagedLookasideList函数的原型如下:
VOID ExFreeToNPagedLookasideList (
Inout PNPAGED_LOOKASIDE_LIST Lookaside,
In PVOID Entry )
其中Lookaside为内存管理池对象指针(就是初始化函数的参数1),Entry指针表示需要释放的内存块,也就是ExAllocateFromNPagedLookasideList的返回值。
最后当一个内存管理池不再需要使用时,可以调用ExDeleteNPagedLookasideList函数来对其进行删除,ExDeleteNPagedLookasideList函数的原型如下:
VOID ExDeleteNPagedLookasideList (Inout PNPAGED_LOOKASIDE_LIST Lookaside);
参数Lookaside表示需要删除的内存管理池对象(就是初始化函数的参数1)。
上面是对非分页类型内存管理池操作的介绍,分页类型的内存管理池操作与此操作高度雷同,只是函数名中没有N这个字母,例如下面的对比:
ExInitializeNPagedLookasideList();//非分页内存管理池初始化
ExInitializePagedLookasideList();//分页内存管理池初始化
最后,在Vista及以后的系统引入了一套Ex版本的内存管理池操作函数,该函数为ExInitializeLookasideListEx,原型如下:
NTSTATUS ExInitializeLookasideListEx (
Out PLOOKASIDE_LIST_EX Lookaside,
In_opt PALLOCATE_FUNCTION_EX Allocate,
In_opt PFREE_FUNCTION_EX Free,
In POOL_TYPE PoolType,
In ULONG Flags,
In SIZE_T Size,
In ULONG Tag,
In USHORT Depth );
这个函数支持分页内存类型与非分页内存类型的内存管理池,不再需要使用两套不同的API。
下面给出一段完整的分页内存管理池例子:
下面涉及到的sizeof(DATA_TEST)只是计算一个申请内的大小而已,也可填写固定值或者计算出其他结构体的大小。
VOID MemoryAllocation()
{
PAGED_LOOKASIDE_LIST LookasideList;
PVOID pFirst = NULL;
PVOID pSecond = NULL;
ExInitializePagedLookasideList(&LookasideList, NULL, NULL, 0, sizeof(DATA_TEST), ‘b’, 0);
pFirst = ExAllocateFromPagedLookasideList(&LookasideList);
if (pFirst == NULL)
{
return;
}
pSecond = ExAllocateFromPagedLookasideList(&LookasideList);
if (pSecond == NULL)
{
return;
}
KdPrint((“第一次申请内存的地址:%p , 第二次申请内存的地址:%p\n”, pFirst, pSecond));
ExFreeToPagedLookasideList(&LookasideList, pFirst);//释放第一次申请的内存
pFirst = NULL;
pFirst = ExAllocateFromPagedLookasideList(&LookasideList);//再次申请内存
if (pFirst == NULL)
{
return;
}
KdPrint((“释放后在申请的内存地址:%p \n”, pFirst));
if (pFirst != NULL)
{
ExFreeToPagedLookasideList(&LookasideList, pFirst);
pFirst = NULL;
}
if (pSecond != NULL)
{
ExFreeToPagedLookasideList(&LookasideList, pSecond);
pSecond = NULL;
}
ExDeletePagedLookasideList(&LookasideList);
}
运行结果如下:
在这里插入图片描述
可以看到pFirst 最开始的值和释放后在次申请的值是一样的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值