Art of Pr0gr4m

[Linux Kernel 5] Slab & Slub Allocator #2 본문

IT/Linux Kernel

[Linux Kernel 5] Slab & Slub Allocator #2

pr0gr4m 2020. 5. 17. 08:41

1. Slub Page 할당

 

저번 포스트의 Slow-path Slub Object 할당에서 new_slab_objects에서 buddy system으로부터 새로운 free object를 할당받는다고 하였다.

new_slab_objects의 내부에서는 new_slab -> allocate_slab 함수를 호출하여 새로운 slab page를 할당받는다.

static struct page *allocate_slab(struct kmem_cache *s, gfp_t flags, int node)
{
	struct page *page;
	struct kmem_cache_order_objects oo = s->oo;
	gfp_t alloc_gfp;
	void *start, *p, *next;
	int idx;
	bool shuffle;
 
	flags &= gfp_allowed_mask;
 
	if (gfpflags_allow_blocking(flags))
		local_irq_enable();
 
	flags |= s->allocflags;
 
	/*
	 * Let the initial higher-order allocation fail under memory pressure
	 * so we fall-back to the minimum order allocation.
	 */
	alloc_gfp = (flags | __GFP_NOWARN | __GFP_NORETRY) & ~__GFP_NOFAIL;
	if ((alloc_gfp & __GFP_DIRECT_RECLAIM) && oo_order(oo) > oo_order(s->min))
		alloc_gfp = (alloc_gfp | __GFP_NOMEMALLOC) & ~(__GFP_RECLAIM|__GFP_NOFAIL);
 
	page = alloc_slab_page(s, alloc_gfp, node, oo);
	if (unlikely(!page)) {
		oo = s->min;
		alloc_gfp = flags;
		/*
		 * Allocation may have failed due to fragmentation.
		 * Try a lower order alloc if possible
		 */
		page = alloc_slab_page(s, alloc_gfp, node, oo);
		if (unlikely(!page))
			goto out;
		stat(s, ORDER_FALLBACK);
	}
 
	page->objects = oo_objects(oo);
 
	page->slab_cache = s;
	__SetPageSlab(page);
	if (page_is_pfmemalloc(page))
		SetPageSlabPfmemalloc(page);
 
	kasan_poison_slab(page);
 
	start = page_address(page);
 
	setup_page_debug(s, page, start);
 
	shuffle = shuffle_freelist(s, page);
 
	if (!shuffle) {
		start = fixup_red_left(s, start);
		start = setup_object(s, page, start);
		page->freelist = start;
		for (idx = 0, p = start; idx < page->objects - 1; idx++) {
			next = p + s->size;
			next = setup_object(s, page, next);
			set_freepointer(s, p, next);
			p = next;
		}
		set_freepointer(s, p, NULL);
	}
 
	page->inuse = page->objects;
	page->frozen = 1;
 
out:
	if (gfpflags_allow_blocking(flags))
		local_irq_disable();
	if (!page)
		return NULL;
 
	inc_slabs_node(s, page_to_nid(page), page->objects);
 
	return page;
}

1) IRQ나 GFP 플래그 등을 설정한다.

2) alloc_slab_page를 호출해 slab으로 사용할 페이지를 할당한다.

2-1) alloc_slab page는 내부에서 인자로 전달받은 oo로 order를 정한다

2-2) NUMA 노드가 설정되어 있지 않다면 alloc_pages로 페이지를 할당한다.

2-3) NUMA 노드가 설정되어 있다면 __alloc_pages_node로 페이지를 할당한다.

3) 실패 시 fragmentation을 염두하여 좀 더 작은 order로 할당을 재시도한다.

4) 할당 받은 page에 slab 관련 정보들을 초기화한다.

4-1) objects에 object 수를 저장한다.

4-2) slab_cache에 캐시를 저장한다.

4-3) PG_slab 플래그를 추가한다.

4-4) pfmemalloc을 사용한 경우 PG_active 플래그를 추가한다.

4-5) object 침범 검출 정보와 디버깅 정보들을 추가한다.

4-6) object들의 free pointer를 연결한다.

4-7) inuse에 object 수를 저장한다.

4-8) frozen에 1을 대입한다.

5) out 레이블에서 후처리를 하고 할당받은 페이지를 반환한다.

pfmemalloc의 경우 free 페이지가 부족한 비상 시에 워터마크 한계 기준에 도달한 경우에도 비상 할당을 할 수 있다.

 

 

 

2. Slub Page 해제

 

마찬가지로 저번 포스트의 slub object 해제의 out 레이블에서 discard_slab을 호출하여 buddy system상에서 slub page를 해제한다고 하였다.

discard_slab은 free_slab -> __free_slab을 호출하여 slub page를 해제한다.

static void __free_slab(struct kmem_cache *s, struct page *page)
{
	int order = compound_order(page);
	int pages = 1 << order;
 
	if (s->flags & SLAB_CONSISTENCY_CHECKS) {
		void *p;
 
		slab_pad_check(s, page);
		for_each_object(p, s, page_address(page),
						page->objects)
			check_object(s, page, p, SLUB_RED_INACTIVE);
	}
 
	__ClearPageSlabPfmemalloc(page);
	__ClearPageSlab(page);
 
	page->mapping = NULL;
	if (current->reclaim_state)
		current->reclaim_state->reclaimed_slab += pages;
	uncharge_slab_page(page, order, s);
	__free_pages(page, order);
}
 
void free_pages(unsigned long addr, unsigned int order)
{
	if (addr != 0) {
		VM_BUG_ON(!virt_addr_valid((void *)addr));
		__free_pages(virt_to_page((void *)addr), order);
	}
}

1) order 값과 page 크기를 계산한다.

2) SLAB_CONSISTENCY_CHECKS 플래그 사용 시 slab padding과 red-zone을 확인하여 침범 여부를 검출한다.

3) PG 플래그를 클리어한다.

4) 페이지 매핑을 해제한다.

5) reclaim slab에 페이지 크기(수)를 더한다.

6) slab 페이지 회수를 memcg에 알려 root cache일 경우의 처리 혹은 count 감소를 한다.

7) __free_pages를 호출해 buddy system 상에서 page를 해제한다.

 

 

 

3. Slab 예제 소스

 

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
 
static struct kmem_cache *my_cachep;
 
struct my {
	void *data;
};
 
static struct my *p;
 
static int __init cache_init(void)
{
	my_cachep = kmem_cache_create(
			"my_cache",				// cache name
			sizeof(struct my),		// slab object size
			0,						// no align
			SLAB_HWCACHE_ALIGN,		// hardware align
			NULL					// no constructor
			);
 
	if (my_cachep != NULL) {
		printk(KERN_INFO "kmem_cache_create success\n");
		p = (struct my *)kmem_cache_alloc(my_cachep, GFP_KERNEL);
	}
	return 0;
}
 
static void __exit cache_exit(void)
{
	kmem_cache_free(my_cachep, p);
	kmem_cache_destroy(my_cachep);
}
 
module_init(cache_init);
module_exit(cache_exit);
MODULE_LICENSE("GPL");

init 함수에서는 slab cache 생성 함수 kmem_cache_create로 my_cache를 생성한다.

exit 함수에서는 kmem_cache_free 함수와 kmem_cache_destroy로 캐시를 해제 및 제거한다.

 

 

 

4. 실행 결과

 

/proc/slabinfo 출력 결과

정상적으로 새로운 slab cache인 my_cache가 등록되어 있는 것을 볼 수 있다.