Slab Memory Allocator -2- (캐시 초기화)

<kernel v5.0>

슬랩 캐시 초기화

커널이 부트업 프로세스 과정에서 slub 메모리 할당자를 활성화 하기 위해 다음 그림과 같이 먼저 캐시 초기화 과정을 수행한다.

 

kmem_cache_init() – (slub)

mm/slub.c

void __init kmem_cache_init(void)
{
        static __initdata struct kmem_cache boot_kmem_cache,
                boot_kmem_cache_node;

        if (debug_guardpage_minorder())
                slub_max_order = 0;

        kmem_cache_node = &boot_kmem_cache_node;
        kmem_cache = &boot_kmem_cache;

        create_boot_cache(kmem_cache_node, "kmem_cache_node",
                sizeof(struct kmem_cache_node), SLAB_HWCACHE_ALIGN, 0, 0);

        register_hotmemory_notifier(&slab_memory_callback_nb);

        /* Able to allocate the per node structures */
        slab_state = PARTIAL;

        create_boot_cache(kmem_cache, "kmem_cache",
                        offsetof(struct kmem_cache, node) +
                                nr_node_ids * sizeof(struct kmem_cache_node *),
                       SLAB_HWCACHE_ALIGN, 0, 0);

        kmem_cache = bootstrap(&boot_kmem_cache);
        kmem_cache_node = bootstrap(&boot_kmem_cache_node);

        /* Now we can use the kmem_cache to allocate kmalloc slabs */
        setup_kmalloc_cache_index_table();
        create_kmalloc_caches(0);

        /* Setup random freelists for each cache */
        init_freelist_randomization();

        cpuhp_setup_state_nocalls(CPUHP_SLUB_DEAD, "slub:dead", NULL,
                                  slub_cpu_dead);

        pr_info("SLUB: HWalign=%d, Order=%u-%u, MinObjects=%u, CPUs=%u, Nodes=%d\n",
                cache_line_size(),
                slub_min_order, slub_max_order, slub_min_objects,
                nr_cpu_ids, nr_node_ids);
}

슬랩캐시를 초기화한다. 슬랩 캐시를사용할 수 있도록 기본 슬랩 캐시kmem_cachekmem_cache_node 슬랩 캐시를 먼저 구성한다.

  • 코드 라인3~4에서kmem_cache 구조체인 boot_kmem_cache와 boot_kmem_cache_node 변수를 준비한다.
    • 이 변수는 kmem_cache와 kmem_cache_node 라는 이름의 캐시가 만들어지는 경우 대체되어 추후 사용되지 않게 된다.
  • 코드 라인  6~7에서”debug_guardpage_minorder=” 커널 파라메터를 사용하여 0~MAX_ORDER/2 범위의 값을 지정할 수 있고 이러한 경우 slub_max_order 값에 0을 대입한다.
  • 코드 라인 9~10에서전역 변수 kmem_cache_node에 boot_kmem_cache_node 초기 구조체 값을 대입한다. 그리고전역 변수 kmem_cache에 boot_kmem_cache 초기 구조체 값도 대입한다.
  • 코드 라인 12~13에서 부트 타임용 “kmem_cache_node” 슬랩 캐시를 캐시 라인에 정렬하게 준비한다.
  • 코드 라인 15에서hotmemory notifier로 slab_memory_callback_nb를 등록한다.
  • 코드 라인 18에서 kmem_cache_node 캐시가 준비되었으므로 슬랩 상태를 PARTIAL로 변경한다.
    • PARTIAL 상태는 추가 슬랩 캐시를 만들지는 못하지만, 이미 생성한 kmem_cache_node 슬랩 캐시에서 슬랩 object를 할당 받을 수 있다.
  • 코드 라인  20~23에서 부트 타임용 “kmem_cache” 슬랩 캐시를 캐시 라인에 정렬하게 준비한다.
    • 사이즈를 산출할 때 sizeof(struct kmem_cache)를 사용하지 않은 것에 유의해야 한다. 컴파일 타임에 산출된 kmem_cache  구조체의 마지막 멤버 node[] 배열은 MAX_NUMNODES 수 만큼 생성을 한다. 이를 런타임에  nr_node_ids 수 만큼 변경하여 사용하면 kmem_cache 구조체 사이즈를 줄여서 사용할 수 있게된다.
  • 코드 라인 25에서 kmem_cache 슬랩 캐시에서 object를 할당받아 임시로 사용하는 kmem_cache를 똑같이 복사한 후 사용한다.
  • 코드 라인 26에서 kmem_cache_node 슬랩 캐시에서 object를 할당받아 임시로 사용하는 kmem_cache_node를 똑같이 복사한 후 사용한다.
  • 코드라인 29에서 kmalloc 슬랩 캐시용 사이즈테이블을 초기화한다.
  • 코드 라인 30에서 kmalloc-<size>, kmalloc-rcl-<size> 및 dma-kmalloc-<size> 이름의 슬랩 캐시를 사이즈별로 미리 생성한다.
    • size=8, 16, 32, 64, 128, 256, 512, 1k, 2k, 4k, 8k 이며, 조건에 따라 96, 192가 추가된다.
  • 코드 라인 33에서 보안을 향상시키기 위해 free object들의 순서를 랜덤하게 만들어 숨길 수 있도록 랜덤 시퀀스 배열을 초기화한다.
  • 코드 라인 35~36에서 CPUHP_SLUB_DEAD 상태로cpu off시 slub_cpu_dead() 함수가 호출되게 등록한다.
  • 코드 라인 38~41에서 생성된 슬랩 캐시 정보를 출력한다.
    • rpi2 & rpi4 예) SLUB: HWalign=64, Order=0-3, MinObjects=0, CPUs=4, Nodes=1

 

다음 그림은 kmem_cache_node와 kmem_cache라는 이름으로 두 개의 kmem_cache 구조체에 대입되는 내용을 보여준다.

 

다음 그림은 디버그 정보를 포함한 커널을 사용했을 때 변경되는 사이즈 및 order 등의 값을 확인할 수 있다.

 

다음 그림은 kmem_cache_init() 함수에서 1단계로 임시 kmem_cache_node 슬랩 캐시를 구성하는 모습을 보여준다.

 

다음 그림은 kmem_cache_init() 함수에서 2단계로 임시 kmem_cache 슬랩 캐시를 구성하는 모습을 보여준다.

 

다음 그림은 kmem_cache_init() 함수에서 3단계로 임시 kmem_cache 슬랩 캐시를 정규 kmem_cache 슬랩 캐시로 전환하는 모습을 보여준다.

 

다음 그림은 kmem_cache_init() 함수에서 4단계로 임시 kmem_cache_node 슬랩 캐시를 정규 kmem_cache_node 슬랩 캐시로 전환하는 모습을 보여준다.

 

부트 타임 슬랩 캐시 생성

create_boot_cache() – (slab, slub)

mm/slab_common.c

#ifndef CONFIG_SLOB             
/* Create a cache during boot when no slab services are available yet */
void __init create_boot_cache(struct kmem_cache *s, const char *name, size_t size,
                unsigned long flags)
{
        int err;

        s->name = name;
        s->size = s->object_size = size;
        s->align = calculate_alignment(flags, ARCH_KMALLOC_MINALIGN, size);

        slab_init_memcg_params(s);

        err = __kmem_cache_create(s, flags);

        if (err)
                panic("Creation of kmalloc slab %s size=%zu failed. Reason %d\n",
                                        name, size, err);

        s->refcount = -1;       /* Exempt from merging for now */
}
#endif

부트 타임에 사용할 슬랩 캐시를생성한다

  • 코드 라인 8~10에서 인수 flags, align, size로 최종 사용할 align을 구한다.
  • 코드 라인 12에서 memcg에 대한 파라메터들을 초기화한다.
  • 코드 라인 14~18에서 요청한 slub 캐시를 생성한다.
  • 코드 라인 20에서 아직 완전히 구성되지 않아서 레퍼런스 카운터 값에 -1을 대입한다.

 

align 산출

calculate_alignment()

mm/slab_common.c

/*
 * Figure out what the alignment of the objects will be given a set of
 * flags, a user specified alignment and the size of the objects.
 */
unsigned long calculate_alignment(unsigned long flags,
                unsigned long align, unsigned long size)
{
        /*
         * If the user wants hardware cache aligned objects then follow that
         * suggestion if the object is sufficiently large.
         *
         * The hardware cache alignment cannot override the specified
         * alignment though. If that is greater then use it.
         */
        if (flags & SLAB_HWCACHE_ALIGN) {
                unsigned long ralign = cache_line_size();
                while (size <= ralign / 2)
                        ralign /= 2;
                align = max(align, ralign);
        }

        if (align < ARCH_SLAB_MINALIGN)
                align = ARCH_SLAB_MINALIGN;

        return ALIGN(align, sizeof(void *));
}

@size와 @align 및 @flags를 사용하여 최종 사용할 align 값을산출한다. 하드웨어 캐시 정렬 요청 플래그를사용한 경우 캐시 라인 사이즈 단위로 최대한 object를 정렬 시킬 수 있는 적절한 align값을 산출한다. 다만 산출된 align 값보다 요청한 @align 값이 큰 경우 요청한 @align 값을 사용한다. 최종 산출되는 align 값은 8의 배수로 정렬한다. (부트 타임에 요청한 @align 값은 ARCH_KMALLOC_MINALIGN(ARM74=128) 값을 사용한다.)

  • 코드라인11~16에서 SLAB_HWCACHE_ALIGN  플래그를 사용한 경우 캐시 라인 사이즈에 맞춰 object가 배치될 수 있도록 적절한 align 값으로설정한다
    • 임시로 사용되는 boot 슬랩 캐시를 만드는 경우 @align 값으로 ARCH_KMALLOC_MINALIGN(ARM32=64, ARM64=128)을 사용한다.
    • size가 캐시라인보다 작은 경우 캐시 라인을 2log한 수들 중 size 값이 가장 가까운 큰 수 선택, 그 후 산출된 align과 요청 @align 값 중 가장 큰 값 사용
      • ARM64 예)  캐시 라인=64
        • size=0~8, @align=8 -> align=8
        • size=9~16, @align=8 -> align=16
        • size=17~32, @align=8 -> align=32
        • size=33~64, @align=8  -> align=64
        • size=0~64, @align=128 -> align=128 (산출된 align과 요청 align 값 중 가장 큰 값 사용)
    • size가 캐시라인보다 큰 경우 align 값을 캐시 라인 값으로 정렬, 그 후 산출된 align과 요청 @align 값 중 가장 큰 값 사용
      • ARM64 예)  캐시 라인=64
        • size=136, @align=8 -> align=64
        • size=136, @align=128 -> align=128
  • 코드 라인 18~19에서 align 값의 최하값을 ARCH_SLAB_MINALIGN(8)로 조정한다
    • ARM32 & ARM64 아키텍처의ARCH_SLAB_MINALIGN 값은 8 이다.
  • 코드 라인 21에서 align 값을 포인터 사이즈(8) 단위로 정렬한 값을 반환한다

 

ARCH_KMALLOC_MINALIGN

include/linux/slab.h

/*
 * Some archs want to perform DMA into kmalloc caches and need a guaranteed
 * alignment larger than the alignment of a 64-bit integer.
 * Setting ARCH_KMALLOC_MINALIGN in arch headers allows that.
 */
#if defined(ARCH_DMA_MINALIGN) && ARCH_DMA_MINALIGN > 8
#define ARCH_KMALLOC_MINALIGN ARCH_DMA_MINALIGN
#define KMALLOC_MIN_SIZE ARCH_DMA_MINALIGN
#define KMALLOC_SHIFT_LOW ilog2(ARCH_DMA_MINALIGN)
#else
#define ARCH_KMALLOC_MINALIGN __alignof__(unsigned long long)
#endif

kmalloc()에 의해 리턴되는 메모리는 DMA를 위해 사용되므로 이런 할당은 cache align되어야 한다.

  • cpu 아키텍처가 지원하는 L1 데이타 캐시 라인 사이즈로 설정하되 최하 8바이트 이상이다.

 

ARCH_DMA_MINALIGN

arch/arm64/include/asm/cache.h

/*
 * Memory returned by kmalloc() may be used for DMA, so we must make
 * sure that all such allocations are cache aligned. Otherwise,
 * unrelated code may cause parts of the buffer to be read into the
 * cache before the transfer is done, causing old data to be seen by
 * the CPU.
 */
#define ARCH_DMA_MINALIGN       (128)

DMA 버퍼는 아키텍처의 L1 캐시 버퍼에 정렬하여 사용하여야 한다.

  • ARM32 rpi2: 64 bytes
  • ARM64: 기존 커널은 64 bytes 였으나 v4.18-rc1 이후로 현존하는 모든 arm64의 L1 캐시 사이즈를 지원하기 위해 최고 값인 128 bytes로 변경하였다.

 

ARCH_SLAB_MINALIGN

include/linux/slab.h

#define ARCH_SLAB_MINALIGN __alignof__(unsigned long long)

슬랩의 최소 정렬 크기는 8 바이트이다.

 


Hot-Plug 메모리notifier

register_hotmemory_notifier()

include/linux/memory.h

#define register_hotmemory_notifier(nb)         register_memory_notifier(nb)

hotmemory notifier로 nb를 등록한다.

 

register_memory_notifier()

drivers/base/memory.c

int register_memory_notifier(struct notifier_block *nb)
{
        return blocking_notifier_chain_register(&memory_chain, nb);
}
EXPORT_SYMBOL(register_memory_notifier);

memory notifier로 nb를 memory_chain 리스트에 추가한다.

 

drivers/base/memory.c

static BLOCKING_NOTIFIER_HEAD(memory_chain);

memory notifier용memory_chain 리스트이다

 


정규 슬랩 캐시로 변환

bootstrap()

mm/slub.c

/*
 * Used for early kmem_cache structures that were allocated using
 * the page allocator. Allocate them properly then fix up the pointers
 * that may be pointing to the wrong kmem_cache structure.
 */
static struct kmem_cache * __init bootstrap(struct kmem_cache *static_cache)
{
        int node;
        struct kmem_cache *s = kmem_cache_zalloc(kmem_cache, GFP_NOWAIT);
        struct kmem_cache_node *n;

        memcpy(s, static_cache, kmem_cache->object_size);

        /*
         * This runs very early, and only the boot processor is supposed to be
         * up.  Even if it weren't true, IRQs are not up so we couldn't fire
         * IPIs around.
         */
        __flush_cpu_slab(s, smp_processor_id());
        for_each_kmem_cache_node(s, node, n) {
                struct page *p;

                list_for_each_entry(p, &n->partial, lru)
                        p->slab_cache = s;

#ifdef CONFIG_SLUB_DEBUG
                list_for_each_entry(p, &n->full, lru)
                        p->slab_cache = s;
#endif 
        }
        slab_init_memcg_params(s);
        list_add(&s->list, &slab_caches);
        return s;
}

kmem_cache object를 새로 할당 받은 주소에 인수로 전달 받은 static_cache를 obj_size 만큼 복사한다. 그런 후 이 캐시를 활성화 시키고 slub 캐시 운용을 위해 slab_caches 리스트에 추가 한다.

  • 코드 라인 4에서 kmem_cache 슬랩 캐시를 통해 object를 할당 받아 온다.
  • 코드 라인 7에서 임시로 사용하고 있는 kmem_cache 구조체 형태로 전달받은 인수 static_cache의 내용을 object_size 만큼 새로 할당 받은 캐시로 복사한다.
  • 코드 라인 14에서 early 부트업 과정에 있으므로 실제 다른 cpu로의 IPI call을 할 수 없으므로 local cpu에 대해서만 flush를 하게된다.
  • 코드 라인 15~25에서 kmem_cache에 대한 슬랩 캐시를 순회하며 per 노드 partial 리스트와 full 리스트에소속된 slab 페이지가 가리키는 slab_cache 구조체 포인터를 새로 할당 받은 kmem_cache object를 가리키게 한다
  • 코드 라인 26에서 memcg 파라메터들을 모두 초기화한다.
  • 코드 라인 27에서 전역 slab_caches에 할당 받은 캐시를 추가한다.
  • 코드라인 28에서 새로 할당 받은 슬랩 캐시를 반환한다.

 


Slub 디버그 커널 옵션

slub_debug

        slub_debug[=options[,slabs]]    [MM, SLUB]
                        Enabling slub_debug allows one to determine the
                        culprit if slab objects become corrupted. Enabling
                        slub_debug can create guard zones around objects and
                        may poison objects when not in use. Also tracks the
                        last alloc / free. For more information see
                        Documentation/vm/slub.txt.

슬랩 디버그 옵션으로 다음에서 자세히 설명한다.

 

slub_max_order

        slub_max_order= [MM, SLUB]
                        Determines the maximum allowed order for slabs.
                        A high setting may cause OOMs due to memory
                        fragmentation. For more information see
                        Documentation/vm/slub.txt.

슬랩 페이지를 생성할 때 object size에 맞춰 최대한 낭비되지 않는 슬렙 페이지를 생성하기 위해 적절한 order 값을 결정하는데 이 때 최대 order 값으로 이 값을 사용한다.

 

slub_min_order

        slub_min_order= [MM, SLUB]
                        Determines the minimum page order for slabs. Must be
                        lower than slub_max_order.
                        For more information see Documentation/vm/slub.txt.

위의 slub_max_order와 같이 사용되며 슬렙 페이지를 생성하기 위해 적절한 order 값을 결정하는데 이 때 최소 order 값으로 이 값을 사용한다.

 

slub_min_objects

        slub_min_objects=       [MM, SLUB]
                        The minimum number of objects per slab. SLUB will
                        increase the slab order up to slub_max_order to
                        generate a sufficiently large slab able to contain
                        the number of objects indicated. The higher the number
                        of objects the smaller the overhead of tracking slabs
                        and the less frequently locks need to be acquired.
                        For more information see Documentation/vm/slub.txt.

슬랩 페이지 생성 시 필요한 최소 object 수를 지정한다. 가능하면 이 값 이상의 object가 들어갈 수 있는 order로 슬랩 페이지를 생성한다.

 

slub_nomerge

        slub_nomerge    [MM, SLUB]
                        Same with slab_nomerge. This is supported for legacy.
                        See slab_nomerge for more information.

슬랩 캐시들이 merge되지 않도록 한다. 이 경우 모든 슬랩 캐시 생성은 merge되지 않으므로 alias 캐시가 만들어지지 않는다.

 

참고

댓글 남기기

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