本文目的在于分析Linux内存管理机制的slab分配器。内核版本为2.6.31。
1. SLAB分配器
内核须要常常分配内存,咱们在内核中最经常使用的分配内存的方式就是kmalloc了。前面讲过的伙伴系统只支持按页分配内存,但这个单位太大了,有时候咱们并不须要这么大的内存,好比我想申请128字节的空间,若是直接使用伙伴系统则需分配4KB的一整页,这显然是浪费。
slab分配器将页拆分为更小的单位来管理,来知足小于一页的内存需求。它将连续的几个页划分出更小的部分拿来分配相同类型的内存对象,对象的位置尽可能作到某种对齐要求(如处理器的L1高速缓存行对齐)以便不对CPU高速缓存和TLB带来显著影响。slab把一类对象统一管理,例如,划出两个页的内存,将其分红n小份用来分配一类对象的实例,同时slab也维护一些通用的对象,用来供kmalloc使用。
提供小块内存并非slab分配器的惟一任务,因为结构上的特色,它也用做一个缓存,主要针对常常分配内存并释放的对象。slab分配器将释放的内存保存在一个内部列表中,并不立刻返还给伙伴系统。在请求为该类对象分配一个实例时,会使用最近释放的内存块,这样就没必要触及伙伴系统以缩短分配消耗的时间,另外因为该内存块是“新”的,其驻留在CPU高速缓存的几率也会较高。
在下面的代码分析中,你会看到slab的这些“手段”是如何实现的。
先说一下“slab着色(slab coloring)”机制,是用来防止高速缓存冲突的:相同类型的slab、对象颇有可能被保存到相同的CPU cache的缓存行中,常用的对象被放到CPU cache中,这固然使咱们想要的,但若是两个不一样的对象每次都被放到相同的缓存行中,那交替的读取这两个对象,会致使缓存行的内容不断的被更新,也就没法体现缓存的好处了。不过个人内核版本是2.6.31,已经没有slab着色机制了,因此你们看到coloring什么的就不要再纠结了。
内核中还提供了slob和slub两种替代品,由于slab的结构很复杂,而且须要使用太多额外的空间去管理一类对象。关于这两个替代品我就很少说了,slub在性能和缓存占用方面都要优于slab,而且在一些嵌入式设备上会看到内核使用slub。
2. SLAB分配器的实现
2.1 SLAB分配器初始化
系统启动时slab分配器初始化的函数为kmem_cache_init()和kmem_cache_init_late()。函数名中的“cache”是指slab分配器,咱们也称做slab缓存,注意,它与CPU中的高速缓存没有关系,但上面讲到slab利用了高速缓存的特性。下面的分析中,我会使用“slab缓存”这种叫法。
kmem_cache_init()函数为分配slab对象准备最基本的环境。在分析这个函数以前,咱们先看一个内核中建立slab缓存的例子:
static struct kmem_cache *nf_conntrack_cachep __read_mostly;
nf_conntrack_cachep= kmem_cache_create("nf_conntrack",
sizeof(struct nf_conn),
0, SLAB_DESTROY_BY_RCU, NULL);
上面的代码在内核协议栈的连接跟踪模块中建立struct nf_conn的slab缓存,这个slab缓存用于分配struct nf_conn对象。
当想申请一个struct nf_conn结构的对象时,使用kmem_cache_alloc()函数进行分配。
struct nf_conn *ct;
ct =kmem_cache_alloc(nf_conntrack_cachep, gfp);
能够看到,建立和使用slab缓存是很是方便的。在/proc/slabinfo文件中能够看到内核中所建立的slab缓存。
kmem_cache_create()用于建立一个slab缓存,在哪里建立呢,kmem_cache_init()函数的工做就是初始化用于建立slab缓存的slab缓存。也就是说,一个slab缓存也应该是经过函数kmem_cache_create()来建立的,可是很容易想到,内核中的第一个slab缓存确定不能经过这个函数来建立,在内核中使用一个编译时生成的静态变量做为第一个slab缓存。
slab缓存用一个struct kmem_cache结构来描述。内核中的第一个slab缓存定义以下:
static struct kmem_cache cache_cache = {
.batchcount = 1,
.limit = BOOT_CPUCACHE_ENTRIES,
.shared = 1,
.buffer_size = sizeof(struct kmem_cache),
.name = "kmem_cache",
};
系统中全部的slab缓存都被放入一个全局链表中:
staticstruct list_head cache_chain;
接下来咱们来分析kmem_cache_init()函数,它的实现分为下面几个步骤:
1. 建立cache_cache,它将用于分配系统中除了它自身之外的全部slab缓存的kmem_cache对象。
2. 建立能够分配struct arraycache_init和struct kmem_list3的slab cache。先建立这两个通用cache的缘由后面会讲到,他们也供kmalloc使用。这两个cache是经过kmem_cache_create()建立的,由于cache_cache已经可用了。这一步以后,将slab_early_init置为0。
3. 使用kmem_cache_create()建立剩余的通用cache,“剩余”是相对第2步中的两个cache,他们都是能够供kmalloc使用的。这些通用cache的名字和对象大小见下面表格。
4. 为cache_cache.array[]和malloc_sizes[INDEX_AC].cs_cachep->array[]从新分配空间。
5. 为cache_cache.nodelists[]、malloc_sizes[INDEX_AC].cs_cachep-> nodelists[]和malloc_sizes[INDEX_L3].cs_cachep-> nodelists[]从新分配空间。
通用cache的名字和对象大小用两个数组来记录:malloc_sizes[]和cache_names[]。
cache_names[]
malloc_sizes[]
size-32
32
size-64
64
size-96
96
size-128
128
……
……
NULL
ULONG_MAX
对于数据结构的其余细节先不作讨论,在讲到在一个cache上分配对象时会说明数据结构。
这部分须要注意一些静态变量,在初始化cache_cache的时候只用到了静态分配的变量,他们之间的关系以下图,其中全局变量用红色标出。
这时尚未用户本身建立的slab cache,因此这里看到的都是通用cache。这些通用cache主要供kmalloc使用。在这期间,不要使用kmalloc函数。
2.2 建立SLAB缓存
除了cache_cache自身,建立一个slab缓存的方法为kmem_cache_create()。slab缓存分为on-slab和off-slab两种,on-slab就是slab管理信息和它所管理的对象放在一块儿,off-slab就是slab管理信息和他所管理的对象分开存放,后面会看到为何会区分这两种类型的slab。
咱们先了解一下struct kmem_cache结构体的成员。
struct kmem_cache {
/* 1)per-cpu data, touched during every alloc/free */
struct array_cache *array[NR_CPUS]; /*per-CPU缓存 */
/* 2)Cache tunables. Protected by cache_chain_mutex */
unsigned int batchcount;
unsigned int limit;
unsigned int shared;
/*每次分配的大小,如nf_conn的cache的buffer_size为sizeof(structnf_conn) */
unsigned int buffer_size;
u32 reciprocal_buffer_size;
/* 3)touched by every alloc & free from the backend */
unsigned int flags; /* constant flags */
unsigned int num; /* # of objs per slab */
/* 4)cache_grow/shrink */
/* order of pgs per slab (2^n) */
unsigned int gfporder;
/* force GFP flags, e.g. GFP_DMA */
gfp_t gfpflags;
size_t colour; /* cache colouring range */
unsigned int colour_off; /* colour offset */
/* 为slab管理信息分配空间的cache。 */
struct kmem_cache *slabp_cache;
/* slab管理信息的size,即struct slab和n个kmem_bufctl_t */
unsigned int slab_size;
unsigned int dflags; /* dynamic flags */
/* constructor func */
void (*ctor)(void *obj);
/* 5) cache creation/removal */
const char *name;
struct list_head next;
/*
* We put nodelists[] at the end ofkmem_cache.
* We still use [MAX_NUMNODES] and not [1] or[0] because cache_cache
* is statically defined, so we reserve themax number of nodes.
*/
struct kmem_list3 *nodelists[MAX_NUMNODES];
/*
* Donot add fields after nodelists[]
*/
};
kmem_cache_create()函数的声明以下:
struct kmem_cache *
kmem_cache_create(const char *name, size_t size, size_t align,
unsigned long flags, void (*ctor)(void *));
五个参数分别为:
name:要建立的cache的名字,将赋值给kmem_cache结构的name成员。
size:要建立的cache每次分配对象的大小,将赋值给kmem_cache结构的buffer_size成员。
align:分配对象以及slab管理信息的对齐量,基本上都为0,即便用默认的对齐方式。
flags:标记,kmem_cache结构的flags成员。
ctor:分配新的slab的时候的构造函数,kmem_cache结构的ctor成员。
这个函数的工做以下:
1. 根据flags、CPU的cache line以及传入的align参数,调整slab管理信息的的对齐量。
2. 用kmem_cache_zalloc(&cache_cache, gfp)在cache_cache上分配一个kmem_cache结构实例cachep。
3. 若是对象的size不小于(PAGE_SIZE >> 3),而且全局标记slab_early_init=0,就强制给flags设置CFLGS_OFF_SLAB。
4. 根据align调整buffer_size大小,并调用calculate_slab_order()函数,该函数从order=0寻找最小的order知足2^order个页的大小可用于分配至少一个对象,找到以后给cachep->num和cachep->gfporder赋值,num成员为2^gfporder个页可分配多少个对象,函数返回值left_over为剩余的空间。对于on slab的cache而言,知足cachep->num * cachep->buffer_size+ cachep->slab_size + left_over = (2 ^ cachep->gfporder) * PAGE_SIZE。而对于off slab的cache而言,知足cachep->num * cachep->buffer_size + left_over = (2 ^ cachep->gfporder)* PAGE_SIZE,即没有slab管理信息的空间,由于off slab的cache的管理信息单独放到另外一个地方。
5. 若是left_over比slab管理信息空间大,且cachep是off slab的,则把cachep改成on slab的,即清除CFLGS_OFF_SLAB标记。同时将left_over的值减掉slab管理信息的大小。
6. 给cachep的一些成员赋值:
cachep->colour_off = cache_line_size();
/* Offset must be a multiple of thealignment. */
if (cachep->colour_off < align)
cachep->colour_off = align;
cachep->colour = left_over /cachep->colour_off;
cachep->slab_size = slab_size;
cachep->flags = flags;
cachep->gfpflags = 0;
if (CONFIG_ZONE_DMA_FLAG && (flags& SLAB_CACHE_DMA))
cachep->gfpflags |= GFP_DMA;
cachep->buffer_size = size;
cachep->reciprocal_buffer_size =reciprocal_value(size);
7. 若是cachep是off slab的, slab管理信息单独放在其余一个地方。这个地方就是根据slab_size(slab管理信息的大小)在通用cache上选择一个合适的cache,注意这里只是选择cache,没有给slab信息分配空间。选好的cache赋值给cachep->slabp_cache。
8. 对cachep->nodelists[0]和cachep->array[0]赋值。调用的函数为setup_cpu_cache(),这个函数中根据全局变量g_cpucache_up的值给cachep的两个成员分配不一样的值。最终结果就是为cachep->array[0]分配sizeof(void *) * cachep->batchcount+ sizeof(struct array_cache)大小的空间,其中cachep->batchcount是struct array_cache中entry的数目。cachep->nodelists[0]中的三个链表都初始为空。
9. 将cachep加入到全局链表:list_add(&cachep->next, &cache_chain);
g_cpucache_up变量:
这个变量记录了在不一样阶段,slab缓存初始化的状态,它可取的值有:
static enum {
NONE,
PARTIAL_AC,
PARTIAL_L3,
EARLY,
FULL
} g_cpucache_up;
在kmem_cache_init()和kmem_cache_init_late()之间没有调用过kmem_cache_create(),即在g_cpucache_up等于EARLY和FULL之间没有建立过cache。也就是说,g_cpucache_up除了FULL以外的全部状态都只在kmem_cache_init()中有用到,即只有建立通用cache有用到。
假定INDEX_AC=0,PARTIAL_L3=3。下表显示了随着建立不一样的slab缓存,g_cpucache_up记录的状态的变化:
cache名称
g_cpucache_up
cache_cache
NONE
size-32(array cache)
NONE
size-64(kmem_list3 structures)
PARTIAL_AC
其余通用cache
PARTIAL_L3
自定义cache
FULL
2.3 在SLAB缓存分配空间
在slab缓存中分配对象,使用的函数是kmem_cache_alloc()或kmem_cache_zalloc(),函数返回void *类型指针。实际的分配工做由____cache_alloc()完成,它的函数体很简单:
static inline void *____cache_alloc(struct kmem_cache *cachep, gfp_t flags)
{
void *objp;
struct array_cache *ac;
ac = cpu_cache_get(cachep); /* ac =cachep->array[0] */
if (likely(ac->avail)) {
ac->touched= 1;
objp = ac->entry[--ac->avail]; /* 最后一个entry */
} else {
objp = cache_alloc_refill(cachep, flags);
}
return objp;
}
若是cachep->array[0]->avail不为0,则直接从cachep->array[0]->entry[]末尾取一个对象返回,并将avail的值减1。
若是cachep->array[0]->avail为0,即没有可用对象可分配,则调用cache_alloc_refill()。
struct kmem_cache的array数组的每一个元素都是一个per-CPU缓存,slab分配一个对象,最终都是先填充到这个缓存中,再在这上面分配出去的。
struct kmem_cache {
/* 1) per-cpu data, touched during everyalloc/free */
struct array_cache *array[NR_CPUS]; /*per-CPU缓存 */
……
}
struct array_cache结构体定义以下:
struct array_cache {
unsigned int avail; /* 该缓存中可用对象的数目 */
unsigned int limit; /* 对象数目的限制,在释放缓存时使用 */
/* 若是该缓存中没有对象可分配了,每次须要向slab申请填充对象的数量 */
unsigned int batchcount;
unsigned int touched; /* 该缓存是不是活动的(最近分配过对象) */
spinlock_t lock;
void *entry[]; /* 实际的对象存放在这儿 */ /*
* Must have thisdefinition in here for the proper
* alignment ofarray_cache. Also simplifies accessing
* the entries.
*/
};
在per-CPU缓存上分配对象时,是从后往前分配的,每分配出一个对象,avail减1,因此avail除了表示可用的对象数量,仍是一个数组下标,能够经过array.entry[avail]直接获取对象。而在释放对象时,则先放到per-CPU缓存的最后,由于内核假设刚释放的对象仍然处在CPU高速缓存中,会尽快在此分配它。
per-CPU缓存的entry[]是一个指针数组,因此它只是存放对象的指针,真正的对象在slab缓存中。
咱们先看一下一个on slab的cache中每一个slab的结构:
图中,slab中的对象总数由cachep->num记录。colour为着色区,咱们不去关注。slab管理信息部分包括一个struct slab结构,以及cachep->num个kmem_bufctl_t的值,方便来定位某个对象。浅蓝色的对象区域存放实际的对象,每一个对象大小由cachep->buffer_size指出。能够看出,一个slab缓存中,有不少空间用做了管理信息。
struct slab结构体定义以下,注意inuse和free两个成员的含义:
struct slab {
struct list_head list;
unsigned long colouroff;
void *s_mem;
/* 已经被使用的对象,最多为cachep->num*/
unsigned int inuse;
/* 标识当前还未被分配的对象的kmem_bufctl_t区域偏移量 */
kmem_bufctl_t free;
unsigned short nodeid;
};
咱们使用kmem_cache_alloc(cachep, flags)在cache上申请一个对象时的分配步骤以下:
1. 先试图在array cache即cachep->array[0]->entry[]上获取,它上面可分配的对象的数目由cachep->array[0]->avail记录。若是这一步找到了可用对象,就返回ac->entry[--ac->avail]。注意,在kmem_cache_create的时候,没有分配对象的空间,因此avail确定是0的。
2. 当avail=0的时候,则array cache已经用完。就试图在kmem_list3即cachep->nodelists[0]上面分配,这就是cache_alloc_refill()函数须要作的事情。structkmem_list3中有三个双向链表,分别指向“已部分使用的slab”、“已用尽的slab”和“彻底空闲的slab”。
1) cache_alloc_refill()先查找slabs_partial链表中有没有slab,若是有则在这里分配;若是没有则查找slabs_free链表中有没有slab,若是有则在这里分配。在这两个链表上分配对象的方式为:将slab中的cachep->array[0]->batchcount数量的对象“分配”给cachep->array[0]->entry[],这个“分配”的过程只是指针指向的操做。而后cachep->array[0]的inuse成员的值增长,free成员前移,同时更新cachep->array[0]->avail的值。这样,array cache上又可分配对象了,因而返回ac->entry[--ac->avail]。
注意,在slab分配出去batchcount个对象后,须要判断该slabs链表是否用尽,若是用尽就将其转移到slabs_full链表中。
2) 若是在slabs_partial和slabs_free链表中都没有slab对象了,就须要在内存中从新分配一个slab。这是cache_grow()函数的工做。
3. cache_grow()函数的主要任务就是在伙伴系统中分配2^(cachep->gfporder)个页,并给分配的页都加上PG_slab标记使其为slab使用。该函数具体实现以下:
a) 算出这次分配的slab的colour空间,其实这些结构体的colour相关的成员在内核中并无用到。下面代码中cachep->colour是在建立cache的时候经过left_over计算的,为颜色数量,l3->colour_next是当前选择cachep->colour中的哪一个颜色,cachep->colour_off是每一个颜色占用的空间大小。最后算出的offset便是上图中最开始的colour区域的长度。
l3 = cachep->nodelists[nodeid];
spin_lock(&l3->list_lock);
offset = l3->colour_next;/* init as 0 */
l3->colour_next++;
if (l3->colour_next >= cachep->colour)
l3->colour_next = 0; /* 取值为0 ~ colour-1 */
spin_unlock(&l3->list_lock);
offset *= cachep->colour_off;
例如若是一个cache的cachep->colour=3,即有三种颜色,则分配的slab开头的colour区域的长度就可能为0、cachep->colour_off和2 * cachep->colour_off。
b) 分配2^(cachep->gfporder)个页,并给分配的页加上PG_slab标记。分配成功后得到第一个页的起始地址的指针objp。
c) 在objp中分配slab管理信息的空间。这里分为两种状况,若是cache是on slab的,则直接在objp的地址上非slab管理信息分配空间,并给struct slab的成员赋值。而若是是off slab的,即slab管理信息和slab对象不在一块儿,这时slab管理信息对象是在cachep->slabp_cache上分配的,分配的函数依然是调用kmem_cache_alloc()。
d) 将分配的全部页与所属的slab和cache创建映射关系,具体作法为,将objp开始的2^(cache->gfporder)个页对应的struct page都进行如下赋值:
page->lru.next = (structlist_head *)cache;
page->lru.prev = (structlist_head *)slab;
其中cache和slab是当前的cache和刚分配的slab信息。这样的目的是为了能够方便的找到一个对象所属于的slab和cache。
e) 调用每一个对象的ctor方法,并给每一个对象对应的kmem_bufctl_t赋个值,这个值从1开始,直到cachep->num-1,最后一个kmem_bufctl_t赋值为BUFCTL_END。
f) 将分配好的slab添加到l3的slabs_free列表中,即cachep->nodelists[0]->slabs_free列表。同时l3->free_objects += cachep->num,注意这个值是l3中的三个列表中可用对象的总数,但不是cachep中可用对象的总数,由于ac->entry[]中还有。
cache_grow()返回后,从新执行cache_alloc_refill()函数,这时即可以在上述的步骤中即可以找到一个对象来返回。
咱们再回过头来看一下为cachep分配slab管理信息的函数alloc_slabmgmt()。上面的c)步骤中讲到在off slab的时候要在cachep->slabp_cache上分配slab管理信息,咱们知道这个slabp_cache是在kmem_cache_create的时候根据slab_size大小在通用cache上选择的一个合适的cache。而在分配slab管理信息的时候,slab管理信息做为cache的对象slabp_cache又会有它本身的slab管理信息,这样又会重复这一分配动做,一定会致使递归,固然递归的前提是slabp_cache是off slab的,也就说,slabp_cache不能是off slab的。我实际看到的slabp_cache都是size-64或size-32,所以都是on slab的,我人为的将size-64或size-32改成off slab的,不出所料,kernel就起不来了。
若是cache是off slab的,那它的slab结构分为两部分:
2.4 在SLAB缓存释放空间
释放slab对象使用kmem_cache_free()函数,它直接调用了__cache_free()函数。
释放一个对象时,分为两种状况:
1. 若是per-CPU缓存中可用对象数目小于其limit的限制,则直接将对象释放到per-CPU缓存中。
2. 若是per-CPU缓存中可用对象数目达到其limit的限制,则须要先将batchcount数目的对象释放到slab缓存中,这个释放动做顺序为从前日后(即释放下标为0~batchcount-1的对象),由于这时最开始释放的对象极可能已经不在高速缓存中了。而后再将咱们要释放的对象释放到per-CPU缓存中,而且将以前下标为batchcount以及以后的对象前移。
static inline void__cache_free(struct kmem_cache *cachep, void *objp)
{
struct array_cache *ac = cpu_cache_get(cachep);
……
if (likely(ac->avail < ac->limit)) {
ac->entry[ac->avail++] = objp;
return;
} else {
cache_flusharray(cachep, ac);
ac->entry[ac->avail++] = objp;
}
}
释放部分对象到slab缓存中的函数为cache_flusharray(),最终经过free_block()完成的,free_block()函数的工做是:
1. 获取对象所在的slab缓存,这是经过virt_to_page()来完成的。前面在分析cache_grow()函数时讲到过slab和page的关系。
2. 将获得的slab从缓存链表中删除。
3. 将对象放回到slab中。
4. 将slab从新添加到缓存链表中,分两种状况:若是这时slab中全部对象都是未使用的,就将其放到slabs_free链表中,不然将其放到slabs_partial链表中。另外,若是将slab放到slabs_free链表,会先检查缓存中空闲对象数目总数是否超过了预约义的free_limit限制,若是超过了,则直接调用slab_destroy()释放掉这个slab。
static voidfree_block(struct kmem_cache *cachep, void **objpp, int nr_objects,
int node)
{
int i;
struct kmem_list3 *l3;
for (i = 0; i < nr_objects; i++) {
void *objp = objpp[i];
struct slab *slabp;
/* 获取对象所在的slab */
slabp = virt_to_slab(objp);
l3 = cachep->nodelists[node];
/* 将slab删除 */
list_del(&slabp->list);
check_spinlock_acquired_node(cachep, node);
check_slabp(cachep, slabp);
/* 将对象放回slab中 */
slab_put_obj(cachep, slabp, objp, node);
STATS_DEC_ACTIVE(cachep);
l3->free_objects++;
check_slabp(cachep, slabp);
/* 将slab从新添加到缓存链表中 */
if (slabp->inuse == 0) {
/* 若是对象总数超出限制,释放整个slab */
if (l3->free_objects > l3->free_limit) {
l3->free_objects -= cachep->num;
/* No need to drop any previously held
* lock here,even if we have a off-slab slab
* descriptor itis guaranteed to come from
* a differentcache, refer to comments before
*alloc_slabmgmt.
*/
slab_destroy(cachep, slabp);
} else {
/* 添加到slabs_free链表中的开头 */
list_add(&slabp->list, &l3->slabs_free);
}
} else {
/* Unconditionally move a slab to the end of the
* partial list onfree - maximum time for the
* other objects tobe freed, too.
*/
/* 添加到slabs_partial链表的末尾 */
list_add_tail(&slabp->list,&l3->slabs_partial);
}
}
}
2.5 销毁SLAB缓存
要销毁一个slab缓存(struct kmem_cache结构的实例),须要调用kmem_cache_destroy()函数,该函数删除缓存的步骤为:
1. 将cachep从cache_cache链表中删除。
2. 将cachep中全部对象释放掉,空间还给伙伴系统。若是该slab缓存是off-slab的,还要将slab管理信息从cachep->slabp_cache中释放。
3. 将cachep的per-CPU缓存和struct kmem_list3结构释放。
4. 因为cachep是cache_cache的一个对象,因此须要将cache_cache中将该对象删除,这一步使用了kmem_cache_free()函数,将slab缓存包括它管理的全部对象都释放。
2. kmalloc
slab是kmalloc的基础,kmalloc使用上面讲到的通用slab缓存来分配空间。
void *kmalloc(size_t size,gfp_t flags);
kmalloc可分配的最大size由KMALLOC_MAX_SIZE定义,这个值在2^25B和buddy的最大分配阶之间取一个小值。
#defineKMALLOC_SHIFT_HIGH ((MAX_ORDER +PAGE_SHIFT - 1) <= 25 ? \
(MAX_ORDER + PAGE_SHIFT - 1) : 25)
#define KMALLOC_MAX_SIZE (1UL<< KMALLOC_SHIFT_HIGH)
#defineKMALLOC_MAX_ORDER (KMALLOC_SHIFT_HIGH -PAGE_SHIFT)
kmalloc的实现很简单:
1. 根据size大小找到最小能装下一个对象的通用cache。
2. 调用kmem_cache_alloc(cachep,flags)进行分配。
由此可知,kmalloc分配的空间是物理上连续的。
原文:https://blog.csdn.net/jasonchen_gbd/article/details/44024009 node