Linux 内存管理之 SLUB分配器(5):slub初始化过程
本小节整理记录下kmem_cache结构的初始化过程,其实呢,在前边4个小节整理完成后,本部分也就呼之欲出了
本部分主要解决如下问题:
- 在slub分配逻辑中可以看到,object的alloc实际是在kmem_cache和kmem_cache_node 结构中分配的,那么必然存在一个蛋与鸡的故事,如何处理的?
- 在初始化的过程中,有哪些关键接口,以及其作用?
首先将本部分的整体调用结构图示出来:
1. 构造静态kmem_cache & kmem_cache_node结构
前边第3小节已经介绍了object的分配方法,object分配的时候会在kmem_cache结构中逐级搜索,即需要kmem_cache和kmem_cache_node作为前置条件,内核的做法是先填充一个kmem_cache的数据结构,然后再调用alloc接口重新分配一次,将其内容填充进来;
- 创建静态的kmem_cache和kmem_cache_node结构
- 填充kmem_cache的结构体
- 填充name、size等变量
- 计算size、order、order_object的数据
- 申请node的object,注意这个时候还没有slub结构,所以调用的是early接口,直接向buddy申请page
- 填充node中变量
- 申请并填充kmem_cache_cpu结构
- 向slub系统申请object,并赋值添加到系统中
- 向slub申请object
- 将boot struct填充到刚才申请的空间中
- 添加到slab_caches的链表上
这里边几个重要的接口:
接口 | 说明 |
---|---|
create_boot_cache | 填充kmem_cache结构体 |
kmem_cache_open | 实际填充kmem_cache的操作 |
calculate_sizes | 计算object size并构造object结构 |
init_kmem_cache_nodes(s) | 初始化node结构 |
early_kmem_cache_node_alloc(node) | 向buddy申请page作为node结构,并赋值 |
alloc_kmem_cache_cpus(s) | 申请并构造cpu结构 |
bootstrap | 申请object将kmem_cache填充进来并添加到系统 |
kmem_cache_zalloc | 通过slub系统申请object结构 |
经过这部分,我们可以知道的是后续构造空间的接口顺序是:
- kmem_cache_zalloc 申请空间
- create_boot_cache 填充数据
- list_add增加到链表管理
此部分比较重要的接口:
static void early_kmem_cache_node_alloc(int node)
{
struct page *page;
struct kmem_cache_node *n;
page = new_slab(kmem_cache_node, GFP_NOWAIT, node);//直接向slub申请object,此接口实际调用allocate_page,之前分析过
n = page->freelist;//接下来几行都是赋值操作
page->freelist = get_freepointer(kmem_cache_node, n);
page->inuse = 1;
page->frozen = 0;
kmem_cache_node->node[node] = n;
init_object(kmem_cache_node, n, SLUB_RED_ACTIVE);//填充object数据,这里实际填充的是inactive值
init_tracking(kmem_cache_node, n);
kasan_kmalloc(kmem_cache_node, n, sizeof(struct kmem_cache_node), GFP_KERNEL);
init_kmem_cache_node(n);//填充node
inc_slabs_node(kmem_cache_node, node, page->objects);//node结构中几个统计数值++
__add_partial(n, page, DEACTIVATE_TO_HEAD);//添加到partial中
}
static struct kmem_cache * __init bootstrap(struct kmem_cache *static_cache)
{
int node;
struct kmem_cache *s = kmem_cache_zalloc(kmem_cache, GFP_NOWAIT);//cache和node已经ready可以alloc了
struct kmem_cache_node *n;
memcpy(s, static_cache, kmem_cache->object_size);//拷贝过来
__flush_cpu_slab(s, smp_processor_id());
for_each_kmem_cache_node(s, node, n) {
struct page *p;
list_for_each_entry(p, &n->partial, lru)//partial链表赋值
p->slab_cache = s;
list_for_each_entry(p, &n->full, lru)//full链表赋值
p->slab_cache = s;
}
slab_init_memcg_params(s);
list_add(&s->list, &slab_caches);//slab_caches ++
return s;
}
2. kmalloc_caches中的几个计算
setup_kmalloc_cache_index_table() size_index的填充
size(bytes) | Index | size_index[index] |
---|---|---|
8 | 0 | 3 |
16 | 1 | 3 |
32 | 3 | 3 |
64 | 7 | 7 |
96 | 11 | 7 |
128 | 15 | 7 |
192 | 23 | 8 |
对应到kmalloc_cache上
i | kmalloc_cache[i] |
---|---|
3 | 8 |
4 | 16 |
5 | 32 |
6 | 64 |
7 | 128 |
8 | 256 |
9 | 512 |
10 | 1024 |
11 | 2048 |
对应上上述的size_index:
size(bytes) | Index | size_index[index] | kmalloc_slab(size) | kmalloc_cache[i] |
---|---|---|---|---|
8 | 0 | 3 | kmalloc_cache[3] | 8 |
16 | 1 | 3 | kmalloc_cache[3] | 8 |
32 | 3 | 3 | kmalloc_cache[3] | 8 |
64 | 7 | 7 | kmalloc_cache[7] | 128 |
96 | 11 | 7 | kmalloc_cache[7] | 128 |
128 | 15 | 7 | kmalloc_cache[7] | 128 |
192 | 23 | 8 | kmalloc_cache[8] | 256 |
这部分计算比较奇怪,size_index数组填充为上述值后,反倒不比原始数据更容易理解:
*/
static s8 size_index[24] = {
3, /* 8 */
4, /* 16 */
5, /* 24 */
5, /* 32 */
6, /* 40 */
6, /* 48 */
6, /* 56 */
6, /* 64 */
1, /* 72 */
1, /* 80 */
1, /* 88 */
1, /* 96 */
7, /* 104 */
7, /* 112 */
7, /* 120 */
7, /* 128 */
2, /* 136 */
2, /* 144 */
2, /* 152 */
2, /* 160 */
2, /* 168 */
2, /* 176 */
2, /* 184 */
2 /* 192 */
};
这里存疑
3. 关键调用逻辑整理记录
mm_init
-- kmem_cache_init() //注意此部分是在mem_init之后就进行了,说明在此之前页表转换系统和buddy系统已经完成了
-- create_boot_cache();// kmem_cache_node,创建静态的kmem_cache
-- s->xxx = xxx //赋值操作
-- __kmem_cache_create(s, flags)
-- kmem_cache_open(s, flags)
-- calculate_sizes(s, -1) //计算object的size,然后构造oo
-- set_min_partial(s, ilog2(s->size) / 2)
//从这里可以看到partial的数量与size相关,8Byte的话1个,8K的话6个
-- s->cpu_partial = 0 /2 /6/ 13/ 30同样与size相关
-- init_kmem_cache_nodes(s)
-- for_each_node_state
-- slab_state == down ==> early_kmem_cache_node_alloc
-- page = new_slab //向buddy申请page,注意再次之前已经计算出order、count、size了;
//inuse 此时为objects数量,frozen此时为1
-- page->xxx赋值
-- init_object
//在申请page的时候allocate中已经操作过了一遍,差别在于
//那时候传入为inactive,此时为active,即此时为分配出去的;
-- init_tracking
-- init_kmem_cache_node(n)//创建两个链表,将partial、nr_slabs、total_objects设置为0
//标准初始化处理
-- inc_slabs_node(kmem_cache_node) //nr_slab数量+1,objects总数添加
-- __add_partial(n, page, DRACTIVATE_TO_TAIL) //添加到partial列表中
-- alloc_kmem_cache_cpus
-- s->cpu_slab = __alloc_percpu()
-- init_kmem_cache_cpus(s)
-- for_each_possible_cpu(cpu)//遍历每个cpu,假设4核咯
-- per_cpu_ptr(s->cpu_slab, cpu)->tid = init_tid(cpu));
-- sysfs_slab_add(s) //添加到sys文件系统中
-- bootstrap()//申请个object 将上述静态kmem_cache丢进来
-- kmem_cache *s = kmem_cahce_zalloc(kmem_cache, GFP_NOWAIT)//申请空间,注意此时已经创建完成两个static的结构:kmem_cache & kmem_cache_node
-- kmem_cache_alloc(kmem_cache, GFP_NOWAIT | __GFP_ZERO)
-- void *ret = slab_alloc(s, gfpflags, _RET_IP_) //分配kmem_cache_node这么大的object出来,这部分在第三小节有详细逻辑描述,这里简单过去了
-- slab_alloc_node(s, gfpflags, NUMA_NO_NODE, addr) //把node加进来,注意这个位置传入的是NUMA_NO_NODE
-- slab_pre_aloc_hook(s, gfpflags)
-- object = c->freelist,page = c->page
-- if(unlikely(!object || !node_match(page, node)))//判断cpu指向的freelist是否有空闲的object
-- case1 可以找到:
-- void* next_object= get_freepointer_safe(s, object)
-- unlikely(!this_cpu_cmpchg_double(s->cpu_slab->freelist, s->cpu_slab->tid, object, tid, next_object, next_tid(tid))) //比较并赋值
-- prefetch_freepointer(s, next_object);//把next_object 预取进来
-- case2 无法找到:object = __slab_alloc(s, gfpflags, node, addr, c)
-- 加个锁
-- ___slab_alloc(s, gfpflags, node, addr, c)
-- 1. page内分配,针对于刚好有释放的情况;
-- 2. cpu->partial内分配
-- 3. node 内分配
-- 4. 直接申请page
-- 去掉锁
-- slab_post_alloc_hook(s, gfpflags, 1 , &object)
-- trace_kmem_cache_alloc()//添加trace操作,
-- memcpy(s, static_cache, kmem_cache->object_size )
-- __flush_cpu_slab(s, smp_processor_id)())
-- p->slab_cache = s //对分配到的每个page赋值
-- list_add(&s->list, &slab_caches) //将这个kmem_cache加入链表中
-- setup_kmalloc_cache_index_table() //填充size_index数组,这个数组在kmalloc_caches初始化的时候会使用
-- create_kmalloc_caches(0)
-- 遍历kmalloc_caches数组
-- new_kmalloc_cache(i, flags)
-- kmalloc_caches[idx] = create_kmalloc_cache(kmalloc_info[idx].name, kmalloc_info[idx].size, flags)
-- kmem_cache *s = kmem_cache_zalloc(kmem_cache, GFP_NOWAIT) //前文也已经分析过了,分配object操作;
-- create_boot_cache(s, name, size, flags)//前文已经分析过了,是赋值操作
-- list_add(&s->list, &slab_caches)