Slub Memory Allocator -5- (Object 할당)

 

 

 

Slub Object 할당(Fastpath, Slowpath)

Fastpath 동작으로 per cpu 캐시의 freelist에 있는 첫 object를 atomic하게 꺼내온다. freelist가 비어 있어 꺼내올 object가 없는 경우 slowpath 동작을 아래와 같이 진행시킨 후 다시 처음으로 되돌아가 Fastpath 동작을 재시도한다.

  • slowpath 1단계: per cpu 캐시의 page에 있는 free object들을 freelist로 이주 시키고 free object가 있는 경우 page는 frozen 시킨다. 만일 free object가 없는 경우 다음 단계를 진행한다.
  • slowpath 2단계: per cpu 캐시의 partial 리스트에 있는 처음 slub을 per cpu 캐시의 page로 이주시킨다. 만일 per cpu 캐시의 partial 리스트가 비어 있는 경우 다음 단계를 진행한다.
  • slowpath 3단계: per 노드의 partial 리스트에 있는 처음 slub을 per cpu 캐시의 page에 이주시키고, 두 번째 slub 부터 반복하여 per cpu 캐시의 partial 리스트에 추가한다. 단 이주 시키고 있는 slub에 있는 free object의 갯수가 kmem_cache.cpu_partial의 절반을 초과하는 경우에 그 slub까지만 이주시키고 더 이상 반복하지 않는다. 만일 per 노드의 partial 리스트도 비어 있는 경우에는 다른 리모트 per 노드를 찾아 재시도한다. 만일 다른 리모트 per 노드들 마저도 사용할 수 없는(일정 퍼센트 이상의 원격 노드 메모리 사용을 제한) 상태이면 다음 단계를 진행한다.
  • slowpath 4단계: 버디 시스템으로 부터 페이지를 할당받아 per 노드의 partial 리스트에 추가한다.

 

 

Frozened Slub Page

slub 할당자는 lock 없이 slub object의 빠른 할당을 위해 slub 페이지를 per-cpu에서 관리한다.

  • cmpxchg double atomic operation을 사용하여 리스트의 추가 삭제를 한다.

page->frozen을 1로 하는 경우 slub 페이지의 freelist를 잠궈 현재 태스크가 직접 관리하도록 한다.

실질적으로 freelist의 관리가 둘로 나뉘어 다음과 같이 동작한다.

  • cpu_slab->freelist
    • 현재 cpu가 freelist를 관리하므로 slub object의 할당과 할당 해제(free)가 가능하다.
  • page->freelist
    • 다른 cpu들은 이 곳을 이용하여 할당 해제(free)만 가능하다.

 

다음 그림은 slub page가 11개의 object로 동작되고 fozen되어 freelist가 두 개로 나뉘어 관리되는 모습을 보여준다.

 

frozen slub 페이지의 inuse object 관리

frozen되어 페이지에 연결된 freelist가 cpu로 관리 이전된 경우 해당 freelist에 연결된 모든 object들에 대해 inuse 카운터에 추가되어 관리된다. 다른 cpu에서 slub object가 할당 해제(free) 되는 경우 frozen slub page라도 inuse에서 할당 해제된 수 만큼 감소시킨다.

 

다음 3개의 그림은 처음 new_slab() 함수로 부터 할당 받은 slub 페이지를 frozen하여 전체 object를 사용중으로 카운팅하고, 그 후 slub object 할당 시 inuse 카운터에 변화가 없음을 보여준다. 나중에 다른 cpu에서 할당 해제되는 slub object들은 cpu_slab->freelist에 해제되지 않고 page->freelist로 직접 해제되므로 이 때에는 inuse 카운터를 감소시키는 것을 볼 수 있다.

 

kmem_cache_zalloc()

include/linux/slab.h

/*
 * Shortcuts
 */
static inline void *kmem_cache_zalloc(struct kmem_cache *k, gfp_t flags)
{
        return kmem_cache_alloc(k, flags | __GFP_ZERO);
}

지정된 캐시에서 0(zero)으로 초기화된 slub object 하나를 할당받아 온다.

 

kmem_cache_alloc()

mm/slub.c

void *kmem_cache_alloc(struct kmem_cache *s, gfp_t gfpflags)
{
        void *ret = slab_alloc(s, gfpflags, _RET_IP_);

        trace_kmem_cache_alloc(_RET_IP_, ret, s->object_size,
                                s->size, gfpflags);

        return ret;
}
EXPORT_SYMBOL(kmem_cache_alloc);

지정된 캐시에서 slub object 하나를 할당받아 온다.

  • void *ret = slab_alloc(s, gfpflags, _RET_IP_);
    • slab object 하나를 할당받아 온다.
  • trace_kmem_cache_alloc(_RET_IP_, ret, s->object_size, s->size, gfpflags);
    • trace 문자열: “call_site=%lx ptr=%p bytes_req=%zu bytes_alloc=%zu gfp_flags=%s”

 

다음 그림은 slub object의 할당 요청의 수행 순서를 함수별로 간략히 보여준다. 단계별로 slub onject의 할당을 시도하는데 마지막 단계까지 할당할 object가 없는 경우 버디 시스템으로 부터 페이지를 할당 받아 slub을 구성하고 그 내부에 있는 object들을 초기화한다.

kmem_cache_alloc-1a

 

kmem_cache_alloc_node()

mm/slub.c
#ifdef CONFIG_NUMA
void *kmem_cache_alloc_node(struct kmem_cache *s, gfp_t gfpflags, int node)
{
        void *ret = slab_alloc_node(s, gfpflags, node, _RET_IP_);

        trace_kmem_cache_alloc_node(_RET_IP_, ret,
                                    s->object_size, s->size, gfpflags, node);

        return ret;
}
EXPORT_SYMBOL(kmem_cache_alloc_node);
#endif

NUMA 시스템에서 GFP 플래그를 참고하여 slub object를 지정된 노드에서  할당받아 온다.

 

include/linux/slab.h

#ifndef CONFIG_NUMA
static __always_inline void *kmem_cache_alloc_node(struct kmem_cache *s, gfp_t flags, int node)
{
        return kmem_cache_alloc(s, flags);
}
#endif

NUMA 시스템이 아닌 경우 GFP 플래그를 참고하여 slub 페이지를 할당받아 온다.

 

slab_alloc()

mm/slub.c

static __always_inline void *slab_alloc(struct kmem_cache *s,
                gfp_t gfpflags, unsigned long addr)
{
        return slab_alloc_node(s, gfpflags, NUMA_NO_NODE, addr);
}

지정된 캐시에서 전체 노드를 대상으로 slub object 하나를 할당받아 온다.

 

slab_alloc_node()

mm/slub.c

/*
 * Inlined fastpath so that allocation functions (kmalloc, kmem_cache_alloc)
 * have the fastpath folded into their functions. So no function call
 * overhead for requests that can be satisfied on the fastpath.
 *
 * The fastpath works by first checking if the lockless freelist can be used.
 * If not then __slab_alloc is called for slow processing.
 *
 * Otherwise we can simply pick the next object from the lockless free list.
 */
static __always_inline void *slab_alloc_node(struct kmem_cache *s,
                gfp_t gfpflags, int node, unsigned long addr)
{
        void **object;
        struct kmem_cache_cpu *c;
        struct page *page;
        unsigned long tid;

        s = slab_pre_alloc_hook(s, gfpflags);
        if (!s)
                return NULL;
redo:
        /*
         * Must read kmem_cache cpu data via this cpu ptr. Preemption is
         * enabled. We may switch back and forth between cpus while
         * reading from one cpu area. That does not matter as long
         * as we end up on the original cpu again when doing the cmpxchg.
         *
         * We should guarantee that tid and kmem_cache are retrieved on
         * the same cpu. It could be different if CONFIG_PREEMPT so we need
         * to check if it is matched or not.
         */
        do {
                tid = this_cpu_read(s->cpu_slab->tid);
                c = raw_cpu_ptr(s->cpu_slab);
        } while (IS_ENABLED(CONFIG_PREEMPT) &&
                 unlikely(tid != READ_ONCE(c->tid)));

        /*
         * Irqless object alloc/free algorithm used here depends on sequence
         * of fetching cpu_slab's data. tid should be fetched before anything
         * on c to guarantee that object and page associated with previous tid
         * won't be used with current tid. If we fetch tid first, object and
         * page could be one associated with next tid and our alloc/free
         * request will be failed. In this case, we will retry. So, no problem.
         */
        barrier();

지정된 캐시의 지정된 노드에서 slub object 하나를 할당받아 온다.

  • s = slab_pre_alloc_hook(s, gfpflags); if (!s) return NULL;
    • 메모리 할당에 문제가 있는 경우 할당을 포기한다. 경우에 따라 per memcg 캐시를 선택할 수 있다.
  • redo:
    • fastpath 용도로 per cpu 캐시로 부터 object 할당이 실패된 경우 다시 반복할 위치다.
  • do { tid = this_cpu_read(s->cpu_slab->tid); c = raw_cpu_ptr(s->cpu_slab); } while (IS_ENABLED(CONFIG_PREEMPT) && unlikely(tid != READ_ONCE(c->tid)));
    • tid 및 cpu 캐시를 atomic하게 알아온다.
    • 현재 이 시점에서 preemption이 언제라도 가능하기 때문에 수행 중 태스크 전환되었다 다시 돌아왔을 수 있다. 따라서 이 루틴에서는 tid와 캐시가 같은 cpu에서 획득된 것을 보장하게 하기 위해 확인 과정을 추가하였다.
  • barrier();
    • 인터럽트 마스크 없이 slab의 할당/해제 알고리즘이 동작하려면 cpu_slab 데이터를 읽는 순서에 의존하게 된다. object와 page보다 먼저 tid를 읽기 위해 컴파일러로 하여금 optimization을 하지 않도록 컴파일러 배리어를 사용하여 명확히 동작 순서를 구분하게 하였다.

 

        /*
         * The transaction ids are globally unique per cpu and per operation on
         * a per cpu queue. Thus they can be guarantee that the cmpxchg_double
         * occurs on the right processor and that there was no operation on the
         * linked list in between.
         */

        object = c->freelist;
        page = c->page;
        if (unlikely(!object || !node_match(page, node))) {
                object = __slab_alloc(s, gfpflags, node, addr, c);
                stat(s, ALLOC_SLOWPATH);
        } else {
                void *next_object = get_freepointer_safe(s, object);

                /*
                 * The cmpxchg will only match if there was no additional
                 * operation and if we are on the right processor.
                 *
                 * The cmpxchg does the following atomically (without lock
                 * semantics!)
                 * 1. Relocate first pointer to the current per cpu area.
                 * 2. Verify that tid and freelist have not been changed
                 * 3. If they were not changed replace tid and freelist
                 *
                 * Since this is without lock semantics the protection is only
                 * against code executing on this cpu *not* from access by
                 * other cpus.
                 */
                if (unlikely(!this_cpu_cmpxchg_double(
                                s->cpu_slab->freelist, s->cpu_slab->tid,
                                object, tid,
                                next_object, next_tid(tid)))) {

                        note_cmpxchg_failure("slab_alloc", s, tid);
                        goto redo;
                }
                prefetch_freepointer(s, next_object);
                stat(s, ALLOC_FASTPATH);
        }

        if (unlikely(gfpflags & __GFP_ZERO) && object)
                memset(object, 0, s->object_size);

        slab_post_alloc_hook(s, gfpflags, object);

        return object;
}
  • object = c->freelist; page = c->page;
    • per cpu 캐시의 freelist에서 object를 받아오고 page도 받아온다.
  • if (unlikely(!object || !node_match(page, node))) { object = __slab_alloc(s, gfpflags, node, addr, c); stat(s, ALLOC_SLOWPATH);
    • 낮은 확률로 할당할 object가 없거나 현재 per cpu 캐시에서 알아온 page의 노드가 지정된 노드와 다른 경우 slowpath 동작을 통해 object를 할당받는다. 이 때 ALLOC_SLOWPATH stat을 증가시킨다.
      • 태스크가 preemption되었다가 다른 노드의 cpu에서 스케쥴되어 재개한 경우 노드가 변경될 수 있다. per cpu 캐시가 인수로 지정된 노드에 속해있지 않은 경우 freelist에서 얻은 object의 사용을 포기하고 현재 cpu 캐시에서 다시 받아야 한다.
      • slowpath: object는 per cpu 캐시의 page로부터 알아온 처음 free object를 할당 받고, per cpu 캐시의 freelist에는 page로부터 알아온 두 번째 free object의 주소를 대입한다.
  • } else { void *next_object = get_freepointer_safe(s, object);
    • 성공리에 object를 가져온 경우 FP(Free Pointer) 값, 즉 다음 object의 주소를 알아온다.
  • if (unlikely(!this_cpu_cmpxchg_double(s->cpu_slab->freelist, s->cpu_slab->tid, object, tid, next_object, next_tid(tid)))) { note_cmpxchg_failure(“slab_alloc”, s, tid); goto  redo; }
    • 낮은 확률로 atomic하게 freelist가 다음 object를 가리키게 하고 tid도 다음 tid를 대입하게 하려는데 실패한 경우 다시 redo 레이블로 되돌아가서 object 할당이 성공될 때 까지 재시도한다.
    • 참고: Per-cpu (atomic operations) | 문c
  •  prefetch_freepointer(s, next_object);
    • 성공한 경우 다음 free object에 대한 캐시라인을 미리 fetch 한다.
  • stat(s, ALLOC_FASTPATH);
    • ALLOC_FASTPATH stat을 증가시킨다.
  • if (unlikely(gfpflags & __GFP_ZERO) && object) memset(object, 0, s->object_size);
    • 작은 확률로 GFP_ZERO 플래그를 사용한 경우 할당된 object 크기 만큼 object를 0으로 클리어한다.
  • slab_post_alloc_hook(s, gfpflags, object);
    • 할당 완료된 상태에 대해 디버그(kmemcheck, kmemleak, kasan) 루틴등에 알리고 memcg에도 알린다.

 

다음 그림은 slub object 할당 요청을 받았을 때 per cpu 캐시의 freelist에서 첫 object를 할당해주는 것을 보여준다.

 

slab_pre_alloc_hook()

mm/slub.c

static inline struct kmem_cache *slab_pre_alloc_hook(struct kmem_cache *s,
                                                     gfp_t flags)
{
        flags &= gfp_allowed_mask;
        lockdep_trace_alloc(flags);
        might_sleep_if(flags & __GFP_WAIT);

        if (should_failslab(s->object_size, flags, s->flags))
                return NULL;

        return memcg_kmem_get_cache(s, flags);
}

CONFIG_FAILSLAB 커널 옵션이 사용되는 경우 object 할당에 문제가 있는 경우 할당을 포기한다. 경우에 따라 per memcg 캐시를 선택할 수도 있다.

  • flags &= gfp_allowed_mask;
    • early 부트 시 허용된 GFP 플래그들만을 사용하게 한다.
  • might_sleep_if(flags & __GFP_WAIT);
    • GFP_WAIT 플래그를 사용한 경우 현재 태스크보다 높은 우선 순위 요청을 처리하도록 현재 태스크를 sleep하고 다른 태스크로 cpu를 이양한다.
  • if (should_failslab(s->object_size, flags, s->flags)) return NULL;
    • CONFIG_FAILSLAB 커널 옵션을 사용하는 경우 fault-injection 기능으로 할당에 문제가 있는 경우 할당을 포기하고 null을 반환한다.
  • return memcg_kmem_get_cache(s, flags);
    • memcg가 적용된 태스크의 경우 per memcg 캐시를 찾아 반환한다.

 

memcg_kmem_get_cache()

/**
 * memcg_kmem_get_cache: selects the correct per-memcg cache for allocation
 * @cachep: the original global kmem cache
 * @gfp: allocation flags.
 *      
 * All memory allocated from a per-memcg cache is charged to the owner memcg.
 */     
static __always_inline struct kmem_cache *
memcg_kmem_get_cache(struct kmem_cache *cachep, gfp_t gfp)
{
        if (!memcg_kmem_enabled())
                return cachep;
        if (gfp & __GFP_NOACCOUNT)
                return cachep;
        if (gfp & __GFP_NOFAIL)
                return cachep;
        if (in_interrupt() || (!current->mm) || (current->flags & PF_KTHREAD))
                return cachep;
        if (unlikely(fatal_signal_pending(current)))
                return cachep;
        
        return __memcg_kmem_get_cache(cachep);
}

현재 user 태스크에 memcg 설정이 되어 있는 경우 per memcg 캐시를 선택한다.

  • memcg가 비활성화되어 있는 경우 cachep를 바꾸지 않는다.
  • GFP_NOACCOUNT 또는 GFP_NOFAIL 플래그를 사용하는 경우에도 cachep를 바꾸지 않는다.
  • 인터럽트 처리 중이거나 current->mm이 null이거나 커널 스레드로 동작하는 경우에는 cachep를 바꾸지 않는다.
  • 낮은 확률로 펜딩된 SIGKILL 시그널이 있는 경우 cachep를 바꾸지 않는다.

 

slab_post_alloc_hook()

mm/slub.c

static inline void slab_post_alloc_hook(struct kmem_cache *s,
                                        gfp_t flags, void *object)
{
        flags &= gfp_allowed_mask;
        kmemcheck_slab_alloc(s, flags, object, slab_ksize(s));
        kmemleak_alloc_recursive(object, s->object_size, 1, s->flags, flags);
        memcg_kmem_put_cache(s);
        kasan_slab_alloc(s, object);
}
  • early 부트 시 허용된 GFP 플래그들만을 사용하게 한다.
    • flags &= gfp_allowed_mask;
  • kmemcheck, kmemleak, kasan 디버그 루틴에 캐시 할당 정보를 전달한다.
  • 메모리 cgroup에 캐시 할당 정보를 전달한다.

 

should_failslab()

mm/failslab.c

bool should_failslab(size_t size, gfp_t gfpflags, unsigned long cache_flags)
{       
        if (gfpflags & __GFP_NOFAIL)
                return false;
        
        if (failslab.ignore_gfp_wait && (gfpflags & __GFP_WAIT))
                return false;

        if (failslab.cache_filter && !(cache_flags & SLAB_FAILSLAB))
                return false;
        
        return should_fail(&failslab.attr, size);
}

CONFIG_FAILSLAB 커널 옵션이 사용되는 경우 호출되고 그렇지 않은 경우 false로 반환한다.

 

should_fail()

lib/fault-inject.c

/*      
 * This code is stolen from failmalloc-1.0
 * http://www.nongnu.org/failmalloc/
 */

bool should_fail(struct fault_attr *attr, ssize_t size)
{
        /* No need to check any other properties if the probability is 0 */
        if (attr->probability == 0)
                return false;

        if (attr->task_filter && !fail_task(attr, current))
                return false;

        if (atomic_read(&attr->times) == 0)
                return false;

        if (atomic_read(&attr->space) > size) {
                atomic_sub(size, &attr->space);
                return false;
        } 
 
        if (attr->interval > 1) {
                attr->count++;
                if (attr->count % attr->interval)
                        return false; 
        }

        if (attr->probability <= prandom_u32() % 100)
                return false;

        if (!fail_stacktrace(attr))
                return false;

        fail_dump(attr);

        if (atomic_read(&attr->times) != -1)
                atomic_dec_not_zero(&attr->times);
                                
        return true;
}
EXPORT_SYMBOL_GPL(should_fail);

속성 값들을 체크하여 문제가 발생된 경우 true를 반환한다.

 

node_match()

mm/slub.c

/*
 * Check if the objects in a per cpu structure fit numa
 * locality expectations.
 */
static inline int node_match(struct page *page, int node)
{
#ifdef CONFIG_NUMA
        if (!page || (node != NUMA_NO_NODE && page_to_nid(page) != node))
                return 0;
#endif
        return 1;
}

UMA 시스템에서는 항상 true를 반환하고, NUMA 시스템에서는 인수로 지정된 page(slub)의 노드가 인수로 지정된 node와 다른 경우 false를 반환한다.

  • NUMA 시스템의 preempt 커널을 사용하여 slub 할당 루틴을 진행 시에 preemption되어 요청한 노드에서 할당받으려 시도할 때 현재 per cpu 캐시의 page(slub)로부터 object를 받게 되는데 그 page(slub)에 소속된 노드와 비교하여 다른 경우 false를 반환하여 현재의 cpu 캐시를 그대로 사용하면 안 된다는 것을 판단할 수 있도록 해야 한다.

 

prefetch_freepointer()

mm/slub.c

static void prefetch_freepointer(const struct kmem_cache *s, void *object)
{
        prefetch(object + s->offset);
}

지정된 object의 FP(Freelist Pointer) 값을 미리 캐시 라인에 로드한다.

 

get_freepointer_safe()

mm/slub.c

static inline void *get_freepointer_safe(struct kmem_cache *s, void *object)
{
        void *p;

#ifdef CONFIG_DEBUG_PAGEALLOC
        probe_kernel_read(&p, (void **)(object + s->offset), sizeof(p));
#else
        p = get_freepointer(s, object);
#endif
        return p;
}

지정된 object의 FP(Freelist Pointer) 값을 읽어 주소로 반환한다.

 

get_freepointer()

mm/slub.c

static inline void *get_freepointer(struct kmem_cache *s, void *object)
{
        return *(void **)(object + s->offset);
}

지정된 object의 FP(Freelist Pointer) 값을 읽어 주소로 반환한다.

 

Object 할당을 위한 Slub 이주(Slowpath)

__slab_alloc()

mm/slub.c

/*
 * Slow path. The lockless freelist is empty or we need to perform
 * debugging duties.
 *
 * Processing is still very fast if new objects have been freed to the
 * regular freelist. In that case we simply take over the regular freelist
 * as the lockless freelist and zap the regular freelist.
 *
 * If that is not working then we fall back to the partial lists. We take the
 * first element of the freelist as the object to allocate now and move the
 * rest of the freelist to the lockless freelist.
 *
 * And if we were unable to get a new slab from the partial slab lists then
 * we need to allocate a new slab. This is the slowest path since it involves
 * a call to the page allocator and the setup of a new slab.
 */
static void *__slab_alloc(struct kmem_cache *s, gfp_t gfpflags, int node,
                          unsigned long addr, struct kmem_cache_cpu *c)
{
        void *freelist;
        struct page *page;
        unsigned long flags;

        local_irq_save(flags);
#ifdef CONFIG_PREEMPT
        /*
         * We may have been preempted and rescheduled on a different
         * cpu before disabling interrupts. Need to reload cpu area
         * pointer.
         */
        c = this_cpu_ptr(s->cpu_slab);
#endif

        page = c->page;
        if (!page)
                goto new_slab;
redo:

        if (unlikely(!node_match(page, node))) {
                int searchnode = node;

                if (node != NUMA_NO_NODE && !node_present_pages(node))
                        searchnode = node_to_mem_node(node);

                if (unlikely(!node_match(page, searchnode))) {
                        stat(s, ALLOC_NODE_MISMATCH);
                        deactivate_slab(s, page, c->freelist);
                        c->page = NULL;
                        c->freelist = NULL;
                        goto new_slab;
                }
        }

Slowpath 루틴으로 per cpu 캐시의 partial 리스트 또는 per 노드의 partial 리스트로부터 slub을 충전 받은 후에 per cpu 캐시를 위해 freelist를 반환한다. Very Slowpath 루틴으로 만일 per 노드의 partial 리스트 마저도 부족한 경우 버디 시스템으로 부터 새로운 페이지를 할당 받아 사용한다.

  • local_irq_save(flags); c = this_cpu_ptr(s->cpu_slab);
    • preemption 되지 않도록 인터럽트를 마스크한 후 per cpu 캐시를 알아온다.
    • PREEMPT 커널에서 동작한 경우 여기까지 오는 동안 인수로 받은 per cpu 캐시가 원래 요청 cpu가 아닌 즉, cpu가 바뀌었을 가능성이 있으므로 다시 지정한다.
  • page = c->page; if (!page) goto new_slab;
    • per cpu 캐시에 page(slub)가 없는 경우 new_slab으로 이동한다.
  • redo:
    • per cpu 캐시의 partial  또는 per 노드의 partial 리스트로부터 slub을 충전 받은 후에 이 레이블로 되돌아와서 object 할당을 재 시도할 목적으로 이동해오는 레이블이다.
  • if (unlikely(!node_match(page, node))) {
    • 낮은 확률로 per cpu 캐시 page가 인수로 요청한 노드와 다른 경우
      • 보통 태스크가 preemption되어 리스케쥴링 되는 경우 이 태스크가 다시 그 위치부터 재개되는 경우 가능하면 원래 cpu 또는 해당 노드에 소속된 cpu로 리스케쥴링되는데 해당 노드의 cpu들이 바쁜 경우 다른 노드의 cpu로 스케쥴링될 수도 있다. 바로 이러한 경우 원래 요청했던 노드와 현재 cpu의 노드가 달라질 수 있는 경우에 해당한다.
  • if (node != NUMA_NO_NODE && !node_present_pages(node)) searchnode = node_to_mem_node(node);
    • 인수로 지정된 노드에 가용 페이지가 하나도 없는 경우 인접 노드 id를 알아온다.
      • 해당 노드의 메모리가 정말 부족한 경우 또는 극히 일부 NUMA 시스템의 경우 노드 중 특정 노드들에 메모리 뱅크를 가지지 않은 경우이다.
  • if (unlikely(!node_match(page, searchnode))) { stat(s, ALLOC_NODE_MISMATCH); deactivate_slab(s, page, c->freelist); c->page = NULL;  c->freelist = NULL; goto new_slab; }
    • 여전히 낮은 확률로 per cpu 캐시에서 slab(page)이 searchnode가 아닌 경우ALLOC_NODE_MISMATCH stat을 증가시키고 page(slub)를 원래 per cpu 캐시에서 deactivate 시키고 page와 freelist에 null을 대입한 후 per cpu 캐시의 slab(page)을 비운다. 그런 후 new_slab으로 이동한다.

 

        /*
         * By rights, we should be searching for a slab page that was
         * PFMEMALLOC but right now, we are losing the pfmemalloc
         * information when the page leaves the per-cpu allocator
         */
        if (unlikely(!pfmemalloc_match(page, gfpflags))) {
                deactivate_slab(s, page, c->freelist);
                c->page = NULL;
                c->freelist = NULL;
                goto new_slab;
        }

        /* must check again c->freelist in case of cpu migration or IRQ */
        freelist = c->freelist;
        if (freelist)
                goto load_freelist;

        freelist = get_freelist(s, page);

        if (!freelist) {
                c->page = NULL;
                stat(s, DEACTIVATE_BYPASS);
                goto new_slab;
        }

        stat(s, ALLOC_REFILL);

load_freelist:
        /*
         * freelist is pointing to the list of objects to be used.
         * page is pointing to the page from which the objects are obtained.
         * That page must be frozen for per cpu allocations to work.
         */
        VM_BUG_ON(!c->page->frozen);
        c->freelist = get_freepointer(s, freelist);
        c->tid = next_tid(c->tid);
        local_irq_restore(flags);
        return freelist;
  • if (unlikely(!pfmemalloc_match(page, gfpflags))) { deactivate_slab(s, page, c->freelist); c->page = NULL; c->freelist = NULL; goto new_slab; }
    • 낮은 확률로 현재 요청이 비상 상황이 아닌데 비상용 slub 페이지가 선택된 경우 다시 다른 slub 페이지를 할당 요청한다.
      • swap for NBD 드라이버를 사용하는 경우 slub object 할당 요청 시 ALLOC_NO_WATERMARKS 플래그를 사용하여  TCP socket 송수신이 비상용 slub 페이지도 활용할 수 있게 한다.
      • 현재 선택된 per cpu 캐시의 slab(page)을 deactivate 시키고 per cpu 캐시의 page와 freelist에 null을 대입한 후 new_slab 레이블로 이동한다.
  • freelist = c->freelist; if (freelist) goto load_freelist;
    • 다시 한 번 per cpu 캐시의 freelist가 비어있는지 확인한다. 만일 다른 cpu 또는 task에서 반납한 object가 freelist에 등록되어 있는 경우 load_freelist 레이블로 이동한다.
  • freelist = get_freelist(s, page);
    • per cpu 캐시의 page로 부터 freelist를 받아온다.
  • if (!freelist) { c->page = NULL; stat(s, DEACTIVATE_BYPASS); goto new_slab; }
    • freelist를 받아오지 못한 경우 cpu 페이지에 null을 대입하고 DEACTIVATE_BYPASS stat을 증가시킨 후 new_slab 레이블로 이동한다.
  • stat(s, ALLOC_REFILL);
    • freelist가 다시 채워진 경우이므로 ALLOC_REFILL stat을 증가시킨다.
  • load_freelist:
    • 이 레이블은 다른 cpu나 task가 object를 반환하였거나 캐시 페이지로부터 freelist에 refill된 경우 이 레이블에 도착하게된다. 즉 per cpu 캐시의 freelist에 object가 있는 경우이다.
  • c->freelist = get_freepointer(s, freelist); c->tid = next_tid(c->tid); local_irq_restore(flags); return freelist;
    • per cpu 캐시의 freelist에 다음 object 주소를 대입하고 tid에도 다음 tid를 대입한다. 그런 후 인터럽트를 다시 복구하고 할당 받은 object를 반환한다.

 

new_slab:

        if (c->partial) {
                page = c->page = c->partial;
                c->partial = page->next;
                stat(s, CPU_PARTIAL_ALLOC);
                c->freelist = NULL;
                goto redo;
        }

        freelist = new_slab_objects(s, gfpflags, node, &c);

        if (unlikely(!freelist)) {
                slab_out_of_memory(s, gfpflags, node);
                local_irq_restore(flags);
                return NULL;
        }

        page = c->page;
        if (likely(!kmem_cache_debug(s) && pfmemalloc_match(page, gfpflags)))
                goto load_freelist;

        /* Only entered in the debug case */
        if (kmem_cache_debug(s) &&
                        !alloc_debug_processing(s, page, freelist, addr))
                goto new_slab;  /* Slab failed checks. Next slab needed */

        deactivate_slab(s, page, get_freepointer(s, freelist));
        c->page = NULL;
        c->freelist = NULL;
        local_irq_restore(flags);
        return freelist;
}
  • new_slab:
    • per cpu 캐시의 page도 비어있어 per cpu 캐시의 partial 리스트에서 찾는다. per cpu 캐시의 page를 채우는 루틴의 시작 레이블이다.
  • if (c->partial) { page = c->page = c->partial; c->partial = page->next; stat(s, CPU_PARTIAL_ALLOC); c->freelist = NULL; goto redo; }
    • per cpu 캐시의 partial 리스트에서 가장 처음 page(slub)를 꺼내서 per cpu 캐시의 page를 채운다. 이 때 CPU_PARTIAL_ALLOC stat도 증가시키고 freelist에 null을 대입한 후 다시 redo 레이블로 이동하여 slub object 할당을 재시도 한다.
  • freelist = new_slab_objects(s, gfpflags, node, &c);
    • per cpu 캐시의 partial 리스트가 비어 있으므로 새로운 page(slab)를 per 노드의 partial 리스트로부터 이주 받고 그 free objects들의 주소를 freelist로 알아온다.
  • if (unlikely(!freelist)) { slab_out_of_memory(s, gfpflags, node); local_irq_restore(flags); return NULL; }
    • 새로운 slab object를 할당 받아 오지 못한 경우 OOM(Out Of Memory) 처리를 한 후 null을 반환한다.
  • page = c->page; if (likely(!kmem_cache_debug(s) && pfmemalloc_match(page, gfpflags))) goto load_freelist;
    • 높은 확률로 slub 디버깅이 필요없는 경우 load_freelist 레이블리 이동하여 free object 할당을 재시도한다.
  • deactivate_slab(s, page, get_freepointer(s, freelist)); c->page = NULL; c->freelist = NULL; local_irq_restore(flags); return freelist;

 

다음 그림은 per cpu 캐시의 page로부터 freelist로 옮기는 과정을 보여준다.

 

다음 그림은 per cpu 캐시의 partial 리스트로부터 page로 옮기는 과정을 보여준다. 기존에 사용하던 slub page는 free object가 없는(모두 사용중) 상태로 kmem_cache이 관리하지 않게된다.

 

get_freelist()

mm/slub.c

/*
 * Check the page->freelist of a page and either transfer the freelist to the
 * per cpu freelist or deactivate the page.
 *
 * The page is still frozen if the return value is not NULL.
 *
 * If this function returns NULL then the page has been unfrozen.
 *
 * This function must be called with interrupt disabled.
 */
static inline void *get_freelist(struct kmem_cache *s, struct page *page)
{
        struct page new;
        unsigned long counters;
        void *freelist;

        do {
                freelist = page->freelist;
                counters = page->counters;

                new.counters = counters;
                VM_BUG_ON(!new.frozen);

                new.inuse = page->objects;
                new.frozen = freelist != NULL;

        } while (!__cmpxchg_double_slab(s, page,
                freelist, counters,
                NULL, new.counters,
                "get_freelist"));

        return freelist;
}

per cpu 캐시의 page의 freelist를 반환하고 그 slub 페이지의 freelist에 null을 대입하고 모두 사용중으로 변경한다.

  • 사용중인 object 갯수가 담기는 inuse에 slub의 객체 갯수 값을 대입하여 모두 사용중인 것으로 한다.
  • page->freelist에 null을 대입하고, page->counter에 new.counters(inuse는 모든 object 갯수, frozen=freelist가 존재하는 경우 true)를 대입하되 실패하면 될 때까지 반복한다
    • x86, arm64, s390 등의 아키텍처에서는 atomic 하게 처리하고 그렇지 않은 경우 bit_spin_lock을 사용하여 처리한다.

 

__cmpxchg_double_slab()

mm/slub.c

/* Interrupts must be disabled (for the fallback code to work right) */
static inline bool __cmpxchg_double_slab(struct kmem_cache *s, struct page *page,
                void *freelist_old, unsigned long counters_old,
                void *freelist_new, unsigned long counters_new,
                const char *n)
{
        VM_BUG_ON(!irqs_disabled());
#if defined(CONFIG_HAVE_CMPXCHG_DOUBLE) && \
    defined(CONFIG_HAVE_ALIGNED_STRUCT_PAGE)
        if (s->flags & __CMPXCHG_DOUBLE) {
                if (cmpxchg_double(&page->freelist, &page->counters,
                                   freelist_old, counters_old,
                                   freelist_new, counters_new))
                        return 1;
        } else
#endif
        {
                slab_lock(page);
                if (page->freelist == freelist_old &&
                                        page->counters == counters_old) {
                        page->freelist = freelist_new;
                        set_page_slub_counters(page, counters_new);
                        slab_unlock(page);
                        return 1;
                }
                slab_unlock(page);
        }

        cpu_relax();
        stat(s, CMPXCHG_DOUBLE_FAIL);

#ifdef SLUB_DEBUG_CMPXCHG
        pr_info("%s %s: cmpxchg double redo ", n, s->name);
#endif

        return 0;
}

page->freelist와 counter를 old 값과 비교하여 동일한 경우 new 값으로 바꾼다.

  • 처리 성능 향상을 위해 CONFIG_HAVE_CMPXCHG_DOUBLE이 동작하는 아키텍처(x86, arm64, s390)에서는 atomic하게 처리를 하고, 그렇지 않은 아키텍처들은 bit_spin_lock()을 사용하여 구현한다.

 

다음 그림에서는 per cpu 캐시에서 사용된 fastpath와 per node에서 사용된 slowpath에서 사용된 두 개의 atomic operation을 보여준다.

 

new_slab_objects()

mm/slub.c

static inline void *new_slab_objects(struct kmem_cache *s, gfp_t flags,
                        int node, struct kmem_cache_cpu **pc)
{
        void *freelist;
        struct kmem_cache_cpu *c = *pc;
        struct page *page;

        freelist = get_partial(s, flags, node, c);

        if (freelist)
                return freelist;

        page = new_slab(s, flags, node);
        if (page) {
                c = raw_cpu_ptr(s->cpu_slab);
                if (c->page)
                        flush_slab(s, c);

                /*
                 * No other reference to the page yet so we can
                 * muck around with it freely without cmpxchg
                 */
                freelist = page->freelist;
                page->freelist = NULL;

                stat(s, ALLOC_SLAB);
                c->page = page;
                *pc = c;
        } else
                freelist = NULL;

        return freelist;
}

per 노드들 중 가능하면 현재 페이지의 노드의 partial 리스트에 있는 몇 개의 slub을 per cpu 캐시의 freelist, page 및 partial 리스트로 이동시킨다. 만일 옮길 page(slub)가 없는 경우 버디 시스템으로부터 새로운 페이지를 할당받아 per cpu 캐시의 freelist 및 page에 대입한다. 페이지 할당마저도 실패하는 경우 null을 반환한다.

  • freelist = get_partial(s, flags, node, c);
    • per 노드들 중 가능하면 현재 페이지의 노드의 partial 리스트에 있는 몇 개의 slub을 per cpu 캐시의 freelist, page 및 partial 리스트로 이동시킨다
  • if (freelist) return freelist;
    • freelist가 있는 경우 freelist를 반환한다.
  • page = new_slab(s, flags, node);
    • 버디 시스템으로부터 새로운 페이지를 할당받아 온다.
  • if (page) { c = raw_cpu_ptr(s->cpu_slab);
    • 페이지를 할당 받아 온 경우 per cpu 캐시를 c에 대입
  • if (c->page) flush_slab(s, c);
    • per cpu 캐시 page(slub)가 존재하는 경우 flush 한다.
  • freelist = page->freelist; page->freelist = NULL; stat(s, ALLOC_SLAB); c->page = page; *pc = c;
    • 반환할 freelist를 준비하고 page의 freelist는 null을 대입한다. ALLOC_SLAB stat을 증가시키고 per cpu 캐시page에 할당 받은 페이지를 대입하고 출력 인수 per cpu 캐시 pc를 갱신한다.
  • } else freelist = NULL;
    • 페이지 할당을 못한 경우 null을 반환한다.

 

다음 그림은 per 노드의 partial 리스트로부터 per cpu 캐시의 freelist, page 및 partial 리스트로 옮기는 과정을 보여준다.

  • per 노드마저도 부족하면 리모트 노드를 검색하여 수행한다.

 

다음 그림은 버디 시스템으로부터 페이지를 할당받아 per cpu 캐시에 추가하는 모습을 보여준다.

slab_alloc_node-5b

 

get_partial()

mm/slub.c

/*
 * Get a partial page, lock it and return it.
 */
static void *get_partial(struct kmem_cache *s, gfp_t flags, int node,
                struct kmem_cache_cpu *c)
{
        void *object;
        int searchnode = node;

        if (node == NUMA_NO_NODE)
                searchnode = numa_mem_id();
        else if (!node_present_pages(node))
                searchnode = node_to_mem_node(node);

        object = get_partial_node(s, get_node(s, searchnode), c, flags);
        if (object || node != NUMA_NO_NODE)
                return object;

        return get_any_partial(s, flags, c);
}

per 노드의 partial 리스트에서 page(slub)을 가져와서 per cpu 캐시로 이주시킨다. 만일 per 노드의 partial 리스트가 비어 있는 경우 리모트 노드에서 시도한다.

  • if (node == NUMA_NO_NODE) searchnode = numa_mem_id();
    • 노드 지정되지 않은 경우 searchnode에 로컬 노드 id 또는 가장 가까운 메모리가 있는 노드 id를 알아온다.
  • else if (!node_present_pages(node)) searchnode = node_to_mem_node(node);
    • 지정된 노드에 사용할 수 있는 페이지가 없는 경우 가장 가까운 메모리가 있는 노드 id를 알아온다.
  • object = get_partial_node(s, get_node(s, searchnode), c, flags);
    • per 노드의 partial 리스트의 slub들을 per cpu 캐시의 page와 partial로 이주시킨다. per cpu 캐시로 이주시키기 위해 첫 slub 상태를 frozen 상태로 변경하고 모두 사용중으로 변경하고 per cpu 캐시의 page에 옮긴다. 연속된 다음 slub들 부터 per cpu 캐시의 partial 리스트에 반복하여 옮기되 kmem_cache->cpu_partial에 지정된 per cpu 캐시에 허용된 free object 수의 절반을 초과한 경우 이주를 중단시킨다. 마지막으로 per cpu 캐시의  freelist에 사용하기 위해 첫 slub의  freelist를 반환 한다. 만일 per cpu 캐시로 이주할 slub이 없는 경우 이주 실패로 null을 반환한다.
  • if (object || node != NUMA_NO_NODE) return object;
    • object를 알아왔거나 노드가 지정된 경우 freelist로 사용할 object를 반환한다.
  • return get_any_partial(s, flags, c);
    • per 노드의 partial 리스트가 부족한 경우 리모트 노드에서 per cpu 캐시의 partial 리스트로 slub을 이주시킨다.

 

 

get_partial_node()

mm/slub.c

/*
 * Try to allocate a partial slab from a specific node.
 */
static void *get_partial_node(struct kmem_cache *s, struct kmem_cache_node *n,
                                struct kmem_cache_cpu *c, gfp_t flags)
{
        struct page *page, *page2;
        void *object = NULL;
        int available = 0;
        int objects;

        /*
         * Racy check. If we mistakenly see no partial slabs then we
         * just allocate an empty slab. If we mistakenly try to get a
         * partial slab and there is none available then get_partials()
         * will return NULL.
         */
        if (!n || !n->nr_partial)
                return NULL;

        spin_lock(&n->list_lock);
        list_for_each_entry_safe(page, page2, &n->partial, lru) {
                void *t;

                if (!pfmemalloc_match(page, flags))
                        continue;

                t = acquire_slab(s, n, page, object == NULL, &objects);
                if (!t)
                        break;

                available += objects;
                if (!object) {
                        c->page = page;
                        stat(s, ALLOC_FROM_PARTIAL);
                        object = t;
                } else {
                        put_cpu_partial(s, page, 0);
                        stat(s, CPU_PARTIAL_NODE);
                }
                if (!kmem_cache_has_cpu_partial(s)
                        || available > s->cpu_partial / 2)
                        break;

        }
        spin_unlock(&n->list_lock);
        return object;
}

per 노드의 partial 리스트에서 page(slub)을 가져와서 per cpu 캐시로 이주시킨다. 이주할 page(slub)가 없는 경우 null을 반환한다.

  • per 노드의 partial 리스트에 있는 첫 page(slub)은 per cpu 캐시의 freelist 및 page로 이주시킨다.
    • free object들은 per cpu 캐시의 freelist에 대입하고, page(slub)는 frozen시킨 후 모두 사용중(inuse=object수)으로 바꾸어 per cpu 캐시의 page에 대입한다.
  • per 노드의 partial 리스트에 있는 첫 page(slub)를 제외한 나머지는 적절히 제한된 수 만큼 per cpu 캐시의 partial 리스트로 이주시킨다.
    • 연속된 다음 slub들 부터 per cpu 캐시의 partial 리스트에 반복하여 옮기되 kmem_cache->cpu_partial에 지정된 per cpu 캐시에 허용된 free object 수의 절반을 초과한 경우 이주를 중단시킨다.

 

  • if (!n || !n->nr_partial) return NULL;
    • per 노드에서 partial slub이 하나도 없는 경우 이주 실패로 null을 반환한다.
  • list_for_each_entry_safe(page, page2, &n->partial, lru) {
    • per 노드에서 partial 리스트에 등록된 모든 slub(page)에 대해 루프를 돈다.
  • if (!pfmemalloc_match(page, flags)) continue;
    • slub 페이지가 reserve 영역에서 할당을 받았고 gfp 플래그 요청을 통해 워터마크 제한이 걸린 경우 skip 한다.
  • t = acquire_slab(s, n, page, object == NULL, &objects);
    • per 노드에서 slub을 제거하고 slub의 freelist를 알아와서 t에 대입하고 free objects의 갯수를 objects에 대입한다. 이 과정에서 slub은 per cpu 캐시로 옮겨지기 위해 frozen 상태로 변경시키고 freelist는 비워지고 모든 객체는 사용중으로 변경된다.
  • if (!t) break;
    • object를 가져오지 못한 경우 루프를 중단하고 null로 반환한다.
  • available += objects;
    • free object의 갯수를 더한다.
  • if (!object) { c->page = page; stat(s, ALLOC_FROM_PARTIAL); object = t;
    • 처음 가져온 slub은 kmem_cache_cpu->page에 대입하고 ALLOC_FROM_PARTIAL stat을 증가시킨 후 반환할 freelist(object)로 t를 대입한다.
  • } else { put_cpu_partial(s, page, 0); stat(s, CPU_PARTIAL_NODE); }
    • 처음 가져온 slub이 아닌 경우 per cpu 캐시의 partial 리스트에 추가하고 CPU_PARTIAL_NODE stat을 증가시킨다.
  • if (!kmem_cache_has_cpu_partial(s) || available > s->cpu_partial / 2) break;
    • 가져온 free object 총 갯수가 s->cpu_partial(per cpu 캐시가 허용하는 free object 수)의 절반을 초과하는 경우 그만 가져오려고 루프를 중단한다.

 

get_any_partial()

mm/slub.c

/*
 * Get a page from somewhere. Search in increasing NUMA distances.
 */
static void *get_any_partial(struct kmem_cache *s, gfp_t flags,
                struct kmem_cache_cpu *c)
{
#ifdef CONFIG_NUMA
        struct zonelist *zonelist;
        struct zoneref *z;
        struct zone *zone;
        enum zone_type high_zoneidx = gfp_zone(flags);
        void *object;
        unsigned int cpuset_mems_cookie;

        /*
         * The defrag ratio allows a configuration of the tradeoffs between
         * inter node defragmentation and node local allocations. A lower
         * defrag_ratio increases the tendency to do local allocations
         * instead of attempting to obtain partial slabs from other nodes.
         *
         * If the defrag_ratio is set to 0 then kmalloc() always
         * returns node local objects. If the ratio is higher then kmalloc()
         * may return off node objects because partial slabs are obtained
         * from other nodes and filled up.
         *
         * If /sys/kernel/slab/xx/defrag_ratio is set to 100 (which makes
         * defrag_ratio = 1000) then every (well almost) allocation will
         * first attempt to defrag slab caches on other nodes. This means
         * scanning over all nodes to look for partial slabs which may be
         * expensive if we do it every time we are trying to find a slab
         * with available objects.
         */
        if (!s->remote_node_defrag_ratio ||
                        get_cycles() % 1024 > s->remote_node_defrag_ratio)
                return NULL;

        do {
                cpuset_mems_cookie = read_mems_allowed_begin();
                zonelist = node_zonelist(mempolicy_slab_node(), flags);
                for_each_zone_zonelist(zone, z, zonelist, high_zoneidx) {
                        struct kmem_cache_node *n;

                        n = get_node(s, zone_to_nid(zone));

                        if (n && cpuset_zone_allowed(zone, flags) &&
                                        n->nr_partial > s->min_partial) {
                                object = get_partial_node(s, n, c, flags);
                                if (object) {
                                        /*
                                         * Don't check read_mems_allowed_retry()
                                         * here - if mems_allowed was updated in
                                         * parallel, that was a harmless race
                                         * between allocation and the cpuset
                                         * update
                                         */
                                        return object;
                                }
                        }
                }
        } while (read_mems_allowed_retry(cpuset_mems_cookie));
#endif
        return NULL;
}

리모트 노드들의 partial 리스트에서 page(slub)를 찾아 per cpu 캐시의 freelist, page, partial 리스트로 이주시킨다.

  • if (!s->remote_node_defrag_ratio || get_cycles() % 1024 > s->remote_node_defrag_ratio) return NULL;
    • delay 타이머를 이용하여 remote_node_defrag_ratio 비율로 루틴을 수행하게 한다. 확률에서 제외되면 null을 반환한다.
    • Delay Loop Timer | 문c
  • cpuset_mems_cookie = read_mems_allowed_begin();
    • cpuset 변동이 있는 경우 재시도되기 위해 cpuset 시퀀스 락 값을 읽어온다
  • zonelist = node_zonelist(mempolicy_slab_node(), flags);
    • mempolicy에서 찾아준 노드의 zonelist를 구해온다.
  • for_each_zone_zonelist(zone, z, zonelist, high_zoneidx) {
    • zonelist에서 루프를 돌되 high_zoneidx 아래의 zone에서부터 가장 하위 zone으로 루프를 돈다.
  • n = get_node(s, zone_to_nid(zone));
    • zone에 해당하는 노드 id를 알아온다.
  • if (n && cpuset_zone_allowed(zone, flags) && n->nr_partial > s->min_partial) {
    • zone이 구성된 노드에서 할당을 받을 수 있는 상태이면서 노드의 partial slub의 수가 캐시의 min_partial 수를 초과하는 경우
  • object = get_partial_node(s, n, c, flags);
    • per 노드의 partial 리스트에서 page(slub)을 가져와서 per cpu 캐시로 이주시킨다. 이주할 page(slub)가 없는 경우 null을 반환한다.
  • if (object) { return object; }
    • object를 할당한 경우 성공적으로 함수를 빠져나간다.
  • } while (read_mems_allowed_retry(cpuset_mems_cookie));
    • cpuset 변동이 있는 경우 재시도를 한다

 

acquire_slab()

mm/slub.c

/*
 * Remove slab from the partial list, freeze it and
 * return the pointer to the freelist.
 *
 * Returns a list of objects or NULL if it fails.
 */
static inline void *acquire_slab(struct kmem_cache *s,
                struct kmem_cache_node *n, struct page *page,
                int mode, int *objects)
{
        void *freelist;
        unsigned long counters;
        struct page new;

        lockdep_assert_held(&n->list_lock);

        /*
         * Zap the freelist and set the frozen bit.
         * The old freelist is the list of objects for the
         * per cpu allocation list.
         */
        freelist = page->freelist;
        counters = page->counters;
        new.counters = counters;
        *objects = new.objects - new.inuse;
        if (mode) {
                new.inuse = page->objects;
                new.freelist = NULL;
        } else {
                new.freelist = freelist;
        }

        VM_BUG_ON(new.frozen);
        new.frozen = 1;

        if (!__cmpxchg_double_slab(s, page,
                        freelist, counters,
                        new.freelist, new.counters,
                        "acquire_slab"))
                return NULL;

        remove_partial(n, page);
        WARN_ON(!freelist);
        return freelist;
}

per 노드의 partial 리스트에서 적절한 수 만큼의 page(slub)을 제거하고 그 page(slub)들을 per cpu 캐시의 page와 partial 리스트에 추가한다.

  • per 노드의 partial 리스트에 있는 첫 page(slub)를 per cpu 캐시의 page에 이동시킨다. 이 때 slub 상태를 frozen 상태로 변경하고 inuse(사용중인 objects 수)를 전체 objects 갯수와 같게 한다. 또한 page(slub)의 freelist에 null을 대입한다.
  • per 노드의 partial 리스트에 있는 첫 페이지를 제외한 페이지들은 per cpu 캐시의 partial 리스트에 이동시킨다.

 

  • freelist = page->freelist;
    • slub에서 free object 리스트
  • counters = page->counters; new.counters = counters;
    • 32bit counters는 inuse:16, objects:15, frozen:1 필드로 구성된다.
  • *objects = new.objects – new.inuse;
    • 출력인수 objects에 slub에서 free objects 갯수를 대입한다.
  • if (mode) { new.inuse = page->objects; new.freelist = NULL;
    • mode가 true(첫 번째 slub)이면 모든 objects를 사용중으로 하기 위해  new.inuse에 slub의 전체 objects 갯수를 대입하고, new.freelist에 null을 대입한다.
  • } else { new.freelist = freelist; }
    • mode가 false(두 번째 slub부터)이면 new.freelist에 freelist를 대입한다.
  • new.frozen = 1;
    • per cpu 캐시로 옮기기 위해 frozen 시킨다.
  • if (!__cmpxchg_double_slab(s, page, freelist, counters, new.freelist, new.counters, “acquire_slab”)) return NULL;
    • slub의 freelist와 counters 값이 변동이 없는 경우 new.freelist와 new.counters로 변경한다. 만일 실패하는 경우 null을 반환한다.
      • per cpu 캐시로 옮겨지기 위해 slub은 frozen되고 사용중인 objects(inuse) 값을 slub의 전체 objects로 대입하고, freelist를 비운다.
  • remove_partial(n, page); return freelist;
    • per 노드 partial 리스트에서 하나의 slub(page)을 제거하고 freelist를 반환한다.

 

참고

 

답글 남기기

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