[커널 19차] 100 주차
2024.04.13 22:01
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 (슬랩 캐시의 링크드 리스트)에 두 캐시를 삽입한다.
댓글 0
번호 | 제목 | 글쓴이 | 날짜 | 조회 수 |
---|---|---|---|---|
공지 | [공지] 스터디 정리 노트 공간입니다. | 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 |
.