内存分配与回收

内存分配

ExAllocatePoolWithTag(内存分配函数)

PVOID ExAllocatePoolWithTag(
POOL_TYPE PoolType,
SIZE_T NumberOfBytes,
ULONG Tag
);

PoolType 为 POOL_TYPE 枚举类型,表示需要申请何种类型的内存。

常用的值是 NonPagedPool 与 PagedPool,NonPagedPool表示非分页内存,PagedPool表示分页内存。

注意: 非分页内存是指这块内存的内容不会被置换到磁盘上,非分页内存非常宝贵,一般用于高IRQL(大于等于DISPATCH_LEVEL)的代码中。

两个类型:NonPagedPoolExecute 与 NonPagedPoolNx
NonPagedPoolNx类型是指分配出来的非分页内存不具备“可执行”属性。对非分页内存进行读写而不需要进行代码或指令执行,那么可以使用NonPagedPoolNx类型的内存

NonPagedPoolExecute 类型与 NonPagedPool 类型等价。

NumberOfBytes参数表示需要申请内存大小,单位是字节。

Tag参数是一个4个字节的标志,一般每个驱动程序定义一个自己的内存标记。也可以在每个模块中定义单独的内存标记。内存标记是随意的32位数字。即使冲突也不会有什么问题,这个Tag一般用于问题排查,如内存泄露,系统蓝屏等。

注意: ExAllocatePoolWithTag 函数成功执行后返回分配内存的首地址,失败返回NULL。使用过程中务必对该函数返回值进行判断。

下的例子,是把一个字符串src拷贝到字符串dst。

// 定义一个内存分配标记
#define MEM_TAG ‘MyTt’
// 目标字符串,接下来它需要分配空间。
UNICODE_STRING dst = { 0 };
// 分配空间给目标字符串。根据源字符串的长度。
dst.Buffer = (PWCHAR)ExAllocatePoolWithTag(NonpagedPool,src->Length,MEM_TAG);
if(dst.Buffer == NULL)
{
// 错误处理
status = STATUS_INSUFFICIENT_RESOUCRES;
……
}
dst.Length = dst.MaximumLength = src->Length;
status = RtlCopyUnicodeString(&dst,&src);
ASSERT(status == STATUS_SUCCESS);

内存释放

ExFreePoolWithTag(内存释放函数)

ExAllocatePool这个函数已经废弃不用了,所以内核中最常见的分配内存的方法就是调用ExAllocatePoolWithTag函数。ExAllocatePoolWithTag的原型如下:

VOID ExFreePoolWithTag(PVOID P,ULONG Tag);

P 参数表示需要释放的内存块地址

Tag 参数对应内存申请时的标记

注意

  1. 内存分配和释放函数要配套使用,分配内存使用ExAllocatePool函数,释放使用相应的ExFreePool函数。
  2. ExAllocatePoolWithTag分配的内存可以使用ExFreePool来释放。如果不释放,则永远泄漏。并不像用户进程关闭后自动释放所有分配的空间。即使驱动程序动态卸载,也不能释放空间。唯一的办法是重启计算机。

旁视列表

旁视列表的原理

开发者首先初始化一个“旁视列表”对象,初始化时设置“旁视列表”中内存块的大小,在需要使用内存的时候,直接向“旁视列表”对象申请内存(申请的内存的大小为初始值申请值),在内存使用完毕后,需要通过“旁视列表”对象来回收这些内存;最后,当不再需要使用“旁视列表”对象时将其删除。

旁视列表内存机制

旁视列表 对象内部会维护内存的使用状态,一块内存使用结束后,会释放回“旁视列表”对象内,但这块内存不会马上被释放到操作系统的Pool中。如果这个时候开发者向“旁视列表”对象申请内存,“旁视列表”对象会把刚才回收的内存块返回给申请者。

这种类似“缓存”的机制,“旁视列表”对象对内存进行了二次管理,减少了向系统Pool申请或释放的次数,提高了性能。

“旁视列表” 初始化

void ExinitializeNPagedLookadideList(
PANPAGED_LOOKASIDE_LIST Lookaside,
PANLLOCATE_FUNCTION Allocate,
PFREE_FUNCTION Free,
ULONG Flages,
SIZE_T Size,
ULONG Tag,
USHORT Depth
);

Lookaside 参数表示被初始化的“旁视列表”对象的指针,在64位系统下,这个指针必须以16字节对齐。

Allocate 参数是一个函数指针,从已初始化的“旁视列表”对象分配内存时,系统会调用开发者设置的 Allocate 函数。

PVOID XxxAllocate(
POOL_TYPE PoolType,
SIZE_T NumberOfBytes,
ULONG Tag
);//Allocate函数的原型

设置Allocate参数为NULL,系统则使用默认的内存分配函数

**Free **参数是一个函数指针,删除从“旁视列表”对象中申请出来的内存块时,系统就会调用 Free 参数指向的函数

VOID XxxFree(PVOID Buffer);

设置 Free 参数为NULL,在这种情况下,系统使用默认的释放函数。

Flags 参数控制“旁视列表”对象的内存分配行为,这个参数只有在Windows 8以及后续 系统中才有意义。

POOL_NX_ALLOCATION:表示分配的非分页内存的属性为“不可执行”,类似上一节 介 绍的NonPagedPoolNx标志。

POOL_RAISE_IF_ALLOCATION_FAILURE:表示如果内存失败,将抛出一个异常。

如果没有特殊要求,可以把Flags参数设置为0。

Size 参数表示每次从“旁视列表”对象中申请内存的固定大小,单位是字节,这个值不能 小于LOOKASIDE_MINIMUM_BLOCK_SIZE。

LOOKASIDE_MINIMUM_BLOCK_SIZE是WDK定义的一个宏,定义如下:

#define LOOKASIDE_MINIMUM_BLOCK_SIZE (RTL_SIZEOF_THROUGH_FIELD (SLIST_ENTRY,Next))

RTL_SIZEOF_THROUGH_FIELD定义如下:

#define RTL_SIZEOF_THROUGH_FIELD(type,field) \
(FIELD_OFFSET(type,field)+RTL_FIELD_SIZE(type,field))

FIELD_OFFSET 以及 RTL_FIELD_SIZE 的定义
#define RTl_FIELD_SIZE(type,field) (sizeof(((type *)0)->field))
#define FIELD_OFFSET(type,field) (LONG)(LONG_PTR)&(((type *)->field))

RTL_SIZEOF_THROUGH_FIELD宏计算的是type结构体中field成员距离结构体首地址的偏移大小,加上field成员本身的大小。

LOOKASIDE_MINIMUM_BLOCK_SIZE宏来说,计算的是Next成员与SLIST_ENTRY首地址的距离加上Next成员自身的大小。

SLIST_ENTRY定义:

typedef struct _SLIST_ENTRY{
struct _SLIST_ENTRY *Next;
}SLIST_ENTRY, *PLIST_ENTRY;

在64位系统下,LOOKASIDE_MINIMUM_BLOCK_SIZE宏的值为8;

Tag 参数表示分配内存时所使用的标记,与ExAllocatePoolWithTag函数中的Tag参数函数一样。

Depth 参数是一个保留参数,没有意义,传递0即可;

内存申请与释放

内存申请函数

ExAllocateFormNPageLookasideList(PNPAGED_LOOKASIDE_LIST Lookaside);

只需要传入“旁氏列表” 对象的地址传入上面的函数,就可以分配函数指定大小的内存。执行成功的话就会返回相应的内存块,否则返回NULL。

内存释放函数

ExFreeToNPagedLookasideList(
PNPAGED_LOOKASIDE_LIST Lookaside,
PVOID Entry
);

Lookaside为“旁视列表”对象指针,Entry指针表示需要释放的内存块,也就是ExAllocateFromNPagedLookasideList的返回值。

旁氏列表删除

删除函数

ExDeleteNPageLookasideList(PNPAGED_LOOKASIDE_LIST Lookaside)

Lookaside 参数表示需要删除的“旁视列表”对象。

⬆︎TOP