Slub Memory Allocator -5- (Slub 할당)

<kernel v5.0>

슬랩 페이지 할당

페이지 할당자로부터 슬랩 페이지를 구성하기 위해 필요한 페이지 수 만큼 Page Frame을 할당 받아 온 후 이를 슬랩 페이지로 초기화한다.

 

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

new_slab-1

 

새 슬랩 페이지 할당

new_slab()

mm/slub.c

static struct page *new_slab(struct kmem_cache *s, gfp_t flags, int node)
{
        if (unlikely(flags & GFP_SLAB_BUG_MASK)) {
                gfp_t invalid_mask = flags & GFP_SLAB_BUG_MASK;
                flags &= ~GFP_SLAB_BUG_MASK;
                pr_warn("Unexpected gfp: %#x (%pGg). Fixing up to gfp: %#x (%pGg). Fix your code!\n""
,
                                invalid_mask, &invalid_mask, flags, &flags);
                dump_stack();
        }

        return allocate_slab(s,
                flags & (GFP_RECLAIM_MASK | GFP_CONSTRAINT_MASK), node);
}

버디 시스템으로 부터 새 슬랩 페이지를 할당 받아온다.

  • 코드 라인 3~10에서 슬랩 페이지 할당자를 통해 dma32 및 highmem 요청을 한 경우 에러를 출력 후  BUG() 호출한다.
  • 코드 라인 12~13에서 새 슬랩 페이지를 할당 받아오며, 허용되는 gfp 플래그들은 다음을 참고한다.

 

GFP_SLAB_BUG_MASK

mm/internal.h

/* Do not use these with a slab allocator */
#define GFP_SLAB_BUG_MASK (__GFP_DMA32|__GFP_HIGHMEM|~__GFP_BITS_MASK)

슬랩 할당자에 요청할 수 없는 gfp 플래그들이다.

 

GFP_RECLAIM_MASK

mm/internal.h

/*
 * The set of flags that only affect watermark checking and reclaim
 * behaviour. This is used by the MM to obey the caller constraints
 * about IO, FS and watermark checking while ignoring placement
 * hints such as HIGHMEM usage.
 */
#define GFP_RECLAIM_MASK (__GFP_RECLAIM|__GFP_HIGH|__GFP_IO|__GFP_FS|\
                        __GFP_NOWARN|__GFP_RETRY_MAYFAIL|__GFP_NOFAIL|\
                        __GFP_NORETRY|__GFP_MEMALLOC|__GFP_NOMEMALLOC|\
                        __GFP_ATOMIC)

슬랩 할당자에 요청할 수 있는 reclaim 관련 gfp 플래그들이다.

 

GFP_CONSTRAINT_MASK

mm/internal.h

/* Control allocation cpuset and node placement constraints */
#define GFP_CONSTRAINT_MASK (__GFP_HARDWALL|__GFP_THISNODE)

슬랩 할당자에 요청할 수 있는 cpuset 및 node 관련 gfp 플래그들이다.

 

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;
        void *start, *p, *next;
        int idx, order;
        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);

        order = compound_order(page);
        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, start, order);

        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;

        mod_lruvec_page_state(page,
                (s->flags & SLAB_RECLAIM_ACCOUNT) ?
                NR_SLAB_RECLAIMABLE : NR_SLAB_UNRECLAIMABLE,
                1 << oo_order(oo));

        inc_slabs_node(s, page_to_nid(page), page->objects);

        return page;
}

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

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

 

  • 코드 라인 10~15에서 부트 타임이 아닌 경우 direct-reclaim이 허용된 경우 irq를 enable 한다.
    • 부트 타임에는 io, fs, reclaim을 허용하지 않는다.
  • 코드 라인 21~23에서 alloc_gfp 플래그에 nowarn, noretry를 추가하고 nofail 플래그를 제거한다. 만일 direct-reclaim이 허용된 경우 nomemalloc을 추가하고, reclaim 및 nofail을 제거한다.
  • 코드 라인 25~37에서 s->oo로 슬랩 페이지를 할당해온다. 만일 실패하는 경우 s->min으로 슬랩 페이지를 할당해온다.
  • 코드 라인 39~69에서 슬랩 페이지의 page 디스크립터를 다음과 같이 초기화하고, free object들의 FP를 모두 연결한다. 보안 옵션 중 CONFIG_SLAB_FREELIST_RANDOM 커널 옵션을 사용하면 free object 들에 대해 랜덤하게 순서를 바꿀 수 있다. 또한 CONFIG_SLAB_FREELIST_HARDENED 커널 옵션을사용하면FP들의내용이 encaptualization되어 알아볼 수 없게 숨길 수 있다.
    • fixup_red_left() 함수에서는 red-zone을 사용한 경우 red_left_pad 만큼 object 주소를 이동시켜 반환한다.
    • page->objects에 결정된 order에 해당하는 object 수를 저장한다.
    • page->slab_cache에 슬랩 캐시를 지정한다.
    • PG_slab 플래그를 추가한다.
    • pfmemalloc 페이지를 사용한 경우 PG_active 플래그를 추가한다.
    • page->inuse에 object 수를 저장한다.
    • page->frozen에 1을 대입한다. (per-cpu 캐시의 partial 리스트로 들어갈 예정이다)
    • poison 디버깅을 사용하는 경우 슬랩 페이지 전체에 POISON_INUSE(0x55)를 기록하여 처음 슬랩 object가 사용되지 않은 상태를 나타낸다.
  • 코드 라인 71~75에서 out: 레이블이다. 함수 처음에 irq를 enable한 경우 disable한다.
  • 코드 라인 77~80에서 reclaimable 슬랩 페이지를 할당한 경우 NR_SLAB_RECLAIMABLE 카운터를 증가시키고, 그렇지 않은 경우 NR_SLAB_UNRECLAIMABLE 카운터를 증가시킨다.
  • 코드 라인 82에서 s->node[]->nr_slabs를 1 증가시키고, s->node[]->total_objects 카운터에 할당한 objects 수를 추가한다.
  • 코드 라인 84에서 할당한 슬랩 페이지를 반환한다.

 

다음 그림은 order 1 슬랩 페이지를 할당 받은 후 초기화된 모습을 보여준다.

 

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 페이지는 일반적인 경우에는 사용할 수 없다.

 

set_freepointer()

mm/slub.c

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

#ifdef CONFIG_SLAB_FREELIST_HARDENED
        BUG_ON(object == fp); /* naive detection of double free or corruption */
#endif

        *(void **)freeptr_addr = freelist_ptr(s, fp, freeptr_addr);
}

slub object의 FP 위치에 다음 object의 주소를 기록한다. 만일 CONFIG_SLAB_FREELIST_HARDENED 커널 옵션을 사용한 경우 FP 내용은 알아볼 수 없게 encaptualization된다.

  • 코드 라인 3에서 FP(Free Pointer)는 항상 object에서 s->offset 만큼을 건너 뛴다.
  • 코드 라인 9에서 FP 위치에 다음 free object 주소를 기록한다.

 

다음 그림은 최대 5개의 object를 가진 slub page에서 3개의 free object가 구성되어 서로 연결된 상태이며, FP가 object 내부에 위치한 일반적인 경우와, FP를 object 다음에 위치한 경우의 FP(Free Pointer) 위치와 offset 값을 보여준다.

  • SLAB_POISON, SLAB_DESTROY_BY_RCU, 별도의 생성자를 사용한 슬랩 캐시는 2) 번과 같이 FP가 object 뒤로 이동된다.

 

gfp_allowed_mask

mm/page_alloc.c

gfp_t gfp_allowed_mask __read_mostly = GFP_BOOT_MASK;

부트 타임에 사용되어선 안될 플래그를 제외한 모든 비트를 가지고 있다.

 

GFP_BOOT_MASK

mm/internal.h

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

 

alloc_slab_page()

mm/slub.c

/*
 * Slab allocation and freeing
 */
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;
        unsigned int order = oo_order(oo);

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

        if (page && memcg_charge_slab(page, flags, order, s)) {
                __free_pages(page, order);
                page = NULL;
        }

        return page;
}

memory control group의 통제하에 slab에 사용할 페이지를 할당 받아 온다. 실패하는 경우 null을 반환한다.

  • 코드 라인 7~10에서 노드를 지정하지 않은 경우 적절한 노드에서 order 페이지 할당을 처리하고, 노드가 지정된 경우 해당 노드에서 order 페이지를 할당한다.
  • 코드 라인 12~15에서 memory control group에 order 페이지 할당 요청하며 통제를 통해 slab 할당이 취소될 수 있다.
  • 코드 라인 17에서 할당한 페이지를 반환한다.

 

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 플래그를 설정한다.

 

참고

댓글 남기기