var g_pPagedLookasideList: PPAGED_LOOKASIDE_LIST; g_ListHead: LIST_ENTRY; g_dwIndex: DWORD; dwCnt: DWORD;
procedure AddEntry; var pEntry: PSOME_STRUCTURE; begin {从后备列表中分配内存块} pEntry := ExAllocateFromPagedLookasideList(g_pPagedLookasideList); if pEntry <> nil then begin DbgPrint('LookasideList: + Memory block allocated from lookaside list at address %08X'#13#10, pEntry); {初始化分配到的内存} memset(pEntry, 0, sizeof(SOME_STRUCTURE)); {一个节点可以添加到链表的头部、尾部或者其他地方,视个人喜好了} {本例添加到表头} InsertHeadList(@g_ListHead, @pEntry^.ListEntry); {使用SomeField1保存表项的索引. 这是为了让我们能看见它在工作.}
inc(g_dwIndex); pEntry^.SomeField1 := g_dwIndex; DbgPrint('LookasideList: + Entry #%d added'#13#10, pEntry^.SomeField1); end else begin DbgPrint('LookasideList: Very bad. Couldn''t allocate from lookaside list'#13#10); end; end;
procedure RemoveEntry; var pEntry, pTemp: pointer; ss: SOME_STRUCTURE; offs: DWORD; begin if IsListEmpty(@g_ListHead) <> TRUE then begin {删除表项也一样,可以从头、尾或者其他地方删除,} {这里我们还是从头部开始删除.}
{向后备列表归还不用的内存} ExFreeToPagedLookasideList(g_pPagedLookasideList, pTemp); DbgPrint('LookasideList: - Memory block at address %08X returned to lookaside list'#13#10, pTemp); end else begin DbgPrint('LookasideList: - An attempt was made to remove entry from empty lookaside list'#13#10); end; end;
function _DriverEntry(pDriverObject: PDRIVER_OBJECT; pusRegistryPath: PUNICODE_STRING): NTSTATUS; stdcall; begin DbgPrint(#13#10'LookasideList: Entering DriverEntry'#13#10); g_pPagedLookasideList := ExAllocatePool(NonPagedPool, sizeof(PAGED_LOOKASIDE_LIST)); if g_pPagedLookasideList <> nil then begin DbgPrint('LookasideList: Nonpaged memory for lookaside list allocated at address %08X'#13#10, g_pPagedLookasideList); ExInitializePagedLookasideList(g_pPagedLookasideList, nil, nil, 0, sizeof(SOME_STRUCTURE), $6D736157{'msaW'}, 0); DbgPrint('LookasideList: Lookaside list initialized'#13#10); InitializeListHead(@g_ListHead); DbgPrint('LookasideList: Doubly linked list head initialized'#13#10); DbgPrint(#13#10'LookasideList: Start to allocate/free from/to lookaside list'); g_dwIndex := 0; dwCnt := 0; while dwCnt < 5 do begin AddEntry; AddEntry; RemoveEntry; inc(dwCnt); end; while true do begin RemoveEntry; if IsListEmpty(@g_ListHead) = true then begin DbgPrint('LookasideList: List is empty'#13#10#13#10); break; end; end; {后备列表已清空,销毁之} ExDeletePagedLookasideList(g_pPagedLookasideList); DbgPrint('LookasideList: Lookaside list deleted'#13#10); ExFreePool(g_pPagedLookasideList); DbgPrint('LookasideList: Nonpaged memory for lookaside list at address %08X released'#13#10, g_pPagedLookasideList); end else begin DbgPrint('LookasideList: Couldn''t allocate nonpaged memory for lookaside list control structure'); end; DbgPrint('LookasideList: Leaving DriverEntry'#13#10); result := STATUS_DEVICE_CONFIGURATION_ERROR; end;
end. 代码:g_pPagedLookasideList := ExAllocatePool(NonPagedPool, sizeof(PAGED_LOOKASIDE_LIST)); if g_pPagedLookasideList <> nil then begin 我们分配PAGED_LOOKASIDE_LIST结构的非分页内存,这个结构用于管理后备列表,并把指针保存在变量g_pPagedLookasideList中。注意:后备列表自身是可以分页的。例如,我们获取的内存可能被页出(page out)。文档关于这一点写的很清楚。 代码:ExInitializePagedLookasideList(g_pPagedLookasideList, nil, nil, 0, sizeof(SOME_STRUCTURE), $6D736157{'msaW'}, 0); ExInitializePagedLookasideList函数初始化我们上一步分配的PAGED_LOOKASIDE_LIST结构。这样后备列表就可以使用了。 注意,在初始化时候,我们并没有指定我们需要多少块。那么系统怎么知道要准确地分配多少内存呢?实际上,内存如果事先没有分配,后备列表就不可能比常用的系统内存池要快了。问题就在于:开始的时候,系统分配只一点内存块(数量由系统来定义)。于是当我们开始从后备列表分配内存的时候,我们将获得这些预分配内存块的指针。每秒钟,系统都会调用ExAdjustLookasideDepth来调整所有的系统后备列表。调整的时候发现空闲的未分配块减少,系统就会分配新的内存块。这些额外分配的块的数量来自于后备列表的负载情况,例如:分配频率。系统会尽量调整让后备列表更有效。 如果我们在调整时间内就耗尽了预分配内存块,系统就使用系统内存池,直到下一次调整。需要理解的一个重要问题是:如果内存分配速度太高,那么跟内存池分配方式比,就没有性能的优势了。你可以使用MS Kernel Debugger的命令"!lookaside",评估你的后备列表的效率。
代码:g_dwIndex := 0; 这个全局变量仅用来在后备列表分配的SOME_STRUCTURE结构里放些内容,然后在调试信息里输出值来。 代码:dwCnt := 0; while dwCnt < 5 do begin AddEntry; AddEntry; RemoveEntry; inc(dwCnt); end; 循环5次。每次增加两个条目,删除一个条目。每个条目代表了某个结构。所有分配的结构都使用双向链表互相链接在一起。 这个循环模拟了后备列表的随机分配过程。我们可以假设是为了保存一些数据而进行分配内存工作。如写一个驱动程序来截取某系统设备的调用(如截取ZwOpenKey)时将截取的信息保存到分配的内存中,这时的内存分配动作就是类似的。
代码:while true do begin RemoveEntry; if IsListEmpty(@g_ListHead) = true then begin DbgPrint('LookasideList: List is empty'#13#10#13#10); break; end; end; 现在我们有了一些用双向链表链接起来的后备列表条目分配块。我们假设我们又不需要他们了…… 我们在一个无限循环里调用RemoveEntry函数。RemoveEntry从双向链表头删除一个条目,并释放回后备列表。这个循环一直运行,直到双向链表成空。可以使用IsListEmpty宏来检查这个状态。IsListEmpty检查双向链表头(LIST_ENTRY structure)的两个域是不是都指向链表头自己。 这时,我们又回到了执行完InitializeListHead宏的状态了(参见图中的1)。