后备列表和系统内存池的主要的区别是什么呢?后备列表只可以分配固定大小的事先定义尺寸的内存块。后备列表会快很多,因为它不需要去搜索可用的未分配的内存。
当你刚接触后备列表,你需要解决的问题不是怎么创建后备列表,而是管理你要分配的内存块。
具体来说,你把你要使用和释放的内存存贮在哪里? 怎么存贮?这不是个简单的问题,因为你不知道块的数量。有三种结构来解决这个问题:
◎ 单向链表(Singly linked list)
◎ S-list排序的单向链表(S-list, sequenced singly-linked list) (单向链表的改进)
◎ 双向链表(Doubly linked list)
我们将只接触双向链表,因为它最通用。
如果你是第一次接触后备列表和双向链表这些概念,你会觉得下面的代码有点复杂,但实际上它们是非常简单的。
后备列表(lookaside list)和双向链表(Doubly linked list)的英文名称都有一个"list",但是它们是完全不同的。后备列表是一组事先分配的相同尺寸的内存块。这些块有些在使用,有些没被使用。当有内存分配请求的时候,系统会遍历这个列表寻找最近的未分配的块。如果未分配的块找到了,分配请求就很快被满足了。否则系统必须从分页或不分页内存池去分配。根据列表中分配行为发生的频率,系统会自动调整未分配块的数量来满足分配请求,分配的频率越高,会有越多的块被存储在后备列表中。后备列表如果总是不被使用,也会自动减少空间大小。
双向链表是数据组织的一种形式。可以很方便地把同类结构连接在一起,并且很容易遍历。双向链表被系统广泛地使用来处理内部结构。
我想了半天,也没有找到一个适合的简单的例子。所以下面这个驱动程序好像意义不大。但是可以帮助你理解相关的概念,还有双向链表。
这里也没有提供驱动控制程序。你可以使用KmdKit4D 包中的KmdManager 或者类似的工具,还可以使用DebugView或SoftICE 控制台来查看调试信息。
unit LookasideList;
interface
uses
nt_status, ntoskrnl, macros;
function _DriverEntry(pDriverObject: PDRIVER_OBJECT;
pusRegistryPath: PUNICODE_STRING): NTSTATUS; stdcall;
implementation
type
PSOME_STRUCTURE = ^SOME_STRUCTURE;
SOME_STRUCTURE = record
SomeField1: DWORD;
SomeField2: DWORD;
{ . . . } { 这里放入一些别的字段 }
ListEntry: LIST_ENTRY; { 主角^_^,可以放在结构的开始 }
{ 放在这里是为了演示的需要 }
{ . . . } { 这里放入一些别的字段 }
SomeFieldX: DWORD;
end ;
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
{ 删除表项也一样,可以从头、尾或者其他地方删除, }
{ 这里我们还是从头部开始删除. }
{ 计算offs是因为RemoveHeadList返回的是SOME_STRUCTURE.ListEntry的地址 }
{ offs就是SOME_STRUCTURE.ListEntry到结构开始处的偏移量 }
offs : = DWORD(@ss.ListEntry) - DWORD(@ss);
{ 这里pEntry ==> SOME_STRUCTURE.ListEntry }
{ 我们需要得到指向包含这个ListEntry的SOMT_STRUCTURE结构的指针 }
{ 以便释放内存,用pEntry - offs即可得到结构的指针. }
pEntry : = RemoveHeadList(@g_ListHead);
{ pTemp ==> SOME_STRUCTURE,这里一定要弄明白 }
pTemp : = pointer(DWORD(pEntry) - offs);
DbgPrint( ' LookasideList: - Entry #%d removed ' # 13 # 10 ,
PSOME_STRUCTURE(pTemp)^.SomeField1);
{ 向后备列表归还不用的内存 }
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 .
interface
uses
nt_status, ntoskrnl, macros;
function _DriverEntry(pDriverObject: PDRIVER_OBJECT;
pusRegistryPath: PUNICODE_STRING): NTSTATUS; stdcall;
implementation
type
PSOME_STRUCTURE = ^SOME_STRUCTURE;
SOME_STRUCTURE = record
SomeField1: DWORD;
SomeField2: DWORD;
{ . . . } { 这里放入一些别的字段 }
ListEntry: LIST_ENTRY; { 主角^_^,可以放在结构的开始 }
{ 放在这里是为了演示的需要 }
{ . . . } { 这里放入一些别的字段 }
SomeFieldX: DWORD;
end ;
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
{ 删除表项也一样,可以从头、尾或者其他地方删除, }
{ 这里我们还是从头部开始删除. }
{ 计算offs是因为RemoveHeadList返回的是SOME_STRUCTURE.ListEntry的地址 }
{ offs就是SOME_STRUCTURE.ListEntry到结构开始处的偏移量 }
offs : = DWORD(@ss.ListEntry) - DWORD(@ss);
{ 这里pEntry ==> SOME_STRUCTURE.ListEntry }
{ 我们需要得到指向包含这个ListEntry的SOMT_STRUCTURE结构的指针 }
{ 以便释放内存,用pEntry - offs即可得到结构的指针. }
pEntry : = RemoveHeadList(@g_ListHead);
{ pTemp ==> SOME_STRUCTURE,这里一定要弄明白 }
pTemp : = pointer(DWORD(pEntry) - offs);
DbgPrint( ' LookasideList: - Entry #%d removed ' # 13 # 10 ,
PSOME_STRUCTURE(pTemp)^.SomeField1);
{ 向后备列表归还不用的内存 }
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
if g_pPagedLookasideList <> nil then
begin
ExInitializePagedLookasideList(g_pPagedLookasideList,
nil
,
nil , 0 , sizeof(SOME_STRUCTURE),
$6D736157 { 'msaW' } , 0 );
nil , 0 , sizeof(SOME_STRUCTURE),
$6D736157 { 'msaW' } , 0 );
注意,在初始化时候,我们并没有指定我们需要多少块。那么系统怎么知道要准确地分配多少内存呢?实际上,内存如果事先没有分配,后备列表就不可能比常用的系统内存池要快了。问题就在于:开始的时候,系统分配只一点内存块(数量由系统来定义)。于是当我们开始从后备列表分配内存的时候,我们将获得这些预分配内存块的指针。每秒钟,系统都会调用ExAdjustLookasideDepth来调整所有的系统后备列表。调整的时候发现空闲的未分配块减少,系统就会分配新的内存块。这些额外分配的块的数量来自于后备列表的负载情况,例如:分配频率。系统会尽量调整让后备列表更有效。
如果我们在调整时间内就耗尽了预分配内存块,系统就使用系统内存池,直到下一次调整。需要理解的一个重要问题是:如果内存分配速度太高,那么跟内存池分配方式比,就没有性能的优势了。你可以使用MS Kernel Debugger的命令"!lookaside",评估你的后备列表的效率。
InitializeListHead(@g_ListHead);
g_dwIndex :
=
0
;
dwCnt :
=
0
;
while dwCnt < 5 do
begin
AddEntry;
AddEntry;
RemoveEntry;
inc(dwCnt);
end ;
while dwCnt < 5 do
begin
AddEntry;
AddEntry;
RemoveEntry;
inc(dwCnt);
end ;
这个循环模拟了后备列表的随机分配过程。我们可以假设是为了保存一些数据而进行分配内存工作。如写一个驱动程序来截取某系统设备的调用(如截取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 ;
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)。
ExDeletePagedLookasideList(g_pPagedLookasideList);
ExFreePool(g_pPagedLookasideList);
result :
=
STATUS_DEVICE_CONFIGURATION_ERROR;
以上内容很简单,尤其如果你学过《数据结构》这门课,或者以前接触过链表和队列这类概念的话。
AddEntry 函数
当我们需要一个新的内存块的时候我们调用AddEntry。它会从后备列表分配一个新的条目,并增加到双向链表。
pEntry :
=
ExAllocateFromPagedLookasideList(g_pPagedLookasideList);
if pEntry <> nil then
begin
if pEntry <> nil then
begin
现在我们有了一个新的SOME_STRUCTURE实例。我们就可以把它链到我们的双向链表了。在AddEntry第一次运行之前,双向链表还是空的。
InsertHeadList(@g_ListHead, @pEntry^.ListEntry);
如果你是往双向链表里第一次加条目,两个函数都产生同样的结果。以后就会象图中的2反映的那样了。
如果当时双向链表不是空的话,InsertHeadList就会在头部和条目右边之间分开双向链表,然后把新条目放在中间(见图中的3)如果在尾部,也可以用InsertTailList,效果一样。
现在一切都很清楚了吧。
inc(g_dwIndex);
pEntry^.SomeField1 : = g_dwIndex;
pEntry^.SomeField1 : = g_dwIndex;
RemoveEntry函数
RemoveEntry 函数和AddEntry相反。它把条目从双向链表头中解除链接,然后归还到后备列表。
if
IsListEmpty(@g_ListHead)
<>
TRUE
then
begin
begin
pEntry := RemoveHeadList(@g_ListHead);
RemoveHeadList 把条目从双向链表中解除链接。你应该已经猜到RemoveTailList函数在尾部的时候可以做同样的事情了。而RemoveEntryList可以删除任何条目(当然包括尾部了)。
这时,被解除的条目就独立存在了,双向链表把随后其余的条目链接在一起。
offs :
=
DWORD(@ss.ListEntry)
-
DWORD(@ss);
pTemp : = pointer(DWORD(pEntry) - offs);
pTemp : = pointer(DWORD(pEntry) - offs);
ExFreeToPagedLookasideList(g_pPagedLookasideList, pTemp);