Slub Memory Allocator -12- (slub 디버깅)

<kernel v5.0>

슬랩 object 디버깅 (slub)

SLAB_POISON

Slub 디버깅을 위해 SLAB_POISON을 사용하는 경우 object의 할당과 해제 시에 특별한 데이터를 object 데이터 영역에 기록하고 이를 통해서 다음의 에러를 찾아낸다.

  • Object already free – 이중 해제
    • 해제를 하게되면 object에는 poison free 데이터(0x6b가 연속되고 마지막에 0xa5)가 기록되는데 만일 해제된 object를 다시 한 번 해제하는 순간 poison free 데이터를 발견하면 반복하여 해제하려고 한다는 것을 알게되어 에러를 출력한다.
  • Poison overwritten – 해제 후 사용
    • 엄밀히 이 free object를 침해하여 사용하였을 때에는 체크를 하지 못한다. 다만 다시 object를 할당할 때 free 라고 생각했던 object의 poison free 데이터가 파괴된 것을 보고 누군가가 침해를 한 것으로 판단하여 에러를 출력한다.

 

커널 파라미터 설정
  • “slub_debug=FP[,<슬랩명>]”

 

다음 그림은 슬랩 object에서의 poison 값의 변화 추이를 보여준다.

 

SLAB_RED_ZONE

Slab(Slub) 디버깅을 위해 RED_ZONE을 사용하는 경우 object의 할당과 해제 시에 특별한 데이터를 red zone 영역에 기록하고 이를 통해서 다음의 에러를 찾아낸다.

  • Redzone overwritten
    • object 데이터가 자신의 영역을 초과하여 red zone 영역을 침범한 경우 object를 할당 또는 해지하는 순간 알아낸 후 에러를 출력한다.

 

커널 파라미터 설정
  • “slub_debug=FZ[,<슬랩명>]”

 

 

다음 그림은 poison 및 red-zone에 대한 값의 처리 과정을 보여준다.

 

슬랩 페이지 및 슬랩 object 구성 체크

슬랩 페이지의 구성 값, 패딩 그리고 슬랩 object의 FP(Freelist) 등에 문제가 있는지 체크하여 다음과 같은 에러를 보고한다.

  • Object padding overwritten
    • 처음 새 슬랩 페이지는 전체 영역에 대해 슬랩 object를 초기화하기 전에 poison inuse 데이터(0x5a) 초기 값으로 기록된다.
    • 슬랩 할당 또는 해제 시 패딩 공간에 기록된 0x5a 값을 확인하여 변경된 것을 알게되면 에러를 출력한다.
  • Alignment padding
    • red-zone을 사용하지 않을 때 s->object_size와 s->inuse가 다르면 에러를 출력한다.
  • Invalid object pointer 0x%p
    • 포인터 주소가 슬랩 페이지 범위를 벗어나는 경우 에러를 출력한다.
  • Freepointer corrupt
    • 슬랩 페이지 범위를 벗어난 FP(Free Pointer) 주소를 사용한 경우 에러를 출력한다.
  • Freechain corrupt
    • FP 값이 슬랩 페이지 범위를 벗어나는 경우 에러를 출력한다.
  • Wrong number of objects. Found %d but should be %d
    • 슬랩 페이지에 들어갈 수 있는 최대 수가 잘못되어 에러를 출력한다.
  • Wrong object count. Counter is %d but counted were %d
    • 슬랩 페이지의 사용 중인 object 수가 잘못된 경우 에러를 출력한다.
    • 정상인 경우 p->objects – free object 수 = p->inuse 이다.
  • Not a valid slab page
    • 슬랩 페이지가 아닌 경우이다.
  • objects %u > max %u
    • 슬랩 페이지에서 사용 중인 object 수가 들어갈 수 있는 최대 슬랩 object 수를 초과한 값을 사용하여 에러를 출력한다.
  • inuse %u > max %u
    • 슬랩 페이지에서 사용(inuse) 중인 object 수가 최대 슬랩 object 수를 초과한 값을 사용한다.
  • Padding overwritten. 0x%p-0x%p
    • 슬랩 페이지 뒷 부분 사용하지 않는 공간의 패딩 값이 변경되었다.

 

SLAB_STORE_USER

Slab(Slub) 디버깅을 위해 SLAB_STORE_USER를 사용하는 경우 object를 할당과 해제를 한 pid, cpu, 호출한 주소(함수 추적). 시각 등을 추적할 수 있다. 만일 CONFIG_STACKTRACE 커널 옵션을 추가 사용하는 경우 16개까지의  function track back을 할 수 있다.

 

커널 파라미터 설정
  • “slub_debug=FU[,<슬랩명>]”

 

 

alloc 유저 트래킹

다음은 유저 트래킹을 허용한 foo 슬랩 캐시에서 두 번 할당(alloc)된 내역을 보여준다.

$ cat /sys/kernel/slab/foo/alloc_calls
      1 0xffff000008b8a068 age=2609 pid=3481
      1 0xffff000008b8a078 age=2596 pid=3481

 

free 유저 트래킹

다음은 유저 트래킹을 허용한 foo 슬랩 캐시에서 한 번 할당 해제(free)된 내역을 보여준다.

$ cat /sys/kernel/slab/foo/free_calls
2 <not-available> age=4295439025 pid=0

 

TRACE

슬랩(Slub) 디버깅을 위해 SLAB_TRACE를 사용하는 경우 object의 할당과 해제 시 해당 object의 주소, inuse 값, FP(Free pointer) 및 object의 16진수 덤프를 수행하여 추적할 수 있다.

 

커널 파라미터 설정
    • “slub_debug=FT[,<슬랩명>]”

 

[  242.214756] TRACE jake free 0xffff800039509d20 inuse=2 fp=0xffff800039508190
[  242.217524] Object ffff800039509d20: 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44  DDDDDDDDDDDDDDDD
[  242.221762] Object ffff800039509d30: 44 44 44 44 44 44 44 44 44 44 44 44 44 44        DDDDDDDDDDDDDD

 


슬랩 할당 관련 플래그들

다음은 디버그와 관련된 플래그들이다.

  • SLAB_CONSISTENCY_CHECKS
    • 슬랩 object의 alloc 및 free 시마다 체크를 수행하게 한다. (expensive)
  • SLAB_RED_ZONE
    • red zone 영역을 사용한 디버깅
  • SLAB_POISON
    • object 영역에 poison 데이터를 기록하여 디버깅
  • SLAB_STORE_USER
    • 디버깅을 위해 last owner 관련 정보를 저장한다.
  • SLAB_TRACE
    • 디버깅을 위해 트레이스를 지원한다.
  • SLAB_DEBUG_OBJECTS
    • 슬랩 object의 free 시 디버깅을 하지 않게 한다.
  • SLAB_KASAN
    • 슬랩 디버깅에 런타임 디버깅을 지원한다.
    • 메모리 소모 및 성능이 크게 떨어지지만 슬랩 object 침해를 즉각 감지한다.
  • SLAB_NOLEAKTRACE
    • kmemleak 트레이스를 하지 않게 한다.
  • SLAB_FAILSLAB
    • 슬랩 object 할당 시 Fault injection으로 에러를 발생시킨다.
  • SLAB_PANIC
    • 슬랩 object 에러 시 panic()을 호출한다.

 


슬랩(slub) 페이지 할당 시 object의 디버그 초기화

setup_object()

mm/slub.c

static void setup_object(struct kmem_cache *s, struct page *page,
                                void *object)
{
        setup_object_debug(s, page, object);
        object = kasan_init_slab_obj(s, object);
        if (unlikely(s->ctor)) {
                kasan_unpoison_object_data(s, object);
                s->ctor(object);
                kasan_poison_object_data(s, object);
        }
}

커널 옵션에 의한 디버깅과 런타임 메모리 디버깅을 위해 슬랩(Slub) object를 초기화한다.

  • 코드 라인 4에서 슬랩 디버깅을 위해 POISON, RED ZONE, 트래킹 데이터를 설치한다.
  • 코드 라인 5에서 런타임 메모리 디버거를 위해 shadow 영역을 0으로 초기화한다.
  • 코드 라인 6~10에서 적은 확률로 생성자가 준비된 슬랩 캐시인 경우 슬랩 object 생성자를 동작시킨다.

 

setup_object_debug()

mm/slub.c

/* Object debug checks for alloc/free paths */
static void setup_object_debug(struct kmem_cache *s, struct page *page,
                                                                void *object)
{
        if (!(s->flags & (SLAB_STORE_USER|SLAB_RED_ZONE|__OBJECT_POISON)))
                return;

        init_object(s, object, SLUB_RED_INACTIVE);
        init_tracking(s, object);
}

슬랩(slub) 디버깅을 위해 POISON, RED ZONE, 트래킹 데이터를 설치한다.

  • 코드 라인 5~6에서 슬랩(slub) 디버깅 플래그가 없는 경우 함수를 빠져나간다.
  • 코드 라인 8에서 POISON 데이터와 RED ZONE 데이터를 설치한다.
  • 코드 라인 9에서 alloc/free 유저 트래킹용 데이터를 초기화한다.

 

init_object()

mm/slub.c

static void init_object(struct kmem_cache *s, void *object, u8 val)
{
        u8 *p = object;
        if (s->flags & SLAB_RED_ZONE)
                memset(p - s->red_left_pad, val, s->red_left_pad);
        if (s->flags & __OBJECT_POISON) {
                memset(p, POISON_FREE, s->object_size - 1);
                p[s->object_size - 1] = POISON_END;
        }

        if (s->flags & SLAB_RED_ZONE)
                memset(p + s->object_size, val, s->inuse - s->object_size);
}

슬랩(Slub) object를 초기화할 때 poison_free(0x6b) 및 red-zone 용 @val값을 지정한다.

  • 코드 라인 4~5에서 좌측 red-zone 영역에 @val 값으로 채운다.
  • 코드 라인 6~9에서 object 내에 poison_free(0x6b) 값으로 채운다.
  • 코드 라인 11~12에서 object 우측의 red-zone 영역에 @val 값으로 채운다.

 

init_tracking()

mm/slub.c

static void init_tracking(struct kmem_cache *s, void *object)
{
        if (!(s->flags & SLAB_STORE_USER))
                return;

        set_track(s, object, TRACK_FREE, 0UL);
        set_track(s, object, TRACK_ALLOC, 0UL);
}

alloc/free 유저 트래킹을 위해 TRACK_FREE 및 TRACK_ALLOC 등 2개 영역의 데이터를 0으로 초기화한다.

 

set_track()

mm/slub.c

static void set_track(struct kmem_cache *s, void *object,
                        enum track_item alloc, unsigned long addr)
{
        struct track *p = get_track(s, object, alloc);

        if (addr) {
#ifdef CONFIG_STACKTRACE
                struct stack_trace trace;
                int i;

                trace.nr_entries = 0;
                trace.max_entries = TRACK_ADDRS_COUNT;
                trace.entries = p->addrs;
                trace.skip = 3;
                metadata_access_enable();
                save_stack_trace(&trace);
                metadata_access_disable();

                /* See rant in lockdep.c */
                if (trace.nr_entries != 0 &&
                    trace.entries[trace.nr_entries - 1] == ULONG_MAX)
                        trace.nr_entries--;

                for (i = trace.nr_entries; i < TRACK_ADDRS_COUNT; i++)
                        p->addrs[i] = 0;
#endif
                p->addr = addr;
                p->cpu = smp_processor_id();
                p->pid = current->pid;
                p->when = jiffies;
        } else
                memset(p, 0, sizeof(struct track));
}

트래킹을 하기 위한 호출 주소, cpu, pid, 시각에 대한 데이터를 저장한다.

  • CONFIG_STACKTRACE 커널 옵션을 사용하는 경우 slub 에서도 16개까지의 함수 역추적을 할 수 있도록 한다.

 


슬랩 object 할당 시 디버그 체크

슬랩 object가 할당될 때 호출되어 체크한다.

alloc_debug_processing()

mm/slub.c

static noinline int alloc_debug_processing(struct kmem_cache *s,
                                        struct page *page,
                                        void *object, unsigned long addr)
{
        if (s->flags & SLAB_CONSISTENCY_CHECKS) {
                if (!alloc_consistency_checks(s, page, object, addr))
                        goto bad;
        }

        /* Success perform special debug activities for allocs */
        if (s->flags & SLAB_STORE_USER)
                set_track(s, object, TRACK_ALLOC, addr);
        trace(s, page, object, 1);
        init_object(s, object, SLUB_RED_ACTIVE);
        return 1;

bad:
        if (PageSlab(page)) {
                /*
                 * If this is a slab page then lets do the best we can
                 * to avoid issues in the future. Marking all objects
                 * as used avoids touching the remaining objects.
                 */
                slab_fix(s, "Marking all objects used");
                page->inuse = page->objects;
                page->freelist = NULL;
        }
        return 0;
}

슬랩 object 할당 시 디버그 체크를 수행한다. 성공 시 1을 반환한다.

  • 코드 라인 5~8에서 “slub_debug=F,<슬랩캐시명>”으로 지정된 슬랩 캐시의 sanity 체크를 수행한다.
  • 코드 라인 11~12에서 “slub_debug=FU,<슬랩캐시명>”으로 지정된 슬랩 캐시의 object에 대한 할당 요청 owner 함수를 기록한다.
  • 코드 라인 13에서 “slub_debug=FT,<슬랩캐시명>”으로 지정된 슬랩 캐시의 트레이스를 수행한다.
    • “TRACE <슬랩명> alloc <object주소> inuse=xx fp=<FP 주소>”가 출력되고 “Object”의 16진수 값들이 덤프된다.
  • 코드 라인 14에서 슬랩(Slub) object를 초기화하면서 poison_free(0x6b) 및 red-zone 용 SLUB_RED_ACTIVE(0xcc)값을 지정한다.
  • 코드 라인 15에서 정상 값인 1을 반환한다.
  • 코드 라인 17에서 sanity 체크에서 실패한 경우 진입되는 bad: 레이블이다.
  • 코드 라인 18~28에서 해당 슬랩 페이지의 모든 free object들의 사용을 중단 시키기 위해 모두 사용 중으로 마크하고 실패 0을 반환한다.

 

슬랩 object 할당 시 consistency 체크

alloc_consistency_checks()

mm/slub.c

static inline int alloc_consistency_checks(struct kmem_cache *s,
                                        struct page *page,
                                        void *object, unsigned long addr)
{
        if (!check_slab(s, page))
                return 0;

        if (!check_valid_pointer(s, page, object)) {
                object_err(s, page, object, "Freelist Pointer check fails");
                return 0;
        }

        if (!check_object(s, page, object, SLUB_RED_INACTIVE))
                return 0;

        return 1;
}

@object 할당 전에 consistency 체크를 수행한다. 정상일 때 1을 반환한다.

  • 코드 라인 5~6에서 슬랩 페이지의 전체 object 수 및 사용 중인 object 수를 체크한다.
  • 코드 라인 8~11에서 @object 가상 주소가 슬랩 페이지 범위내에 포함되는지 여부를 조사한다.
  • 코드 라인 13~14에서 슬랩 object를 할당하기 전에 poison 값(0x6b) 및 red-zone 값(SLUB_RED_INACTIVE-0xbb)들이 잘 기록되어 있는지 체크한다.
  • 코드 라인 16에서 슬랩 object가 정상인 경우이며 1을 반환한다.

 


슬랩 object 할당 해제 시 디버그 체크

슬랩 object의 할당 해제 시 호출되어 체크한다.

free_debug_processing()

mm/slub.c

/* Supports checking bulk free of a constructed freelist */
static noinline int free_debug_processing(
        struct kmem_cache *s, struct page *page,
        void *head, void *tail, int bulk_cnt,
        unsigned long addr)
{
        struct kmem_cache_node *n = get_node(s, page_to_nid(page));
        void *object = head;
        int cnt = 0;
        unsigned long uninitialized_var(flags);
        int ret = 0;

        spin_lock_irqsave(&n->list_lock, flags);
        slab_lock(page);

        if (s->flags & SLAB_CONSISTENCY_CHECKS) {
                if (!check_slab(s, page))
                        goto out;
        }

next_object:
        cnt++;

        if (s->flags & SLAB_CONSISTENCY_CHECKS) {
                if (!free_consistency_checks(s, page, object, addr))
                        goto out;
        }

        if (s->flags & SLAB_STORE_USER)
                set_track(s, object, TRACK_FREE, addr);
        trace(s, page, object, 0);
        /* Freepointer not overwritten by init_object(), SLAB_POISON moved it */
        init_object(s, object, SLUB_RED_INACTIVE);

        /* Reached end of constructed freelist yet? */
        if (object != tail) {
                object = get_freepointer(s, object);
                goto next_object;
        }
        ret = 1;

out:
        if (cnt != bulk_cnt)
                slab_err(s, page, "Bulk freelist count(%d) invalid(%d)\n",
                         bulk_cnt, cnt);

        slab_unlock(page);
        spin_unlock_irqrestore(&n->list_lock, flags);
        if (!ret)
                slab_fix(s, "Object at 0x%p not freed", object);
        return ret;
}

슬랩 object 할당 해제시 디버그 체크를 수행한다. 성공 시 1을 반환한다.

  • 코드 라인 16~19에서 “slub_debug=F,<슬랩캐시명>”으로 지정된 슬랩 캐시의 해당 슬랩 페이지에 대한 sanity 체크를 수행한다.
  • 코드 라인 21~22에서 전체 free object를 체크하기 위해 반복되는 next_object: 레이블이다.
  • 코드 라인 24~27에서 “slub_debug=F,<슬랩캐시명>”으로 지정된 슬랩 캐시의 순회 중인 object에 대한 alloc sanity 체크를 수행한다.
  • 코드 라인 29~30에서 “slub_debug=FU,<슬랩캐시명>”으로 지정된 슬랩 캐시의 object에 대한 해제 요청 owner 함수를 기록한다.
  • 코드 라인 31에서 “slub_debug=FT,<슬랩캐시명>”으로 지정된 슬랩 캐시의 트레이스를 수행한다.
    • “TRACE <슬랩명> free <object주소> inuse=xx fp=<FP 주소>”가 출력되고 “Object”의 16진수 값들이 덤프된다.
  • 코드 라인 33에서 슬랩(Slub) object를 초기화하면서 poison_free(0x6b) 및 red-zone 용 SLUB_RED_INACTIVE(0xbb)값을 지정한다.
  • 코드 라인 36~39에서 @tail object까지 순회를 위해 next_object: 레이블로 이동한다.
  • 코드 라인 40에서 완료된 경우 정상 값인 1을 반환하도록 준비한다.
  • 코드 라인 42에서 함수를 빠져나갈때 진행될 out: 레이블이다.
  • 코드 라인 43~45에서 @head ~ @tail 까지의 object 수와 @bulk_cnt 수가 서로 다르면 에러 메시지를 출력한다.
  • 코드 라인 49~50에서 에러 발견 시 해당 object가 free되지 않았다고 에러 메시지를 출력한다.

 

슬랩 object 할당 해제시 consistency 체크

free_consistency_checks()

mm/slub.c

static inline int free_consistency_checks(struct kmem_cache *s,
                struct page *page, void *object, unsigned long addr)
{
        if (!check_valid_pointer(s, page, object)) {
                slab_err(s, page, "Invalid object pointer 0x%p", object);
                return 0;
        }

        if (on_freelist(s, page, object)) {
                object_err(s, page, object, "Object already free");
                return 0;
        }

        if (!check_object(s, page, object, SLUB_RED_ACTIVE))
                return 0;

        if (unlikely(s != page->slab_cache)) {
                if (!PageSlab(page)) {
                        slab_err(s, page, "Attempt to free object(0x%p) outside of slab",
                                 object);
                } else if (!page->slab_cache) {
                        pr_err("SLUB <none>: no slab for object 0x%p.\n",
                               object);
                        dump_stack();
                } else
                        object_err(s, page, object,
                                        "page slab pointer corrupt.");
                return 0;
        }
        return 1;
}

@object 할당 해제전에 consistency 체크를 수행한다. 정상일 때 1을 반환한다.

  • 코드 라인 4~7에서 @object 가상 주소가 슬랩 페이지 범위내에 포함되는지 여부를 조사한다.
  • 코드 라인 9~12에서 요청한 슬랩 페이지의 freelist 체인에서 @object까지 검색하며 각 free object들의 FP(Free Pointer) 값이 유효한지 체크한다.
  • 코드 라인 14~15에서 슬랩 object를 할당 해제하기 전에 red-zone 값(SLUB_RED_ACTIVE-0xcc)들이 잘 기록되어 있는지 체크한다. 또한 red-zone을 사용하지 않고 poison을 만을 사용하는 경우라면 object 다음에 위치한 패딩 위치에 poison 값(0x6b)이 잘 기록되어 있는지도 체크한다.
  • 코드 라인 17~29에서 슬렙 페이지가 슬랩 캐시 @s를 가리키지 않는 경우 에러를 출력하고 0을 반환한다.
  • 코드 라인 30에서 정상이므로 1을 반환한다.

 


공통 체크 함수

슬랩 체크

check_slab()

mm/slub.c

static int check_slab(struct kmem_cache *s, struct page *page)
{
        int maxobj;

        VM_BUG_ON(!irqs_disabled());

        if (!PageSlab(page)) {
                slab_err(s, page, "Not a valid slab page");
                return 0;
        }

        maxobj = order_objects(compound_order(page), s->size);
        if (page->objects > maxobj) {
                slab_err(s, page, "objects %u > max %u",
                        page->objects, maxobj);
                return 0;
        }
        if (page->inuse > page->objects) {
                slab_err(s, page, "inuse %u > max %u",
                        page->inuse, page->objects);
                return 0;
        }
        /* Slab_pad_check fixes things up after itself */
        slab_pad_check(s, page);
        return 1;
}

슬랩 페이지의 전체 object 수 및 사용 중인 object 수를 체크한다. 또한 슬랩 페이지에서 사용하지 않는 패딩 영역을 체크하고 교정한다. 성공 시 1을 반환한다.

  • 코드 라인 7~10에서 요청한 페이지가 슬랩 페이지가 아닌 경우 실패 0을 반환한다.
  • 코드 라인 12~17에서 최대 object 수가 잘못 지정된 경우 에러를 출력하고 0을 반환한다.
  • 코드 라인 18~22에서 사용 중인 object 수가 최대 object 수를 넘는 경우에도 에러를 출력하고 0을 반환한다.
  • 코드 라인 24에서 슬랩 페이지에서 사용하지 않는 패딩 영역을 체크하여 poison 값이 잘못 기록된 부분의 에러를 출력하고 교정한다.
  • 코드 라인 25에서 성공 1을 반환한다.

 

패드 영역 체크

slab_pad_check()

mm/slub.c

/* Check the pad bytes at the end of a slab page */
static int slab_pad_check(struct kmem_cache *s, struct page *page)
{
        u8 *start;
        u8 *fault;
        u8 *end;
        u8 *pad;
        int length;
        int remainder;

        if (!(s->flags & SLAB_POISON))
                return 1;

        start = page_address(page);
        length = PAGE_SIZE << compound_order(page);
        end = start + length;
        remainder = length % s->size;
        if (!remainder)
                return 1;

        pad = end - remainder;
        metadata_access_enable();
        fault = memchr_inv(pad, POISON_INUSE, remainder);
        metadata_access_disable();
        if (!fault)
                return 1;
        while (end > fault && end[-1] == POISON_INUSE)
                end--;

        slab_err(s, page, "Padding overwritten. 0x%p-0x%p", fault, end - 1);
        print_section(KERN_ERR, "Padding ", pad, remainder);

        restore_bytes(s, "slab padding", POISON_INUSE, fault, end);
        return 0;
}

슬랩 페이지에서 사용하지 않는 패딩 영역을 체크한다. 성공 시 1을 반환한다.

  • 코드 라인 11~12에서 poison을 사용하지 않는 경우 poison 값을 체크할 필요 없으므로 성공 1을 반환한다.
  • 코드 라인 14~19에서 슬랩 페이지에서 남는 영역이 없는 경우 성공 1을 반환한다.
  • 코드 라인 21~26에서 남는 패딩 영역에 POISON_INUSE(0x5a)값이 잘 기록되어 있는 경우 성공 1을 반환한다.
  • 코드 라인 27~34에서 실패한 위치의 주소를 찾아 “Padding overwritten” 에러를 출력하고, 잘못된 부분의 poison 값을 다시 기록하며 실패 0을 반환한다.

 

Object 체크

check_object()

mm/slub.c

static int check_object(struct kmem_cache *s, struct page *page,
                                        void *object, u8 val)
{
        u8 *p = object;
        u8 *endobject = object + s->object_size;

        if (s->flags & SLAB_RED_ZONE) {
                if (!check_bytes_and_report(s, page, object, "Redzone",
                        object - s->red_left_pad, val, s->red_left_pad))
                        return 0;

                if (!check_bytes_and_report(s, page, object, "Redzone",
                        endobject, val, s->inuse - s->object_size))
                        return 0;
        } else {
                if ((s->flags & SLAB_POISON) && s->object_size < s->inuse) {
                        check_bytes_and_report(s, page, p, "Alignment padding",
                                endobject, POISON_INUSE,
                                s->inuse - s->object_size);
                }
        }

        if (s->flags & SLAB_POISON) {
                if (val != SLUB_RED_ACTIVE && (s->flags & __OBJECT_POISON) &&
                        (!check_bytes_and_report(s, page, p, "Poison", p,
                                        POISON_FREE, s->object_size - 1) ||
                         !check_bytes_and_report(s, page, p, "Poison",
                                p + s->object_size - 1, POISON_END, 1)))
                        return 0;
                /*
                 * check_pad_bytes cleans up on its own.
                 */
                check_pad_bytes(s, page, p);
        }

        if (!s->offset && val == SLUB_RED_ACTIVE)
                /*
                 * Object and freepointer overlap. Cannot check
                 * freepointer while object is allocated.
                 */
                return 1;

        /* Check free pointer validity */
        if (!check_valid_pointer(s, page, get_freepointer(s, p))) {
                object_err(s, page, p, "Freepointer corrupt");
                /*
                 * No choice but to zap it and thus lose the remainder
                 * of the free objects in this slab. May cause
                 * another error because the object count is now wrong.
                 */
                set_freepointer(s, p, NULL);
                return 0;
        }
        return 1;
}

슬랩 object를 할당/해제하기 전에 poison 값 및 red-zone 값들이 잘 기록되어 있는지 체크한다. 성공 시 1을 반환한다.

  • 코드 라인 7~14에서 두 개의 red-zone 영역이 @val 값으로 채워져있는지 체크한다.
  • 코드 라인 15~21에서 red-zone을 사용하지 않지만 poison을 사용하는 경우 object 뒤에 위치한 alignment 패딩 영역에 POISON_INUSE(0x5a) 값이 잘 기록되어 있는지 체크한다.
  • 코드 라인 23~34에서 poison을 사용하는 경우 패딩 영역이 POISON_INUSE(0x5a)로 잘 기록되어 있는지 체크한다. 또한 object 할당을 위해 진입한 경우 object 영역에 POISON_FREE(0x6b) 값으로 채워져 있었는지 확인한다. 단 마지막 바이트에는 POISON_END(0xa5)가 위치하여야 한다.
  • 코드 라인 36~41에서 fp 이동 사유가 없어 fp를 가리키는 offset이 0이고, 할당 해제를 위해 진입한 경우 정상 1을 반환한다.
  • 코드 라인 44~53에서 다음 object를 가리키는 FP(Free Pointer) 값이 해당 슬랩 페이지의 주소 범위를 사용하는지 체크하며, 문제가 있는 경우 null 값을 기록하고, 실패 0을 반환한다.
  • 코드 라인 54에서 성공 1을 반환한다.

 

check_pad_bytes()

mm/slub.c

/*
 * Object layout:
 *
 * object address
 *      Bytes of the object to be managed.
 *      If the freepointer may overlay the object then the free
 *      pointer is the first word of the object.
 *
 *      Poisoning uses 0x6b (POISON_FREE) and the last byte is
 *      0xa5 (POISON_END)
 *
 * object + s->object_size
 *      Padding to reach word boundary. This is also used for Redzoning.
 *      Padding is extended by another word if Redzoning is enabled and
 *      object_size == inuse.
 *
 *      We fill with 0xbb (RED_INACTIVE) for inactive objects and with
 *      0xcc (RED_ACTIVE) for objects in use.
 *
 * object + s->inuse
 *      Meta data starts here.
 *
 *      A. Free pointer (if we cannot overwrite object on free)
 *      B. Tracking data for SLAB_STORE_USER
 *      C. Padding to reach required alignment boundary or at mininum
 *              one word if debugging is on to be able to detect writes
 *              before the word boundary.
 *
 *      Padding is done using 0x5a (POISON_INUSE)
 *
 * object + s->size
 *      Nothing is used beyond s->size.
 *
 * If slabcaches are merged then the object_size and inuse boundaries are mostly
 * ignored. And therefore no slab options that rely on these boundaries
 * may be used with merged slabcaches.
 */
static int check_pad_bytes(struct kmem_cache *s, struct page *page, u8 *p)
{
        unsigned long off = s->inuse;   /* The end of info */

        if (s->offset)
                /* Freepointer is placed after the object. */
                off += sizeof(void *);

        if (s->flags & SLAB_STORE_USER)
                /* We also have user information there */
                off += 2 * sizeof(struct track);

        off += kasan_metadata_size(s);

        if (size_from_object(s) == off)
                return 1;

        return check_bytes_and_report(s, page, p, "Object padding",
                        p + off, POISON_INUSE, size_from_object(s) - off);
}

object 다음에 위치한 alignment 패딩 영역의 poison 값들을 체크한다. 성공 시 1을 반환한다.

  • 코드 라인 3에서 off 값은 패딩 위치를 계산하기 위해 준비하며, 먼저 object와 object에 따른 alignment 값이 포함된 s->inuse 값을 지정한다.
  • 코드 라인 5~7에서 free object를 가리키는 FP(Free Pointer)의 위치 값이 주어진 경우 그 영역을 넘어선다.
  • 코드 라인 9~11에서 유저 트래킹을 사용하는 경우 해당 영역을 넘어선다.
  • 코드 라인 13에서 KASAN에서 사용하는 영역을 넘어선다.
  • 코드 라인 15~16에서 정확히 alignment되어 최후의 패딩이 필요 없는 경우 성공 1을 반환한다.
  • 코드 라인 18~19에서 마지막 alignment 패딩 영역이 POISON_INUSE(0x5a) 값으로 채워져 있는지 체크한다.

 

check_bytes_and_report()

mm/slub.c

static int check_bytes_and_report(struct kmem_cache *s, struct page *page,
                        u8 *object, char *what,
                        u8 *start, unsigned int value, unsigned int bytes)
{
        u8 *fault;
        u8 *end;

        metadata_access_enable();
        fault = memchr_inv(start, value, bytes);
        metadata_access_disable();
        if (!fault)
                return 1;

        end = start + bytes;
        while (end > fault && end[-1] == value)
                end--;

        slab_bug(s, "%s overwritten", what);
        pr_err("INFO: 0x%p-0x%p. First byte 0x%x instead of 0x%x\n",
                                        fault, end - 1, fault[0], value);
        print_trailer(s, page, object);

        restore_bytes(s, what, value, fault, end);
        return 0;
}

@start 주소부터 @bytes만큼 @value값이 기록되어 있는지 체크한다. 성공 시 1을 반환하고, 문제가 있는 경우 에러 메시지를 출력하고, 덤프하며 0을 반환한다. 다음과 같은 에러 명을 출력하기 위해 사용된다.

  • “Object padding overwritten”
  • “Redzone overwritten”
  • “Alignment padding overwritten”
  • “Poison overwritten”

 

freelist 체크

on_freelist()

mm/slub.c

/*
 * Determine if a certain object on a page is on the freelist. Must hold the
 * slab lock to guarantee that the chains are in a consistent state.
 */
static int on_freelist(struct kmem_cache *s, struct page *page, void *search)
{
        int nr = 0;
        void *fp;
        void *object = NULL;
        int max_objects;

        fp = page->freelist;
        while (fp && nr <= page->objects) {
                if (fp == search)
                        return 1;
                if (!check_valid_pointer(s, page, fp)) {
                        if (object) {
                                object_err(s, page, object,
                                        "Freechain corrupt");
                                set_freepointer(s, object, NULL);
                        } else {
                                slab_err(s, page, "Freepointer corrupt");
                                page->freelist = NULL;
                                page->inuse = page->objects;
                                slab_fix(s, "Freelist cleared");
                                return 0;
                        }
                        break;
                }
                object = fp;
                fp = get_freepointer(s, object);
                nr++;
        }

        max_objects = order_objects(compound_order(page), s->size);
        if (max_objects > MAX_OBJS_PER_PAGE)
                max_objects = MAX_OBJS_PER_PAGE;

        if (page->objects != max_objects) {
                slab_err(s, page, "Wrong number of objects. Found %d but should be %d",
                         page->objects, max_objects);
                page->objects = max_objects;
                slab_fix(s, "Number of objects adjusted.");
        }
        if (page->inuse != page->objects - nr) {
                slab_err(s, page, "Wrong object count. Counter is %d but counted were %d",
                         page->inuse, page->objects - nr);
                page->inuse = page->objects - nr;
                slab_fix(s, "Object count adjusted.");
        }
        return search == NULL;
}

요청한 슬랩 페이지의 freelist 체인에서 @search free object까지 검색하며 각 free object들의 FP(Free Pointer) 값이 유효한지 체크한다. 성공 시 1을 반환하며, @search object가 발견되지 못하거나 FP 값에 문제가 있는 경우 실패 0을 반환한다. (@search가 null인 경우 끝 까지 검색한다)

  • 코드 라인 8~29에서 슬랩 페이지의 freelist에 연결된 모든 free object들을 @search object가 발견될 떄 까지 순회하며 FP(Free Pointer) 값들의 유효함을 체크한다. @search object가 발견되는 경우 정상 1을 반환한다. 만일 문제가 있는 경우 문제가 발생된 free object 뒤의 연결을 끊고 “Freechain corrupt” 에러를 출력한다.
  • 코드 라인 31~40에서 order 페이지에 포함가능한 object 수가 s->objects와 동일한지 체크하며, 다른 경우 “Wrong number of objects. Found %d but should be %d” 메시지를 출력하고, s->objects 값을 교정한다.
  • 코드 라인 41~46에서 사용 중인 object 수가 다른 경우에도 “Wrong object count. Counter is %d but counted were %d” 메시지를 출력하고, s->inuse 값을 교정한다.
  • 코드 라인 47에서 입력 인자 @search가 null로 진입한 경우에만 성공 1을 반환하고, 그렇지 않고 @search object를 지정하였지만 못 찾은 경우 실패 0을 반환한다.

 

check_valid_pointer()

mm/slub.c

/* Verify that a pointer has an address that is valid within a slab page */
static inline int check_valid_pointer(struct kmem_cache *s,
                                struct page *page, void *object)
{
        void *base;

        if (!object)
                return 1;

        base = page_address(page);
        object = kasan_reset_tag(object);
        object = restore_red_left(s, object);
        if (object < base || object >= base + page->objects * s->size ||
                (object - base) % s->size) {
                return 0;
        }

        return 1;
}

@object 가상 주소가 슬랩 페이지 범위내에 포함되는지 여부를 조사한다. 정상일 때 1을 반환한다.

 


디버깅 사례

 

Object already free

슬랩 object를 두 번 free 할 때 이를 알아내어 출력한다.

[ 2202.358934] =============================================================================
[ 2202.362125] BUG jake (Tainted: G    B   W  O     ): Object already free
[ 2202.365019] -----------------------------------------------------------------------------
[ 2202.365019]
[ 2202.369155] INFO: Allocated in foo_init+0x54/0x1000 [poison] age=131 cpu=0 pid=6663
[ 2202.372442]  __slab_alloc+0x24/0x34
[ 2202.374002]  kmem_cache_alloc+0x1b8/0x264
[ 2202.375754]  foo_init+0x54/0x1000 [poison]
[ 2202.377567]  do_one_initcall+0x48/0x160
[ 2202.379247]  do_init_module+0x54/0x1c4
[ 2202.380937]  load_module+0x1ec8/0x20ac
[ 2202.382578]  __se_sys_finit_module+0xc8/0xdc
[ 2202.384429]  __arm64_sys_finit_module+0x18/0x20
[ 2202.386411]  el0_svc_common+0xc0/0x100
[ 2202.388060]  el0_svc_handler+0x2c/0x70
[ 2202.389726]  el0_svc+0x8/0xc
[ 2202.390985] INFO: Freed in foo_init+0xfc/0x1000 [poison] age=12 cpu=0 pid=6663
[ 2202.394121]  kmem_cache_free+0x220/0x230
[ 2202.395815]  foo_init+0xfc/0x1000 [poison]
[ 2202.397603]  do_one_initcall+0x48/0x160
[ 2202.399291]  do_init_module+0x54/0x1c4
[ 2202.400931]  load_module+0x1ec8/0x20ac
[ 2202.402580]  __se_sys_finit_module+0xc8/0xdc
[ 2202.404422]  __arm64_sys_finit_module+0x18/0x20
[ 2202.406395]  el0_svc_common+0xc0/0x100
[ 2202.408001]  el0_svc_handler+0x2c/0x70
[ 2202.409637]  el0_svc+0x8/0xc
[ 2202.410917] INFO: Slab 0xffff7e0000e5cf00 objects=20 used=0 fp=0xffff80003973c008 flags=0xfffc00000010201
[ 2202.415035] INFO: Object 0xffff80003973c008 @offset=8 fp=0xffff80003973dd20
[ 2202.415035]
[ 2202.418671] Redzone ffff80003973c000: bb bb bb bb bb bb bb bb                          ........
[ 2202.422397] Object ffff80003973c008: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b  kkkkkkkkkkkkkkkk
[ 2202.426349] Object ffff80003973c018: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b a5        kkkkkkkkkkkkk.
[ 2202.430291] Redzone ffff80003973c026: bb bb                                            ..
[ 2202.433803] Padding ffff80003973c160: 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a  ZZZZZZZZZZZZZZZZ
[ 2202.437785] Padding ffff80003973c170: 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a  ZZZZZZZZZZZZZZZZ
[ 2202.441817] Padding ffff80003973c180: 5a 5a 5a 5a 5a 5a 5a 5a                          ZZZZZZZZ
[ 2202.445556] CPU: 0 PID: 6663 Comm: insmod Tainted: G    B   W  O      5.0.0+ #24
[ 2202.448676] Hardware name: linux,dummy-virt (DT)
[ 2202.450626] Call trace:
[ 2202.451732]  dump_backtrace+0x0/0x154
[ 2202.453299]  show_stack+0x14/0x1c
[ 2202.454739]  dump_stack+0x88/0xb0
[ 2202.456150]  print_trailer+0x108/0x194
[ 2202.457787]  object_err+0x3c/0x4c
[ 2202.459198]  free_debug_processing+0x278/0x484
[ 2202.461125]  __slab_free+0x74/0x4b0
[ 2202.462602]  kmem_cache_free+0x220/0x230
[ 2202.464400]  foo_init+0x108/0x1000 [poison]
[ 2202.466332]  do_one_initcall+0x48/0x160
[ 2202.468010]  do_init_module+0x54/0x1c4
[ 2202.469673]  load_module+0x1ec8/0x20ac
[ 2202.471324]  __se_sys_finit_module+0xc8/0xdc
[ 2202.473174]  __arm64_sys_finit_module+0x18/0x20
[ 2202.475137]  el0_svc_common+0xc0/0x100
[ 2202.476783]  el0_svc_handler+0x2c/0x70
[ 2202.478387]  el0_svc+0x8/0xc
[ 2202.484819] FIX jake: Object at 0xffff80003973c008 not freed

 

Poison overwritten

free된 32바이트 object에 1바이트를 기록하였다.

  • foo_init+0x58/0x1000에서 free된 후 데이터가 기록되어 문제가 있었다는 것을 슬랩 object가 다시 할당(alloc)될 때 알아내어 출력한다.
[ 1774.452218] =============================================================================
[ 1774.455625] BUG jake (Tainted: G    B   W  O     ): Poison overwritten
[ 1774.458420] -----------------------------------------------------------------------------
[ 1774.458420]
[ 1774.462528] INFO: 0xffff8000364ae008-0xffff8000364ae008. First byte 0x11 instead of 0x6b
[ 1774.465971] INFO: Allocated in foo_init+0x54/0x1000 [poison] age=132 cpu=0 pid=5940
[ 1774.469231]  __slab_alloc+0x24/0x34
[ 1774.470709]  kmem_cache_alloc+0x1b8/0x264
[ 1774.472424]  foo_init+0x54/0x1000 [poison]
[ 1774.474216]  do_one_initcall+0x48/0x160
[ 1774.475892]  do_init_module+0x54/0x1c4
[ 1774.477545]  load_module+0x1ec8/0x20ac
[ 1774.479150]  __se_sys_finit_module+0xc8/0xdc
[ 1774.480982]  __arm64_sys_finit_module+0x18/0x20
[ 1774.482968]  el0_svc_common+0xc0/0x100
[ 1774.484590]  el0_svc_handler+0x2c/0x70
[ 1774.486226]  el0_svc+0x8/0xc
[ 1774.487470] INFO: Freed in foo_init+0xfc/0x1000 [poison] age=11 cpu=0 pid=5940
[ 1774.490571]  kmem_cache_free+0x220/0x230
[ 1774.492256]  foo_init+0xfc/0x1000 [poison]
[ 1774.494028]  do_one_initcall+0x48/0x160
[ 1774.495697]  do_init_module+0x54/0x1c4
[ 1774.497343]  load_module+0x1ec8/0x20ac
[ 1774.498957]  __se_sys_finit_module+0xc8/0xdc
[ 1774.500790]  __arm64_sys_finit_module+0x18/0x20
[ 1774.502726]  el0_svc_common+0xc0/0x100
[ 1774.504325]  el0_svc_handler+0x2c/0x70
[ 1774.505964]  el0_svc+0x8/0xc
[ 1774.507206] INFO: Slab 0xffff7e0000d92b80 objects=20 used=20 fp=0x0000000000000000 flags=0xfffc00000010200
[ 1774.511296] INFO: Object 0xffff8000364ae008 @offset=8 fp=0xffff8000364afd20
[ 1774.511296]
[ 1774.514915] Redzone ffff8000364ae000: bb bb bb bb bb bb bb bb                          ........
[ 1774.518603] Object ffff8000364ae008: 11 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b  .kkkkkkkkkkkkkkk
[ 1774.522580] Object ffff8000364ae018: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b a5        kkkkkkkkkkkkk.
[ 1774.526446] Redzone ffff8000364ae026: bb bb                                            ..
[ 1774.529898] Padding ffff8000364ae160: 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a  ZZZZZZZZZZZZZZZZ
[ 1774.533904] Padding ffff8000364ae170: 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a  ZZZZZZZZZZZZZZZZ
[ 1774.537913] Padding ffff8000364ae180: 5a 5a 5a 5a 5a 5a 5a 5a                          ZZZZZZZZ
[ 1774.541595] CPU: 0 PID: 5940 Comm: insmod Tainted: G    B   W  O      5.0.0+ #24
[ 1774.544697] Hardware name: linux,dummy-virt (DT)
[ 1774.546662] Call trace:
[ 1774.547769]  dump_backtrace+0x0/0x154
[ 1774.549315]  show_stack+0x14/0x1c
[ 1774.550895]  dump_stack+0x88/0xb0
[ 1774.552466]  print_trailer+0x108/0x194
[ 1774.554235]  check_bytes_and_report+0xe0/0x124
[ 1774.556293]  check_object+0x210/0x258
[ 1774.558037]  alloc_debug_processing+0x154/0x1c4
[ 1774.560215]  ___slab_alloc+0x89c/0x8bc
[ 1774.562112]  __slab_alloc+0x24/0x34
[ 1774.563769]  kmem_cache_alloc+0x1b8/0x264
[ 1774.565689]  foo_init+0x110/0x1000 [poison]
[ 1774.567645]  do_one_initcall+0x48/0x160
[ 1774.569487]  do_init_module+0x54/0x1c4
[ 1774.571259]  load_module+0x1ec8/0x20ac
[ 1774.573014]  __se_sys_finit_module+0xc8/0xdc
[ 1774.575059]  __arm64_sys_finit_module+0x18/0x20
[ 1774.577203]  el0_svc_common+0xc0/0x100
[ 1774.578981]  el0_svc_handler+0x2c/0x70
[ 1774.580742]  el0_svc+0x8/0xc
[ 1774.582118] FIX jake: Restoring 0xffff8000364ae008-0xffff8000364ae008=0x6b

 

Redzone overwritten

32바이트 object 사이즈 좌측에 있는 red-zone의 끝 1바이트를 침범당하였다.

  • foo_init+0x58/0x1000에서 할당된 후 red-zone이 침범되었다는 것을 슬랩 object를 free할 때 알아내어 출력한다.
[  901.853653] =============================================================================
[  901.857016] BUG jake (Tainted: G    B   W  O     ): Redzone overwritten
[  901.860153] -----------------------------------------------------------------------------
[  901.860153]
[  901.864665] INFO: 0xffff80003971c007-0xffff80003971c007. First byte 0x11 instead of 0xcc
[  901.868451] INFO: Allocated in foo_init+0x54/0x1000 [poison] age=139 cpu=0 pid=4085
[  901.872126]  __slab_alloc+0x24/0x34
[  901.873805]  kmem_cache_alloc+0x1b8/0x264
[  901.875729]  foo_init+0x54/0x1000 [poison]
[  901.877650]  do_one_initcall+0x48/0x160
[  901.879484]  do_init_module+0x54/0x1c4
[  901.881251]  load_module+0x1ec8/0x20ac
[  901.883053]  __se_sys_finit_module+0xc8/0xdc
[  901.885051]  __arm64_sys_finit_module+0x18/0x20
[  901.887255]  el0_svc_common+0xc0/0x100
[  901.888996]  el0_svc_handler+0x2c/0x70
[  901.890799]  el0_svc+0x8/0xc
[  901.892227] INFO: Slab 0xffff7e0000e5c700 objects=20 used=1 fp=0xffff80003971dd20 flags=0xfffc00000010201
[  901.896674] INFO: Object 0xffff80003971c008 @offset=8 fp=0xffff80003971c190
[  901.896674]
[  901.900681] Redzone ffff80003971c000: cc cc cc cc cc cc cc 11                          ........
[  901.904770] Object ffff80003971c008: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  901.909141] Object ffff80003971c018: 00 00 00 00 00 00 00 00 00 00 00 00 00 00        ..............
[  901.913406] Redzone ffff80003971c026: cc cc                                            ..
[  901.917241] Padding ffff80003971c160: 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a  ZZZZZZZZZZZZZZZZ
[  901.921614] Padding ffff80003971c170: 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a  ZZZZZZZZZZZZZZZZ
[  901.926018] Padding ffff80003971c180: 5a 5a 5a 5a 5a 5a 5a 5a                          ZZZZZZZZ
[  901.930064] CPU: 0 PID: 4085 Comm: insmod Tainted: G    B   W  O      5.0.0+ #24
[  901.933449] Hardware name: linux,dummy-virt (DT)
[  901.935553] Call trace:
[  901.936723]  dump_backtrace+0x0/0x154
[  901.938436]  show_stack+0x14/0x1c
[  901.939978]  dump_stack+0x88/0xb0
[  901.941523]  print_trailer+0x108/0x194
[  901.943253]  check_bytes_and_report+0xe0/0x124
[  901.945320]  check_object+0x184/0x258
[  901.946981]  free_debug_processing+0x1e0/0x484
[  901.949706]  __slab_free+0x74/0x4b0
[  901.951371]  kmem_cache_free+0x220/0x230
[  901.953287]  foo_init+0x104/0x1000 [poison]
[  901.955290]  do_one_initcall+0x48/0x160
[  901.957125]  do_init_module+0x54/0x1c4
[  901.958932]  load_module+0x1ec8/0x20ac
[  901.960718]  __se_sys_finit_module+0xc8/0xdc
[  901.962721]  __arm64_sys_finit_module+0x18/0x20
[  901.964827]  el0_svc_common+0xc0/0x100
[  901.966606]  el0_svc_handler+0x2c/0x70
[  901.968403]  el0_svc+0x8/0xc
[  901.969785] FIX jake: Restoring 0xffff80003971c007-0xffff80003971c007=0xcc
[  901.969785]
[  901.978507] FIX jake: Object at 0xffff80003971c008 not freed

 

32바이트 object 사이즈이지만 2자를 더 overwrite하여 우측 red-zone이 침범당하였다.

  • foo_init+0x54/0x1000에서 할당된 후 red-zone이 침범되었다는 것을 슬랩 object를 free할 때 알아내어 출력한다.
[ 1416.793614] =============================================================================
[ 1416.796913] BUG jake (Tainted: G    B   W  O     ): Redzone overwritten
[ 1416.799971] -----------------------------------------------------------------------------
[ 1416.799971]
[ 1416.804445] INFO: 0xffff80003b1c6026-0xffff80003b1c6027. First byte 0x11 instead of 0xcc
[ 1416.808208] INFO: Allocated in foo_init+0x54/0x1000 [poison] age=126 cpu=0 pid=4905
[ 1416.811812]  __slab_alloc+0x24/0x34
[ 1416.813468]  kmem_cache_alloc+0x1b8/0x264
[ 1416.815368]  foo_init+0x54/0x1000 [poison]
[ 1416.817276]  do_one_initcall+0x48/0x160
[ 1416.819100]  do_init_module+0x54/0x1c4
[ 1416.820886]  load_module+0x1ec8/0x20ac
[ 1416.822650]  __se_sys_finit_module+0xc8/0xdc
[ 1416.824629]  __arm64_sys_finit_module+0x18/0x20
[ 1416.826814]  el0_svc_common+0xc0/0x100
[ 1416.828593]  el0_svc_handler+0x2c/0x70
[ 1416.830363]  el0_svc+0x8/0xc
[ 1416.831725] INFO: Slab 0xffff7e0000ec7180 objects=20 used=1 fp=0xffff80003b1c7d20 flags=0xfffc00000010201
[ 1416.836152] INFO: Object 0xffff80003b1c6008 @offset=8 fp=0xffff80003b1c6190
[ 1416.836152]
[ 1416.840133] Redzone ffff80003b1c6000: cc cc cc cc cc cc cc cc                          ........
[ 1416.844160] Object ffff80003b1c6008: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[ 1416.848511] Object ffff80003b1c6018: 00 00 00 00 00 00 00 00 00 00 00 00 00 00        ..............
[ 1416.852767] Redzone ffff80003b1c6026: 11 11                                            ..
[ 1416.856576] Padding ffff80003b1c6160: 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a  ZZZZZZZZZZZZZZZZ
[ 1416.860998] Padding ffff80003b1c6170: 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a  ZZZZZZZZZZZZZZZZ
[ 1416.865338] Padding ffff80003b1c6180: 5a 5a 5a 5a 5a 5a 5a 5a                          ZZZZZZZZ
[ 1416.869421] CPU: 0 PID: 4905 Comm: insmod Tainted: G    B   W  O      5.0.0+ #24
[ 1416.872812] Hardware name: linux,dummy-virt (DT)
[ 1416.875023] Call trace:
[ 1416.876164]  dump_backtrace+0x0/0x154
[ 1416.877885]  show_stack+0x14/0x1c
[ 1416.879479]  dump_stack+0x88/0xb0
[ 1416.881010]  print_trailer+0x108/0x194
[ 1416.882789]  check_bytes_and_report+0xe0/0x124
[ 1416.884901]  check_object+0x1b4/0x258
[ 1416.886653]  free_debug_processing+0x1e0/0x484
[ 1416.888706]  __slab_free+0x74/0x4b0
[ 1416.890352]  kmem_cache_free+0x220/0x230
[ 1416.892196]  foo_init+0x104/0x1000 [poison]
[ 1416.894140]  do_one_initcall+0x48/0x160
[ 1416.895959]  do_init_module+0x54/0x1c4
[ 1416.897739]  load_module+0x1ec8/0x20ac
[ 1416.899555]  __se_sys_finit_module+0xc8/0xdc
[ 1416.901553]  __arm64_sys_finit_module+0x18/0x20
[ 1416.903754]  el0_svc_common+0xc0/0x100
[ 1416.905550]  el0_svc_handler+0x2c/0x70
[ 1416.907376]  el0_svc+0x8/0xc
[ 1416.908711] FIX jake: Restoring 0xffff80003b1c6026-0xffff80003b1c6027=0xcc
[ 1416.908711]
[ 1416.917035] FIX jake: Object at 0xffff80003b1c6008 not freed

 

Object padding overwritten

패딩 위치에 1바이트를 기록하여 패딩을 침범하였다.

  • foo_init+0x10c/0x1000에서 슬랩 object를 free할 때 패딩이 침범되었다는 것을 알아내어 출력한다.
[  641.785286] =============================================================================
[  641.788638] BUG jake (Tainted: G    B   W  O     ): Object padding overwritten
[  641.791951] -----------------------------------------------------------------------------
[  641.791951]
[  641.796458] INFO: 0xffff80003b2e4187-0xffff80003b2e4187. First byte 0x11 instead of 0x5a
[  641.800288] INFO: Allocated in foo_init+0x54/0x1000 [poison] age=124 cpu=0 pid=3812
[  641.803902]  __slab_alloc+0x24/0x34
[  641.805508]  kmem_cache_alloc+0x1b8/0x264
[  641.807441]  foo_init+0x54/0x1000 [poison]
[  641.809350]  do_one_initcall+0x48/0x160
[  641.811194]  do_init_module+0x54/0x1c4
[  641.812974]  load_module+0x1ec8/0x20ac
[  641.814741]  __se_sys_finit_module+0xc8/0xdc
[  641.816755]  __arm64_sys_finit_module+0x18/0x20
[  641.818906]  el0_svc_common+0xc0/0x100
[  641.820700]  el0_svc_handler+0x2c/0x70
[  641.822490]  el0_svc+0x8/0xc
[  641.823938] INFO: Slab 0xffff7e0000ecb900 objects=20 used=1 fp=0xffff80003b2e5d20 flags=0xfffc00000010201
[  641.828332] INFO: Object 0xffff80003b2e4008 @offset=8 fp=0xffff80003b2e4190
[  641.828332]
[  641.832301] Redzone ffff80003b2e4000: cc cc cc cc cc cc cc cc                          ........
[  641.836372] Object ffff80003b2e4008: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  641.840714] Object ffff80003b2e4018: 00 00 00 00 00 00 00 00 00 00 00 00 00 00        ..............
[  641.844984] Redzone ffff80003b2e4026: cc cc                                            ..
[  641.848801] Padding ffff80003b2e4160: 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a  ZZZZZZZZZZZZZZZZ
[  641.853194] Padding ffff80003b2e4170: 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a  ZZZZZZZZZZZZZZZZ
[  641.857547] Padding ffff80003b2e4180: 5a 5a 5a 5a 5a 5a 5a 11                          ZZZZZZZ.
[  641.861674] CPU: 0 PID: 3812 Comm: insmod Tainted: G    B   W  O      5.0.0+ #24
[  641.865067] Hardware name: linux,dummy-virt (DT)
[  641.867282] Call trace:
[  641.868449]  dump_backtrace+0x0/0x154
[  641.870200]  show_stack+0x14/0x1c
[  641.871793]  dump_stack+0x88/0xb0
[  641.873373]  print_trailer+0x108/0x194
[  641.875134]  check_bytes_and_report+0xe0/0x124
[  641.877221]  check_object+0xb4/0x258
[  641.878925]  free_debug_processing+0x1e0/0x484
[  641.880980]  __slab_free+0x74/0x4b0
[  641.882649]  kmem_cache_free+0x220/0x230
[  641.884519]  foo_init+0x104/0x1000 [poison]
[  641.886502]  do_one_initcall+0x48/0x160
[  641.888325]  do_init_module+0x54/0x1c4
[  641.890102]  load_module+0x1ec8/0x20ac
[  641.891877]  __se_sys_finit_module+0xc8/0xdc
[  641.893882]  __arm64_sys_finit_module+0x18/0x20
[  641.896013]  el0_svc_common+0xc0/0x100
[  641.897772]  el0_svc_handler+0x2c/0x70
[  641.899587]  el0_svc+0x8/0xc
[  641.900994] FIX jake: Restoring 0xffff80003b2e4187-0xffff80003b2e4187=0x5a

 

샘플 소스 – poison.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/slab.h>
#include <linux/version.h>      /* LINUX_VERSION_CODE & KERNEL_VERSION() */
#include <linux/printk.h>

static int __init foo_init(void)
{
        int ret = 0;
        void *obj;
        int size = 768;

        printk("%s\n", __func__);

        s = kmem_cache_create("jake", OBJ_SIZE, OBJ_ALIGN, 0, NULL);

        obj = kmem_cache_alloc(s, 0);

        printk(KERN_ERR "\n");
        printk(KERN_ERR "--------------------------------------------------\n");
        print_hex_dump(KERN_ERR, "", DUMP_PREFIX_ADDRESS, 16, 1, obj, size, 1);

        memset(obj, 0, OBJ_SIZE);

        // memset(obj - 1, 0x11, 1);    /* Redzone overwritten */

        // memset(obj + 383, 0x11, 1);  /* Object padding overwritten */

        printk(KERN_ERR "\n");
        printk(KERN_ERR "--------------------------------------------------\n");
        print_hex_dump(KERN_ERR, "", DUMP_PREFIX_ADDRESS, 16, 1, obj, size, 1);

        kmem_cache_free(s, obj);

        // kmem_cache_free(s, obj);     /* Object already free */

        // memset(obj, 0x11, 1);        /* Poison overwritten */
        // obj = kmem_cache_alloc(s, 0);
        // kmem_cache_free(s, obj);

        printk(KERN_ERR "\n");
        printk(KERN_ERR "--------------------------------------------------\n");
        print_hex_dump(KERN_ERR, "", DUMP_PREFIX_ADDRESS, 16, 1, obj, size, 1);

        return ret;     /* 0=success */
}

static void __exit foo_exit(void)
{

        printk("%s\n", __func__);

        kmem_cache_destroy(s);
}

module_init(foo_init);
module_exit(foo_exit);
MODULE_LICENSE("GPL");

 

다음은 정상 출력 결과이다.

$ insmod poison.ko
[81403.699138] foo_init
[  448.848125] --------------------------------------------------
[  448.968221] ffff80003b1b6008: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b  kkkkkkkkkkkkkkkk
[  448.978732] ffff80003b1b6018: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b a5 cc cc  kkkkkkkkkkkkk...
[  448.985722] ffff80003b1b6028: 90 61 1b 3b 00 80 ff ff 54 a0 b8 08 00 00 ff ff  .a.;....T.......
[  448.988850] ffff80003b1b6038: 5c b0 22 10 00 00 ff ff 24 b2 22 10 00 00 ff ff  \.".....$.".....
[  448.993154] ffff80003b1b6048: 54 a0 b8 08 00 00 ff ff 18 42 08 10 00 00 ff ff  T........B......
[  448.996288] ffff80003b1b6058: 18 19 16 10 00 00 ff ff e0 39 16 10 00 00 ff ff  .........9......
[  449.000385] ffff80003b1b6068: 80 3e 16 10 00 00 ff ff cc 3e 16 10 00 00 ff ff  .>.......>......
[  449.004501] ffff80003b1b6078: 04 4e 09 10 00 00 ff ff 70 4e 09 10 00 00 ff ff  .N......pN......
[  449.008632] ffff80003b1b6088: 48 40 08 10 00 00 ff ff 00 00 00 00 00 00 00 00  H@..............
[  449.012671] ffff80003b1b6098: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.016834] ffff80003b1b60a8: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.020962] ffff80003b1b60b8: 00 00 00 00 d0 0d 00 00 25 91 00 00 01 00 00 00  ........%.......
[  449.025210] ffff80003b1b60c8: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.028337] ffff80003b1b60d8: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.032464] ffff80003b1b60e8: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.036560] ffff80003b1b60f8: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.040701] ffff80003b1b6108: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.044866] ffff80003b1b6118: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.049156] ffff80003b1b6128: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.052225] ffff80003b1b6138: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.056406] ffff80003b1b6148: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.060468] ffff80003b1b6158: 00 00 00 00 00 00 00 00 5a 5a 5a 5a 5a 5a 5a 5a  ........ZZZZZZZZ
[  449.064601] ffff80003b1b6168: 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a  ZZZZZZZZZZZZZZZZ
[  449.068740] ffff80003b1b6178: 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a  ZZZZZZZZZZZZZZZZ
[  449.072893] ffff80003b1b6188: bb bb bb bb bb bb bb bb 6b 6b 6b 6b 6b 6b 6b 6b  ........kkkkkkkk
[  449.077104] ffff80003b1b6198: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b  kkkkkkkkkkkkkkkk
[  449.080262] ffff80003b1b61a8: 6b 6b 6b 6b 6b a5 bb bb 00 00 00 00 00 00 00 00  kkkkk...........
[  449.084328] ffff80003b1b61b8: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.088401] ffff80003b1b61c8: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.092573] ffff80003b1b61d8: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.096696] ffff80003b1b61e8: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.100803] ffff80003b1b61f8: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.104885] ffff80003b1b6208: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.109140] ffff80003b1b6218: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.112250] ffff80003b1b6228: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.116340] ffff80003b1b6238: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.120410] ffff80003b1b6248: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.124536] ffff80003b1b6258: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.128709] ffff80003b1b6268: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.132914] ffff80003b1b6278: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.137103] ffff80003b1b6288: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.140199] ffff80003b1b6298: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.144304] ffff80003b1b62a8: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.148720] ffff80003b1b62b8: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.152893] ffff80003b1b62c8: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.157051] ffff80003b1b62d8: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.160192] ffff80003b1b62e8: 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a  ZZZZZZZZZZZZZZZZ
[  449.164503] ffff80003b1b62f8: 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a  ZZZZZZZZZZZZZZZZ
[  449.168664]
[  449.169693] --------------------------------------------------
[  449.171833] ffff80003b1b6008: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.176219] ffff80003b1b6018: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 cc cc  ................
[  449.180475] ffff80003b1b6028: 90 61 1b 3b 00 80 ff ff 54 a0 b8 08 00 00 ff ff  .a.;....T.......
[  449.184559] ffff80003b1b6038: 5c b0 22 10 00 00 ff ff 24 b2 22 10 00 00 ff ff  \.".....$.".....
[  449.188792] ffff80003b1b6048: 54 a0 b8 08 00 00 ff ff 18 42 08 10 00 00 ff ff  T........B......
[  449.192897] ffff80003b1b6058: 18 19 16 10 00 00 ff ff e0 39 16 10 00 00 ff ff  .........9......
[  449.197151] ffff80003b1b6068: 80 3e 16 10 00 00 ff ff cc 3e 16 10 00 00 ff ff  .>.......>......
[  449.200274] ffff80003b1b6078: 04 4e 09 10 00 00 ff ff 70 4e 09 10 00 00 ff ff  .N......pN......
[  449.204465] ffff80003b1b6088: 48 40 08 10 00 00 ff ff 00 00 00 00 00 00 00 00  H@..............
[  449.208564] ffff80003b1b6098: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.212645] ffff80003b1b60a8: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.216746] ffff80003b1b60b8: 00 00 00 00 d0 0d 00 00 25 91 00 00 01 00 00 00  ........%.......
[  449.220898] ffff80003b1b60c8: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.225150] ffff80003b1b60d8: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.228231] ffff80003b1b60e8: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.232343] ffff80003b1b60f8: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.236527] ffff80003b1b6108: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.240811] ffff80003b1b6118: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.244950] ffff80003b1b6128: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.249140] ffff80003b1b6138: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.252289] ffff80003b1b6148: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.256440] ffff80003b1b6158: 00 00 00 00 00 00 00 00 5a 5a 5a 5a 5a 5a 5a 5a  ........ZZZZZZZZ
[  449.260563] ffff80003b1b6168: 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a  ZZZZZZZZZZZZZZZZ
[  449.264670] ffff80003b1b6178: 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a  ZZZZZZZZZZZZZZZZ
[  449.268835] ffff80003b1b6188: bb bb bb bb bb bb bb bb 6b 6b 6b 6b 6b 6b 6b 6b  ........kkkkkkkk
[  449.272971] ffff80003b1b6198: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b  kkkkkkkkkkkkkkkk
[  449.277139] ffff80003b1b61a8: 6b 6b 6b 6b 6b a5 bb bb 00 00 00 00 00 00 00 00  kkkkk...........
[  449.280274] ffff80003b1b61b8: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.284607] ffff80003b1b61c8: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.288818] ffff80003b1b61d8: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.293014] ffff80003b1b61e8: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.296146] ffff80003b1b61f8: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.300283] ffff80003b1b6208: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.304305] ffff80003b1b6218: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.308369] ffff80003b1b6228: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.312433] ffff80003b1b6238: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.316562] ffff80003b1b6248: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.320615] ffff80003b1b6258: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.324791] ffff80003b1b6268: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.329059] ffff80003b1b6278: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.332165] ffff80003b1b6288: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.336283] ffff80003b1b6298: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.340471] ffff80003b1b62a8: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.345025] ffff80003b1b62b8: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.348173] ffff80003b1b62c8: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.352216] ffff80003b1b62d8: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.356293] ffff80003b1b62e8: 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a  ZZZZZZZZZZZZZZZZ
[  449.360331] ffff80003b1b62f8: 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a  ZZZZZZZZZZZZZZZZ
[  449.408843]
[  449.409632] --------------------------------------------------
[  449.411684] ffff80003b1b6008: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b  kkkkkkkkkkkkkkkk
[  449.415920] ffff80003b1b6018: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b a5 bb bb  kkkkkkkkkkkkk...
[  449.419903] ffff80003b1b6028: 20 7d 1b 3b 00 80 ff ff 54 a0 b8 08 00 00 ff ff   }.;....T.......
[  449.424012] ffff80003b1b6038: 5c b0 22 10 00 00 ff ff 24 b2 22 10 00 00 ff ff  \.".....$.".....
[  449.427968] ffff80003b1b6048: 54 a0 b8 08 00 00 ff ff 18 42 08 10 00 00 ff ff  T........B......
[  449.431977] ffff80003b1b6058: 18 19 16 10 00 00 ff ff e0 39 16 10 00 00 ff ff  .........9......
[  449.435914] ffff80003b1b6068: 80 3e 16 10 00 00 ff ff cc 3e 16 10 00 00 ff ff  .>.......>......
[  449.439910] ffff80003b1b6078: 04 4e 09 10 00 00 ff ff 70 4e 09 10 00 00 ff ff  .N......pN......
[  449.443825] ffff80003b1b6088: 48 40 08 10 00 00 ff ff 00 00 00 00 00 00 00 00  H@..............
[  449.447773] ffff80003b1b6098: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.451670] ffff80003b1b60a8: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.455609] ffff80003b1b60b8: 00 00 00 00 d0 0d 00 00 25 91 00 00 01 00 00 00  ........%.......
[  449.459796] ffff80003b1b60c8: fc a0 b8 08 00 00 ff ff c4 cb 22 10 00 00 ff ff  ..........".....
[  449.464003] ffff80003b1b60d8: fc a0 b8 08 00 00 ff ff 18 42 08 10 00 00 ff ff  .........B......
[  449.468412] ffff80003b1b60e8: 18 19 16 10 00 00 ff ff e0 39 16 10 00 00 ff ff  .........9......
[  449.472887] ffff80003b1b60f8: 80 3e 16 10 00 00 ff ff cc 3e 16 10 00 00 ff ff  .>.......>......
[  449.477496] ffff80003b1b6108: 04 4e 09 10 00 00 ff ff 70 4e 09 10 00 00 ff ff  .N......pN......
[  449.480951] ffff80003b1b6118: 48 40 08 10 00 00 ff ff 00 00 00 00 00 00 00 00  H@..............
[  449.485555] ffff80003b1b6128: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.489479] ffff80003b1b6138: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.493448] ffff80003b1b6148: 00 00 00 00 00 00 00 00 00 00 00 00 d0 0d 00 00  ................
[  449.496857] ffff80003b1b6158: a3 91 00 00 01 00 00 00 5a 5a 5a 5a 5a 5a 5a 5a  ........ZZZZZZZZ
[  449.501630] ffff80003b1b6168: 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a  ZZZZZZZZZZZZZZZZ
[  449.505645] ffff80003b1b6178: 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a  ZZZZZZZZZZZZZZZZ
[  449.509587] ffff80003b1b6188: bb bb bb bb bb bb bb bb 6b 6b 6b 6b 6b 6b 6b 6b  ........kkkkkkkk
[  449.513589] ffff80003b1b6198: 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b  kkkkkkkkkkkkkkkk
[  449.517409] ffff80003b1b61a8: 6b 6b 6b 6b 6b a5 bb bb 00 00 00 00 00 00 00 00  kkkkk...........
[  449.520900] ffff80003b1b61b8: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.525481] ffff80003b1b61c8: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.528980] ffff80003b1b61d8: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.533127] ffff80003b1b61e8: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.536551] ffff80003b1b61f8: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.541202] ffff80003b1b6208: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.544601] ffff80003b1b6218: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.549210] ffff80003b1b6228: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.552662] ffff80003b1b6238: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.557293] ffff80003b1b6248: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.560751] ffff80003b1b6258: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.565518] ffff80003b1b6268: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.568934] ffff80003b1b6278: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.573518] ffff80003b1b6288: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.577492] ffff80003b1b6298: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.581436] ffff80003b1b62a8: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.585435] ffff80003b1b62b8: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.588860] ffff80003b1b62c8: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.593552] ffff80003b1b62d8: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
[  449.597851] ffff80003b1b62e8: 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a  ZZZZZZZZZZZZZZZZ
[  449.601905] ffff80003b1b62f8: 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a 5a  ZZZZZZZZZZZZZZZZ

 


kptr_restrict

커널 v4.15부터 커널에서 사용하는 포인터 주소를 숨긴다. 이를 볼 수 있게 하려면 printk() 함수의 format에 사용하는 “%p” 대신 “%px”를 사용하거나, “%pK”를 사용하고 admin 권한을 가진 유저가 “echo 1 > /proc/sys/kernel/kptr_restrict“을 수행하여 커널 포인터 출력 제한을 풀어야 한다.

 

Makefile

obj-m := foo.o
KER := $(shell uname -r)
KER_DIR := /lib/modules/$(KER)/build

all:
        make -C ${KER_DIR} M=$(PWD) modules

clean:
        make -C ${KER_DIR} M=$(PWD) clean

 

foo.c

#include <linux/kernel.h>
#include <linux/module.h>

int param = 0;
module_param(param, int, 0);

int foo_init(void)
{
        void *p = foo_init;

        printk(KERN_INFO "kptr_restrict=%d, x=0x%llx, p=%p, pK=%pK, px=%px\n",
                        param, (long long unsigned int) p, p, p, p);
        return 0;
}

void foo_exit(void)
{
}

module_init(foo_init);
module_exit(foo_exit);

MODULE_DESCRIPTION("foo");
MODULE_LICENSE("GPL");

 

run.sh

echo 0 > /proc/sys/kernel/kptr_restrict
insmod foo.ko param=0
rmmod foo

echo 1 > /proc/sys/kernel/kptr_restrict
insmod foo.ko param=1
rmmod foo

echo 2 > /proc/sys/kernel/kptr_restrict
insmod foo.ko param=2
rmmod foo

 

실행 결과

admin 권한으로 printk() 함수에서 함수 포인터를 출력하면 kptr_restrict 설정 값에 따라 다음과 같은 결과를 보여준다.

  • %llx 옵션
    • kptr_restrict 값에 상관없이 정확한 값을 보여준다.
  • %p 옵션
    • kptr_restrict 값에 상관없이 항상 거짓 값을 보여준다.
  • %pK 옵션
    • kptr_restrict=1인 경우에는 정확히 보여준다.
    • kptr_restrict=2인 경우에는 0000000000000000 값으로 출력한다.
  • %px
    • kptr_restrict 값에 상관없이 정확한 값을 보여준다.
$ ./run.sh
kptr_restrict=0, x=0xffff800008ae9000, p=00000000467430b8, pK=00000000467430b8, px=ffff800008ae9000
kptr_restrict=1, x=0xffff800008ae9000, p=00000000467430b8, pK=ffff800008ae9000, px=ffff800008ae9000
kptr_restrict=2, x=0xffff800008ae9000, p=00000000467430b8, pK=0000000000000000, px=ffff800008ae9000

 

참고

 

NUMA -2- (Fallback Node)

<kernel v5.10>

NUMA 아키텍처를 채용한 시스템에서 특정 노드에 메모리 뱅크가 없는 경우 인접한 메모리 뱅크를 사용하기 위해 사용하는 API가 있다.

 

NUMA -2- (Fallback node)

현재 노드에서 메모리를 할당하고자 시도할 때 자신의 노드에 메모리가 존재하지 않는 경우 대체 노드(Fallback node)로 메모리가 있는 가장 인접한(빠른) 노드를 선택하게 한다.

fallback-node-1b

  • node_numa_mem_[3]: node#3에 메모리가 없어서 메모리가 있는 가장 인접한 노드 id 2가 담겨있다.
  • per-cpu _numa_mem_: cpu#6번과 cpu#7번에 해당하는 노드 #3가 3번이지만 메모리가 없어서 메모리가 있는 가장 인접한 노드 id 2가 담겨있다.
  • per-cpu numa_node: cpu#6번과 cpu#7번에 해당하는 노드 #3 그대로 담겨 있다. (메모리 유무와 관계 없음)

 

numa_node_id()

include/linux/topology.h

#ifdef CONFIG_USE_PERCPU_NUMA_NODE_ID
#ifndef numa_node_id
/* Returns the number of the current Node */
static inline int numa_node_id(void)
{
        return raw_cpu_read(numa_node);
}
#endif
#else
/* Returns the number of the current Node. */
#ifndef numa_node_id
static inline int numa_node_id(void)
{
        return cpu_to_node(raw_smp_processor_id());
}
#endif
#endif

현재 cpu가 소속된 노드 id를 반환한다.

  • CONFIG_USE_PERCPU_NUMA_NODE_ID 커널 옵션을 사용하는 경우 per-cpu 방식으로 numa_node 값을 읽어서 numa 노드 id를 알아오고, 이 옵션을 사용하지 않는 경우 현재 cpu 번호로 노드 id를 구해온다.
  • 32bit arm에서 NUMA를 사용하는 경우가 흔한 경우가 아니지만 만일 사용하는 경우 per-cpu 방식을 사용하면 노드의 계산에 메모리를 사용하지 않고 TPIDRPRW 레지스터를 사용하므로 더 빠르게 처리할 수 있다.
  • x86, powerpc, ia64 등의 아키텍처에서도 빠르게 처리할 수 있는 구현이 준비되어 있다.

 

numa_mem_id()

include/linux/topology.h

#ifdef CONFIG_HAVE_MEMORYLESS_NODES
#ifndef numa_mem_id
/* Returns the number of the nearest Node with memory */
static inline int numa_mem_id(void)
{
        return raw_cpu_read(_numa_mem_);
}
#endif
#else
#ifndef numa_mem_id
/* Returns the number of the nearest Node with memory */
static inline int numa_mem_id(void)
{
        return numa_node_id();
}
#endif
#endif

현재 cpu가 소속된 노드 id를 반환하는데 만일 이 노드가 메모리가 없는 경우 메모리에 있는 가장 인접한 노드 id를 알아온다.

  • CONFIG_HAVE_MEMORYLESS_NODES 커널 옵션을 사용하는 경우 per-cpu _numa_mem_에 담긴 노드 id를 반환하는데 현재 cpu가 소속된 노드가 메모리를 가지지 않는 경우 이 값은 현재 노드에서 메모리가 있는 가장 인접한 노드 id를 반환하고, 커널 옵션을 사용하지 않는 경우 단순히 현재 cpu가 소속된 노드 id를 반환한다.
  • 참고:

 

node_to_mem_node()

include/linux/topology.h

#ifdef CONFIG_HAVE_MEMORYLESS_NODES
#ifndef node_to_mem_node
static inline int node_to_mem_node(int node)
{
        return _node_numa_mem_[node];
}
#endif
#else
#ifndef node_to_mem_node
static inline int node_to_mem_node(int node)
{
        return node;
}
#endif
#endif

지정된 노드에 메모리가 있으면 지정된 노드 id를 그대로 반환하고 메모리가 없는 경우 메모리가 있는  가장 인접한 노드 id를 알아온다.

  • CONFIG_HAVE_MEMORYLESS_NODES 커널 옵션을 사용하는 경우 _node_numa_mem_[] 배열 값을 반환한다. _node_numa_mem_[] 배열에는 배열 인자로 사용된 노드id와 똑같은 값이 들어있는데 메모리가 없는 노드인 경우는 메모리가 있는 가장 인접한 노드 id를 담고 있다.

 

관련 전역 변수

per-cpu numa_node 변수

mm/page_alloc.c

#ifdef CONFIG_USE_PERCPU_NUMA_NODE_ID
DEFINE_PER_CPU(int, numa_node);
#endif

각 cpu가 소속된 노드 id를 담고 있다.

 

_node_numa_mem 변수

mm/page_alloc.c

#ifdef CONFIG_HAVE_MEMORYLESS_NODES
int _node_numa_mem_[MAX_NUMNODES];
#endif

배열의 각 노드에 해당되는 노드 id를 그대로 담고 있지만, 해당 노드가 메모리가 없는 노드인 경우 대체 노드로 메모리가 있는 인접한 노드 id를 담고 있다.

 

per-cpu _numa_mem_ 변수

mm/page_alloc.c

#ifdef CONFIG_HAVE_MEMORYLESS_NODES
/*
 * N.B., Do NOT reference the '_numa_mem_' per cpu variable directly.
 * It will not be defined when CONFIG_HAVE_MEMORYLESS_NODES is not defined.
 * Use the accessor functions set_numa_mem(), numa_mem_id() and cpu_to_mem()
 * defined in <linux/topology.h>.
 */
DEFINE_PER_CPU(int, _numa_mem_);                /* Kernel "local memory" node */
#endif

cpu가 소속된 노드 id를 그대로 담고 있지만, 해당 노드가 메모리가 없는 노드인 경우 대체 노드로 메모리가 있는 인접한 노드 id를 담고 있다.

 

참고

Slub Memory Allocator -3- (캐시 생성)

<kernel v5.0>

슬랩 캐시 생성

빠른 성능으로 규격화된 object를 지속적으로 공급하기 위해 커널 메모리용 슬랩 캐시를 생성 시켜 준비한다.

 

Hardened usercopy whitelisting

보안을 목적으로 커널 데이터를 유저측과 교환할 때 슬랩 캐시도 그 영역을 제한하는 방법을 추가하였다. 따라서 슬랩 캐시를 생성할 때 슬랩 캐시에서 object의 useroffset 위치 부터 usersize 만큼만 허용할 수 있도록 지정하는 kmem_cache_create_usercopy() 함수가 추가되었다. 커널 전용 슬랩 캐시를 만드는 경우에는 유저로 데이터 복사를 허용하지 않는다. 그러나 kmalloc의 경우는 전체 object size를 허용하도록 생성하는 것이 특징이다.

 

alias 슬랩 캐시

만일 기존에 생성한 슬랩 캐시와 object 사이즈가 같고 플래그 설정이 유사한 슬랩 캐시를 생성하는 경우 기존에 생성한 슬랩 캐시를 공유하여 사용하는데, 이렇게 만들어진 슬랩 캐시는 alias 슬랩 캐시라고 한다.

 

다음 그림은 슬랩 캐시를 만들기 위한 함수 호출 관계를 보여준다.

 

kmem_cache_create()

mm/slab_common.c

/**
 * kmem_cache_create - Create a cache.
 * @name: A string which is used in /proc/slabinfo to identify this cache.
 * @size: The size of objects to be created in this cache.
 * @align: The required alignment for the objects.
 * @flags: SLAB flags
 * @ctor: A constructor for the objects.
 *
 * Cannot be called within a interrupt, but can be interrupted.
 * The @ctor is run when new pages are allocated by the cache.
 *
 * The flags are
 *
 * %SLAB_POISON - Poison the slab with a known test pattern (a5a5a5a5)
 * to catch references to uninitialised memory.
 *
 * %SLAB_RED_ZONE - Insert `Red` zones around the allocated memory to check
 * for buffer overruns.
 *
 * %SLAB_HWCACHE_ALIGN - Align the objects in this cache to a hardware
 * cacheline.  This can be beneficial if you're counting cycles as closely
 * as davem.
 *
 * Return: a pointer to the cache on success, NULL on failure.
 */
struct kmem_cache *
kmem_cache_create(const char *name, unsigned int size, unsigned int align,
                slab_flags_t flags, void (*ctor)(void *))
{
        return kmem_cache_create_usercopy(name, size, align, flags, 0, 0,
                                          ctor);
}
EXPORT_SYMBOL(kmem_cache_create);

요청한 @size 및 @align 단위로 @name 명칭의 슬랩 캐시를 생성한다. 유사한 사이즈와 호환 가능한 플래그를 사용한 슬랩 캐시가 있는 경우 별도로 생성하지 않고, alias 캐시로 등록한다. 실패하는 경우 null을 반환한다.

 

kmem_cache_create_usercopy()

mm/slab_common.c

/**
 * kmem_cache_create_usercopy - Create a cache with a region suitable
 * for copying to userspace
 * @name: A string which is used in /proc/slabinfo to identify this cache.
 * @size: The size of objects to be created in this cache.
 * @align: The required alignment for the objects.
 * @flags: SLAB flags
 * @useroffset: Usercopy region offset
 * @usersize: Usercopy region size
 * @ctor: A constructor for the objects.
 *
 * Cannot be called within a interrupt, but can be interrupted.
 * The @ctor is run when new pages are allocated by the cache.
 *
 * The flags are
 *
 * %SLAB_POISON - Poison the slab with a known test pattern (a5a5a5a5)
 * to catch references to uninitialised memory.
 *
 * %SLAB_RED_ZONE - Insert `Red` zones around the allocated memory to check
 * for buffer overruns.
 *
 * %SLAB_HWCACHE_ALIGN - Align the objects in this cache to a hardware
 * cacheline.  This can be beneficial if you're counting cycles as closely
 * as davem.
 *
 * Return: a pointer to the cache on success, NULL on failure.
 */
struct kmem_cache *
kmem_cache_create_usercopy(const char *name,
                  unsigned int size, unsigned int align,
                  slab_flags_t flags,
                  unsigned int useroffset, unsigned int usersize,
                  void (*ctor)(void *))
{
        struct kmem_cache *s = NULL;
        const char *cache_name;
        int err;

        get_online_cpus();
        get_online_mems();
        memcg_get_cache_ids();

        mutex_lock(&slab_mutex);

        err = kmem_cache_sanity_check(name, size);
        if (err) {
                goto out_unlock;
        }

        /* Refuse requests with allocator specific flags */
        if (flags & ~SLAB_FLAGS_PERMITTED) {
                err = -EINVAL;
                goto out_unlock;
        }

        /*
         * Some allocators will constraint the set of valid flags to a subset
         * of all flags. We expect them to define CACHE_CREATE_MASK in this
         * case, and we'll just provide them with a sanitized version of the
         * passed flags.
         */
        flags &= CACHE_CREATE_MASK;

        /* Fail closed on bad usersize of useroffset values. */
        if (WARN_ON(!usersize && useroffset) ||
            WARN_ON(size < usersize || size - usersize < useroffset))
                usersize = useroffset = 0;

        if (!usersize)
                s = __kmem_cache_alias(name, size, align, flags, ctor);
        if (s)
                goto out_unlock;

        cache_name = kstrdup_const(name, GFP_KERNEL);
        if (!cache_name) {
                err = -ENOMEM;
                goto out_unlock;
        }

        s = create_cache(cache_name, size,
                         calculate_alignment(flags, align, size),
                         flags, useroffset, usersize, ctor, NULL, NULL);
        if (IS_ERR(s)) {
                err = PTR_ERR(s);
                kfree_const(cache_name);
        }

out_unlock:
        mutex_unlock(&slab_mutex);

        memcg_put_cache_ids();
        put_online_mems();
        put_online_cpus();

        if (err) {
                if (flags & SLAB_PANIC)
                        panic("kmem_cache_create: Failed to create slab '%s'. Error %d\n",
                                name, err);
                else {
                        pr_warn("kmem_cache_create(%s) failed with error %d\n",
                                name, err);
                        dump_stack();
                }
                return NULL;
        }
        return s;
}
EXPORT_SYMBOL(kmem_cache_create_usercopy);

요청한 @size 및 @align 단위로 @name 명칭의 슬랩 캐시를 생성한다. 유사한 사이즈와 호환 가능한 플래그를 사용한 슬랩 캐시가 있는 경우 별도로 생성하지 않고, alias 캐시로 등록한다. 실패하는 경우 null을 반환한다.

  • 코드 라인 12에서 CONFIG_HOTPLUG_CPU 커널 옵션을 사용하는 경우에만 cpu_hotplug.refcount를 증가시켜 cpu를 분리하는 경우 동기화(지연)시킬 목적으로 설정한다.
  • 코드라인13에서 CONFIG_MEMORY_HOTPLUG 커널 옵션을 사용하는 경우에만 mem_hotplug.refcount를 증가시켜 memory를 분리하는 경우 동기화(지연)시킬 목적으로 설정한다.
  • 코드 라인 14에서 MEMCG_KMEM 커널 옵션을 사용하여 슬랩 캐시 사용량을 제어하고자 할 목적으로 read 세마포어 락을 사용한다.
  • 코드 라인 18~21에서 CONFIG_DEBUG_VM 커널 옵션을 사용하는 경우에만 name과 size에 대한 간단한 체킹을 수행한다. 이 커널 옵션을 사용하지 않는 경우에는 항상 false를 반환한다.
  • 코드 라인 24~27에서 허가된 플래그가 아닌 경우 -EINVAL 에러를 반환한다.
  • 코드 라인 35에서 kmem_cache를 생성할 때 유효한 플래그만 통과시킨다.
  • 코드 라인 38~40에서 유저에서 접근 가능해야 하는 영역의 useroffset 및 usersize가 size 범위를 벗어나는 경우 0으로 만든다.
  • 코드 라인 42~45에서 기존 슬랩 캐시 중 병합가능한 슬랩 캐시를 찾은 경우 캐시 생성을 포기하고 alias 캐시로 등록한다.
  • 코드 라인 47~51에서 name을 clone한 후 cache_name으로 반환한다. 단 name이 .rodata 섹션에 있는 경우 name을 그대로 반환한다.
  • 코드 라인 53~57에서 새 캐시를 생성한다.

 

다음 그림은 각종 플래그들에 대한  매크로 상수이다. (각 플래그 다음에 위치한 한 글자의 알파벳 문자는 slabinfo 유틸리티를 사용하여 플래그 속성값이 출력될 때 사용되는 알파벳 문자이다.)

 

kmem_cache_sanity_check()

mm/slab_common.c

static int kmem_cache_sanity_check(const char *name, unsigned int size)
{
        if (!name || in_interrupt() || size < sizeof(void *) ||
                size > KMALLOC_MAX_SIZE) {
                pr_err("kmem_cache_create(%s) integrity check failed\n", name);
                return -EINVAL;
        }

        WARN_ON(strchr(name, ' '));     /* It confuses parsers */
        return 0;
}

CONFIG_DEBUG_VM 커널 옵션을 사용하는 경우 슬랩 캐시를 생성하기 위한 간단한 체크를 수행한다. 성공 시 0을 반환한다.

  • 인터럽트 수행 중에 호출되는 경우 -EINVAL 에러를 반환한다.
  • 32비트 시스템에서 4 바이트, 64비트 시스템에서 8 바이트보다 작은 사이즈를 지정하는 경우 -EINVAL 에러를 반환한다.

 


Alias 캐시 생성

__kmem_cache_alias()

mm/slub.c

struct kmem_cache *
__kmem_cache_alias(const char *name, unsigned int size, unsigned int align,
                   slab_flags_t flags, void (*ctor)(void *))
{
        struct kmem_cache *s, *c;

        s = find_mergeable(size, align, flags, name, ctor);
        if (s) {
                s->refcount++;

                /*
                 * Adjust the object sizes so that we clear
                 * the complete object on kzalloc.
                 */
                s->object_size = max(s->object_size, size);
                s->inuse = max(s->inuse, ALIGN(size, sizeof(void *)));

                for_each_memcg_cache(c, s) {
                        c->object_size = s->object_size;
                        c->inuse = max(c->inuse, ALIGN(size, sizeof(void *)));
                }

                if (sysfs_slab_alias(s, name)) {
                        s->refcount--;
                        s = NULL;
                }
        }

        return s;
}

유사한 사이즈와 호환 가능한 플래그를 사용한 슬랩 캐시가 있는 경우 별도로 생성하지 않고, alias 캐시로 등록한다. alias 캐시로 등록하지 않고 별도로 캐시를 만들어야 하는 경우에는 null을 반환한다.

  • 코드 라인 7에서 병합 가능한 캐시를 알아온다. 병합할 수 없으면 null을 반환한다.
    • 실제 생성되는 캐시는 /sys/kernel/slabs에서 슬랩명으로 디렉토리가 생성되지만, alias 캐시는 병합된 캐시 디렉토리를 가리키는 링크를 생성한다.
  • 코드 라인 8~16에서 요청한 캐시는 alias 캐시로 등록되고 실제 병합될 캐시를 사용하므로 레퍼런스 카운터를 증가시키고, 병합될 캐시의 object_size보다 요청 캐시의 @size가 더 큰 경우 갱신한다. 또한 병합될 캐시의 inuse보다 요청한 @size가 큰 경우도 갱신한다.
    • c->object_size <- 요청한 @size가 담긴다.
      • 메타 데이터를 제외한 실제 object 데이터가 저장될 공간의 사이즈
    • c->size
      • 메타 데이터를 포함한 사이즈
    • c->inuse
      • 메타 데이터(REDZONE은 포함되지 않음)까지의 offset 값(메타 데이터가 없는 경우 워드 단위로 정렬한 size와 동일)
  • 코드 라인 18~21에서 병합될 캐시의 모든 memcg 캐시들을 순회하며 memcg용 object size를 병합될 캐시의 object size와 동일하게 갱신한다. 또한 memcg용 캐시의 inuse보다 @size가 더 큰 경우 갱신한다.
  • 코드 라인 23~26에서 CONFIG_SYSFS 커널 옵션을 사용하는 경우 생성된 캐시에 대한 sysfs 링크를 만든다. 단 커널이 부트업 프로세스 중인 경우 링크 생성을 slab_sysfs 드라이버 가동후로 미룬다. 링크가 만들어지지 않는 경우 에러(0이 아닌)를 반환한다.
    • 에러를 반환한 경우 병합될 캐시로의 사용을 포기하기 위해 refcount를 감소 시키고 null을 반환한다.

 

다음 그림은 생성 요청한 캐시에 대해 병합될 캐시를 찾은 경우 새로운 캐시를 등록하지 않고 그냥 alias 캐시 리스트에 등록하는 경우를 보여준다.

__kmem_cache_alias-1b

 

병합 캐시 검색

find_mergeable()

mm/slab_common.c

struct kmem_cache *find_mergeable(unsigned int size, unsigned int align,
                slab_flags_t flags, const char *name, void (*ctor)(void *))
{
        struct kmem_cache *s;

        if (slab_nomerge)
                return NULL;

        if (ctor)
                return NULL;

        size = ALIGN(size, sizeof(void *));
        align = calculate_alignment(flags, align, size);
        size = ALIGN(size, align);
        flags = kmem_cache_flags(size, flags, name, NULL);

        if (flags & SLAB_NEVER_MERGE)
                return NULL;

        list_for_each_entry_reverse(s, &slab_root_caches, root_caches_node) {
                if (slab_unmergeable(s))
                        continue;

                if (size > s->size)
                        continue;

                if ((flags & SLAB_MERGE_SAME) != (s->flags & SLAB_MERGE_SAME))
                        continue;
                /*
                 * Check if alignment is compatible.
                 * Courtesy of Adrian Drzewiecki
                 */
                if ((s->size & ~(align - 1)) != s->size)
                        continue;

                if (s->size - size >= sizeof(void *))
                        continue;

                if (IS_ENABLED(CONFIG_SLAB) && align &&
                        (align > s->align || s->align % align))
                        continue;

                return s;
        }
        return NULL;
}

병합 가능한 캐시를 검색하여 아래의 엄격한 조건을 만족하는 캐시를 찾아 반환하고 찾지 못한 경우 null을 반환한다.

  • “slub_nomerge” 커널 파라메터를 사용한 경우는 병합하지 않는다
  • 요청 캐시의 플래그에서 SLAB_NEVER_MERGE 플래그들 중 하나라도 사용하면 안된다. (대부분 디버그용 플래그)
  • 요청 캐시가 별도의 object 생성자를 사용하면 안된다.
  • 전체 캐시들에 대해 루프를 돌며 다음 조건을 비교한다.
    • 캐시의 플래그에서 SLAB_NEVER_MERGE 플래그들 중 하나라도 사용하면 안된다.
    • 루트 캐시가 아니면 안된다.
    • 캐시에 별도의 object 생성자가 사용되면 안된다.
    • 유저사이즈가 지정된 경우는 안된다.
    • 레퍼런스 카운터가 0보다 작으면 안된다. (초기화 중)
    • 기존 캐시 사이즈와 유사해야한다 (기존 캐시 사이즈와 같거나 워드 범위 이내로 작아야한다. )
    • 요청 플래그에 사용한 SLAB_MERGE_SAME 플래그들이 캐시에서 사용된 플래그와 동일하지 않으면 안된다.
    • 캐시의 size가 재조정된 요청 align 단위로 정렬되지 않으면 안된다.

 

mm/slab_common.c

/*
 * Set of flags that will prevent slab merging
 */
#define SLAB_NEVER_MERGE (SLAB_RED_ZONE | SLAB_POISON | SLAB_STORE_USER | \
                SLAB_TRACE | SLAB_DESTROY_BY_RCU | SLAB_NOLEAKTRACE | \
                SLAB_FAILSLAB | SLAB_KASAN)

디버그 옵션들을 사용한 캐시에 대해 병합을 할 수 없다.

 

#define SLAB_MERGE_SAME (SLAB_RECLAIM_ACCOUNT | SLAB_CACHE_DMA | \
                SLAB_ACCOUNT)

캐시 병합을 하기 위해서는 대상 캐시의 플래그와 요청 캐시의 플래그에 대해 위의 3개 플래그가 서로 동일하여야 한다.

 

kmem_cache_flags()
/*
 * kmem_cache_flags - apply debugging options to the cache
 * @object_size:        the size of an object without meta data
 * @flags:              flags to set
 * @name:               name of the cache
 * @ctor:               constructor function
 *
 * Debug option(s) are applied to @flags. In addition to the debug
 * option(s), if a slab name (or multiple) is specified i.e.
 * slub_debug=<Debug-Options>,<slab name1>,<slab name2> ...
 * then only the select slabs will receive the debug option(s).
 */
slab_flags_t kmem_cache_flags(unsigned int object_size,
        slab_flags_t flags, const char *name,
        void (*ctor)(void *))
{
        char *iter;
        size_t len;

        /* If slub_debug = 0, it folds into the if conditional. */
        if (!slub_debug_slabs)
                return flags | slub_debug;

        len = strlen(name);
        iter = slub_debug_slabs;
        while (*iter) {
                char *end, *glob;
                size_t cmplen;

                end = strchr(iter, ',');
                if (!end)
                        end = iter + strlen(iter);

                glob = strnchr(iter, end - iter, '*');
                if (glob)
                        cmplen = glob - iter;
                else
                        cmplen = max_t(size_t, len, (end - iter));

                if (!strncmp(name, iter, cmplen)) {
                        flags |= slub_debug;
                        break;
                }

                if (!*end)
                        break;
                iter = end + 1;
        }

        return flags;
}

CONFIG_SLUB_DEBUG 커널 옵션을 사용하는 경우 전역 변수 slub_debug에 저장된 플래그들을 추가한다.

  • “slub_debug=” 커널 파라메터를 통해서 각종 디버그 옵션을 선택하면 slub_debug 값(SLAB_DEBUG_FREE, SLAB_RED_ZONE, SLAB_POISON, SLAB_STORE_USER, SLAB_TRACE, SLAB_FAILSLAB)이 결정되고 “,” 뒤의 문자열이 slub_debug_slabs에 저장된다.

 

병합 불가능 조건

slab_unmergeable()

mm/slab_common.c

/*
 * Find a mergeable slab cache
 */
int slab_unmergeable(struct kmem_cache *s)
{       
        if (slab_nomerge || (s->flags & SLAB_NEVER_MERGE))
                return 1;
        
        if (!is_root_cache(s))
                return 1;
        
        if (s->ctor)
                return 1;

        if (s->usersize)
                return 1;
        /*
         * We may have set a slab to be unmergeable during bootstrap.
         */     
        if (s->refcount < 0)
                return 1;
                        
        return 0;
}

캐시를 병합할 수 없는 경우 true를 반환한다. 다음은 병합이 불가능한 조건이다.

  • “slub_nomerge” 커널 파라메터를 사용하는 경우 또는 캐시의 플래그에 SLAB_NEVER_MERGE 관련 플래그를 사용한 경우 병합 불가능
  • 루트 캐시가 아닌 경우
  • 별도의 object 생성자가 주어진 경우
  • usersize가 지정된 경우
  • refcount가 0보다 작은 경우
    • 캐시가 부트업 프로세스 중에 만들어져 아직 동작하지 않는 상태

 

alias 캐시용 sysfs 링크 생성

sysfs_slab_alias()

mm/slub.c

static int sysfs_slab_alias(struct kmem_cache *s, const char *name)
{
        struct saved_alias *al;

        if (slab_state == FULL) {
                /*
                 * If we have a leftover link then remove it.
                 */
                sysfs_remove_link(&slab_kset->kobj, name);
                return sysfs_create_link(&slab_kset->kobj, &s->kobj, name);
        }

        al = kmalloc(sizeof(struct saved_alias), GFP_KERNEL);
        if (!al)
                return -ENOMEM;

        al->s = s;
        al->name = name;
        al->next = alias_list;
        alias_list = al;
        return 0;
}

CONFIG_SYSFS 커널 옵션을 사용하는 경우 생성된 캐시에 대한 sysfs 링크를 만든다. 단 커널이 부트업 프로세스 중인 경우 링크 생성을 slab_sysfs 드라이버 가동 후로 미룬다. 링크가 만들어지지 않는 경우 에러(0이 아닌)를 반환한다.

  • 코드 라인 5~11에서slub 메모리 할당자가 완전히 동작을 시작한 경우 기존 링크를 삭제하고, 다시 name으로 링크를 만들고 반환한다.
  • 코드 라인 13~21에서 saved_alias 구조체 메모리 영역을 할당하고 초기화한다.  초기화된alias 캐시정보를 전역 alias_list에 추가한다.
    • 이렇게 추가된 alias_list는 나중에 커널이 각 드라이버를 호출할 때 __initcall() 함수에 등록된 slab_sysfs_init() 루틴이 호출될 때 slab_state를 full 상태로 바꾸고 alias_list에 있는 모든 alias 캐시에 대해 sysfs_slab_alias() 루틴이 다시 호출되면서 sysfs를 사용하여 링크를 만들게 된다.

 


정규 슬랩 캐시 생성

create_cache()

mm/slab_common.c

static struct kmem_cache *create_cache(const char *name,
                unsigned int object_size, unsigned int align,
                slab_flags_t flags, unsigned int useroffset,
                unsigned int usersize, void (*ctor)(void *),
                struct mem_cgroup *memcg, struct kmem_cache *root_cache)
{
        struct kmem_cache *s;
        int err;

        if (WARN_ON(useroffset + usersize > object_size))
                useroffset = usersize = 0;

        err = -ENOMEM;
        s = kmem_cache_zalloc(kmem_cache, GFP_KERNEL);
        if (!s)
                goto out;

        s->name = name;
        s->size = s->object_size = object_size;
        s->align = align;
        s->ctor = ctor;
        s->useroffset = useroffset;
        s->usersize = usersize;

        err = init_memcg_params(s, memcg, root_cache);
        if (err)
                goto out_free_cache;

        err = __kmem_cache_create(s, flags);
        if (err)
                goto out_free_cache;

        s->refcount = 1;
        list_add(&s->list, &slab_caches);
        memcg_link_cache(s);
out:
        if (err)
                return ERR_PTR(err);
        return s;

out_free_cache:
        destroy_memcg_params(s);
        kmem_cache_free(kmem_cache, s);
        goto out;
}

정규 슬랩 캐시를 생성한다.

  • 코드 라인 10~11에서 유저영역에 복사할 영역이 object 범위를 벗어나는경우 useroffset와 usersize를0으로만든다
  • 코드 라인 12~22에서새로운 캐시를 만들어 관리하기 위해 kmem_cache에서 slub object를 할당 받고 초기화한다.
  • 코드 라인 23~25에서 CONFIG_MEMCG_KMEM 커널 옵션을 사용하는 경우 memcg에서 커널 메모리에 대한 관리를 위해 각 파라메터들을 초기화한다.
  • 코드 라인 27~29에서 요청한 캐시를 생성한다.
  • 코드 라인 30~31에서 캐시 레퍼런스  카운터를 1로 만들고 캐시를 전역 slab_caches에 추가한다.
    • 실제 캐시 또는 alias 캐시를 생성할 때 마다 실제 캐시의 레퍼런스가 증가되고, 반대로 캐시 또는 alias 캐시를 소멸(삭제)시킬 때 마다 실제 캐시의 레퍼런스 카운터 값을 감소시킨다. 0 값이 되는 경우 실제 캐시를 소멸(삭제)시킬 수 있다.
  • 코드라인 32에서  memcg에 슬랩 캐시 링크를 추가한다.
  • 코드라인 33~36에서 out: 레이블이다 결과를 반환한다.

 

다음은 anon_vma 슬랩 캐시가 sysfs에 반영되어동작중인예를 보여준다. (위치: /sys/kernel/slab/<캐시명>)

$ cd /sys/kernel/slab/anon_vma
KVM /sys/kernel/slab/anon_vma$ ls
aliases      destroy_by_rcu  objects_partial  red_zone                  slabs_cpu_partial
align        free_calls      objs_per_slab    remote_node_defrag_ratio  store_user
alloc_calls  hwcache_align   order            sanity_checks             total_objects
cpu_partial  min_partial     partial          shrink                    trace
cpu_slabs    object_size     poison           slab_size                 usersize
ctor         objects         reclaim_account  slabs                     validate

 

memcg_link_cache()

mm/slab_common.c

void memcg_link_cache(struct kmem_cache *s)
{
        if (is_root_cache(s)) {
                list_add(&s->root_caches_node, &slab_root_caches);
        } else {
                list_add(&s->memcg_params.children_node,
                         &s->memcg_params.root_cache->memcg_params.children);
                list_add(&s->memcg_params.kmem_caches_node,
                         &s->memcg_params.memcg->kmem_caches);
        }
}

캐시가 루트 캐시인 경우 전역 slab_root_caches 리스트에 추가하고 루트 캐시가 아닌 경우 memcg에추가한다

 

__kmem_cache_create()

mm/slub.c

int __kmem_cache_create(struct kmem_cache *s, unsigned long flags)
{
        int err;

        err = kmem_cache_open(s, flags);
        if (err)
                return err;

        /* Mutex is not taken during early boot */
        if (slab_state <= UP)
                return 0;

        memcg_propagate_slab_attrs(s);
        err = sysfs_slab_add(s);
        if (err)
                kmem_cache_close(s);

        return err;
}

캐시를 생성하고, 커널이 부트업 중이 아닌 경우 memcg용 속성들을 읽어서 설정한 후 sysfs에 생성한 캐시에 대한 링크들을 생성한다.

  • 코드 라인 5~7에서 캐시를 생성한다.
  • 코드 라인 10~11에서 완전히 full slab 시스템이 가동되기 전, 즉 early bootup이 진행 중에는 함수를 빠져나간다.
  • 코드 라인 13에서 CONFIG_MEMCG_KMEM 커널 옵션을 사용한 경우 생성한 캐시의 루트 캐시에 대한 모든 속성을 읽어서 다시 한 번 재 설정한다.
  • 코드 라인 14~16에서 생성된 캐시에 대한 내용을 파일 시스템을 통해 속성들을 보거나 설정할 수 있도록 링크들을 생성한다.

 

캐시 할당자 상태

mm/slab.h

/*              
 * State of the slab allocator.
 *
 * This is used to describe the states of the allocator during bootup.
 * Allocators use this to gradually bootstrap themselves. Most allocators
 * have the problem that the structures used for managing slab caches are
 * allocated from slab caches themselves.
 */
enum slab_state {
        DOWN,                   /* No slab functionality yet */
        PARTIAL,                /* SLUB: kmem_cache_node available */
        PARTIAL_NODE,           /* SLAB: kmalloc size for node struct available */
        UP,                     /* Slab caches usable but not all extras yet */
        FULL                    /* Everything is working */
};

slab(slub) 메모리 할당자의 운영 상태로 slub 메모리 시스템에 대해서는 다음과 같다. (PARTIAL_NODE는 slub에서 사용하지않는다.)

  • DOWN
    • 아직 커널이 부트업 프로세스를 진행중이며 slub 메모리 시스템이 만들어지지 않은 상태
  • PARTIAL
    • 슬랩 캐시를 생성하지 못하지만 이미 생성된 슬랩 캐시로부터 슬랩 object의 할당은 가능한 상태
    • kmem_cache_node 슬랩 캐시가 존재하는 상태
      • 캐시를 생성하기 위해 캐시 내부에 필요한 kmem_cache_node가 필요한데 이를 만들기 위해 부트업 처리 중 가장 먼저 만든 캐시이다.
  • UP
    • kmem_cache 시스템은 동작하나 다른 엑스트라 시스템이 아직 활성화 되지 않은 상태
  • FULL
    • slub 메모리 할당자에 대한 부트업 프로세스가 완료되어 slub에 대한 모든 것이 동작하는 상태

 

kmem_cache_open()

mm/slub.c

static int kmem_cache_open(struct kmem_cache *s, slab_flags_t flags)
{
        s->flags = kmem_cache_flags(s->size, flags, s->name, s->ctor);
#ifdef CONFIG_SLAB_FREELIST_HARDENED
        s->random = get_random_long();
#endif

        if (!calculate_sizes(s, -1))
                goto error;
        if (disable_higher_order_debug) {
                /*
                 * Disable debugging flags that store metadata if the min slab
                 * order increased.
                 */
                if (get_order(s->size) > get_order(s->object_size)) {
                        s->flags &= ~DEBUG_METADATA_FLAGS;
                        s->offset = 0;
                        if (!calculate_sizes(s, -1))
                                goto error;
                }
        }

#if defined(CONFIG_HAVE_CMPXCHG_DOUBLE) && \
    defined(CONFIG_HAVE_ALIGNED_STRUCT_PAGE)
        if (system_has_cmpxchg_double() && (s->flags & SLAB_NO_CMPXCHG) == 0)
                /* Enable fast mode */
                s->flags |= __CMPXCHG_DOUBLE;
#endif

        /*
         * The larger the object size is, the more pages we want on the partial
         * list to avoid pounding the page allocator excessively.
         */
        set_min_partial(s, ilog2(s->size) / 2);

        set_cpu_partial(s);

#ifdef CONFIG_NUMA
        s->remote_node_defrag_ratio = 1000;
#endif

        /* Initialize the pre-computed randomized freelist if slab is up */
        if (slab_state >= UP) {
                if (init_cache_random_seq(s))
                        goto error;
        }

        if (!init_kmem_cache_nodes(s))
                goto error;

        if (alloc_kmem_cache_cpus(s))
                return 0;

        free_kmem_cache_nodes(s);
error:
        if (flags & SLAB_PANIC)
                panic("Cannot create slab %s size=%u realsize=%u order=%u offset=%u flags=%lx\n",
                      s->name, s->size, s->size,
                      oo_order(s->oo), s->offset, (unsigned long)flags);
        return -EINVAL;
}

캐시를 생성한다.

  • 코드 라인 3에서”slab_debug=” 커널 파라메터에 의해 몇 개의 디버그 요청이 있는 경우 object를 만들 때 반영하기 위해 해당 기능의 slub 디버그 플래그를 추가한다.
  • 코드 라인 8~9에서 객체 size에 따른 order 및 객체 수 등이 산출되지 않는 경우 에러 처리로 이동 한다.
  • 코드 라인 10~21에서 “slub_debug=O” 커널 파라메터가 사용된 경우 디버깅 기능으로 인해 order 값이 상승되는 경우 해당캐시의  디버깅 기능을 disable하게 한다. s->size를 할당하기 위해 계산된 order 값이 s->object_size를 할당하기 위해 계산된 order 값보다 큰 경우 즉, 디버그등 목적으로 메타데이터가 추가되어 페이지 할당이 더 필요한 경우이다. 이러한 경우 메타 데이타가 추가되는 플래그들을 클리어하고, offset을 0으로 대입하여 메타 데이터로 인해 order가 커지게 되면 메타 데이터를 추가하지 못하게 막는다.
  • 코드 라인 25~27에서 시스템이 더블 워드 데이터 형에 대해 cmpxchg 기능을 지원하면서 slab 디버그 플래그를 사용하지 않는 경우 플래그에 __CMPXCHG_DOUBLE가 추가된다.
    • x86 아키텍처나 64bit arm 아키텍처 등에서 지원하고 32bit arm에서는 지원하지 않는다.
  • 코드 라인 34에서 object의 size를 표현하는데 필요한 비트 수의 절반을 min_partial에 저장한다. 단 5~10 범위 사이로 조정한다.
    • 예)
      • size가 4K -> 필요한 비트 수=12 -> min_partial = 6
      • size가 1M -> 필요한 비트 수=20 -> min_partial = 10
  • 코드 라인 36에서 size에 적합한 cpu_partial 갯수를 산출한다.
  • 코드 라인 39에서 NUMA 시스템인 경우 remote_node_defrag_ratio에 1000을 대입한다.
    •  로컬 노드의 partial 리스트가 부족할 때 리모트 노드의 partial 리스트를 이용할 수 있도록 하는데 이의 허용률을 1000으로 설정한다.
      • 허용 수치는 0~100까지 입력하는 경우 그 값을 10배 곱하고, 100이상 수치 입력하는 경우 그대로 허용한다.
      • 1000으로 설정하는 경우 약 98%의 성공률로 설정된다.
        • 하드웨어 딜레이 타이머를 1024로 나눈 나머지가 이 수치 이하인 경우에만 허용(성공)한다.
        • 1024 이상으로 설정하는 경우 100% 허용(성공)
  • 코드 라인 43~46에서 슬랩이 정상 동작하는 경우  FP 값을 엔코딩하여 숨길 랜덤 시퀀스를 초기화한다.
  • 코드 라인 48~49에서per 노드의 초기화가 실패하는 경우 error로 이동한다.
  • 코드 라인 51~52에서 per cpu에 대한 할당이 성공하면 함수를 종료한다.
  • 코드 라인 54에서 슬랩 캐시의 할당이 실패한 경우이다. per 노드를 해제하고 에러를 리턴한다.
  • 코드 라인 55~59에서 error: 레이블이다.  SLAB_PANIC 플래그가설정된경우panic 로그를출력하고 panic 동작에들어간다.
  • 코드 라인 60에서 -EINVAL 에러를반환한다.

 

set_min_partial()

mm/slub.c

static void set_min_partial(struct kmem_cache *s, unsigned long min)
{
        if (min < MIN_PARTIAL)
                min = MIN_PARTIAL;
        else if (min > MAX_PARTIAL)
                min = MAX_PARTIAL;
        s->min_partial = min;
}

지정한 캐시의 min_partial 값을 설정한다. 단 min 값은 MIN_PARTIAL(5) ~ MAX_PARTIAL(10)을 벗어나는 경우 조정된다.

 

set_cpu_partial()

mm/slub.c

static void set_cpu_partial(struct kmem_cache *s)
{
#ifdef CONFIG_SLUB_CPU_PARTIAL
        /*
         * cpu_partial determined the maximum number of objects kept in the
         * per cpu partial lists of a processor.
         *
         * Per cpu partial lists mainly contain slabs that just have one
         * object freed. If they are used for allocation then they can be
         * filled up again with minimal effort. The slab will never hit the
         * per node partial lists and therefore no locking will be required.
         *
         * This setting also determines
         *
         * A) The number of objects from per cpu partial slabs dumped to the
         *    per node list when we reach the limit.
         * B) The number of objects in cpu partial slabs to extract from the
         *    per node list when we run out of per cpu objects. We only fetch
         *    50% to keep some capacity around for frees.
         */
        if (!kmem_cache_has_cpu_partial(s))
                s->cpu_partial = 0;
        else if (s->size >= PAGE_SIZE)
                s->cpu_partial = 2;
        else if (s->size >= 1024)
                s->cpu_partial = 6;
        else if (s->size >= 256)
                s->cpu_partial = 13;
        else
                s->cpu_partial = 30;
#endif
}

size에따라  per-cpu의 partial 리스트에서 유지 가능한 최대 슬랩 object 수(s->cpu_partial)를 정한다.

  • 1 페이지 이상 -> 2개
  • 1024 이상 -> 6개
  • 256 이상 -> 13개
  • 256 미만 -> 30개

 

kmem_cache_has_cpu_partial()

mm/slub.c

static inline bool kmem_cache_has_cpu_partial(struct kmem_cache *s)
{
#ifdef CONFIG_SLUB_CPU_PARTIAL
        return !kmem_cache_debug(s);
#else
        return false;
#endif
}

CONFIG_SLUB_CPU_PARTIAL 커널 옵션이 사용되는 경우 지정된 캐시에서 디버그 플래그가 설정되어 사용되지 않는 경우에 cpu partial 리스트의 사용이 지원된다. 그렇지 않은 경우 false를 반환한다.

  • true=지정된 캐시의 cpu partial 리스트 사용을 지원한다.
  • false=지정된 캐시의 cpu partial 리스트 사용을 지원하지 않는다.

 

kmem_cache_debug()

mm/slub.c

static inline int kmem_cache_debug(struct kmem_cache *s)
{
#ifdef CONFIG_SLUB_DEBUG
        return unlikely(s->flags & SLAB_DEBUG_FLAGS);
#else
        return 0;
#endif
}

CONFIG_SLUB_DEBUG 커널 옵션이 사용되는 경우 지정된 캐시에서 SLAB_DEBUG_FLAGS 들 중 하나라도 설정되었는지 여부를 반환하고 그렇지 않은 경우 0을 반환한다.

  • 1=지정된 캐시의 디버그 플래그 설정됨
  • 0=지정된 캐시의 디버그 플래그가 설정되지 않음

 

kmem_cache_node 초기화

init_kmem_cache_nodes()

mm/slub.c

static int init_kmem_cache_nodes(struct kmem_cache *s)
{
        int node;

        for_each_node_state(node, N_NORMAL_MEMORY) {
                struct kmem_cache_node *n;

                if (slab_state == DOWN) {
                        early_kmem_cache_node_alloc(node);
                        continue;
                }
                n = kmem_cache_alloc_node(kmem_cache_node,
                                                GFP_KERNEL, node);

                if (!n) {
                        free_kmem_cache_nodes(s);
                        return 0;
                }

                init_kmem_cache_node(n); 
                s->node[node] = n;
        }
        return 1;
}

메모리를 가진 모든 노드에 대해 슬랩 시스템 상태에 따라 다음 2가지 중 하나를 수행한다.

  • 아직 슬랩 시스템이 하나도 가동되지 않은 상태인 경우에는 슬랩 object를 할당 받지 못한다. 따라서 kmem_cache_node 구조체의 할당을 위해 슬랩 object가 아닌 버디 시스템을 통해 페이지를 할당 받고 이 페이지를 kmem_cache_node 크기 단위로 나누어 수동으로 슬랩 object를 구성한다. 그런 후 첫 object 항목을 할당 받아 kmem_cache_node 구조체 정보를 구성한다.
  • 슬랩 시스템이 조금이라도 가동하면 가장 처음 준비한 슬랩 캐시가 kmem_cache_node 슬랩 캐시이다. 이를 통해 object를 할당 받고 초기화 루틴을 수행한다.

 

kmem_cache_node early 초기화

early_kmem_cache_node_alloc()

mm/slub.c

/*
 * No kmalloc_node yet so do it by hand. We know that this is the first
 * slab on the node for this slabcache. There are no concurrent accesses
 * possible.
 *
 * Note that this function only works on the kmem_cache_node
 * when allocating for the kmem_cache_node. This is used for bootstrapping
 * memory on a fresh node that has no slab structures yet.
 */
static void early_kmem_cache_node_alloc(int node)
{
        struct page *page;
        struct kmem_cache_node *n;

        BUG_ON(kmem_cache_node->size < sizeof(struct kmem_cache_node));

        page = new_slab(kmem_cache_node, GFP_NOWAIT, node);

        BUG_ON(!page);
        if (page_to_nid(page) != node) {
                pr_err("SLUB: Unable to allocate memory from node %d\n", node);
                pr_err("SLUB: Allocating a useless per node structure in order to be able to continuu
e\n");
        }

        n = page->freelist;
        BUG_ON(!n);
#ifdef CONFIG_SLUB_DEBUG
        init_object(kmem_cache_node, n, SLUB_RED_ACTIVE);
        init_tracking(kmem_cache_node, n);
#endif
        n = kasan_kmalloc(kmem_cache_node, n, sizeof(struct kmem_cache_node),
                      GFP_KERNEL);
        page->freelist = get_freepointer(kmem_cache_node, n);
        page->inuse = 1;
        page->frozen = 0;
        kmem_cache_node->node[node] = n;
        init_kmem_cache_node(n);
        inc_slabs_node(kmem_cache_node, node, page->objects);

        /*
         * No locks need to be taken here as it has just been
         * initialized and there is no concurrent access.
         */
        __add_partial(n, page, DEACTIVATE_TO_HEAD);
}

아직 슬랩 시스템이 하나도 가동되지 않은 상태인 경우에는 슬랩 object를 할당받는 kmem_cache_alloc() 함수 등을 사용할 수 없다. 그래서 버디시스템으로 부터 슬랩 용도의 페이지를 할당 받아올 수 있는 new_slab() 함수를 대신 사용하였다. 이 과정은 동시 처리를 보장하지 않으며 오직 슬랩 캐시를 만들기 위해 처음 kmem_cache_node를 구성하기 위해서만 사용될 수 있다. (커널 메모리 캐시 시스템을 운영하기 위해 가장 처음에 만들 캐시는 kmem_cache_node 라는 이름이며 이 캐시를 통해 kmem_cache_node object를 공급해 주어야 한다.)

  • 코드 라인 8에서 kmem_cache_node 구조체에 사용할 슬랩 페이지를 할당 받아온다
    • slub 시스템이 아직 동작하지 않아 slub object로 할당을 받지 못하므로 버디 시스템으로 부터 페이지를 할당 받아 슬랩 페이지로 구성한다.
  • 코드 라인 11~15에서 할당 받은 슬랩 페이지가 리모트 노드인경우 경고를 출력한다.
    • “SLUB: Unable to allocate memory from node %d\n”
    • “SLUB: Allocating a useless per node structure in order to be able to continue\n”
  • 코드 라인 17~29에서 할당받은 kmem_cache_node용 슬랩 페이지를 초기화하고 per 노드에 할당받은 첫 object를 연결한다.
  • 코드 라인 30~31에서 kmem_cache_node를 초기화한다. 슬랩 캐시 카운터를 증가시킨다.
  • 코드 라인 37에서 노드의 partial 리스트에 추가한다.

 

init_kmem_cache_node()

mm/slub.c

static void
init_kmem_cache_node(struct kmem_cache_node *n)
{
        n->nr_partial = 0;
        spin_lock_init(&n->list_lock);
        INIT_LIST_HEAD(&n->partial);
#ifdef CONFIG_SLUB_DEBUG
        atomic_long_set(&n->nr_slabs, 0);
        atomic_long_set(&n->total_objects, 0);
        INIT_LIST_HEAD(&n->full);
#endif
}

per 노드에 대한 초기화를 수행한다.

  • per 노드의 partial 리스트에 등록된 slub 페이지는 0으로 초기화
  • per 노드의 partial 리스트를 초기화

 

inc_slabs_node()
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 커널 옵션이 사용될 때에 동작하며 지정 노드에 대한 slab 수를 증가시킨다.

 

__add_partial()
/*
 * Management of partially allocated slabs.
 */
static inline void
__add_partial(struct kmem_cache_node *n, struct page *page, int tail)
{
        n->nr_partial++;
        if (tail == DEACTIVATE_TO_TAIL)
                list_add_tail(&page->lru, &n->partial);
        else
                list_add(&page->lru, &n->partial);
}

slub 페이지를 지정된 per 노드의 partial 리스트의 선두 또는 후미에 tail 옵션에 따라 방향을 결정하여 추가한다. 또한 해당 노드의 partial 수를 증가시킨다.

 

참고

Slab Memory Allocator -1- (구조)

<kernel v5.0>

Slab Memory Allocator

슬랩(Slab, Slub, Slob) object는 커널이 사용하는 정규 메모리 할당의 최소 단위이다. 커널은 다음과 같이 3가지 중 하나를 선택하여 빌드되어 사용된다. 서로에 대한 차이점을 알아본다. 차이점을 구분하지 않고 설명할 때에는 한글로 슬랩으로 표현한다. 또한 구조 및 소스 분석은 모두 Slub 구현만 분석한다.

 

Slab

  • 커널 메모리 관리의 핵심으로 2007~8년까지 default로 사용해 왔었다.
  • 배열로 된 Slab object 큐가 CPU와 노드에서 사용된다.
  • 메타 데이터 조각이 앞쪽에 배치되어 오브젝트의 정렬이 어려워 메모리 부족으로 캐시를 클리닝하는 경우 매우 복잡한 처리가 필요하다.
  • 처음 생성된 slab은 full 리스트에서 관리되다가 object가 하나라도 사용되는 경우 그 slab은 partial 리스트로 이동되어 관리된다. 다 사용하는 경우 다시 이동되어 empty 리스트에서 관리된다.
  • slab은 성능을 향상시키기 위해 노드별(per-node) 및 cpu별(per-cpu)로 각각 관리된다.

 

Slub

  • 2007~8년부터 메모리가 충분히 있는 임베디드 시스템뿐만 아니라 PC나 서버에서 사용되며, 현재 default로 사용한다.
  • Slab과 다르게 메모리 오버헤드를 줄이기 위해  Slab object 큐 대신 단순히 한 페이지의 slub page를 지정하여 사용한다.
  • Slab과 다르게 freelist 관리에 대한 공간을 별도로 사용하지 않는다.
  • Slab과 다르게 처음 생성된 slub은 object 사용 개수가 0으로 시작하고 partial 리스트로 관리된다. 만일 object가 다 사용되는 경우 slub은 partial 리스트에서 제거되고 관리 매커니즘에서 제외된다. 그러나 그 slub이 하나의 object라도 free 되는 경우 다시 partial 리스트에 추가되어 관리된다.
  • Slub 역시 slab과 동일하게 성능을 향상시키기 위해 노드(per-node) 및 cpu별(per-cpu)로 각각 관리된다.
  • Slab에 비해 slub을 사용하면서 시스템 내의 slab 캐시가 줄었고(50% 정도), slab 할당자의 지역성(locality)이 향샹되었으며, slab 메모리의 단편화가 줄어들었다.

 

Slob

  • low memory footprint를 갖는 임베디드 리눅스에서 선택하여 사용한다.
  • 속도는 가장 느리지만 메모리 소모가 가장 적다.

 

슬랩 구조

슬랩 object는 1 개 이상의 페이지 프레임에 배치되는데, 지정된 object 사이즈만큼씩 배치되어 사용된다. 디버그 정보를 위해 object에 메타 정보들이 포함될 수도 있다.

 

슬랩 페이지 내 object 배치

슬랩 object 사이즈에 맞춰 산출된 order 페이지를 버디 시스템으로 부터 할당 받아 1개의 슬랩 페이지를 구성한다. 슬랩 페이지는 동일한 슬랩 object 사이즈로 모두 채운다. 산출된 order는 디폴트로 0~3까지 사용한다.

 

다음 그림은 버디 시스템에서 order-N 페이지를 사용하여 슬랩 페이지를 구성하고, 이 슬랩 페이지에서 할당 가능한 슬랩 object를 배치한 모습을 보여준다.

 

슬랩 캐시

다음 그림과 1개 이상의 슬랩 페이지가 모여 슬랩 캐시가 구성된다. 즉 슬랩 캐시내의 모든 object들은 동일한 사이즈만을 제공한다.

 

다음 그림과 같이 필요한 object 사이즈가 다른 경우 각각의 object 사이즈별로 슬랩 캐시를 만들어 구성할 수 있다. 커널에서 특정 구조체를 많이 사용하는 경우 이렇게 슬랩 캐시를 미리 등록하여 준비한다.

  • 예) page, anon_vma, vm_area_struct, task_struct, dentry, skb, …

 

per-node 및 per-cpu 관리 지원

  • per-node
    • 노드별로 메모리 접근 속도가 다르므로 슬랩 페이지들을 노드별로 나눠 관리한다.
  • per-cpu
    • lock-less를 사용한 빠른 슬랩 캐시 할당을 위해 cpu별로 나눠 관리한다. per-cpu 슬랩 캐시에는 partial 리스트와 1 개의 page가 지정된다.

 

다음 그림은 슬랩 페이지들이 노드별 및 cpu별로 관리되는 모습을 보여준다.

  • cpu별로 할당/회수에 관련된 슬랩 페이지가 지정되고, 나머지는 partial 리스트에서 관리한다.

 

Freelist

per-cpu 슬랩 페이지에서 free object의 처음을 가리킨다. 각 free object들은 다음 free object들을 가리키므로 하나의 free object 리스트로 사용된다. 이 freelist에는 해당 cpu 전담으로 할당 가능하고, 해제는 다른 어떠한 cpu들도 사용 가능하다.

 

다음 그림은 cpu별로 슬랩 페이지를 지정하고, freelist를 통해 지정된 슬랩 페이지의 free object를 가리키고 있는 모습을 보여준다.

 

다음 그림은 freelist가 첫 free 슬랩 object를 가리키고, 각 free object끼리 순서대로 연결되는 모습을 보여준다.

 

슬랩 Object 내부 항목

슬랩 Object를 구성하는 항목은 다음과 같다.

Object 영역
  • 슬랩 object의 최소 사이즈는 32비트 시스템에서 4 바이트이고, 64비트 시스템에서 8 바이트이다.

 

FP(Free Pointer)
  • 페이지 프레임 내에 존재하는 각 object들이 free 상태일 때 object의 선두에 있는 FP(Free Pointer)를 통해 다음 free object를 가리키게한다. object를 디버깅하는 경우 object를 사용하지 않아도 object의 모든 데이터가 uninitialized poison 데이터로 설정되고 이를 모니터하여 혹시 침해를 입지 않는지 확인하는데 사용한다. 따라서 이러한 경우에는 FP(Free Pointer)를 object의 뒤로 이동시켜 사용한다. 그 다음에 owner track 필드와 red zone 필드가 추가되어 디버깅에 사용된다.
  • free object들은 offset를 0으로 하여 object의 가장 선두에 FP(Free Pointer)를 사용해 다음 free object의 주소를 가리키고 있다. 다만 SLAB_DESTROY_BY_RCU 및 SLAB_POISON 플래그 옵션을 사용하였거나 생성자를 사용한 경우 offset에 object_size를 대입하여 FP(Free Pointer)를 object_size 만큼 뒤로 이동시킨다.
  • 보안 향상을 위해 CONFIG_SLAB_FREELIST_HARDENED 커널 옵션을 사용하여 free 포인터 값들을 encaptualation하여 숨길 수 있다.

 

Poison
  • 데이터 주소 침범 등을 검출하기 위해 사용한다.
  • 슬랩 object의 소멸 후에 object_size에 해당하는 공간에 poison 값을 기록하고, 슬랩 object의 생성 시 이 값이 변경되는 것을 검출하여 에러 출력으로 리포트한다.
  • poison 값은 다음과 같다.
    • 슬랩 object가 사용 중이지만 초기화되지 않은 경우 0x5a=’Z’ 값으로 채운다.
    • 슬랩 object가 사용되지 않을 때 0x6b=’k’ 값으로 채우고 마지막 바이트만 0xa5를 기록한다.
  • “slub_debug=P” 커널 파라미터를 사용하여 poison 디버깅을 할 수 있다.
  • 디버깅 관련 참고:

     

Red-Zone
  • 데이터 주소 침범 등을 검출하기 위해 사용한다.
  • object_size의 좌우에 red-zone 값을 기록한 후 슬랩 object의 생성과 소멸 시에 이 값이 변경되는 것을 검출하여 에러 출력으로 리포트한다.
  • red zone 값은 다음과 같다.
    • inactive 상태일 때 0xbb 값으로 채운다.
    • active 상태일 때 0xcc 값으로 채운다.
  • “slub_debug=Z” 커널 파라미터를 사용하여 Red-zone 디버깅을 할 수 있다.

 

Owner(User) Track
  • 슬랩 object의 생성과 소멸 시 호출한 함수를 각각 최대 16개까지 출력하는 기능이다.
  • “slub_debug=U” 커널 파라미터를 사용하여 유저 추적을 할 수 있다.

 

Padding
  • Align 정렬에 따른 패딩 값으로 0x5a=’Z’를 채운다.

 

슬랩 Object 내부 구조

주의: 아래 그림에서 FP의 위치는 object_size의 포인터 사이즈 정렬 단위를 사용하여 중앙으로 이동하였다.

 

1) 메타 정보 없는 slub object

  • 전체 사이즈는 실제 object 사이즈 + align 단위로 정렬한 패딩을 포함한다.
    • 예) object_size=22, align=8
      • inuse=24, size=24
    • 예) object_size=22, align=64
      • inuse=24, size=64
  • FP를 가리키는 offset은 0이다.
  • 최소 정렬 사이즈는 워드(32bit=4, 64bit=8) 단위를 사용한다.
    • 예) object_size=22, align=0
      • size=24
  • SLAB_HWCACHE_ALIGN GPF 플래그를 사용 시 L1 캐시 라인 사이즈보다 작은 경우 캐시 라인 바운싱을 최소화 시키기 위해 align 단위를 2의 차수  단위로 줄인 수에 맞게 사용한다.
    • 예: …, 64, 32, 16, 8
  • 예) object_size=22, align=22, flags=SLAB_SWCACHE_ALIGN
    • size=32

 

2) red-zone 정보가 포함된 slub object

  • 이전 object가 overwrite 하여도 다음 object를 보호하기 위해 red_left_pad 공간을 추가하였다.
  • object 우측에 인접하여 최소 1~최대 워드사이즈(32bit=4, 64bit=8)인 red zone이 들어간다.
    • object 사이즈가 워드 단위로 이미 정렬되어 redzone 자리가 없는 경우 redzone 자리로 워드 사이즈 길이만큼 추가한다.
    • 또한 노란색 공간 즉 사용자용 object 시작 위치는 align 정렬되고, 전체 object 사이즈도 align 정렬된다.

 

3) fp 이동(poison, rcu, ctor)이 포함된 slub object

  • object 위치에 별도의 정보를 기록해야 하는 poison 디버깅, rcu를 사용한 free object 지원 또는 생성자를 사용하는 슬랩 캐시들은 FP(Free Pointer) 위치를 object 다음으로 옮겨야 한다. FP(Free Pointer)를 가리키는 offset은 inuse와 동일하다.
  • 다음은 FP를 옮겨야 하는 3 가지 항목이다.
    • SLAB_POISON 플래그를 사용한 경우 다음 두 경우에 poison 데이터를 기록한다.
      • object가 free 상태
      • object가 할당되었지만 초기화되지 않은 상태
    • SLAB_TYPESAFE_BY_RCU 플래그를 사용한 경우 rcu를 사용한 free 함수 포인터가 저장된다.
    • 생성자가 사용된 슬랩 캐시의 경우 생성자 함수 포인터를 기록한다.

 

4) owner track 정보가 포함된 slub object

  • object 할당/해제하는 사용자를 추적하기 위해 owner track 정보를 추가하였다.
    • SLAB_STORE_USER 플래그 사용 시 owner track 정보를 위해 track 구조체를 두 개 사용한다.

 

5) red-zone + fp 이동 + owner-track 정보가 포함된 slub object

  • 그림에는 표기하지 않았지만 KASAN 디버깅을 하는 경우 owner track 뒤에 KASAN 관련 정보가 추가된다.

 


구조체

kmem_cache 구조체 (slub)

include/linux/slub_def.h

/*
 * Slab cache management.
 */
struct kmem_cache {
        struct kmem_cache_cpu __percpu *cpu_slab;
        /* Used for retriving partial slabs etc */
        slab_flags_t flags;
        unsigned long min_partial;
        unsigned int size;      /* The size of an object including meta data */
        unsigned int object_size;/* The size of an object without meta data */
        unsigned int offset;    /* Free pointer offset. */
#ifdef CONFIG_SLUB_CPU_PARTIAL
        /* Number of per cpu partial objects to keep around */
        unsigned int cpu_partial;
#endif
        struct kmem_cache_order_objects oo;

        /* Allocation and freeing of slabs */
        struct kmem_cache_order_objects max;
        struct kmem_cache_order_objects min;
        gfp_t allocflags;       /* gfp flags to use on each alloc */
        int refcount;           /* Refcount for slab cache destroy */
        void (*ctor)(void *);
        unsigned int inuse;             /* Offset to metadata */
        unsigned int align;             /* Alignment */
        unsigned int red_left_pad;      /* Left redzone padding size */
        const char *name;       /* Name (only for display!) */
        struct list_head list;  /* List of slab caches */
#ifdef CONFIG_SYSFS
        struct kobject kobj;    /* For sysfs */
        struct work_struct kobj_remove_work;
#endif
#ifdef CONFIG_MEMCG
        struct memcg_cache_params memcg_params;
        /* for propagation, maximum size of a stored attr */
        unsigned int max_attr_size;
#ifdef CONFIG_SYSFS
        struct kset *memcg_kset;
#endif
#endif

#ifdef CONFIG_SLAB_FREELIST_HARDENED
        unsigned long random;
#endif

#ifdef CONFIG_NUMA
        /*
         * Defragmentation by allocating from a remote node.
         */
        unsigned int remote_node_defrag_ratio;
#endif

#ifdef CONFIG_SLAB_FREELIST_RANDOM
        unsigned int *random_seq;
#endif

#ifdef CONFIG_KASAN
        struct kasan_cache kasan_info;
#endif

        unsigned int useroffset;        /* Usercopy region offset */
        unsigned int usersize;          /* Usercopy region size */

        struct kmem_cache_node *node[MAX_NUMNODES];
};

슬랩 캐시를 관리한다.

  • cpu_slab
    • per-cpu 캐시
  • flags
    • 캐시 생성 시 적용된 플래그 옵션
  • min_partial
    • 캐시에서 유지할 최소 partial 슬랩 페이지 수
  • size
    • object 및 메타 데이터를 포함하고 align된 사이즈
  • object_size
    • 메타 데이터를 제외한 object의 사이즈
  • offset
    • 오브젝트 내에서 FP(Free Pointer)의 위치 오프셋
    • SLAB_POISON 및 SLAB_DESTROY_BY_RCU를 사용하지 않는 경우 0
  • cpu_partial
    • per-cpu partial 리스트에서 유지 가능한 최대 슬랩 object 수
  • oo
    • 슬랩 페이지 생성 시 적용할 권장 order
    • 메모리 부족 상황이 아닌 경우에는 권장 order로 슬랩 페이지를 할당한다.
  • max
    • 슬랩 페이지 생성 시 최대 order
  • min
    • 슬랩 페이지 생성 시 최소 order
    • 메모리 부족 상황에서는 min order로 슬랩 페이지를 할당한다.
  • allocflags
    • object 할당 시 사용할 GFP 플래그
  • refcount
    • 슬랩 캐시를 삭제하기 위해 사용할 참조 카운터로 alias 캐시 생성 시 계속 증가한다.
  • (*ctor)
    • object 생성자
  • inuse
    • 메타 데이터로 인해 추가된 공간을 제외한 실제 사이즈(actual size)
    • 메타 데이터(SLAB_POISON, SLAB_DESTROY_BY_RCU, SLAB_STORE_USER, KASAN)를 사용하지 않을 경우의 size와 동일하다.
  • align
    • 정렬할 바이트 수
  • red_left_pad
    • 좌측 red-zone 패딩 사이즈
  • reserved
    • slub object의 끝에 reserve 시켜야 할 바이트 수
  • *name
    • 오직 출력을 위해 사용되는 이름
  • list
    • 슬랩 캐시들을 연결할 때 사용되는 리스트 노드
  • kobj
    • sysfs에 생성할 때 사용할 디렉토리 정보
  • kobj_remove_work
    • 슬랩 캐시 삭제 시 워크를 통해 연동되어 sysfs에 생성한 슬랩 캐시명의 디렉토리를 제거한다.
  • memcg_param
    • 메모리 cgroup에서 사용하는 파라메터
  • max_attr_size
    • 속성이 저장될 최대 사이즈
  • *memcg_kset
    • 메모리 cgroup에서 사용하는 kset
  • random
    • CONFIG_SLAB_FREELIST_HARDENED 커널 옵션 사용 시 보안을 목적으로 FP(Free Pointer)를 encaptualization 하여 알아볼 수 없게 숨기기 위한 랜덤 값이다.
  • remote_node_defrag_ratio
    • 할당할 slub object가 로컬노드의 partial 리스트에서 부족한 경우 리모트 노드의 partail 리스트에서 시도할 확률
    • 0~1023 (100=로컬노드의 partial 리스트에서 slub object를 할당할 수 없을 때 약 10%의 확률로 리모트 노드에서 시도)
  • *random_seq
    • CONFIG_SLAB_FREELIST_RANDOM 커널 옵션을 사용 시 heap 오버플로우 침입에 대한 보안 강화를 목적으로 free object들의 순서를 섞기 위한 배열이 할당된다.
  • kasan_info
    • CONFIG_KASAN 커널 옵션을 사용 시 KASAN(Kernel Address SANitizer) 런타임 디버거를 사용할 수 있다.
  • useroffset
    • 유저 copy 영역 offset
  • usersize
    • 유저 copy 영역 사이즈
  • *node
    • 노드별 partial 리스트를 관리하는 kmem_cache_node 배열 포인터

 

kmem_cache_cpu 구조체 (slub)

include/linux/slub_def.h

struct kmem_cache_cpu {
        void **freelist;        /* Pointer to next available object */
        unsigned long tid;      /* Globally unique transaction id */
        struct page *page;      /* The slab from which we are allocating */
#ifdef CONFIG_SLUB_CPU_PARTIAL
        struct page *partial;   /* Partially allocated frozen slabs */
#endif
#ifdef CONFIG_SLUB_STATS
        unsigned stat[NR_SLUB_STAT_ITEMS];
#endif
};

per-cpu로 관리되는 슬랩 캐시

  •  **freelist
    • 아래 page 멤버 중 할당 가능한 free object를 가리키는 포인터
  • tid
    • 글로벌하게 유니크한 트랜잭션 id
  • *page
    • 할당/해제에 사용 중인 슬랩 캐시 페이지
  • *partial
    • 일부 object가 사용(in-use)된 frozen 슬랩 페이지 리스트
  • stat
    • 슬랩 캐시 통계

 

kmem_cache_node 구조체 (slab, slub)

mm/slab.h

#ifndef CONFIG_SLOB
/*      
 * The slab lists for all objects.
 */     
struct kmem_cache_node {
        spinlock_t list_lock;
#ifdef CONFIG_SLUB
        unsigned long nr_partial;
        struct list_head partial;
#ifdef CONFIG_SLUB_DEBUG
        atomic_long_t nr_slabs;
        atomic_long_t total_objects;
        struct list_head full;
#endif
#endif
};

per-node로 관리하는 슬랩 캐시

  •  list_lock
    • spin lock에서 사용
  • nr_partial
    • 유지할 partial 리스트의 수
  • partial
    • 해당 노드에서 partial된 슬랩 페이지 리스트
    • 단 slab과 다르게 slub은 특별히 partial 상태를 구별하지 않는다.
  • nr_slabs
    • 디버그용 슬랩 페이지 수
  • total_objects
    • 디버그용 슬랩 object의 총 갯수
  • full
    • 디버그용 다 사용된(in-use) slub 페이지 리스트

 

page 구조체에서 slub 용도로 사용되는 멤버

include/linux/mm_types.h

struct page {
...
                struct {        /* slab, slob and slub */
                        union {
                                struct list_head slab_list;     /* uses lru */
                                struct {        /* Partial pages */
                                        struct page *next;
#ifdef CONFIG_64BIT
                                        int pages;      /* Nr of pages left */
                                        int pobjects;   /* Approximate count */
#else
                                        short int pages;
                                        short int pobjects;
#endif
                                };
                        };
                        struct kmem_cache *slab_cache; /* not slob */
                        /* Double-word boundary */
                        void *freelist;         /* first free object */
                        union {
                                void *s_mem;    /* slab: first object */
                                unsigned long counters;         /* SLUB */
                                struct {                        /* SLUB */
                                        unsigned inuse:16;
                                        unsigned objects:15;
                                        unsigned frozen:1;
                                };
                        };
                };
...

슬랩 페이지에 사용되는 페이지 디스크립터이다.

  • slab_list
    • per-cpu 캐시 또는 per-node의 partial 리스트에 연결 시 사용될 노드
  • *next
    • 다음 partial 페이지를 가리킨다.
  • pages
    • 남은 partial 페이지 수
  • pobjects
    • 대략 남은 object 수
  • *slab_cache
    • 슬랩 캐시
  • *freelist
    • 첫 번째 free object를 가리키는 포인터
  • counters
    • 다음 정보를 담고 있다.
      • inuse:16
        • 사용중인 object의 총 갯수
        • counters[15:0]
      • objects:15
        • object의 총 갯수
        • counters[30:16]
      • frozen:1
        • frozen 여부를 나타낸다.
        • counters[31]

 

슬랩 페이지의 frozen 상태

  • 슬랩 페이지가 특정 cpu가 전용으로 사용할 수 있는 상태가 frozen 상태이다.
    • c->page에 연결된 슬랩 페이지이거나, c->partial에 연결된 슬랩 페이지들이 frozen 상태에 있다.
  • 노드별 partial 리스트 관리에 있는 슬랩 페이지들은 un-frozen 상태이다.
    • node[]->partial에 연결된 슬랩 페이지들이 un-frozen 상태에 있다.
  • 전담 cpu는 frozen된 페이지가 가진 freelist에서 슬랩 object 탐색과 할당/해제가 가능하다.
  • 전담 cpu가 아닌 다른 cpu는 freelist에서 슬랩 object의 탐색 및 할당이 불가능하고 오직 슬랩 object의 할당 해제만 허용된다.

 

kmem_cache_order_objects 구조체

include/linux/slub_def.h

/*
 * Word size structure that can be atomically updated or read and that
 * contains both the order and the number of objects that a slab of the
 * given order would contain.
 */
struct kmem_cache_order_objects {
        unsigned long x;
};
  • x[15:0]
    • slub 페이지를 만들 때 사용할 최대 object 수
  • x[31:16]
    • slub 페이지를 만들 때 사용할 order

 

참고

 

 

Slub Memory Allocator -4- (order 계산)

<kernel v5.0>

슬랩 캐시 사이즈 및 order 산출

calculate_sizes()

mm/slub.c -1/2-

/*
 * calculate_sizes() determines the order and the distribution of data within
 * a slab object.
 */
static int calculate_sizes(struct kmem_cache *s, int forced_order)
{
        slab_flags_t flags = s->flags;
        unsigned int size = s->object_size;
        unsigned int order;

        /*
         * Round up object size to the next word boundary. We can only
         * place the free pointer at word boundaries and this determines
         * the possible location of the free pointer.
         */
        size = ALIGN(size, sizeof(void *));

#ifdef CONFIG_SLUB_DEBUG
        /*
         * Determine if we can poison the object itself. If the user of
         * the slab may touch the object after free or before allocation
         * then we should never poison the object itself.
         */
        if ((flags & SLAB_POISON) && !(flags & SLAB_TYPESAFE_BY_RCU) &&
                        !s->ctor)
                s->flags |= __OBJECT_POISON;
        else
                s->flags &= ~__OBJECT_POISON;

        /*
         * If we are Redzoning then check if there is some space between the
         * end of the object and the free pointer. If not then add an
         * additional word to have some bytes to store Redzone information.
         */
        if ((flags & SLAB_RED_ZONE) && size == s->object_size)
                size += sizeof(void *);
#endif

        /*
         * With that we have determined the number of bytes in actual use
         * by the object. This is the potential offset to the free pointer.
         */
        s->inuse = size;

        if (((flags & (SLAB_TYPESAFE_BY_RCU | SLAB_POISON)) ||
                s->ctor)) {
                /*
                 * Relocate free pointer after the object if it is not
                 * permitted to overwrite the first word of the object on
                 * kmem_cache_free.
                 *
                 * This is the case if we do RCU, have a constructor or
                 * destructor or are poisoning the objects.
                 */
                s->offset = size;
                size += sizeof(void *);
        }

지정된 forced_order 또는 객체 사이즈(s->size)로 적절히 산출된 order 둘 중 하나를 선택하여 slab에 들어갈 최소 객체 수(s->min), 적절한 객체 수(s->oo) 및 최대 객체 수(s->max)를 설정하고 1을 반환한다. 객체 수가 0인 경우 에러로 0을 반환한다.

  • 코드 라인 12에서 size를 포인터 사이즈 단위로 올림 정렬한다.
  • 코드 라인 20~24에서 poison 디버그를 요청한 경우에 한해 __OBJECT_POISON 플래그를 추가하되 별도의 생성자를 사용하는 슬랩 캐시 또는  RCU를 사용한 슬랩 캐시는 RCU가 우선되므로 poison 디버깅을 지원하지 않는다. RCU를 이용한 free 기능을 사용하는 경우 object의 free 시점이 delay 되므로 원본 object 데이터가 poison 값으로 파괴되면 안되는 상태이다.
  • 코드 라인 31~32에서 red-zone 디버깅 시 size와 object_size가 같은 경우 object와 FP(Free Pointer) 사이에 red-zone으로 사용할 패딩 공간이 없어 별도의 red-zone 용도의 패딩 공간을 포인터 길이 만큼 추가한다.
  • 코드 라인 39에서 지금 까지 산출된 실제 object에 사용하는 사이즈(actual size)를 s->inuse에 저장한다.
  • 코드 라인 41~53에서 object 자리에 FP(Free Pointer)가 기록되는데, rcu를 사용한 object free나 poison 디버깅 및 생성자를 별도로 사용하는 경우 object 위치에 관련 정보를 기록한다. 따라서 FP(Free Pointer)를 뒤로 이동시켜 추가하여야 한다. s->offset은 FP의 위치를 가리킨다.
    • RCU는 free 시점이 delay 되므로 원본 object 데이터가 FP 값으로 overwrite되어 파괴되면 안되는 상태이다.

 

mm/slub.c -2/2-

#ifdef CONFIG_SLUB_DEBUG
        if (flags & SLAB_STORE_USER)
                /*
                 * Need to store information about allocs and frees after
                 * the object.
                 */
                size += 2 * sizeof(struct track);
#endif

        kasan_cache_create(s, &size, &s->flags);
#ifdef CONFIG_SLUB_DEBUG
        if (flags & SLAB_RED_ZONE) {
                /*
                 * Add some empty padding so that we can catch
                 * overwrites from earlier objects rather than let
                 * tracking information or the free pointer be
                 * corrupted if a user writes before the start
                 * of the object.
                 */
                size += sizeof(void *);

                s->red_left_pad = sizeof(void *);
                s->red_left_pad = ALIGN(s->red_left_pad, s->align);
                size += s->red_left_pad;
        }
#endif

        /*
         * SLUB stores one object immediately after another beginning from
         * offset 0. In order to align the objects we have to simply size
         * each object to conform to the alignment.
         */
        size = ALIGN(size, s->align);
        s->size = size;
        if (forced_order >= 0)
                order = forced_order;
        else
                order = calculate_order(size);

        if ((int)order < 0)
                return 0;

        s->allocflags = 0;
        if (order)
                s->allocflags |= __GFP_COMP;

        if (s->flags & SLAB_CACHE_DMA)
                s->allocflags |= GFP_DMA;

        if (s->flags & SLAB_RECLAIM_ACCOUNT)
                s->allocflags |= __GFP_RECLAIMABLE;

        /*
         * Determine the number of objects per slab
         */
        s->oo = oo_make(order, size);
        s->min = oo_make(get_order(size), size);
        if (oo_objects(s->oo) > oo_objects(s->max))
                s->max = s->oo;

        return !!oo_objects(s->oo);
}
  • 코드 라인 2~7에서 SLAB_STORE_USER GFP 플래그가 사용된 경우 alloc/free 호출한 owner 트래킹을 위해 alloc 용 track 구조체와 free용 track 구조체 사이즈를 추가한다.
  • 코드 라인 10에서 런타임 디버거인 KASAN을 사용하는 경우 관련 정보들을 추가한다.
  • 코드 라인 12~25에서 red-zone 디버깅을 사용하는 경우 좌측에 red_left_pad를 추가하고, 마지막에 red-zone 정보용으로 포인터 사이즈만큼 추가한다.
  • 코드 라인 33~34에서 지금까지 산출된 size에 align을 적용한다.
  • 코드 라인 35~41에서 forced_order가 지정된 경우를 제외하고 size에 따른 적절한 order를 산출한다. (가능하면 0~3 이내)
  • 코드 라인 43~51에서 다음과 같은 요청의 슬랩 캐시인 경우 플래그를 추가한다.
    • order가 1 이상인 경우 compound 페이지를 만들기 위해 __GFP_COMP 플래그를 추가한다.
    • dma용 슬랩 캐시를 요청한 경우 GFP_DMA 플래그를 추가한다.
    • reclaimable 슬랩 캐시를 요청한 경우 __GFP_RECLAIMABLE 플래그를 추가한다.
      • migrate type을 reclaimable로 한다.
  • 코드 라인 56~59에서 다음 3개의 order 및 object 수를 산출한다.
    • s->oo에 산출한 order와 object 수를 대입한다.
    • s->min에 최소 order와 object 수를 대입한다.
    • s->max에 s->oo의 최고치를 갱신한다.
  • 코드 라인 61에서 적절한 order의 산출 여부를 반환한다. (성공 시 1)

 

다음 그림은 slab에 사용할 최소 order와 적절한 order 및 최대 order가 산출된 모습을 보여준다.

 


order 산출

calculate_order()

mm/slub.c

static inline int calculate_order(unsigned int size)
{
        unsigned int order;
        unsigned int min_objects;
        unsigned int max_objects;

        /*
         * Attempt to find best configuration for a slab. This
         * works by first attempting to generate a layout with
         * the best configuration and backing off gradually.
         *
         * First we increase the acceptable waste in a slab. Then
         * we reduce the minimum objects required in a slab.
         */
        min_objects = slub_min_objects;
        if (!min_objects)
                min_objects = 4 * (fls(nr_cpu_ids) + 1);
        max_objects = order_objects(slub_max_order, size);
        min_objects = min(min_objects, max_objects);

        while (min_objects > 1) {
                unsigned int fraction;

                fraction = 16;
                while (fraction >= 4) {
                        order = slab_order(size, min_objects,
                                        slub_max_order, fraction);
                        if (order <= slub_max_order)
                                return order;
                        fraction /= 2;
                }
                min_objects--;
        }

        /*
         * We were unable to place multiple objects in a slab. Now
         * lets see if we can place a single object there.
         */
        order = slab_order(size, 1, slub_max_order, 1);
        if (order <= slub_max_order)
                return order;

        /*
         * Doh this slab cannot be placed using slub_max_order.
         */
        order = slab_order(size, 1, MAX_ORDER, 1);
        if (order < MAX_ORDER)
                return order;
        return -ENOSYS;
}

slub 할당을 위해 @size에 따른 적절한 order를 산출한다. 가능하면 커널 파라미터의  “slub_min_order=”(디폴트=0) ~ “slub_max_order=”(디폴트=3) 범위 내에서 적절한 order를 산출한다. 그러나 size가 slub_max_order  페이지 크기보다 큰 경우 버디 시스템 최대 order 범위내에서 산출한다.

  • 코드 라인 15~17에서 슬랩 페이지당 관리할 최소 object 수를 지정하기 위해 다음 중 하나를 사용한다.
    •  “slub_min_order=” 커널 파라메터로 설정된 값(디폴트=0)을 사용한다.
    • cpu 수에 비례한 수를 산출한다.
      • 수식: 4 * (2log(cpu 수) + 1 + 1)
      • rpi2 예) 4 * (2 + 1 + 1) = 16
  • 코드 라인 18에서 “slub_max_order=”(default=3) 사이즈에 들어갈 수 있는 최대 object 수를 구한다.
  • 코드 라인 19에서 위의 두 값 중 가장 작은 값으로 min_objects 수를 갱신한다.

 

1st phase: slab order 범위 내 적절한 order 산출

위에서 산출된 min_objects 수를 포함 시킬 수 있는 최소 order 부터 시작하되 “slub_min_order=”(디폴트=0) ~ “slub_max_order=”(디폴트=3) 범위 order 순으로 순회하며 @size에 따른 object들을 배치시키고 남은 나머지 공간이 낭비가 적은 order를 반환한다. 남은 나머지 공간이 진행 중인 order 페이지 사이즈의 1/16보다 큰 경우 낭비가 있다고 판단하여 다음 order로 넘어간다. 만일 1/16으로 안되면 1/8로 시도하고, 마지막으로 1/4로 시도한다. 마지막 시도마저 안되면 min_objects 수를 1씩 줄여가며 다시 처음 부터 시도하고, min_objects가 마지막 2가 될 때까지 시도한다.

  • 코드 라인 21에서 산출된 min_objects가 2개 이상인 경우에만 루프를 돈다.
    • min_objects가 16으로 산출된 경우 예) 16, 15, 14, … 2까지 루프를 돈다.
  • 코드 라인 24~25에서 낭비 여부를 판단하기 위해 진행 중인 order 페이지 사이즈의 1/16, 1/8, 1/4로 변경하며 시도한다.
  • 코드 라인 26~29에서 min_objects 수가 포함된 order 부터 “slub_min_order=”(디폴트=0) ~ “slub_max_order=”(디폴트=3) 범위 order  순으로 순회하며 fraction(1/16, 1/8, 1/4)으로 지정된 낭비 사이즈 이내에 배치가능한 order를 반환한다.
  • 코드 라인30~31에서fraction을 반으로 줄이고 다시 반복한다.
  • 코드 라인 32~33에서 min_objects를 1 감소시키고 다시 반복한다.

 

2nd phase: 1개 object가 들어갈 수 있는 slab order 범위 내 산출
  • 코드 라인 39~41에서 “slub_max_order=”(디폴트=3) 범위 order 까지 순회하며 낭비 사이즈 범위를 고려하지 않고, 1개의 object라도 들어갈 수 있는 order를 산출한다.

 

3rd phase: 1개 object가 들어갈 수 있는 order 산출 (order 무제한)
  • 코드 라인 46~48에서 버디 시스템의 최대 order 까지 순회하며 낭비 사이즈 범위를 고려하지 않고, 1개의 object라도 들어갈 수 있는 order를 산출한다.

 

다음 그림은 slab 할당을 위해 적절한 order를 산출하는 모습을 보여준다.

 

order_objects()

mm/slub.c

static inline unsigned int order_objects(unsigned int order, unsigned int size)
{
        return ((unsigned int)PAGE_SIZE << order) / size;
}

요청된 order 페이지에서  생성할 수 있는 최대 object 수를 구한다.

  • 객체가 할당되는 2^order 페이지에서 size로 나눈 수를 반환한다.

 

다음 그림은 order 만큼의 페이지에서 생성할 수 있는 최대 object의 수를 알아오는 것을 보여준다.

 

slab_order()

mm/slub.c

/*
 * Calculate the order of allocation given an slab object size.
 *
 * The order of allocation has significant impact on performance and other
 * system components. Generally order 0 allocations should be preferred since
 * order 0 does not cause fragmentation in the page allocator. Larger objects
 * be problematic to put into order 0 slabs because there may be too much
 * unused space left. We go to a higher order if more than 1/16th of the slab
 * would be wasted.
 *
 * In order to reach satisfactory performance we must ensure that a minimum
 * number of objects is in one slab. Otherwise we may generate too much
 * activity on the partial lists which requires taking the list_lock. This is
 * less a concern for large slabs though which are rarely used.
 *
 * slub_max_order specifies the order where we begin to stop considering the
 * number of objects in a slab as critical. If we reach slub_max_order then
 * we try to keep the page order as low as possible. So we accept more waste
 * of space in favor of a small page order.
 *
 * Higher order allocations also allow the placement of more objects in a
 * slab and thereby reduce object handling overhead. If the user has
 * requested a higher mininum order then we start with that one instead of
 * the smallest order which will fit the object.
 */
static inline unsigned int slab_order(unsigned int size,
                unsigned int min_objects, unsigned int max_order,
                unsigned int fract_leftover)
{
        unsigned int min_order = slub_min_order;
        unsigned int order;

        if (order_objects(min_order, size) > MAX_OBJS_PER_PAGE)
                return get_order(size * MAX_OBJS_PER_PAGE) - 1;

        for (order = max(min_order, (unsigned int)get_order(min_objects * size));
                        order <= max_order; order++) {

                unsigned int slab_size = (unsigned int)PAGE_SIZE << order;
                unsigned int rem;

                rem = slab_size % size;

                if (rem <= slab_size / fract_leftover)
                        break;
        }

        return order;
}

slab 페이지 생성에 필요한 order를 산출한다. @size로 @min_objects 만큼 배치 가능해야 하고, 최대 @max_order 범위내에서 object를 배치하고 남은 공간이 슬랩 페이지의 1/fract_leftover 보다 크면 안된다.

  • 코드 라인 5에서 “slub_min_order=”(디폴트=0) 값을 min_order에 대입한다.
  • 코드 라인 8~9에서 min_order 페이지에 포함 가능한 object 수가 MAX_OBJS_PER_PAGE(32767)를 초과하는 경우 size * MAX_OBJS_PER_PAGE(32767)를 처리할 수 있는 order 값 – 1을 반환한다.
  • 코드 라인 11~12에서 min_order 또는 @min_objects * @size 만큼 포함 가능한 order 둘 중 큰 order 부터 @max_order 까지 순회한다.
  • 코드 라인 14~20에서 순회 중인 order 페이지에 object들을 배치하고 남은 공간이 슬랩 페이지의 1/@fract_leftover 보다 작은 경우만 낭비가 적다 판단하여 루프를 벗어나서 order를 반환한다.

 

다음 그림은 최대 3 order 페이지 범위내에서 object의 사이즈가 1032이고, 최소 4개 이상을 배치하여 남은 사이즈 공간이 슬랩 페이지의 1/16보다 작은 order를 산출하는 모습을 보여준다.

 

get_order()

include/asm-generic/getorder.h

/**
 * get_order - Determine the allocation order of a memory size
 * @size: The size for which to get the order
 *
 * Determine the allocation order of a particular sized block of memory.  This
 * is on a logarithmic scale, where:
 *
 *      0 -> 2^0 * PAGE_SIZE and below
 *      1 -> 2^1 * PAGE_SIZE to 2^0 * PAGE_SIZE + 1
 *      2 -> 2^2 * PAGE_SIZE to 2^1 * PAGE_SIZE + 1
 *      3 -> 2^3 * PAGE_SIZE to 2^2 * PAGE_SIZE + 1
 *      4 -> 2^4 * PAGE_SIZE to 2^3 * PAGE_SIZE + 1
 *      ...
 *
 * The order returned is used to find the smallest allocation granule required
 * to hold an object of the specified size. 
 *
 * The result is undefined if the size is 0.
 *
 * This function may be used to initialise variables with compile time
 * evaluations of constants.
 */
#define get_order(n)                                            \
(                                                               \
        __builtin_constant_p(n) ? (                             \
                ((n) == 0UL) ? BITS_PER_LONG - PAGE_SHIFT :     \
                (((n) < (1UL << PAGE_SHIFT)) ? 0 :              \
                 ilog2((n) - 1) - PAGE_SHIFT + 1)               \
        ) :                                                     \
        __get_order(n)                                          \
)

@n 사이즈에 따른 order 값을 산출한다.

  • 예) 4K 페이지, n=1025 ~ 2048
    • -> 1

 

__get_order()

include/asm-generic/getorder.h

/*
 * Runtime evaluation of get_order()
 */
static inline __attribute_const__
int __get_order(unsigned long size)
{
        int order;

        size--;
        size >>= PAGE_SHIFT;
#if BITS_PER_LONG == 32
        order = fls(size);
#else
        order = fls64(size);
#endif
        return order;
}

@size를 버디 시스템의 order로 표현할 때의 값을 구한다.

  • size에서 1을 뺀 값을 페이지 수로 변경하고 이를 표현할 수 있는 필요 비트 수를 구한다.
    • fls()
      • lsb -> msb로 검색하여 가장 마지막에 발견되는 1로된 비트 번호 + 1
        • 예) 0xf000_0000 -> 32
  • 예) size=0x10_0000 (1MB)
    • -> 8

 

oo_make()

mm/slub.c

static inline struct kmem_cache_order_objects oo_make(unsigned int order,
                unsigned int size)
{
        struct kmem_cache_order_objects x = {
                (order << OO_SHIFT) + order_objects(order, size)
        };

        return x;
}

@order 및 @size를 사용하여 kmem_cache_order_objects 구조체 객체에 담고 그 값을 반환한다.

  • 객체는 하나의 unsigned int 값을 사용하는데 lsb 16bit에 객체 수를 담고 나머지 bits에 order 값을 담는다.
  • OO_SHIFT=16

 

아래 그림은 oo_make() 인라인 함수를 사용하여 order 값과 산출된 객체 수를 kmem_cache_order_objects라는 내부 규조체 객체에 담아 반환을 하는 모습을 보여준다.

 

oo_objects()

mm/slub.c

static inline unsigned int oo_objects(struct kmem_cache_order_objects x)
{
        return x.x & OO_MASK;
}

@x 값에서 object 수 만을 반환한다.

  • x 값의 lsb 16bit를 반환한다.

 

다음 그림은  kmem_cache_order_objects 구조체 객체에서 객체 수 값만을 반환받는 모습을 보여준다.

 

참고