码迷,mamicode.com
首页 > 其他好文 > 详细

[转载]使用 Lookaside List 分配内存

时间:2015-02-02 19:35:47      阅读:275      评论:0      收藏:0      [点我收藏+]

标签:

1. 概述

windows 提供了一种基于 lookaside list 的快速内存分配方案,区别于一般的使用 ExAllocatePoolWithTag() 系列函数的内存分配方式。每次从 lookaside list 里分配 fixed size 的内存。 系统构建两个条 lookaside 链表:ExNPagedLookasideListHead 和 ExPagedLookasideListHead,分别用于 Non-paged 内存和 paged 内存。

lookaside list 链表头是 LIST_ENTRY 结构,因此:整个 lookaside list 链是双向链表。ExNPagedLookasideListHead 和 ExPagedLookasideListHead 是 kernel 的全局变量,由系统进行初始化。

下图是由 ExNPagedLookasideListHead 链头构造的 non-paged lookaside list 示意图:

技术分享

在使用 ExInitializeNPagedLookasideList() 函数进行初始化 NPAGED_LOOKASIDE_LIST 结构(或 PAGED_LOOKASIDE_LIST 结构)时,将节点的 LIST_ENTRY 域挂接在链表头里,形成一条双向链表。

2. lookaside list 节点结构

lookaside list 的节点是 NPAGED_LOOKASIDE_LIST 结构(用于 non-paged 链)或 PAGED_LOOKASIDE_LIST(用于 paged 链),它们的定义如下

typdef struct _NPAGED_LOOKASIDE_LIST
{
GENERAL_LOOKASIDE L;
KSPIN_LOCK Lock;
} NPAGED_LOOKASIDE_LIST, *PNPAGED_LOOKASIDE_LIST;
 
 
typdef struct _PAGED_LOOKASIDE_LIST
{
GENERAL_LOOKASIDE L;
FAST_MUTEX Lock;
} PAGED_LOOKASIDE_LIST, *PPAGED_LOOKASIDE_LIST;

内部的 GENERAL_LOOKASIDE 子结构维护着 lookaside 的整个工作,下面是 GENERAL_LOOKASIDE 结构的定义:

typedef struct _GENERAL_LOOKASIDE {
SLIST_HEADER ListHead;// 单向链表,用来挂接 pool block
USHORT Depth;// lookaside 的 depth,缺省为 4
USHORT MaximumDepth;// maximum depth,缺省为 256
ULONG TotalAllocates;                 // 记录进行多少次分配
ULONG AllocateMisses;               // 记录分配从 lookaside list 中分配失败次数
ULONG TotalFrees;                   // 记录进行多少次释放
ULONG FreeMisses;                   // 记录从 lookaside list 中释放 miss 次数
POOL_TYPE Type;                     // pool type 类型,与 ExAllocatePoolWithTag() 参数一致
ULONG Tag;                                  // 用于 ExAllocatePoolWithTag()
ULONG Size;                                 // 用于 ExAllocatePoolWithTag()
PALLOCATE_FUNCTION Allocate;       // 自定义的分配函数,缺省使用 ExAllocatePoolWithTag()
PFREE_FUNCTION Free;                 // 自定义的释放函数,缺省使用 ExFreePool()
LIST_ENTRY ListEntry;                 // 双向 link list
ULONG LastTotalAllocates;           // 记录上一次 Allocate 次数
ULONG LastAllocateMisses;         // 记录上一次 Allocate  miss 次数
ULONG Future[2];                            // 保留
} GENERAL_LOOKASIDE, *PGENERAL_LOOKASIDE; 

每个节点有两条链表:

  • 单向链表:用于挂接 pool 块,这些内存块就是从系统 pool 里分配而来的(non-paged 内存或 paged 内存)。
  • 双向链表:用于挂接在整个 lookaside list 里,也是就前面所说的 ExNPagedLookasideListHead 链表头和 ExPagedLookasideListHead 链表头。

在前面描述图的基础上,形成像下面的结构:

技术分享

因此:每个节点都有一条 SLIST_ENTRY 链,挂接着内存块!,这些内存链是在释放 lookaside list 时挂接上去的。在初始化时,这些内存块链是不存在的!

假如:Lookaside 指向其中一个 NPAGED_LOOKASIDE_LIST 节点 有三个 Depth 值是需要分清楚的:

  1. 节点当前的深度:Lookaside->L.ListHead.Depth 它指示节点当前有多少个内存块挂接着。
  2. 节点的最小深度:Lookaside->L.Depth 它指示着每个节点最少以挂接多少个内存块。在缺省的情况下,这个 Depth 值为 4,最少可以挂接 3 个内存块。
  3. 节点的最大深度:Lookaside->L.MaximumDepth 它指示节点最大可以挂接多少个内存块。这个值为 256,也就是最多可以挂接 255 个。

在缺省的时候,每个节点的 Depth(深度)的为 4,那么 kernel 根据使用情况来动态修改这个 Depth 值,最大值为 MaximumDepth。因此:Depth 相当于一个“额度”的概念!

3. 节点内存块 size

在一条 lookaside list 链里,每个节点可以有不同 size 的内存块,如下图所示:

技术分享

在图中的 lookaside 链里,其中 3 个节点分别挂接着 1K,2K 以及 4K 大小的内存块,当需要分配 1K 内存时,从第1个节点里分配。如下面代码所示:

PVOID p = ExAllocateFromNPagedLookasideList(lookaside1);// 从第 1 个节点分配 1K 内存块

在初始化每个节点时,可以为每个节点的内存块 size 设置不同的值,因此:整个 lookaside list 可以挂接着不同 size 的内存块,软件根据需要从不同 size 的节点里分配内存。

4. 初始化节点

在使用 lookaside list 之前,必须构建和初始化一个 NPAGED_LOOKASIDE_LIST 结构(或 PAGED_LOOKASIDE_LIST 结构用于 paged 内存),如下面代码所示:

NPAGED_LOOKASIDE_LIST Lookaisde;// 节点结构
 
//
// 下面初始化节点
//
 
ExInitializeNPagedLookasideList(
&Lookaside,// 节点地址
NULL,// 使用缺省的 Allocate() 例程
NULL,// 使用缺省的 Free() 例程
0,// Flags 保留
1024,// 1K size
‘tag1‘,// Tag
0// Depth 值,未使用
); 

在对节点初始化完成后,将节点挂接在以 ExNPagedLookasideListHead 为表头的双向链表里,组织成一个 lookaside list 体系。

下面的 ExInitializeNPagedLookasideList() 例程 C 代码是从 windows 2003 里逆出来的:

VOID ExInitializeNPagedLookasideList(
IN PNPAGED_LOOKASIDE_LIST Lookaside,
IN PALLOCATE_FUNCTION Allocate,
IN PFREE_FUNCTION  Free,
IN ULONG  Flags,
IN SIZE_T  Size,
IN ULONG  Tag,
IN USHORT  Depth
)
{
PGENERAL_LOOKASDIDE  L = &Lookaside->L;
 
//
// 初始化  SLIST_HEADER
//
L->ListHead.Next = NULL;
L->ListHead.Depth = 0;
L->ListHead.Spquence = 0;
 
L->Depth = ExMinimulLooasideDepth;// depth 最小值,为 4
L->Type = NonPagedPool | Flags;
L->Tag = Tag;
L->Size = Size;
L->MaximumDepth = 256;// depth 最大值, 256
L->TotalAllocates = 0;
L->AllocateMisses = 0;
L->TotalFrees = 0;
L->FreeMisses = 0;
 
if (Allocate == NULL)
{
L->Allocate = ExAllocatePoolWithTag;
}
else
{
L->Allocate = Allocate;
}
 
 
if (Free == NULL)
{
L->Free = ExFreePool;
}
else
{
L->Free = Free;
}
 
L->LastTotalAllocates = 0;
L->LastAllocateMisses = 0;
 
//
// 将 &L->ListEntry 挂接在 ExNPagedLookasideListHead 链表头里
//
ExfInterlockedInsertTailList(&ExNPagedLookasideListHead, &L->ListEntry, &ExNPagedLookasideLock);

从这个代码里,我们可以清楚地看到,节点的单向链表是空的,Depth 和 MaximumDepth 分别是 4 和 256,在对 non-paged 节点初始化时,Flags 值为 NoPagedPool 类型。

对 paged 节点的初始化例程与 non-paged 节点结构是一致的,只是使用了不同的 Flags 值和链表头,下面是 ExInitializePagedLookasideList() 的代码:

VOID ExInitializePagedLookasideList(
IN PPAGED_LOOKASIDE_LIST Lookaside,
IN PALLOCATE_FUNCTION Allocate,
IN PFREE_FUNCTION  Free,
IN ULONG  Flags,
IN SIZE_T  Size,
IN ULONG  Tag,
IN USHORT  Depth
)
{
....
 
L->Type = PagedPool | Flags;// PagedPool 类型
 
...
 
//
// 将 &L->ListEntry 挂接在 ExPagedLookasideListHead 链表头里
//
ExfInterlockedInsertTailList(&ExPagedLookasideListHead, &L->ListEntry, &ExPagedLookasideLock);

代码的最后,使用 ExfInterlockedInsertTailList() 例程将节点挂在双向链表里(non-paged 或 paged 类型)

下面代码是从 windows 2003 里逆出来的:

PLIST_ENTRY
ExfInterlockedInsertTailList(
PLIST_ENTRY ListHead,// 双向链表头
PLIST_ENTRY ListEntry,// 双向节点
PKSPIN_LOOK Look// 保留接口
)
{
DISABLEINTERRUPT();// 关中断
 
PLIST_ENTRY Blink = ListHead->Blink;
 
// 
// 下面将节点挂接在尾部
//
ListEntry->Flink = ListHead;
ListEntry->Blink = Blink;
ListHead->Blink = ListEntry;
Blink->Fink = ListEntry;

ENABLEINTERRUPT();// 开中断
 
//
// 空 list
//
if (Blink == ListHead)
{
return NULL;
}

return ListHead;

5. 分配内存

软件使用 ExAllocateFromNPagedLookasideList() 例程从 non-paged 链的节点里分配 non-paged 内存块,使用 ExAllocateFromPagedLookasideList() 例程从 paged 链的节点里分配 paged 内存块。

如下面代码所示:

PVOID p = ExAllocateFromNPagedLookasideList(lookaside);// 从 non-paged 链节点里分配内存,返回内存块到 p

在从节点里分配内存块时遇到两种情形:

  1. 当节点的内存块链为空时,直接调用节点的 Allocate 例程来在 pool 里分配内存,此时将记录一次 AllocateMisses
  2. 当节点的内存块链不为空时,将重复使用节点内存块,无需直接分配内存。

当节点挂接着内存块时(不为空),将返回节点地址(SLIST_ENTRY 结构地址),节点为空时将返回内存块地址(如果分配成功的话)。

技术分享

如上图所示:有两个节点 A 和 B,从节点 A 分配内存将得到节点地址(作为内存块使用),从节点 B 分配将记录一次 miss(从 lookaside 中分配失败),直接调用 Allocate 例程分配内存。

节点挂着的内存块从哪里来?答案是:释放之前分配的内存块时挂接
      当使用 ExFreeToNPagedLookasideList() 例程释放内存块时,在 Depth 允许的情况下,kernel 将内存块挂接在节点内存块链里,以备以续的分配重复使用,而无需进行物理的分配内存操作。
      因此:当节点内存块链为空时,将产生 miss 情形。表示从节点内存块链里分配不到内存。

当返回节点地址时,它属于 SLIST_ENTRY 指针值。而实际上,这个指针值虽然指向 SLIST_ENTRY 结构,但是无需理会类型,它被当作一个空的内存块使用。这是因为:SLIST_ENTRY 结构里只有一个成员,就是 Next 指针(指向下一条 SLIST_ENTRY 结构)。

当返回这个 SLIST_ENTRY 结构指针值的,它里面的 Next 成员被作为“脏”数据而忽略这个数据(作为空的内存块)。

下面我们来看看 ExAllocateFromNPagedLookasideList() 例程的实现,下面代码是从 windows 2003 里逆出来的:

PVOID ExAllocateFromNPagedLookasideList(
IN PNPAGED_LOOKASIDE_LIST Lookaside
)
{
PSLIST_ENTRY ListEntry;

//
//  TotalAllocates  记录分配的次数,包括成功和失败
//
Lookaside->L.TotalAllocates++;

//
// 从节点里读取内存块链
//
ListEntry = ExInterlockedPopEntrySList(&Lookaside->L.ListHead);
 
//
// 分配内存有两个情形:
// 1. 返回 lookaside list 节点(SLIST_ENTRY) 
// 2. 返回内存,假如 lookaside list 为空
//
if (ListEntry == NULL)
{
//
//  AllocateMisses 记录从 lookaside list 中分配失败的次数
// 也就是从 pool 中分配的次数
//
Lookaside->L.AllocateMisses++;
 
//
// 调用 lookaside list 节点中的 Allocate() 例程
//
return Lookaside->L.Allocate(Lookaside->L.Type, 
Lookaside->L.Size, 
Lookaside->L.Tag);
}
else
return ListEntry;
}

代码使用 ExInterlockedPopEntrySList() 例程来读取节点内存块地址,如果为空时就分配物理内存块,记录一次 miss 事件!这个 miss 事件将被用作 kernel 定期维护和调整 lookaside 链的 Depth 值之用! 根据 miss 次数在分配操作数的频率而提升链的 Depth,达到更好的性能。

对于 paged 类型的内存分配,它看起来像这样:

#define ExAllocateFromPagedLookasideList(Lookaside) \
ExAllocateFromNPagedLookasideList((PNPAGED_LOOKASIDE_LIST)Lookaside) 

ExAllocateFromPagedLookasideList() 实际上也是调用 ExAllocateFromNPagedLookasideList() 例程,只不过传递的是 PAGED_LOOKASIDE_LIST 节点!

6. 释放内存

使用 ExFreeToNPagedLookasideList() 例程来释放 non-paged 链的内存块,而 ExFreeToPagedLookasideList() 释放 paged 链的内存块。当 lookaside 节点的 Depth 允许的情况下,释放的内存块被挂接在节点的 SLIST_ENTRY 链上。

下面的代码将分配 4 个内存块,然后分别释放它们:

NPAGED_LOOKAISDE_LIST Lookaside;
ExInitializeNPagedLookasideList(Lookaside, ...);// 初始化 Lookaside 节点
 
PVOID p1 = ExAllocateFromNPagedLookasideList(Lookaside);// 从节点里第 1 次分配内存
PVOID p2 = ExAllocateFromNPagedLookasideList(Lookaside);// 第 2 次分配内存
PVOID p3 = ExAllocateFromNPagedLookasideList(Lookaside);// 第 3 次分配内存
PVOID p4 = ExAllocateFromNPagedLookasideList(Lookaside);// 第 4 次分配内存
 
... ...
 
ExFreeToNPpagedLookasideList(Lookaside, p1);// 释放 p1
ExFreeToNPpagedLookasideList(Lookaside, p2);// 释放 p2
ExFreeToNPpagedLookasideList(Lookaside, p3);// 释放 p3
ExFreeToNPpagedLookasideList(Lookaside, p4);// 释放 p4 
 
... ...
 
//
// 下面代码再次进行分配内存
//
p1 = ExAllocateFromNPagedLookasideList(Lookaside);         // 此时从节点 ListHead 链里重复使用上次释放过的内存块!(也就是第1个释放的内存块)

在前面的分配 4 次内存里,都调用节点的 Allocate() 例程来进行物理的分配内存操作(因为节点的 ListHead 链是空的),将产生 4 个 AllocateMisses 事件!

在后续代码对分配得到的内存块进行释放,这里也遇到两种情形:

  1. 在 Depth 值允许的情况下:将内存块挂接在节点的 ListHead 链里,以备后续代码重复分配使用,它不会进行物理的释放工作。
  2. 在节点的 Depth 超限的情况下:将调用节点的 Free() 例程进行释放内存块工作,此时会产生 FreeMisses 事件,指示释放到 lookaside 链中失败!

技术分享

前面的三次释放(p1,p2 以及 p3)会成功地挂接在节点的 ListHead 链表头里,而最后一次释放 p4 指向的内存块会导致进行物理地释放操作,回收到系统内存 pool 里(会记录在节点的 FreeMiesses 域里)。

Depth 超限
      当节点的 ListHead 域的 Depth 大于或等于节点的 Depth 值时,就属于 Depth 超限!
      也就是 Lookaside->L.ListHead.Depth >= Lookaside->L.Depth 时就超限了。

Lookaside->L.ListHead.Depth 代表着节点 SLIST_HEADER(也就是内存块链)的 Depth,而 Lookaside->L.Depth 代表示节点的 Depth,从前面的初始化例程里,我们知道节点的 Depth 最初的值为 4。因此:在初始化状态下节点的内存块链最多只能挂接 3 个内存块!超这个数量都将被释放掉,回收到系统 pool 池里。

在运行一段时间后(经过 N 次的分配与释放操作后),系统会定期检查分配的频率以及 miss 的频率,通过这些检查来动态调整节点的 Depth 值,提升节点的 Depth 额度来容纳更多的内存块。由此来达到更好的分配性能。

下面我们来看看 ExFreeToNPagedLookasideList() 例程的实现,在 windows 2003 里逆向出来下面的代码:

VOID ExFreeToNPagedLookasideList(
PNPAGED_LOOKASIDE_LIST Lookaside,
PVOID Entry
)
{
//
// TotalFrees 记录释放的次数,包括:物理释放和回收到 lookaside list 中
//
Lookaside->L.TotalFrees++;

 
//
// 释放的工作,主要有两个情形:
// 1.  当已经被分配的内存块超过 Lookaside list 所支持的深度时,就执行物理的释放工作(调用  Free() )
 // 2.  当小于 lookaside list 深度时,回收到 lookaside list 里,等待下次分配,不执行释放!(挂收在 list 里)
//
 
if (Lookaside->L.ListHead.Depth < Lookaside->L.Depth)
{
//
// 回收到 lookaside list 中
//
RtlpInterlockedPushEntrySList(Lookaside, (PSLIST_ENTRY)Entry);
}
else
{
//
// 记录回收到 lookaside list 失败次数,也就是:物理释放内存次数
//
Lookaside->L.FreeMisses++;
 
//
// 调用 lookaside list  节点中的 Free() 例程释放内存
//
Lookaside->L.Free(Entry);
}

我们清楚地看到两种情形:在回收到 lookaside list 时,代码调用 RtlpInterlockedPushEntrySList() 例程挂接在节点的 ListHead 链头里。在回到收系统内存 pool 时,就调用 Free() 来释放内存。

RtlpInterlockedPushEntrySList() 例程和 ExInterlocakPushEntrySList() 例程执行的操作是一样的,目的是:挂接在单向链表里。 在回收到 lookaside list 节点的 ListHead 链后,下次使用这个节点进行分配内存时,直接从 ListHead 链里取出内存块就行了(使用 ExInterlockedPopEntrySList() 例程)。

技术分享

内存块被释放后,内存块里的数据变为“脏”数据(无用的),ExInterlocakPushEntrySList() 例程将内存块的第 1 个 4 字节数据作为 SINGLE_LIST_ENTRY 结构来挂接在 ListHead 链表头上。

在接着的分配内存中,ExInterlockedPopEntrySList() 例程取出 ListHead 链头的下一个节点,因此:下次分配时是取出第1个释放的内存块(也就是 p1 内存块)。那么:内存块首 4 个字节的 SINGLE_LIST_ENTRY 结构数据会被作为无用数据,在正常使用内存块会被覆盖!这一点巧妙地解决了 PVOID 指针与 PSLIST_ENTRY 指针的兼容性!

7. 动态调整 lookaside list 节点深度

kernel 会定期地监视 lookaside list 的使用情况,根据分配的次数频率来动态调整节点的深度。在初始化节点时 Depth 被置为 4(只能挂接 3 个内存块)。

kernel 会考虑下面情况:

  1. 当节点被频繁地分配内存,这 3 个内存块是不够的!在不足够的情况下会调用节点的 Allocate() 例程进行物理地内存分配操作。kernel 发现这种情况后会提升节点的 Depth 值,使节点能容纳更多的内存块。 如果这种情况持续发生,结果会提升到 256 值(最大的 Depth 值)。
  2. 在提升节点的 Depth 后,发现后续的分配次数和频率被降下来时。kernel 也会降低节点的 Depth 值。如果这种情况持续发生,最后会降至 4 值(最小的 Depth 值)。

通过这些调整手段来达到最好地利用 lookaside list 目的。kernel 使用 ExAdjustLookasideDepth() 例程来监视来调整 lookaside list,它的实现是:(从 windows 2003 逆出来)

VOID ExAdjustLookasideDepth()
{
//
// 分别扫描 3 个 lookaside list。
// 每次扫描的 lookaside list 都是不同的
//
switch (ExpScanCount)
{
case 0:
ExpScanGeneralLookasideList(ExNPagedLookasideListHead, ExNPagedLookasideLock);
break;
case 1:
ExpScanGeneralLookasideList(ExPagedLookasideListHead, ExPagedLookasideLock);
break;
case 2:
ExpScanSystemLookasideList();
break;
}
 
//
// 只有 3 个 lookaside list 需要扫描
//
ExpScanCount++;
if (ExpScanCount == 3)
{
ExpScanCount = 0;
}

这个例程会扫描 non-paged 链,paged 链以及 system 链,每次扫描的对象是不同的。对于 non-paged 链和 paged 链使用 ExpScanGeneralLookasideList() 例程来监视和调整

下面我们看看它的实现(从 windows 2003 里逆出来):

//
// 这个 routine 用来根据分配的频率动态调整 lookaside list 的深度。
// 
VOID ExpScanGeneralLookasideList(
PLIST_ENTRY ListHead,
PKSPIN_LOOK Look
)
{
KIRQL OldIrql = KeRaiseIrqlToDpcLevel();
 
PLIST_ENTRY ListEntry;
PNPAGED_LOOKASIDE_LIST Lookaside;
 
 
//
// 从尾部开始遍历整个挂在 ExNPagedLookasideListHead 链表头的 所有 PNPAGED_LOOKASIDE_LIST 节点!
//
// 这个链由 ExNPagedLookasideListHead 作为链表头(LIST_ENTRY 结构)
// 它的节点是 PNPAGED_LOOKASIDE_LIST 结构的内的  ListEntry 成员!
//
 
for (ListEntry = ListHead->Flink; // 由尾部点节开始
ListEntry != ListHead; // 直到 head 部
ListEntry = ListEntry->Flink// 迭代
)
{
 
//
// 得到宿主的 NPAGED_LOOKASIDE_LIST 结构的地址
// 结果等于: Lookaside = (NPAGED_LOOKASIDE_LIST *)((ULONG)ListEntry - 0x30)
//
Lookaside = CONTAINING_RECORD(ListEntry, NPAGED_LOOKASIDE_LIST, ListEntry);
 
 
//
// 计算扫描时的分配情况
//
ULONG AllocateMisses = Lookaside->L.AllocateMisses - Lookaside->L.LastAllocateMisses;
ULONG TotalAllocates = Lookaside->L.TotalAllocates - Lookaside->L.LastTotalAllocates;

Lookaside->L.LastAllocateHits = Lookaside->L.AllocateHits;
Lookaside->L.LastTotalAllocates = Lookaside->L.TotalAllocates;

USHORT Depth = Lookaside->L.Depth;
USHORT MaximumDepth = Lookaside->L.MaximumDepth;
 
if (ExMinimumLookasideDepth != 0)
{
 
//
// 测试是否频繁使用 lookaside 进行分配
// 当分配次数(包括成功和失败) 达到 75 次就视为频繁
//
if (TotalAllocates >= 75)
{
ULONG MissPercent = AllocateMisses * 1000 / TotalAllocates;
 
//
// 假如 miss 次数低于  50% 则降低 depth ,最小的 depth 为 4
//
if (MissPercent < 5)
{
Depth--;// 逐步降低节点 Depth 值
if (Depth < 4)
Depth = 4;
}
else
{
//
// 计算提高 depth 的幅度,
// 根据剩余的额度来调整:最多每次提升 30 ,最大的 depth 为 256
//
LONG RaiseDepth = ((MaximumDepth - Depth) * MissPercent) / 2000;
if (RaiseDepth > 30)
{
RaiseDepth = 30;
}

Depth = Depth + RaiseDepth;// 提升节点  Depth 值
 
if (Depth > MaximumDepth)
{
Depth = MaximumDepth;
}
}
}
else
{
// 
// 降低 lookaside list 节点的 depth
// 当使用不频繁时,下降的幅度为 10
//
Depth = Depth - 10;
if (Depth < 4)
Depth = 4;
}

 
Lookaside->L.Depth = Depth;// 调整节点 Depth 
}

kfLowerIrql(OldIrql);

Depth 调整的范围始终在 4 到 256 之间,这是 kernel 定义的最小值与最大值。

至此,lookaside list 的实现基本分析完毕!

[转载]使用 Lookaside List 分配内存

标签:

原文地址:http://www.cnblogs.com/Acg-Check/p/4268469.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!