[커널 19차] 100 주차

2024.04.13 22:01

Min 조회 수:16

kmem_cache_init()

kmem_cache_init()은 슬랩 할당자가 초기화되는 함수로,

start_kernel()→mm_core_init()→kmem_cache_init() 순서로 부팅 과정에서 호출된다.

void __init kmem_cache_init(void)
{
	static __initdata struct kmem_cache boot_kmem_cache,
		boot_kmem_cache_node;

kmem_cache 캐시가 생성되기 전에 사용할 kmem_cache와 kmem_cache_node의 struct kmem_cache가 필요한데, 이는 init data 섹션에 정적으로 생성된다. __initdata 속성이 있기 때문에, 이 두 변수는 부팅 과정 이후에 해제된다. 이 두 변수는 임시로만 사용하고, kmem_cache 캐시가 초기화된 이후에 동적으로 할당한 struct kmem_cache를 사용하게 된다. (bootstrap() 함수 참고)


	int node;

	if (debug_guardpage_minorder())
		slub_max_order = 0;

CONFIG_DEBUG_PAGEALLOC 옵션을 켜고 빌드했을 때, guard page의 minimum order가 0보다 크다면 slab의 max order를 0으로 설정한다. 이렇게 할 경우 메모리 접근 위반으로 인한 버그를 더 찾을 확률이 높아진다. 단, 왜 minimum order = 0인 경우에는max order를 왜 조정하지 않는지는 모르겠다.

	/* Print slub debugging pointers without hashing */
	if (__slub_debug_enabled())
		no_hash_pointers_enable(NULL);

printk로 포인터 값을 출력할 때, 커널이 사용하는 가상 주소는 민감한 정보이기 때문에 해시 함수를 거친 값을 출력한다. 이 기능은 디버깅을 어렵게 하기 때문에 SLUB debugging이 활성화된 경우 raw pointer 포인터 값을 그대로 출력하도록 한다.

kmem_cache_node = &boot_kmem_cache_node;
kmem_cache = &boot_kmem_cache;

부팅 프로세스에서는 kmem_cache 캐시가 아직 생성되지 않았기 때문에, kmem_cache와 kmem_cache_node의 struct kmem_cache는 전역 변수를 사용한다.

	/*
	 * Initialize the nodemask for which we will allocate per node
	 * structures. Here we don't need taking slab_mutex yet.
	 */
	for_each_node_state(node, N_NORMAL_MEMORY)
		node_set(node, slab_nodes);

slab_nodes 비트마스크에 normal memory를 포함하는 모든 노드에 대한 비트를 설정한다. 예를 들어서 high memory만 포함하는 노드가 있다면 슬랩을 할당할 수 없기 때문에 그런 노드들은 무시한다. slab_nodes에 비트가 켜져 있는 노드들에 대해서 per-node structure (struct kmem_cache_node)를 할당한다.

	create_boot_cache(kmem_cache_node, "kmem_cache_node",
		sizeof(struct kmem_cache_node), SLAB_HWCACHE_ALIGN, 0, 0);

create_boot_cache()로 kmem_cache_node 캐시를 생성한다.

	hotplug_memory_notifier(slab_memory_callback, SLAB_CALLBACK_PRI);

메모리가 hot-plug 혹은 hot-unplug 될 때 호출되어야 될 콜백 함수를 등록한다.

	/* Able to allocate the per node structures */
	slab_state = PARTIAL;

kmem_cache_node 캐시가 생성되었으므로 slab_state를 PARTIAL (kmem_cache_node available)로 수정한다.

	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);

kmem_cache 캐시를 생성한다.

	kmem_cache = bootstrap(&boot_kmem_cache);
	kmem_cache_node = bootstrap(&boot_kmem_cache_node);

이제 kmem_cache 캐시가 생성되었기 때문에, bootstrap() 함수로 boot_kmem_cache{,_node}를 동적으로 할당한 struct kmem_cache로 갈아끼운다.

	/* Now we can use the kmem_cache to allocate kmalloc slabs */
	setup_kmalloc_cache_index_table();
	create_kmalloc_caches(0);

kmalloc cache 인덱스 테이블을 초기화한 후 kmalloc 캐시들을 생성한다.

	/* Setup random freelists for each cache */
	init_freelist_randomization();

각 캐시에서 freelist randomization을 할 수 있도록 초기화한다.

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

CPU hotplug 관련 콜백 함수를 등록한다.

	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);
}

 

create_boot_cache()

create_boot_cache() 함수는 부팅 과정에서 kmem_cache, kmem_cache_node, kmalloc-* 캐시를 만들 때 호출된다.

/* Create a cache during boot when no slab services are available yet */
void __init create_boot_cache(struct kmem_cache *s, const char *name,
		unsigned int size, slab_flags_t flags,
		unsigned int useroffset, unsigned int usersize)
{
	int err;
	unsigned int align = ARCH_KMALLOC_MINALIGN;

	s->name = name;
	s->size = s->object_size = size;

kmalloc-* 캐시를 만들 때 적절한 align 값을 사용할 수 있도록, 적어도 ARCH_KMALLOC_MINALIGN 을 alignment 값으로 사용한다. name과 object_size, size를 struct kmem_cache에 저장한다.

	/*
	 * For power of two sizes, guarantee natural alignment for kmalloc
	 * caches, regardless of SL*B debugging options.
	 */
	if (is_power_of_2(size))
		align = max(align, size);
	s->align = calculate_alignment(flags, align, size);

만약 size가 2의 거듭제곱 꼴(2^n)이라면, align이 size보다 작은 경우, size를 align으로 사용한다. 이는 size가 2^n인 kmalloc 객체의 alignment가, 디버깅 옵션 등에 의해 깨지지 않도록 하기 위함이다. size가 2^n인 kmalloc 객체들은 size 크기로 정렬되어있다고 가정하는 경우가 있다.

#ifdef CONFIG_HARDENED_USERCOPY
	s->useroffset = useroffset;
	s->usersize = usersize;
#endif

user가 복사할 수 있는 offset, size를 지정한다.

	err = __kmem_cache_create(s, flags);

__kmem_cache_create()로 캐시를 생성한다.

	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로 설정해서 머지가 되지 않도록 한다. (create_kmalloc_cache())의 경우 refcount를 다시 1로 설정하는 루틴이 존재한다.

 

slab_memory_callback

SLUB은 kmem_cache_init에서 slab_memory_callback 함수를 hotplug_memory_notifier() 함수로 등록한다. 메모리 hotplug로 인해 특정 노드가 online or offline되는 경우를 처리한다.

이때, MEM_GOING_{ONLINE,OFFLINE}은 online, offline 상태가 되기 전을 말하고,

MEM_{ONLINE,OFFLINE}은 online, offline 상태가 된 후를 말한다.

static int slab_memory_callback(struct notifier_block *self,
				unsigned long action, void *arg)
{
	int ret = 0;

	switch (action) {
	case MEM_GOING_ONLINE:
		ret = slab_mem_going_online_callback(arg);
		break;

새로운 노드가 onlining 될 때, 모든 캐시에 대하여 해당 노드에 대한 kmem_cache_node를 할당하고, slab_nodes 비트마스크에 노드의 비트를 설정한다.


	case MEM_GOING_OFFLINE:
		ret = slab_mem_going_offline_callback(arg);
		break;

노드가 offlining 될때는 모든 캐시에 대해서 flush_aall_cpus_locked()와 __kmem_cache_do_shrink()로 해제할 수 있는 partial slab들을 해제한다.

	case MEM_OFFLINE:
	case MEM_CANCEL_ONLINE:
		slab_mem_offline_callback(arg);
		break;
	case MEM_ONLINE:
	case MEM_CANCEL_OFFLINE:
		break;
	}

onlining에 실패했거나 offline이 되는 경우는 slab_nodes에서 노드 비트를 클리어한다.

 

bootstrap()

bootstrap() 함수는 boot_kmem_cache{,_node}를 동적 할당한 struct kmem_cache로 교체한다.

(kmem_cache 캐시가 생성되었기 때문)

/*
 * Used for early kmem_cache structures that were allocated using
 * the page allocator. Allocate them properly then fix up the pointers
 * that may be pointing to the wrong kmem_cache structure.
 */

static struct kmem_cache * __init bootstrap(struct kmem_cache *static_cache)
{
	int node;
	struct kmem_cache *s = kmem_cache_zalloc(kmem_cache, GFP_NOWAIT);
	struct kmem_cache_node *n;

	memcpy(s, static_cache, kmem_cache->object_size);

kmem_cache_zalloc()으로 0으로 초기화된 struct kmem_cache를 할당하고 기존에 사용하던 static 하게 정의된 struct kmem_cache의 값을 복사한다.

	/*
	 * 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());

bootstrap()은 모든 슬랩에 대해서 kmem_cache 포인터를 갱신해야 한다. 바로 다음 코드는 for_each_kmem_cache_node() 매크로로 per-node partial list에 대해서만 처리를 하는데, 위 코드에서 __flush_cpu_slab()을 호출했기 때문에 cpu slab과 cpu partial slab이 모두 node로 이동한다.

	for_each_kmem_cache_node(s, node, n) {
		struct slab *p;

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

boot_kmem_cache{,_node}는 부팅이 끝나면 사라지기 때문에, __flush_cpu_slab()을 호출한 다음, per-node partial list에서 struct slab이 새로 할당한 struct kmem_cache의 주소를 참조하도록 바꿔준다. (여기서 struct slab은 슬랩 (or slab page)의 디스크립터이다.)


#ifdef CONFIG_SLUB_DEBUG
		list_for_each_entry(p, &n->full, slab_list)
			p->slab_cache = s;
#endif
	}

CONFIG_SLUB_DEBUG 옵션이 켜져있다면 partial 뿐만 아니라 full list도 똑같이 struct slab에서 kmem_cache의 주소를 갱신한다.

	list_add(&s->list, &slab_caches);
	return s;
}

마지막으로 slab_caches (슬랩 캐시의 링크드 리스트)에 두 캐시를 삽입한다.

 

번호 제목 글쓴이 날짜 조회 수
공지 [공지] 스터디 정리 노트 공간입니다. woos 2016.05.14 627
248 [커널 19차] 103 주차 Min 2024.04.28 10
247 [커널 20차] 48주차 무한질주 2024.04.25 25
246 [커널 19차] 102 주차 Min 2024.04.20 40
245 [커널 19차] 101 주차 Min 2024.04.13 63
» [커널 19차] 100 주차 Min 2024.04.13 16
243 [커널 19차] 99 주차 Min 2024.03.30 82
242 [커널 19차] 98 주차 Min 2024.03.23 55
241 [커널 19차] 97 주차 Min 2024.03.16 50
240 [커널 19차] 96 주차 Min 2024.03.14 32
239 [커널 19차] 95 주차 [2] Min 2024.03.03 111
238 [커널 20차] 32주차 brw 2023.12.16 387
237 [커널 20차] 29주차 brw 2023.11.27 161
236 [커널 20차] 27주차 brw 2023.11.21 86
235 [커널 20차] 26주차 brw 2023.11.21 49
234 [커널 20차] 28주차 이민찬 2023.11.19 64
233 [커널 20차] 25주차 이민찬 2023.10.30 120
232 [커널 20차] 24주차 이민찬 2023.10.22 752
231 [커널 20차] 23주차 이민찬 2023.10.14 81
230 [커널 20차] 22주차 이민찬 2023.10.08 76
229 [커널 20차] 21주차 이민찬 2023.09.23 116
XE Login