使用Lookaside内存

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

频繁地申请内存,会导致一个问题,就是在内存中产生“空洞”。图 5-11显示了这种情况,在内存中先后申请三块内存。最开始可用的内 存是连续的。当某个时刻内存块2被回收以后,如果系统想分配一块略 微大于原先内存块2的内存,这时候原先的内存2就不能被申请成功。 因此,频繁地申请、回收内存会导致在内存上产生大量的内存“空洞”。

image-20240217165148212

如果系统中存在大量的内存“空洞”,即使内存中有大量的可用内 存,也会导致申请内存失败。在操作系统空闲的时候,系统会整理内 存中的“空洞”,将内存中的“空洞”进行合并。

如果驱动程序需要频繁地从内存中申请、回收固定大小的内存,DDK提 供了一种机制来解决这个问题,这就是使用Lookaside对象。 可以将Lookaside对象想象成一个内存容器。在初始的时候,它先向 Windows申请了一块比较大的内存。以后程序员每次申请内存的时候, 不是直接向Windows申请内存,而是向Lookaside对象申请内存。 Lookaside对象会智能地避免产生内存“空洞”。如果Lookaside对象 内部的内存不够用时,它会向操作系统申请更多的内存。当Lookaside 对象内部有大量的未使用的内存时,它会自动让Windows回收一部分内存。总之,Lookaside是一个自动的内存分配容器。通过对Lookaside 对象申请内存,效率要高于直接向Windows申请内存。Lookaside一般 会在以下情况使用:

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

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

如果程序员遇到上述两种情况,可以考虑使用Lookaside对象。驱动程序中的运行效率是程序员必须考虑的问题。

使用Lookaside内存的方法

  1. 初始化Lookaside结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
_IRQL_requires_max_(DISPATCH_LEVEL)
NTKERNELAPI
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
);

void InitializeLookAsideList()
{
if (__IsLookAsideList == FALSE)
{
ExInitializeNPagedLookasideList(&__LookAsideList, NULL, NULL, 0, BUFFER_SIZE_MAX, 'XXXX', 0);
__IsLookAsideList = TRUE;
}
}
  1. 向LookAside申请内存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
PVOID AllocateBufferFromLookAsideList()
{
KIRQL CurrentIrql;
PVOID VirtualAddress = NULL;

if (__IsLookAsideList == FALSE)
return NULL;

CurrentIrql = KeGetCurrentIrql();
if (CurrentIrql > DISPATCH_LEVEL)
return NULL;

VirtualAddress = ExAllocateFromNPagedLookasideList(&__LookAsideList);
if (VirtualAddress != NULL)
{
RtlZeroMemory(VirtualAddress, BUFFER_SIZE_MAX);
}

return VirtualAddress;
}
  1. 从Lookaside中释放内存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void FreeBufferToLookAsideList(
PVOID VirtualAddress)
{
KIRQL CurrentIrql;

if (__IsLookAsideList == FALSE)
return;

if (VirtualAddress == NULL)
return;

CurrentIrql = KeGetCurrentIrql();
if (CurrentIrql > DISPATCH_LEVEL)
return;

ExFreeToNPagedLookasideList(&__LookAsideList, VirtualAddress);
}
  1. 释放Lookaside结构
1
2
3
4
5
6
7
8
void UninitializeLookAsideList()
{
if (__IsLookAsideList == TRUE)
{
ExDeleteNPagedLookasideList(&__LookAsideList);
__IsLookAsideList = FALSE;
}
}