[커널 17차] 81주차

2022.03.19 23:46

ㅇㅇㅇ 조회 수:132

kmem_cache_init()
- L4830 부터
- L4830 : 함수 bootstrap()을 이용해서 boot_kmem_cache를 기존 static 변수 메모리에서 새로 buddy로 할당받은 공간으로 옮기고, kmem_cache type의 slab cache를 추가한다
- L4831 : 함수 bootstrap()을 이용해서 boot_kmem_cache_node를 기존 static 변수 메모리에서 새로 buddy로 할당받은 공간으로 옮기고, kmem_cache_node type의 slab cache를 추가한다
- L4834 : 함수 setup_kmalloc_cache_index_table()로 전역 변수 배열 size_index[] 값을 재계산한다. 현재 세팅으로 KMALLOC_MIN_SIZE = 128, KMALLOC_SHIFT_LOW = 7이므로 size_index[]는 8 ~ 128까지는 7이 되고, 그 이상은 8이 되는 것으로 보인다. 이 index는 함수 kmalloc_slab()에서 메모리 할당할 때 사용하는 것으로 보인다.
- L4835 : 함수 create_kmalloc_caches()를 이용해서 kmalloc()을 사용하기 위한 slab cache를 생성한다. 현재 세팅 기준으로는 128/256/512/1k/2k/4k/8k 까지만 존재한다. 또한 타입 별로 normal, dma, cgroup, reclaim이 존재한다. 이런 타입들은 s->flags에 반영된다.
- L4838 : 함수 init_freelist_randomization()로 생성된 각 slab cache에 대해서 freelist randomize한다
- L4840 : CPU hotplug가 down되는 과정에서 CPUHP_SLUB_DEAD 시점이 되면 함수 slub_cpu_dead()가 수행되도록 등록한다
- L4843 : SLUB 설정 상태, 즉 cache line size, slub min/max order, slub min objects, cpu ids, node ids를 로그에 출력하고 리턴한다

 

bootstrap()
- 인자로 static 변수 메모리 kmem_cache를 받아서 새로 buddy에서 할당 받은 메모리로 옮겨준다
- L4768 : 함수 kmem_cache_zalloc()로 kmem_cache slab page를 buddy system에서 새로 할당받아 온다. 지금 kmem_cache를 위한 node partial list에는 kmem_cache slab page가 하나도 달려 있지 않으므로 결국 slab allocator에서는 buddy system에서 메모리를 받아오게 된다. 결론적으로 c->freelist에 여러 kmem_cache object들이 달려 있고 첫 object에 해당한는 kmem_cache object가 s로 리턴되어 사용된다.
- L4771 : memcpy()로 static 변수 kmem_cache로부터 새로 buddy에서 할당 받은 kmem_cache로 자료구조를 카피한다
- L4778 : 함수 __flush_cpu_slab()으로 현재 구동중인 0번 cpu의 pcpu c->freelist, c->page, c->partial list를 모두 node partial list로 보내버린다. 결국 c->freelist에 달려 있던 kmem_cache object들은 사용하지 않으므로 바로 node partial로 보내는 것으로 보인다.
- L4779 : 각 노드에 대해 루프를 돌면서 node partial list에 달려 있는 모든 kmem_cache slab page p의 p->slab_cache를 모두 버디에서 새로 할당 받은 kmem_cache인 s로 바꿔 준다
- L4785 : 같은 작업을 node full list에 대해서도 수행해준다
- L4790 : 전역 변수 slab_caches에 kmem_cache s를 달아준다. 이는 새로운 slab cache type을 추가하는 것이다
- L4791 : s를 반환한다

 

put_cpu_partial()
- drain = 1인 경우
- 기존 cpu partial list에 있었던 pobject 수가 cpu partial 기준 보다 많으면 기존에 있었던 slab page들은 모두 node partial list로 보내고 새로 들어온 page를 cpu partial list에 추가한다. 따라서 c->partial에는 1개의 slab page만 존재하게 된다.

 

__flush_cpu_slab()
- 인자로 들어온 percpu freelist, page를 모두 node partial list로 넘기고 percpu slab은 모두 NULL로 만든다
- c->page, c->freelist는 NULL로 만든다
- c->tid는 next tid로 업데이트해준다
- c->page는 함수 deactivate_slab()로 node partial list로 보내준다
- 함수 unfreeze_partials_cpu()로 c->partial list에 있는 slab page들도 조건에 따라 node partial list로 보내버린다

 

unfreeze_partials_cpu()
- c->partial이 존재하면 함수 __unfreeze_partials()로 partial list의 slab page들도 node partial list로 보낸다

 

__unfreeze_partials()
- 인자로 들어온 slab page를 unfrozen으로 만들어서 node partial list로 보낸다
- page->next == NULL이 될 때까지 계속 unfrozen 및 node partial list로 보낸다
- 만약 slab page에서 쓰이는 object가 없고 nr_partial >= min_partial이면 현 slab page는 discard page가 된다. 루프를 돌면서 또 discard page가 생기면 discard page list에 줄줄이 연결한다
- page 루프를 모두 돌고나서 discard page가 있으면 discard page list를 돌면서 함수 discard_slab()로 discard page들을 모두 메모리 해제한다

 

slab allocator에서 메모리 할당 중에 요청된 노드와 c->page의 소속 노드가 다른 경우엔?
- 노드 미스매치가 발생하면 함수 deactivate_slab()으로 c->page는 page 소속 노드 partial list로 반환한다. 그리고 c->partial list의 page를 가져와서 c->page로 만들어 다시 노드 매치를 시도한다. 여기서 매칭되면 그냥 그 page를 쓰면 되고, 아니면 찾을 때까지 계속 소속 node partial list로 보낸다. 만약 c->partial에 매칭되는 노드 페이지가 없으면 요청된 node partial list에서 slab page들을 c->page와 c->partial로 가져 와서 할당한다
- 여기서 중요한 건 cpu 번호는 노드 번호와 무관하다는 것으로, slab page 노드와 요청된 노드의 매칭이 중요하지 cpu 노드의 매치는 중요하지 않다
- 첫 부팅 초기에는 0번 cpu만 살아있으므로 모든 노드 페이지의 처리를 0번 pcpu에서 처리하게 된다

 

===============================================================

정리


1. 우선 kmem_cache_node에 대해서 create_boot_cache()로 각 노드에 buddy system으로부터 kmem_cache_node slab page를 1개 씩 받고 각 node partial list에 달아 준다. 이 slab page의 첫 번째 object가 kmem_cache_node->node[node]가 된다
- pcpu의 경우, kmem_cache_node에 대한 pcpu slab이 0번 cpu에 대해 생성된다.

 

2. kmem_cache에 대해서 create_boot_cache()를 하는 과정에서 각 노드의 kmem_cache_node->node[node] partial list로 부터 slab page를 c->freelist, c->partial로 가져온다. 그리고 리턴된 kmem_cache_node object를 kmem_cache->node[node]에 달아준다. 이 때, 루프를 돌면서 다음 노드를 처리할 때 노드 미스매치가 발생하므로 c->freelist, c->page, c->partial에 달려 있던 slab page들은 모두 소속 노드의 partial list로 다시 보내지고 요청된 노드의 partial list의 slab page, slab object로 채워진다. 이 과정이 반복되어 결국 마지막에는 마지막 노드의 slab page로 c->freelist, c->partial이 채워지게 된다
- pcpu의 경우, kmem_cache에 대한 pcpu slab이 0번 cpu에 대해 생성된다.

 

3. kmem_cache에 대해서 bootstrap()을 수행하면서, kmem_cache에 대한 첫 slab page가 buddy system에서 만들어진다. 이 slab page의 첫 object가 전역 변수 kmem_cache가 되고, 나머지 object는 c->freelist에 할당되었다가 flush되어 다시 node partial list로 보내진다. 그리고 각 노드의 kmem_cache->node[node] partial list에 달려있는 모든 slab page에 대해서 p->slab_cache를 새로운 kmem_cache로 변경한다. 또한 kmem_cache slab cache type을 전역 변수 slab_caches에 등록한다.

 

4. kmem_cache_node()에 대해서 bootstrap()을 수행하면서, kmem_cache_node slab page를 slab allocator에서 받아온다. 이 때 요청 노드가 NUMA_NO_NODE이므로 노드와 무관하게 c->freelist에서 받아오게 된다. 이 slab page의 첫 object가 전역 변수 kmem_cache_node가 되고, c->freelist, c->page, c->partial은 모두 소속 node partial list로 보내진다. 그리고 각 노드의 kmem_cache_node->node[node] partial list에 달려있는 모든 slab page에 대해서 p->slab_cache를 새로운 kmem_cache_node로 변경한다. 또한 kmem_cache_node slab cache type을 전역 변수 slab_caches에 등록한다.

 

===============================================================

 

create_kmalloc_caches()
- kmalloc() 함수를 사용하기 위한 slab cache들을 생성한다. 현재 세팅 기준으로 크기는 128/256/512/1k/2k/4k/8k까지 이고, 타입은 normal, dma, cgroup, reclaim가 존재한다. 타입에 따라 slab cache의 s->flags에 DMA, ACCOUNT, RECLAIMABLE 등의 flags가 붙는다
- 전역 변수 배열 kmalloc_info[]에 kmalloc 크기와 타입에 대한 정보가 정의되어 있다
- size_index[] 배열은 slab allocator에서 지원하는 크기 중 256B 이하만 지원하므로 현재 세팅으로는 7/8만 존재한다

- L893 : Normal에서 reclaim 타입까지 루프를 돌면서 kmalloc cache를 생성한다
- L894 : 크기를 7부터 13까지 (128/256/512/1k/2k/4k/8k) 루프를 돌면서 kmalloc cache를 생성한다
- L895 : 함수 new_kmalloc_cache()를 이용해서 해당 크기, 타입, flags에 대한 kmalloc cache를 생성한다
- L903 ~ 908 : 크기가 power of two가 아닌 96, 192의 경우 각각 64와 128 바로 다음 순서로 kmalloc cache를 생성해준다. 주석에 따르면 이 생성 순서가 중요하다고 한다. ARM64의 경우에는 min align = 128이므로 해당 사항이 없다
- L913 : kmalloc cache가 생성되었으므로 slab_state를 UP으로 만든다
- L916 : CONFIG_ZONE_DMA = y인 경우 DMA type에 대한 kmalloc cache를 생성한다. 이 때 크기 0은 거르고 96, 192, 8, 16, … 순서로 만들게 된다. flags에 SLAB_CACHE_DMA가 추가되는 것이 다르다.

 

new_kmalloc_cache()
- L857 : 타입이 reclaim이면 flags에 SLAB_RECLAIM_ACCOUNT 추가한다
- L859 : 타입이 cgroup이고 CONFIG_MEMCG_KMEM = y이면 flags에 SLAB_ACCOUNT를 추가한다. 그런데 만약 cgroup_memory_nokmem = false인 경우에는 그냥 cgroup type은 사용하지 않고 normal kmalloc cache를 쓰도록 한다.
- L867 : 함수 create_kmalloc_cache()로 크기와 타입, flags에 맞는 kmalloc cache를 만든다
- L876 : 만약 type = normal이고 CONFIG_MEMCG_KMEM = y이면 kmalloc cache의 recount = -1로 하여 merging이 안되도록 한다

 

create_kmalloc_cache()
- L675 : 함수 kmem_cache_zalloc()으로 kmem_cache object를 받아온다
- L680 : 함수 create_boot_cache()로 크기와 타입에 맞는 kmalloc 슬랩 캐시를 생성한다
- L682 : 생성된 kmalloc cache를 전역 변수 slab_caches에 등록한다
- L683 : s->refcount = 1로 하여 merging이 가능하게 한다
- L684 : 생성된 kmalloc cache를 반환한다

 

=================================================

 

kmalloc cache setup 이후의 메모리 할당은 함수 kmalloc()으로 수행된다

init_freelist_randomization()
- L1810 : slab_mutex를 잡는다
- L1812 : 모든 slab cache에 대해서 함수 init_cache_random_seq()로 freelist random sequence를 생성한다
- L1815 : slab_mutex를 푼다

 

init_cache_random_seq()
- 인자로 slab cache (kmem_cache)를 받는다
- L1785 : 이미 random sequence가 만들어졌으면 그냥 리턴
- L1788 : 함수 cache_random_seq_create()로 현재 slab cache에 대해서 random sequence를 생성한다
- L1796 : random sequence 각각에 대해서 s->size 만큼 곱해서 바로 object에 접근할 수 있게 한다
- L1802 : 0을 리턴한다

 

cache_random_seq_create()
- L1005 : slab page에 object가 들어있는 수가 1개 이거나 random sequence가 이미 있으면 0을 리턴한다
- L1008 : 함수 kcalloc()으로 각 slab cache의 random_seq 메모리를 할당받는다
- L1013 : 함수 prandom_seed_state()로 random state를 구한다
- L1015 : random state를 바탕으로 함수 freelist_randomize()로 random_seq를 만든다
- L1016 : 0을 리턴한다

 

kcalloc()
- 배열에 대해 zeroing을 하면서 메모리를 할당받는다
- 함수 kmalloc_array()로 메모리를 할당하여 리턴한다

 

kmalloc_array()
- size 크기의 object를 n개 만큼 할당받는다
- L627 : n x size = bytes를 계산한다
- L629 : n과 size가 컴파일 타임 상수이면 함수 kmalloc()을 수행한다
- L631 : 아니면 함수 __kmalloc()를 수행한다

 

kmalloc()
- L579 : size가 컴파일 타임 상수이면 fast path를 탄다
- L583 : size가 8KB보다 크면 함수 kmalloc_large()로 버디 시스템으로부터 직접 메모리를 받는다
- L586 : 함수 kmalloc_index()로 kmalloc_index를 계산한다. 함수 kmalloc_index()는 컴파일 타임 상수에 맞게 하드코딩되어 숫자로 변환된다
- L588 : 0번 인덱스는 크기가 0이므로 zero size pointer를 리턴한다
- L591 : 함수 kmem_cache_alloc_trace()로 인덱스와 flags에 맞는 kmalloc cache로부터 메모리를 할당받는다
- L596 : size가 변수이면 함수 __kmalloc()로 slow path를 통해 메모리를 할당받는다

 

kmalloc_large()
- 결국 alloc_pages()를 부르게 된다
- flags에 GFP_COMP를 붙여주고, pcpu stat에 NR_SLAB_UNRECLAIMABLE_B 스탯을 바이트 단위로 올려준다
- 할당된 메모리를 리턴한다

 

kmem_cache_alloc_trace()
- 함수 slab_alloc()를 부르고, 이는 다시 slab_alloc_node()를 호출해서 메모리를 할당받는다
- 이때 node는 NUMA_NO_NODE 설정으로 다른 노드에서도 받아올 수 있다

 

__kmalloc()
- L4388 : 크기가 8KB보다 크면 함수 kmalloc_large()를 부른다
- L4391 : 크기에 따라 알맞은 kmalloc cache를 함수 kmalloc_slab()을 통해 가져온다
- L4396 : 함수 slab_alloc()을 통해 kmalloc cache로부터 메모리를 받는다
- L4402 : 받은 메모리를 리턴한다

 

kmalloc_slab()
- 크키가 192 이하면 size_index[]로부터 인덱스를 계산하고 그 이상이면 fls()로 계산한다
- 계산한 index에 맞는 kmalloc_cache를 반환한다

 

freelist_randomize()
- Fisher-Yates shuffle로 random seq 순서를 섞어준다

XE Login