背景

省流

如果已经对该逻辑比较熟悉,直接跳转底部初始化逻辑总结开启知识回顾。本文目前只分析初始化slab 相关流程,对于一些其他的比如kmalloc内存申请部分的细节,或CONFIG_SLAB_FREELIST_RANDOMCONFIG_SLAB_FREELIST_HARDENED等防护措施的初始化细节后续会有章节单独分析。

前情回顾

上一章讲了关于slab 几个数据结构,前面的文章请见:

[linux kernel]slub内存管理分析(0) 导读

[linux kernel]slub内存管理分析(1) 结构体

描述方法约定

PS:为了方便描述,这里我们将一个用来切割分配内存的page 称为一个slab page,而struct kmem_cache我们这里称为slab管理结构,它管理的真个slab 体系成为slab cachestruct kmem_cache_node这里就叫node。单个堆块称为object或者堆块内存对象

初始化操作总览

初始化简介

slab 的初始化在kernel 启动的时候初始化,在mm_init 中调用kmem_cache_init 进行slab 的初始化。

\linux\init\main.c:

  • start_kernel(void)
    • mm_init(void)
      • kmem_cache_init()

根据编译选项slab、slob、slub的配置不同会调用不同文件中的kmem_cache_init这里只分析slub 的。

slub 算法的slab 初始化操作其实非常简单高效:

由于想要用kmalloc 申请内存,那么必须要有slab 管理结构,而内核刚启动的时候自然是没有的,这里内核采取的办法是,首先在临时空间生成一个临时的slab 管理结构,然后用这个管理结构去申请一段内存,然后将管理结构拷贝到新申请的可以长期使用的内存之中,然后后续使用这个管理结构申请出其他的管理结构。完成初始化。

调用栈

kmem_cache_init 开始的调用栈如下:

  • kmem_cache_init 在init data区准备两个slab结构体
    • create_boot_cache 初始化这两个slab 结构体
      • __kmem_cache_create 核心初始化函数
        • kmem_cache_open 初始化一些通用信息
          • init_kmem_cache_nodes 分配空间和初始化node结构体
            • early_kmem_cache_node_alloc 只有第一次还没有node的时候调用这个函数
              • new_slab申请一个新slab page
              • 用新slab page给node分配内存,初始化信息,把slab page给node管理
            • kmem_cache_alloc_node node有了之后调用这个
              • slab_alloc_node 标准kmalloc 的核心函数,参考正常申请流程
          • alloc_kmem_cache_cpus 初始化cpu_slab
    • bootstrap把临时slab 管理结构拷贝到正式内存中
      • kmem_cache_zalloc->…->slab_alloc_node 从slab 中分配一段内存,参考正常申请流程
    • create_kmalloc_caches 对每一个slab 调用:
      • new_kmalloc_cache 根据下标初始化相应的kmalloc_caches
        • create_kmalloc_cache 具体初始化操作,主要调用下面两个:
          • kmem_cache_zalloc->…->slab_alloc_node 从slab 中分配一段内存,参考正常申请流程
          • create_boot_cache 上面调用过了,实际初始化操作
      • create_kmalloc_cache(if CONFIG_ZONE_DMA) 初始化DAM相关空间
    • init_freelist_randomizationCONFIG_SLAB_FREELIST_RANDOM的初始化

详细分析

kmem_cache_init

首先是整个初始化的入口,kmem_cache_init函数,kmem_cache_init函数先从临时内存区域初始化两个slab 大小分别可以分配struct kmem_cache_nodestruct kmem_cache 结构体,然后使用这两个slab去分配出正式内存空间并将这两个slab 迁移过去,之后正式开启slab 的初始化,初始化过程中同样是通过这两个slab申请的各个slab 的结构体内存,代码分析如下:

linux\mm\slub.c : kmem_cache_init

struct kmem_cache *kmem_cache;
struct kmem_cache *kmem_cache_node;
void __init kmem_cache_init(void)
{
	static __initdata struct kmem_cache boot_kmem_cache,
		boot_kmem_cache_node;//[1] 在initdata区域申请两个kmem_cache 结构
    int node;

	if (debug_guardpage_minorder())
		slub_max_order = 0;

	kmem_cache_node = &boot_kmem_cache_node;//给全局变量赋值
	kmem_cache = &boot_kmem_cache;

    for_each_node_state(node, N_NORMAL_MEMORY)//[2]NUMA相关,初始化一下node
		node_set(node, slab_nodes);
	//[3] 分别初始化kmem_cache_node和kmem_cache
	create_boot_cache(kmem_cache_node, "kmem_cache_node",
		sizeof(struct kmem_cache_node), SLAB_HWCACHE_ALIGN, 0, 0);
	
	register_hotmemory_notifier(&slab_memory_callback_nb);

	/* Able to allocate the per node structures */
	slab_state = PARTIAL;//[4] 设置现在的slab 状态
	//[3.1] struct kmem_cache 最后一个node数组成员长度和NUMA的node 数量一样
	create_boot_cache(kmem_cache, "kmem_cache",
			offsetof(struct kmem_cache, node) +
				nr_node_ids * sizeof(struct kmem_cache_node *),
		       SLAB_HWCACHE_ALIGN, 0, 0);
	//[5] 将临时区域的结构体申请真正的空间(用刚初始化完的kmem_cache)
	kmem_cache = bootstrap(&boot_kmem_cache); 
	kmem_cache_node = bootstrap(&boot_kmem_cache_node);

	/* Now we can use the kmem_cache to allocate kmalloc slabs */
	setup_kmalloc_cache_index_table();
	create_kmalloc_caches(0);//[6] 正式初始化各个slab

	/* Setup random freelists for each cache */
	init_freelist_randomization();//[7] 开启了CONFIG_SLAB_FREELIST_RANDOM需要操作一下

	cpuhp_setup_state_nocalls(CPUHP_SLUB_DEAD, "slub:dead", NULL,
				  slub_cpu_dead);

	pr_info("SLUB: HWalign=%d, Order=%u-%u, MinObjects=%u, CPUs=%u, Nodes=%u\n",
		cache_line_size(),
		slub_min_order, slub_max_order, slub_min_objects,
		nr_cpu_ids, nr_node_ids);
}

[1] 由于没有slab 管理结构(struct kmem_cache),就没法用slab 分配内存空间,而slab 自己也需要内存空间存放才行,那么这就造成了一个先有鸡还是先有蛋的问题,这里内核采用的办法是,先在initdata 数据区域(用__initdata表示,init数据区域只有在内核启动的时候存在,之后就会被回收,也可以理解为是临时内存)申请两个slab 结构体,然后先使用这两个slab来分配其他slab 结构的空间。

[2] NUMA相关的bitmaps 结构 slab_nodes初始化

[3] 分别调用create_boot_cache 函数初始化上述两个slab 结构。之前提到过,每种slab 只能申请特定大小的内存,这里两个slab是为了申请后续更多slab 而准备的,所以大小固定为slab 涉及的两个结构体的大小。kmem_cache_node 是为了申请 struct kmem_cache_node准备的slab,而kmem_cache 是为了申请 struct kmem_cache 准备的slab。后续会详细分析create_boot_cache 函数。

​ [3.1] 由于 struct kmem_cache 最后一个成员node是变长数组,数组长度跟NUMA 的node节点数量一样,一般我们起的qemu 都是只有一个node的。

[4] 初始化完kmem_cache_node 之后就会把slab 状态slab_state 设置为PARTIAL,各状态意义如下:

enum slab_state {
	DOWN,			/* No slab functionality yet */
	PARTIAL,		/* SLUB: kmem_cache_node available */
	PARTIAL_NODE,		/* SLAB: kmalloc size for node struct available */
	UP,			/* Slab caches usable but not all extras yet */
	FULL			/* Everything is working */
};

[5] 上述两个结构体初始化完毕就说明已经可以申请slab 相关的两个结构体struct kmem_cache_nodestruct kmem_cache 大小的内存了,那么由于上述两个临时slab 结构还存在在initdata区域,终归是个临时区域,这里调用bootstrap 函数为这两个结构体重新分配内存空间,并将其拷贝过来(还有进一步初始化操作)。后续详细分析bootstrap函数。

[6] 调用create_kmalloc_caches 函数正式初始化全部的slab,也是slab 的真正初始化核心函数。后续详细分析。

[7] 如果开启了CONFIG_SLAB_FREELIST_RANDOM防护编译选项需要做一些初始化。后续会专门分析slab 的防护。

create_boot_cache

create_boot_cache 函数第一次使用是上面说的kmem_cache_init 函数中,对临时内存中的两个struct kmem_cache结构体进行初始化,但这不是create_boot_cache 的唯一使用场景,后续每次对struct kmem_cache结构体初始化都会用到create_boot_cache,对于结构体成员的详细功能,可以见上一节结构体分析。

linux\mm\slab_common.c : create_boot_cache

/* Create a cache during boot when no slab services are available yet */
void __init create_boot_cache(struct kmem_cache *s, const char *name,//[1] 各参数含义见下文
		unsigned int size, slab_flags_t flags,
		unsigned int useroffset, unsigned int usersize)
{
	int err;
	unsigned int align = ARCH_KMALLOC_MINALIGN;

	s->name = name;                   //初始化slab名字
	s->size = s->object_size = size;  //初始化slab管理的size

	if (is_power_of_2(size))//对齐操作,不重要
		align = max(align, size);
	s->align = calculate_alignment(flags, align, size);

	s->useroffset = useroffset;
	s->usersize = usersize;

	err = __kmem_cache_create(s, flags); //[2] 核心初始化函数

	if (err)
		panic("Creation of kmalloc slab %s size=%u failed. Reason %d\n",
					name, size, err);

	s->refcount = -1;	/* Exempt from merging for now */
}

[1] 初始化struct kmem_cache结构体需要提供slab名字符串、slab管理的大小、slab 的flag、还有useroffset 和usersize。

[2] 然后调用__kmem_cache_create 作为初始化的核心部分。接下来详细分析__kmem_cache_create函数。

__kmem_cache_create

linux\mm\slub.c : __kmem_cache_create

int __kmem_cache_create(struct kmem_cache *s, slab_flags_t flags)
{
	int err;

	err = kmem_cache_open(s, flags);//[1] 调用kmem_cache_open 初始化
	if (err)
		return err;

	/* Mutex is not taken during early boot */
	if (slab_state <= UP)//[2] 在初始化 临时slab 的时候这里就返回了
		return 0; 

	err = sysfs_slab_add(s);//[3] 调用sysfs_slab_add,sys文件系统相关
	if (err)
		__kmem_cache_release(s);

	return err;
}

[1] 调用kmem_cache_open 进行详细初始化操作,后面详细分析kmem_cache_open

[2] 只有slab_state > UP 才会执行后面的sysfs_slab_add,所以第一次初始化临时的slab结构的时候这里就直接返回了,并不会继续向后执行。

[3] 调用sysfs_slab_add,sys文件系统相关,不重要

kmem_cache_open->init_kmem_cache_nodes

kmem_cache_open 中会对很多功能复杂的成员进行详细的初始化,必要的还会分配空间。关于成员的具体功能也可以参考上一节。

linux\mm\slub.c : kmem_cache_open

static int kmem_cache_open(struct kmem_cache *s, slab_flags_t flags)
{
	s->flags = kmem_cache_flags(s->size, flags, s->name);//[1] 设置flag
#ifdef CONFIG_SLAB_FREELIST_HARDENED //[2] 开启CONFIG_SLAB_FREELIST_HARDENED防护
	s->random = get_random_long();
#endif

	if (!calculate_sizes(s, -1))
		goto error;
	if (disable_higher_order_debug) {//一般是0
		/*
		 * Disable debugging flags that store metadata if the min slab
		 * order increased.
		 */
		if (get_order(s->size) > get_order(s->object_size)) {//检查slab size是否是满足使用该slab的对象的最小size
			s->flags &= ~DEBUG_METADATA_FLAGS;
			s->offset = 0;
			if (!calculate_sizes(s, -1))
				goto error;
		}
	}

#if defined(CONFIG_HAVE_CMPXCHG_DOUBLE) && \ 
    defined(CONFIG_HAVE_ALIGNED_STRUCT_PAGE)
	if (system_has_cmpxchg_double() && (s->flags & SLAB_NO_CMPXCHG) == 0)
		/* Enable fast mode */
		s->flags |= __CMPXCHG_DOUBLE;//可以使用cmpxchg_double 
#endif

	/*
	 * The larger the object size is, the more pages we want on the partial
	 * list to avoid pounding the page allocator excessively.
	 */
	set_min_partial(s, ilog2(s->size) / 2); //[3] 初始化struct kmem_cache->min_partial

	set_cpu_partial(s);//[4] 初始化struct kmem_cache->cpu_partial

#ifdef CONFIG_NUMA //[5] NUMA架构相关,初始化remote_node_defrag_ratio
	s->remote_node_defrag_ratio = 1000;
#endif

	/* Initialize the pre-computed randomized freelist if slab is up */
	if (slab_state >= UP) {//[6] CONFIG_SLAB_FREELIST_RANDOM 相关
		if (init_cache_random_seq(s))
			goto error;
	}

	if (!init_kmem_cache_nodes(s))//[7] 分配空间与初始化struct kmem_cache->node[]
		goto error;

	if (alloc_kmem_cache_cpus(s))//[8] 分配空间与初始化struct kmem_cache->cpu_slab
		return 0;

	free_kmem_cache_nodes(s);//[9] 失败则会释放已经申请好的node
error:
	return -EINVAL;
}

[1] 调用kmem_cache_flags 初始化一下flag,这里不详细分析了。

[2] 开启CONFIG_SLAB_FREELIST_HARDENED防护的时候需要初始化一下struct kmem_cache结构体的random成员。后续分析slub的防护手段的时候详细分析。

[3] 根据该slab 结构管理的内存块大小来调用set_min_partial 初始化struct kmem_cache结构体的min_partial 成员,该成员代表每个node结点中部分为空的slab结构的最小数量。该值根据slab 管理的内存块大小来决定,为(log 2 size) / 2 ,如果该值大于最大值或小于最小值则设定为最大/最小值:

static void set_min_partial(struct kmem_cache *s, unsigned long min)
{
	if (min < MIN_PARTIAL) //MIN_PARTIAL = 5
		min = MIN_PARTIAL;
	else if (min > MAX_PARTIAL)//MAX_PARTIAL = 10
		min = MAX_PARTIAL;
	s->min_partial = min;
}

[4] 调用set_cpu_partial 来初始化struct kmem_cache结构体的cpu_partial 成员,同样根据slab 管理的内存块大小来决定:

static void set_cpu_partial(struct kmem_cache *s)
{
#ifdef CONFIG_SLUB_CPU_PARTIAL //开启
	if (!kmem_cache_has_cpu_partial(s))//如果没开启cpu_slab->partial链表的话
		slub_set_cpu_partial(s, 0);//设置cpu_slab->cpu_partial,即cpu->partial的slab数最大值为0
	else if (s->size >= PAGE_SIZE)//开启了,但slab管理的堆大小大于4096 的只能持有2个slab
		slub_set_cpu_partial(s, 2);//以此类推
	else if (s->size >= 1024)
		slub_set_cpu_partial(s, 6);
	else if (s->size >= 256)
		slub_set_cpu_partial(s, 13);
	else
		slub_set_cpu_partial(s, 30);//小于256 的都可以持有30个slab
#endif
}

[5] NUMA架构相关,初始化remote_node_defrag_ratio,该值越小,越倾向于在本结点分配对象 ,这里看出默认是1000。

[6] CONFIG_SLAB_FREELIST_RANDOM 相关,后续分析防护手段再分析。

[7] 根据NUMA架构,调用init_kmem_cache_nodesstruct kmem_cache结构体的node成员进行分配空间和初始化操作,本章后面回分析这个函数

[8] 调用alloc_kmem_cache_cpus 函数初始化struct kmem_cache结构体的cpu_slab 成员。

static inline int alloc_kmem_cache_cpus(struct kmem_cache *s)
{
	BUILD_BUG_ON(PERCPU_DYNAMIC_EARLY_SIZE <
			KMALLOC_SHIFT_HIGH * sizeof(struct kmem_cache_cpu));

	/*
	 * Must align to double word boundary for the double cmpxchg
	 * instructions to work; see __pcpu_double_call_return_bool().
	 */
	s->cpu_slab = __alloc_percpu(sizeof(struct kmem_cache_cpu),
				     2 * sizeof(void *));//[8.1] 调用每个cpu资源分配cpu私有的cpu_slab结构

	if (!s->cpu_slab)
		return 0;

	init_kmem_cache_cpus(s);//[8.2] 初始化一下cpu_slab 的一些成员(tid)

	return 1;
}

​ [8.1] __alloc_perc会调用单个cpu 的资源申请内存,申请一个cpu 私有的struct kmem_cache_cpu结构,也就是struct kmem_cache->cpu_slab。per_cpu变量其实就是在gs_base寄存器指向的内存区域内申请空间,每个cpu都会在这里申请空间,不同CPU保存自己申请的内存的偏移,然后不同cpu访问同一个per_cpu变量就可以访问到自己的内容了。

​ [8.2] 调用init_kmem_cache_cpus初始化一下struct kmem_cache_cpu->tid

[9] 如果cpu_slab 初始化失败,则会调用free_kmem_cache_nodes 释放已经初始化好的node 的内存空间。free_kmem_cache_nodes 细节在后续slab销毁章节中分析。

init_kmem_cache_nodes->early_kmem_cache_node_alloc

该函数对struct kmem_cache结构体的node成员进行分配空间和初始化:

linux\mm\slub.c : init_kmem_cache_nodes

static int init_kmem_cache_nodes(struct kmem_cache *s)
{
	int node;

	for_each_node_mask(node, slab_nodes) {//[1] 对每个node
		struct kmem_cache_node *n;

		if (slab_state == DOWN) {//[2] 刚开始的时候才调用
			early_kmem_cache_node_alloc(node);
			continue;
		}
		n = kmem_cache_alloc_node(kmem_cache_node,//[3] 调用kmem_cache_node slab申请空间
						GFP_KERNEL, node);

		if (!n) {
			free_kmem_cache_nodes(s);//失败就把刚申请的释放掉
			return 0;
		}

		init_kmem_cache_node(n);//[4] 初始化一些kmem_cache_node 中的成员
		s->node[node] = n;//初始化node完毕
	}
	return 1;
}

[1] NUMA架构时,根据每个node 分别初始化其node节点

[2] 只有slab_state状态是DOWN 的时候才会调用early_kmem_cache_node_alloc,也就是说只有最开始负责申请struct kmem_cache_node结构体的kmem_cache_node slab还没初始化好的时候才会调用。后续详细分析

[3] 当kmem_cache_node slab 已经初始化完毕,会将slab_state 设置成PARTIAL,这时会调用kmem_cache_alloc_node 分配一个struct kmem_cache_node结构体大小的空间。这里不进去细看了,跟正常slab 分配逻辑一样,因为node结构体本身也是用的普通内存空间,所以这里直接调用正常kmalloc分配的部分逻辑分配的。可以参考后续章节后续分析kmalloc时会分析。

[4] 最后调用init_kmem_cache_node 初始化两个成员:

static void
init_kmem_cache_node(struct kmem_cache_node *n)
{
	n->nr_partial = 0;//初始化nr_partial
	spin_lock_init(&n->list_lock);
	INIT_LIST_HEAD(&n->partial);//初始化链表结构
#ifdef CONFIG_SLUB_DEBUG
	atomic_long_set(&n->nr_slabs, 0);
	atomic_long_set(&n->total_objects, 0);
	INIT_LIST_HEAD(&n->full);
#endif
}
early_kmem_cache_node_alloc

只会在kmem_cache_node slab 还没被初始化的时候调用一次,用于给kmem_cache_node生成node成员的内存空间:

linux\mm\slub.c : early_kmem_cache_node_alloc

static void early_kmem_cache_node_alloc(int node)
{
	struct page *page;
	struct kmem_cache_node *n;
	//[1] 之前设定的kmem_cache_node 管理的slab 的size 不能比kmem_cache_node 结构小
	BUG_ON(kmem_cache_node->size < sizeof(struct kmem_cache_node));

	page = new_slab(kmem_cache_node, GFP_NOWAIT, node);//[2] 申请一个新slab page

	BUG_ON(!page);
	if (page_to_nid(page) != node) {
		pr_err("SLUB: Unable to allocate memory from node %d\n", node);
		pr_err("SLUB: Allocating a useless per node structure in order to be able to continue\n");
	}

	n = page->freelist; //[3] 给node分配空间
	BUG_ON(!n);
#ifdef CONFIG_SLUB_DEBUG //一些不太重要的初始化
	init_object(kmem_cache_node, n, SLUB_RED_ACTIVE);
	init_tracking(kmem_cache_node, n);
#endif
	n = kasan_slab_alloc(kmem_cache_node, n, GFP_KERNEL, false);
	page->freelist = get_freepointer(kmem_cache_node, n);//[4] freelist向后移动
	page->inuse = 1; //page 使用状态
	page->frozen = 0;
	kmem_cache_node->node[node] = n;//初始化完毕
	init_kmem_cache_node(n);//上面说过了,初始化一下node 的一些成员
	inc_slabs_node(kmem_cache_node, node, page->objects);//[5] 增长计数成员

	/*
	 * No locks need to be taken here as it has just been
	 * initialized and there is no concurrent access.
	 */
	__add_partial(n, page, DEACTIVATE_TO_HEAD);//[6] 把page->slab插入到n->partial链表中。
}

[1] 由于kmem_cache_node 这个管理结构就是为了给struct kmem_cache_node申请空间而存在的,所以这里kmem_cache_node 管理的大小不能小于struct kmem_cache_node的大小。

[2] 调用new_slab 申请一个新的slab page,也就是用来分割内存块的页结构,使用而这些页结构必须要有struct kmem_cache 结构体去管理才行。这里使用的是kmem_cache_node的 slab 管理结构,虽然还没有初始化完毕,但只有cpu_slab 和node 没有初始化完毕了,已经是"可以使用"的状态了。但由于刚初始化中,所以里面的slab 自然也是空的。这里使用new_slab 申请一个新的页面用于分割内存块。具体new_slab 会在后续kmalloc 内存申请中详细分析。这里暂时理解成申请一个新页即可。

[3] page->freelist 指向这个页面按照所属slab 大小分割成相应数量的内存块中的第一块,直接把这一块内存给n作为struct kmem_cache_node *n的内存。

[4] 由于freelist第一块已经分配出去了,这里freelist 向后移动一块。

[5] 由于kmem_cache_node 管理的slab 已经参与过一次分配了,增长相应的计数成员,nr_slabs代表该node现有的slab page数量,total_objects代表该node现有的内存对象数量。

static inline void inc_slabs_node(struct kmem_cache *s, int node, int objects)
{
	struct kmem_cache_node *n = get_node(s, node);

	/*
	 * May be called early in order to allocate a slab for the
	 * kmem_cache_node structure. Solve the chicken-egg
	 * dilemma by deferring the increment of the count during
	 * bootstrap (see early_kmem_cache_node_alloc).
	 */
	if (likely(n)) {
		atomic_long_inc(&n->nr_slabs);
		atomic_long_add(objects, &n->total_objects);
	}
}

[6] 按照数据结构之间的关系,需要把page->slab插入到n->partial链表中。

static inline void
__add_partial(struct kmem_cache_node *n, struct page *page, int tail)
{
	n->nr_partial++;
	if (tail == DEACTIVATE_TO_TAIL)
		list_add_tail(&page->slab_list, &n->partial);
	else
		list_add(&page->slab_list, &n->partial);
}

bootstrap

虽然在调用bootstrap 之前已经进行了好多初始化了,而且已经成功分配过 struct kmem_cache_node结构体的空间了,但两个slab 管理结构kmem_cache_nodekmem_cache 还都是在initdata 这个临时内存中。那么现在要调用bootstrap 分别给他们分配正式的内存并将他们从临时内存中迁移到正式内存中:

linux\mm\slub.c : bootstrap

static struct kmem_cache * __init bootstrap(struct kmem_cache *static_cache)
{
	int node;
	struct kmem_cache *s = kmem_cache_zalloc(kmem_cache, GFP_NOWAIT);//[1]分配内存
	struct kmem_cache_node *n;

	memcpy(s, static_cache, kmem_cache->object_size);//[1]拷贝

	/*
	 * This runs very early, and only the boot processor is supposed to be
	 * up.  Even if it weren't true, IRQs are not up so we couldn't fire
	 * IPIs around.
	 */
	__flush_cpu_slab(s, smp_processor_id());//[2]刷新slab 的cpu_slab
	for_each_kmem_cache_node(s, node, n) {//[2]修正每个node 的partial指针
		struct page *p;

		list_for_each_entry(p, &n->partial, slab_list)
			p->slab_cache = s;

#ifdef CONFIG_SLUB_DEBUG
		list_for_each_entry(p, &n->full, slab_list)
			p->slab_cache = s;
#endif
	}
	list_add(&s->list, &slab_caches);// [3]所有的slab 管理结构都要连入slab_caches列表
	return s;
}

[1] 调用kmem_cache_zalloc 分配内存,kmem_cache_zalloc 里面其实就是slab_alloc_node ,后面在kmalloc内存分配章节详细分析。然后使用memcpy 将临时内存的slab 管理结构拷贝过来。

[2] 刷新cpu_slab__flush_cpu_slab 该函数在后面slab销毁章节中有介绍,具体功能就是将当前cpu_slabpagefreelist 指针置空,然后并把page 原本指向的slab 放入对应的node链表(partialfull)中或释放(slab为空)。之后再将cpu_slab->partial中的slab 都放入node->partial中。

[3] 将正式的slab 管理结构链入slab_caches 链表,所有slab 管理结构都要接入slab_caches链表。

create_kmalloc_caches

create_kmalloc_caches函数负责初始化所有kmalloc_caches,走到这里准备阶段已经完成,接下来初始化通用的slab们,也就是kmem_caches全局变量,初始化完成后其他模块就可以进行kamlloc分配了:

linux\mm\slab_common.c : create_kmalloc_caches

void __init create_kmalloc_caches(slab_flags_t flags)
{
	int i;
	enum kmalloc_cache_type type;
	//[1]初始化kmalloc_caches[NR_KMALLOC_TYPES][KMALLOC_SHIFT_HIGH + 1]
	for (type = KMALLOC_NORMAL; type <= KMALLOC_RECLAIM; type++) {
		for (i = KMALLOC_SHIFT_LOW; i <= KMALLOC_SHIFT_HIGH; i++) {
			if (!kmalloc_caches[type][i])
				new_kmalloc_cache(i, type, flags);//[2]如果还没申请过,在这里申请

			/*
			 * Caches that are not of the two-to-the-power-of size.
			 * These have to be created immediately after the
			 * earlier power of two caches
			 */
            //[3]对于特殊的大小(非2倍数的两个)特殊处理
			if (KMALLOC_MIN_SIZE <= 32 && i == 6 &&
					!kmalloc_caches[type][1])
				new_kmalloc_cache(1, type, flags);
			if (KMALLOC_MIN_SIZE <= 64 && i == 7 &&
					!kmalloc_caches[type][2])
				new_kmalloc_cache(2, type, flags);
		}
	}

	/* Kmalloc array is now usable */
	slab_state = UP;//更新slab_state,现在kmalloc_caches已经可以使用了

#ifdef CONFIG_ZONE_DMA//[4]默认开启,配置DMA相关的kmalloc_caches
	for (i = 0; i <= KMALLOC_SHIFT_HIGH; i++) {
		struct kmem_cache *s = kmalloc_caches[KMALLOC_NORMAL][i];

		if (s) {/
			kmalloc_caches[KMALLOC_DMA][i] = create_kmalloc_cache(
				kmalloc_info[i].name[KMALLOC_DMA],
				kmalloc_info[i].size,
				SLAB_CACHE_DMA | flags, 0,
				kmalloc_info[i].size);
		}
	}
#endif
}

[1] 到这里就要正式初始化kmalloc 的核心slab 管理结构数组kmalloc_caches 了,之前介绍过,这是一个二维数组,根据类型分为2-3 类,并且每一类都根据所管理的slab 大小分为KMALLOC_SHIFT_HIGH(13)个:

extern struct kmem_cache *
kmalloc_caches[NR_KMALLOC_TYPES][KMALLOC_SHIFT_HIGH + 1];
//其中
enum kmalloc_cache_type {
	KMALLOC_NORMAL = 0,
	KMALLOC_RECLAIM,
#ifdef CONFIG_ZONE_DMA
	KMALLOC_DMA,
#endif
	NR_KMALLOC_TYPES
};

#define KMALLOC_SHIFT_HIGH	(PAGE_SHIFT + 1)//PAGE_SHIFT 12 总之就是一页

[2] 依次按照类型、大小的顺序遍历kmalloc_caches,并且调用new_kmalloc_cache 函数进行单个kmalloc_cache 的内存分配和初始化。后面分析new_kmalloc_cache 函数。

[3] 之前提到过,kmalloc_caches 管理的slab 大小是根据下标计算2的下标次幂,但最小是8,所以下标1 和2 分别表示大小96 和192,弥补了小内存之间的差距。这里对非2下标大小进行单独初始化。

[4] 如果存在ZONE_DMA区域的话,这里对DMA区域的kmalloc_caches 单独重新初始化一遍,依据NORMAL区域来进行初始化,初始化过程跟上面是一样的。

new_kmalloc_cache->create_kmalloc_cache

linux\mm\slab_common.c : new_kmalloc_cache

static void __init
new_kmalloc_cache(int idx, enum kmalloc_cache_type type, slab_flags_t flags)
{
	if (type == KMALLOC_RECLAIM)//对于KMALLOC_RECLAIM类型的kmalloc_cache 设置一下flag
		flags |= SLAB_RECLAIM_ACCOUNT;

	kmalloc_caches[type][idx] = create_kmalloc_cache(//[1]调用create_kmalloc_cache
					kmalloc_info[idx].name[type],
					kmalloc_info[idx].size, flags, 0,
					kmalloc_info[idx].size);
}

[1] 这里直接调用了create_kmalloc_cache 进行初始化,但值得注意的是参数是kmalloc_infokmalloc_info 其实包括了kmalloc_caches 的一些基本信息,如下:

#define INIT_KMALLOC_INFO(__size, __short_size)			\
{								\
	.name[KMALLOC_NORMAL]  = "kmalloc-" #__short_size,	\
	.name[KMALLOC_RECLAIM] = "kmalloc-rcl-" #__short_size,	\
	.name[KMALLOC_DMA]     = "dma-kmalloc-" #__short_size,	\
	.size = __size,						\
}
const struct kmalloc_info_struct kmalloc_info[] __initconst = {
	INIT_KMALLOC_INFO(0, 0),
	INIT_KMALLOC_INFO(96, 96),
	INIT_KMALLOC_INFO(192, 192),
	INIT_KMALLOC_INFO(8, 8),
	INIT_KMALLOC_INFO(16, 16),
	INIT_KMALLOC_INFO(32, 32),
	INIT_KMALLOC_INFO(64, 64),
	INIT_KMALLOC_INFO(128, 128),
	INIT_KMALLOC_INFO(256, 256),
	INIT_KMALLOC_INFO(512, 512),
	INIT_KMALLOC_INFO(1024, 1k),
	INIT_KMALLOC_INFO(2048, 2k),
	INIT_KMALLOC_INFO(4096, 4k),
	INIT_KMALLOC_INFO(8192, 8k),
	··· ···
};

所以name 就是kmalloc-size 这种字符串。这里也可以看出,kmalloc_caches 下标和管理slab 大小之间的关系。

create_kmalloc_cache

linux\mm\slab_common.c : create_kmalloc_cache

struct kmem_cache *__init create_kmalloc_cache(const char *name,
		unsigned int size, slab_flags_t flags,
		unsigned int useroffset, unsigned int usersize)
{
	struct kmem_cache *s = kmem_cache_zalloc(kmem_cache, GFP_NOWAIT);//[1]分配内存

	if (!s)
		panic("Out of memory when creating slab %s\n", name);
	//[2]初始化slab 管理结构
	create_boot_cache(s, name, size, flags, useroffset, usersize);
	kasan_cache_create_kmalloc(s);
	list_add(&s->list, &slab_caches);//[3]所有slab都要添加到slab_caches之中
	s->refcount = 1;
	return s;
}

[1] 先调用kmem_cache_zalloc(前面提到过) 给struct kmem_cache结构体分配内存。

[2] 调用create_boot_cache 给刚分配内存的struct kmem_cache初始化,这里的场景跟最开始启动的时候初始化临时内存区域的那两个kmem_cachekmem_cache_node 是一样的,调用的也是create_boot_cache 函数,唯一不同的是,在__kmem_cache_create之中会根据slab_state 状态来决定是否调用sysfs_slab_add函数:

if (slab_state <= UP)
	return 0;
err = sysfs_slab_add(s);//sysfs相关,不重要

最初的时候是直接跳过了sysfs_slab_add的,这里虽然slab_state 已经更新为了UP,但还是会在这里直接返回,并不会执行sysfs_slab_add

[3] 之前提到过,所有kmem_cache 都要加入到slab_caches链表之中。

其他初始化

初始化到这里其实已经完事了,但slab_state 还是UP,并不是FULL,其实是还有一些跟用户面的接口没初始化好,比如/sys/kernel/slab 相关的sysfs 的或者/proc/slabinfo。初始化完毕之后就会将slab_state设置为FULL。

特殊slab的初始化

在上面初始化流程中只是初始化了通用slab,也就是通常调用kmalloc的时候用的slab。但内核中还有很多特殊slab,用于存放一些敏感的结构体,让他们和其他结构体的申请空间分开,这些结构体有自己专门的slab_cache。比如申请文件结构体struct file的名为filp_cachep的slab缓存,初始化逻辑如下 :

static struct kmem_cache *filp_cachep __read_mostly;

void __init files_init(void)
{
	filp_cachep = kmem_cache_create("filp", sizeof(struct file), 0,
			SLAB_HWCACHE_ALIGN | SLAB_PANIC | SLAB_ACCOUNT, NULL);
	percpu_counter_init(&nr_files, 0, GFP_KERNEL);
}

可以看到拥有一个独立的负责初始化单独slab缓存的函数,具体的功能会在下一节[linux kernel]slub内存管理分析(2.5) slab重用中介绍。

初始化逻辑总结

首先根据之前提到的slab 数据结构,slab 根据大小不同分为不同类型,分别由slab 管理结构体管理,每种slab 管理结构体只管理一种大小的slab。而slab 管理结构本身也需要内存存放,在最开始没有slab 和slab管理结构的时候如何给slab管理结构和node 分配内存就成了先有鸡还是先有蛋的问题。这里内核采用的方法是:

  • 在临时内存区域initdata整两块slab管理结构struct kmem_cache的内存。管理的大小分别为sizeof(struct kmem_cache)sizeof(struct kmem_cache_node),即用来给slab 管理结构和node 分配内存的,对应全局变量kmem_cachekmem_cache_node
    • 先初始化kmem_cache_node,先初始化一些通用信息(名字、管理大小、flag、min_partial…)。
      • kmem_cache_node 分配node,由于这时kmem_cache_node 自己还没初始化好(没node),所以也是整个操作系统的第一次从slab 分配内存,这时直接先申请一个新slab page,并分配一个node结构体的内存,并初始化这个node结构体。然后将这个新申请的slab page放到node里。
      • 再初始化cpu_slab 相关信息。
      • kmem_cache_node初始化完毕。
    • 再初始化kmem_cache,先初始化一些通用信息(名字、管理大小、flag、min_partial…)。
      • 再申请node,由于kmem_cache_node 已经初始化完毕,直接正常走slab 申请就可以申请到node 的内存,然后初始化。
      • 初始化cpu_slab
      • kmem_cache初始化完毕
  • 现在kmem_cachekmem_cache_node 都初始化完毕了,也就是说struct kmem_cachestruct kmem_cache_node结构体都可以正常申请分配了。但kmem_cachekmem_cache_node 两个管理结构还在临时内存中。需要给他们自己先分配正常内存
    • 调用bootstrapkmem_cachekmem_cache_node 两个管理结构分配正常内存。
      • 直接使用kmem_cache 分配struct kmem_cache结构,然后将内容memcpy 进去,然后刷新一下slab 里的指向slab管理结构的指针。
  • 接下来开始正式初始化extern struct kmem_cache * kmalloc_caches[NR_KMALLOC_TYPES][KMALLOC_SHIFT_HIGH + 1];全局变量,kmalloc_caches 是一个根据kmalloc内存类型,和管理slab大小来分类的二维数组。
    • 调用create_kmalloc_caches 初始化kmalloc_caches。遍历kmalloc_caches
      • 对每一个kmalloc_cache,给他们分配struct kmem_cachestruct kmem_cache_node结构体空间并初始化信息,不同kmalloc_cache之间名字、管理大小均不同,根据kmalloc_info记录的信息初始化。
      • 如果开启了ZONE_DMA,再初始化一遍DMA的kmalloc_caches,和上面方法相同。
GitHub 加速计划 / li / linux-dash
10.39 K
1.2 K
下载
A beautiful web dashboard for Linux
最近提交(Master分支:2 个月前 )
186a802e added ecosystem file for PM2 4 年前
5def40a3 Add host customization support for the NodeJS version 4 年前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐