Slub Memory Allocator -4- (Slub 할당)

페이지 할당자로부터 slub 페이지를 구성하기 위해 필요한 페이지 수 만큼 Page Frame을 할당 받아 온 후 이를 slub 페이지로 초기화하고 각 object의 초기화도 수행한다.

 

다음 그림은 함수 흐름으로 new_slab() 함수가 페이지 할당자로 새로운 페이지를 할당 요청하는 과정을 나타낸다.

new_slab-1

 

new_slab()

mm/slub.c

static struct page *new_slab(struct kmem_cache *s, gfp_t flags, int node)
{
        struct page *page;
        void *start;
        void *p;
        int order;
        int idx;

        if (unlikely(flags & GFP_SLAB_BUG_MASK)) {
                pr_emerg("gfp: %u\n", flags & GFP_SLAB_BUG_MASK);
                BUG();
        }

        page = allocate_slab(s,
                flags & (GFP_RECLAIM_MASK | GFP_CONSTRAINT_MASK), node);
        if (!page)
                goto out;

        order = compound_order(page);
        inc_slabs_node(s, page_to_nid(page), page->objects);
        page->slab_cache = s;
        __SetPageSlab(page);
        if (page->pfmemalloc)
                SetPageSlabPfmemalloc(page);

        start = page_address(page);

        if (unlikely(s->flags & SLAB_POISON))
                memset(start, POISON_INUSE, PAGE_SIZE << order);

        kasan_poison_slab(page);

        for_each_object_idx(p, idx, s, start, page->objects) {
                setup_object(s, page, p);
                if (likely(idx < page->objects))
                        set_freepointer(s, p, p + s->size);
                else
                        set_freepointer(s, p, NULL);
        }

        page->freelist = start;
        page->inuse = page->objects;
        page->frozen = 1;
out:
        return page;
}

slub 페이지 할당을 받고 그 페이지에 포함된 free object의 초기화를 수행한다.

  • 모든 free object들을 순서대로 연결하고 마지막 object의 연결은 null로 한다.
    • slub에서 free object들끼리 단방향으로 연결된다.
    • slub을 처음 할당 받게 되면 그에 속한 모든 obect는 free 상태이므로 전체 object를 순서대로 연결한다.

 

  • if (unlikely(flags & GFP_SLAB_BUG_MASK)) { pr_emerg(“gfp: %u\n”, flags & GFP_SLAB_BUG_MASK); BUG(); }
    • flags에 GFP_DMA 또는 GFP_HIGHMEM이 설정되거나 사용하지 않는 플래그 비트가 설정된 경우 에러를 출력 후  BUG() 호출
    • GFP_SLAB_BUG_MASK=0xffe0_0003 (0b1111_1111_1110_0000_0000_0000_0000_0011)
      • GFP 플래그는 현재 bit0~bit20번까지 사용되고 있다.
      • 1=GFP_DMA
      • 2=GFP_HIGHMEM
  • page = allocate_slab(s, flags & (GFP_RECLAIM_MASK | GFP_CONSTRAINT_MASK), node);
    • flag에 GFP_RECLAIM_MASK 및 GFP_CONSTRAINT_MASK를 추가하여 지정된 노드에서 slab을 할당한다.
  • if (!page) goto out;
    • slab 할당이 실패한 경우 null을 반환한다.
  • order = compound_order(page);
    • compound 페이지이면 compound_order를 알아오고 compound 페이지가 아닌 경우 0 order를 반환한다
      • 두 번째 페이지 구조체에서 compound_order를 알아온다.
  • page->slab_cache = s;
    • 페이지의 멤버 변수 slab_cache에 현재 slab 정보를 지정한다.
  • __SetPageSlab(page);
    • slab 플래그를 설정한다.
  • if (page->pfmemalloc) SetPageSlabPfmemalloc(page);
    • 페이지가 pfmemalloc가 설정된 경우 active 플래그를 설정한다.
  • start = page_address(page);
    • 페이지의 가상 주소
  • if (unlikely(s->flags & SLAB_POISON)) memset(start, POISON_INUSE, PAGE_SIZE << order);
    • 드문 확률로 flags에 SLAB_POISON 디버깅을 요청한 경우 할당 받은 페이지 메모리 전체를 POISON_INUSE(0x5a)로 설정하여 초기화 되지 않은 상태를 나타내게 한다.
  • kasan_poison_slab(page);
    • 디버깅을 위해 shadow 페이지에 KASAN_KMALLOC_REDZONE(0xfc)를 설정하여 slub 객체의 redzone임을 나타내게 한다.

지정된 slab의 모든 object를 연결한다. 즉 각 object에 대해 다음 순서의 object 주소를 기록하되 마지막 object에서는 null을 기록한다.

  • for_each_object_idx(p, idx, s, start, page->objects) {
    • 지정된 slab의 모든 객체에 대해 루프를 돌며 object p를 알아온다.
  • setup_object(s, page, p);
    • 디버깅을 위해 object를 설정한다.
  • if (likely(idx < page->objects)) set_freepointer(s, p, p + s->size);
    • 높은 확률로 인덱스(based 1) 번호가 객체 수보다 작은 경우 즉, 마지막 인덱스 번호를 제외한 모든 object에 대해 다음 object의 주소를 기록한다.
  • else set_freepointer(s, p, NULL);
    • 마지막 object에는 null을 기록한다.
  • page->freelist = start;
    • slab 페이지의 멤버 변수 freelist에 첫 object의 주소를 대입하여 연결한다.
  • page->inuse = page->objects;
    • slab에서 사용할 수 있는 object 전체 수를 대입한다.
  • page->frozen = 1;
    • 페이지에 frozen을 설정한다.

 

Slub with PFMEMALLOC

  • PF_MEMALLOC 또는 TIF_MEMDIE 플래그를 사용하는 경우 free 페이지가 부족하여 low 워터마크 기준 아래에 도달한 경우에도 ALLOC_NO_WATERMARKS 플래그를 사용하여 페이지를 확보할 수 있다.
    • 이렇게 확보한 페이지에는 page->pfmemalloc이 설정되어 있다.
      • get_page_from_freelist() -> 버디 할당 후 -> prep_new_page() 함수내에서 다음과 같이 설정된다.
        • page->pfmemalloc = !!(alloc_flags & ALLOC_NO_WATERMARKS);
    • slub 에서 PF_MEMALLOC 사용처
      • Swap over the NBD(Network Block Device)
        • NBD(Network Block Device) 드라이버로 TCP 통신을 통해 swap 할 수 있다.
          • SOCK_MEMALLOC 플래그가 PF_MEMALLOC을 사용하게 한다.
        • 디스크기반의 swap을 사용하지 않고 네트워크 기반의 swap 을 사용하는 경우에 메모리 부족시 skb 관련한 작은 버퍼들을 많이 할당받아 사용하는데 이의 할당이 원할하게 하도록 이러한 pfmemalloc 설정된 페이지를 사용할 수 있게 한다.
        • slub 페이지의 Active 플래그를 설정하여 이러한 pfmemalloc 상황으로 할당된 slub 페이지를 구별한다.
          • 이렇게 active 설정된 slub 페이지는 일반적인 경우에는 사용할 수 없다.

 

compound_order()

include/linux/mm.h

static inline int compound_order(struct page *page)
{
        if (!PageHead(page))
                return 0;
        return page[1].compound_order;
}

compound 페이지이면 compound_order를 알아오고 compound 페이지가 아닌 경우 0 order를 반환한다

  • 첫 번째 페이지의 Head 플래그가 설정된 경우 compound 페이지를 의미한다.
  • 두 번째 페이지 구조체에서 compound_order를 알아온다.

 

다음 그림은 compound 페이지로부터 order 값을 알아오는 과정을 보여준다.

compound_order-1a

 

for_each_object_idx()

mm/slub.c

#define for_each_object_idx(__p, __idx, __s, __addr, __objects) \
        for (__p = (__addr), __idx = 1; __idx <= __objects;\
                        __p += (__s)->size, __idx++)

인덱스 번호 1번 부터 __objects 수 까지 object __p의 주소를 반환한다.

 

set_freepointer()

mm/slub.c

static inline void set_freepointer(struct kmem_cache *s, void *object, void *fp)
{
        *(void **)(object + s->offset) = fp;
}

slub object 주소에 캐시의 offset를 더한 위치에 다음 object 연결점인 fp를 기록한다.

  • FP(Free Pointer)는 항상 object에서 offset 만큼을 건너 뛴다.
  • 보통 slub object에서 FP는 object의 첫 시작 주소로 구성되어 offset=0으로 구성된다. 그러나 object POISON 등의 메타 정보가 담긴 디버그 정보를 사용하려 하는 경우 FP(Free Pointer)를 워드 단위로 정렬한 object size 만큼 뒤로 이동시킨다.

 

다음 그림은 최대 5개의 object를 가진 slub page에서 3개의 free object가 구성되어 서로 연결된 상태이며, slub 캐시가 만들어질 때 메타 정보 없는 slub 캐시로 만든 경우와 메타 정보 있는 slub 캐시로 만든 경우의 FP(Free Pointer) 위치와 offset 값을 보여준다.

  • SLAB_POISON, SLAB_DESTROY_BY_RCU, SLAB_STORE_USER 등 메타데이터가 필요한  속성은 2) 번 그림의 FP 뒤에 메타데이터가 더 추가된다

 

allocate_slab()

mm/slub.c

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;

        flags &= gfp_allowed_mask;

        if (flags & __GFP_WAIT)
                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;

        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 (page)
                        stat(s, ORDER_FALLBACK);
        }

        if (kmemcheck_enabled && page
                && !(s->flags & (SLAB_NOTRACK | DEBUG_DEFAULT_FLAGS))) {
                int pages = 1 << oo_order(oo);

                kmemcheck_alloc_shadow(page, oo_order(oo), alloc_gfp, node);

                /*
                 * Objects from caches that have a constructor don't get
                 * cleared when they're allocated, so we need to do it here.
                 */
                if (s->ctor)
                        kmemcheck_mark_uninitialized_pages(page, pages);
                else
                        kmemcheck_mark_unallocated_pages(page, pages);
        }

        if (flags & __GFP_WAIT)
                local_irq_disable();
        if (!page)
                return NULL;

        page->objects = oo_objects(oo);
        mod_zone_page_state(page_zone(page),
                (s->flags & SLAB_RECLAIM_ACCOUNT) ?
                NR_SLAB_RECLAIMABLE : NR_SLAB_UNRECLAIMABLE,
                1 << oo_order(oo));

        return page;
}

slab 페이지를 할당받는다. 적절한 order로 할당 시도 시 실패하면 min_order로 다시 시도한다. 실패한 경우 null을 반환하고 성공하면 할당 받은 page 를 반환한다.

1st slab(slub) 페이지 할당 시도 – 권장 order 사용

  • struct kmem_cache_order_objects oo = s->oo;
    • 적정 권장 order 값과 object 수가 담긴 oo 값
  • flags &= gfp_allowed_mask;
    • slab을 할당 받을 때 gfp_allowed_mask를 사용하여 허용되지 않는 플래그 비트를 제거한다.
      • 처음 부트업 프로세스를 진행 중에는 __GFP_WAIT, __GFP_IO, __GFP_FS 플래그 요청을 허용하지 않게한다.
      • hibernation 또는 suspend 기능이 동작중인 경우 __GFP_IO, __GFP_FS 플래그 요청을 허용하지 않게한다.
  • if (flags & __GFP_WAIT) local_irq_enable();
    • __GFP_WAIT 플래그 요청이 있는 경우 인터럽트를 활성화 시켜 preemption을 허용한다.
  • flags |= s->allocflags
    • flags에 slab에 설정된 할당속성 플래그 비트를 추가한다.
  • alloc_gfp = (flags | __GFP_NOWARN | __GFP_NORETRY) & ~__GFP_NOFAIL;
    • slab을 할당하기 위해 __GFP_NOWARN 및 __GFP_NORETRY를 추가하고 __GFP_NOFAIL을 제거한다.
      • 첫 번째 할당 시도 때는 warning 및 retry 되면 안되고 fail은 가능하게 한다.
  • page = alloc_slab_page(s, alloc_gfp, node, oo);
    • slab 페이지를 할당받는다.

2nd slab(slub) 페이지 할당 시도 – 최소 order 사용

  • if (unlikely(!page)) {
    • 적은 확률로 slab 페이지를 할당 받지 못한 경우
  • oo = s->min;
    • 최소 order 값과 object 수가 담긴 min 값을 사용
  • alloc_gfp = flags
    • 두 번째 시도 때에는 원래 flags 즉, fail 되지 않고 성공할 떄까지 시도하게 한다.
  • page = alloc_slab_page(s, alloc_gfp, node, oo);
    • min 옵션으로 다시 할당을 시도한다.
  • if (page) stat(s, ORDER_FALLBACK);
    • slab 페이지가 할당된 경우 ORDER_FALLBACK stat을 증가시킨다.

shadow 페이지를 할당 받아 사용하는 slab 디버그 기능을 사용하는 경우 해당 shadow 페이지를 설정한다.

  • if (kmemcheck_enabled && page && !(s->flags & (SLAB_NOTRACK | DEBUG_DEFAULT_FLAGS))) {
    • CONFIG_KMEMCHECK 커널 옵션이 사용된 경우 디버깅 기능을 사용하는 경우
      • 이 옵션은 현재 x86 아키텍처에만 적용되어 사용된다.
    • 참고: Slub Memory Allocator (slub 디버깅) | 문c
  • int pages = 1 << oo_order(oo);
    • 할당 받은 slab 페이지 수
  • kmemcheck_alloc_shadow(page, oo_order(oo), alloc_gfp, node);
    • shadow 페이지를 할당 받는다.
  • if (s->ctor) kmemcheck_mark_uninitialized_pages(page, pages);
    • slab에 생성자가 설정된 경우 shadow 페이지 영역 전체에 KMEMCHECK_SHADOW_UNALLOCATED(1) 값을 설정한다.
  • else kmemcheck_mark_unallocated_pages(page, pages);
    • slab에 생성자가 설정되지 않은 경우 shadow 페이지 영역 전체에 KMEMCHECK_SHADOW_UNALLOCATED(0) 값을 설정한다.
  • if (flags & __GFP_WAIT) local_irq_disable();
    • __GFP_WAIT을 허용한 경우 인터럽트를 enable 하였던 것을 다시 disable 한다.
  • if (!page) return NULL;
    • 2 번의 시도에도 slab 페이지가 할당되지 않은 경우 null을 반환한다.
  • page->objects = oo_objects(oo);
    • slab 페이지에 전체 object 수를 설정한다.
  • mod_zone_page_state(page_zone(page), (s->flags & SLAB_RECLAIM_ACCOUNT) ? NR_SLAB_RECLAIMABLE : NR_SLAB_UNRECLAIMABLE, 1 << oo_order(oo));
    • SLAB_RECLAIM_ACCOUNT flags 요청 여부에 따라 다음과 같이 동작한다.
      • 요청된 경우 NR_SLAB_RECLAIMABLE stat을 order 페이지 크기만큼 증가시킨다.
      • 요청되지 않은 경우 NR_SLAB_UNRECLAIMABLE stat을 order 페이지 크기만큼 증가시킨다.

mm/page_alloc.c

gfp_t gfp_allowed_mask __read_mostly = GFP_BOOT_MASK;

 

include/linux/gfp.h

/* Control slab gfp mask during early boot */
#define GFP_BOOT_MASK (__GFP_BITS_MASK & ~(__GFP_WAIT|__GFP_IO|__GFP_FS))
  •  GFP_BOOT_MASK
    • __GFP_WAIT, GFP_IO, GFP_FS 플래그 요청은 커널 부트 프로세스 중에서 사용하면 안되므로 GFP 표준 마스크에서 제거한다.

 

stat()

mm/slub.c

static inline void stat(const struct kmem_cache *s, enum stat_item si)
{
#ifdef CONFIG_SLUB_STATS
        /*
         * The rmw is racy on a preemptible kernel but this is acceptable, so
         * avoid this_cpu_add()'s irq-disable overhead.
         */
        raw_cpu_inc(s->cpu_slab->stat[si]);
#endif
}

slab의 per-cpu 멤버 변수인 cpu_slab에 있는 stat[si]을 증가시킨다.

 

alloc_slab_page()

mm/slub.c

static inline struct page *alloc_slab_page(struct kmem_cache *s,
                gfp_t flags, int node, struct kmem_cache_order_objects oo)
{
        struct page *page;
        int order = oo_order(oo);

        flags |= __GFP_NOTRACK;

        if (memcg_charge_slab(s, flags, order))
                return NULL;

        if (node == NUMA_NO_NODE)
                page = alloc_pages(flags, order);
        else
                page = alloc_pages_exact_node(node, flags, order);

        if (!page)
                memcg_uncharge_slab(s, order);

        return page;
}

memory control group의 통제하에 slab에 사용할 페이지를 할당 받아 온다.

  • int order = oo_order(oo);
    • order 값만 분리해온다.
  • flags |= __GFP_NOTRACK;
    • 초기화 되지 않은 메모리의 사용에 대해 추적을 하지 않게 한다.
  • if (memcg_charge_slab(s, flags, order)) return NULL;
    • memory control group에 order 페이지 할당 요청하며 통제를 통해 slab 할당이 취소될 수 있다.
  • if (node == NUMA_NO_NODE) page = alloc_pages(flags, order);
    • 노드를 지정하지 않은 slab 할당 요청을 처리한다.
  • else page = alloc_pages_exact_node(node, flags, order);
    • 특정 노드를 지정한 경우에 대한 slab 할당 요청 처리를 한다.
  • if (!page) memcg_uncharge_slab(s, order);
    • memory control group에 order 페이지 할당을 취소한다고 통지한다.

 

inc_slabs_node()

mm/slub.c

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

CONFIG_SLUB_DEBUG 커널 옵션을 사용하는 경우에만 높은 확률로 per 노드에 대한 nr_slabs를 증가시키고 total_objects에 objects 만큼을 더한다.

 

SetPageSlabPfmemalloc()

include/linux/page-flags.h

static inline void SetPageSlabPfmemalloc(struct page *page)
{       
        VM_BUG_ON_PAGE(!PageSlab(page), page);
        SetPageActive(page);
}

페이지에서 PG_Active 플래그를 설정한다.

 

참고

답글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다.