set_mems_allowed()

 

set_mems_allowed()

include/linux/cpuset.h

#ifdef CONFIG_CPUSETS
static inline void set_mems_allowed(nodemask_t nodemask)
{
        unsigned long flags;

        task_lock(current);
        local_irq_save(flags);
        write_seqcount_begin(&current->mems_allowed_seq);
        current->mems_allowed = nodemask;
        write_seqcount_end(&current->mems_allowed_seq);
        local_irq_restore(flags);
        task_unlock(current);
}
#else /* !CONFIG_CPUSETS */

NUMA 시스템 및 Cpuset Control Group과 관련되어 현재 태스크에서 사용할 수 있는 노드를 배정한다.

  • 이 기능을 통해 Control Group의 cpuset 서브시스템에서 현재 태스크가 특정 노드에서만 메모리를 할당 받을 수 있도록 제한을 할 수 있다.

kmalloc

<kernel v5.0>

kmalloc 할당자

커널에서 주로 작은 사이즈의 연속된 물리 주소 공간을 할당 받아 사용한다. 물리적으로 연속된 공간이므로 dma 버퍼로도 사용 가능하다.

 

특징

커널에서 물리 및 가상 주소가 연속된 메모리의 할당이 필요한 경우 사용된다. kmalloc을 이용한 커널 메모리의 할당은 다음과 같은 특징이 있다.

  • 미리 매핑된 커널 메모리를 사용하므로 별도의 매핑 작업없이 곧바로 사용할 수 있어 빠르다.
  • 미리 준비된 kmalloc용 슬랩 캐시를 사용한다.
    • 슬랩 캐시를 사용 시 물리적으로 연속된 메모리를 제공하므로 장점으로는 DMA 용으로 사용될 수 있다.
    • 단점으로는 싱글 페이지를 사용하지 않고 order 단위의 페이지를 사용하므로 fragment 관리에 민감하다.

 

kmalloc 타입

커널에서 자주 사용되는 사이즈를 지정하여 슬랩 캐시를 미리 만들어 제공하는 형태이며, 사이즈별로 다음과 같이 3종류가 지원된다.

  • kmalloc-<size>
    • 일반적으로 사용되는 타입으로 메모리 회수 불가능하다.
  • kmalloc-rcl-<size>
  • dma-kmalloc-<size>
    • 주소 제한이 있는 dma 존이 필요한 시스템에서만 사용되는 타입이다.
    • __GFP_DMA 플래그를 사용하여 dma용 커널 메모리의 할당을 위해 ZONE_DMA 영역을 사용한다.
      • 별도의 dma 존이 필요하지 않는 시스템에서는 dma를 위한 메모리 할당 시 일반 타입의 kmalloc을 사용한다.

 

kmalloc 사이즈

  • 아키텍처의 L1 캐시 라인 크기부터 2의 배수단위 사이즈 까지 슬랩 캐시를 미리 준비하여 제공한다.
  • Slub 캐시의 경우 2 페이지 사이즈까지 미리 준비하여 사용된다. 이 크기를 넘어가는 메모리를 요청하는 경우 kmalloc용 슬랩 캐시가 아니라 버디 시스템을 사용하는 페이지 할당자를 사용하여 제공한다.
    • 아키텍처에 따라 96 또는 192 사이즈를 제공하는 시스템도 있다.

 

슬랩(Slub) 캐시를 사용하는 kmalloc에서 지원하는 사이즈는 다음과 같다. (ARM64의 경우 오렌지 색상으로 표기된 항목을 사용한다)

  • kmalloc-8
  • kmalloc-16
  • kmalloc-32
  • kmalloc-64
  • kmalloc-96
  • kmalloc-128
  • kmalloc-192
  • kmalloc-256
  • kmalloc-512
  • kmalloc-1k
  • kmalloc-2k
  • kmalloc-4k
  • kmalloc-8k

 

다음은 다양한 kmalloc 타입을 보여준다. (커널/tools/vm/slabinfo.c를 컴파일하여 사용하는 유틸리티)

$ slabinfo kmalloc
Name                   Objects Objsize           Space Slabs/Part/Cpu  O/S O %Fr %Ef Flg
kmalloc-128               8081     128            1.0M      246/61/14   32 0  23  97
kmalloc-256               1232     256          315.3K        65/0/12   16 0   0 100
kmalloc-512                707     512          483.3K        50/28/9   16 1  47  74
kmalloc-1k                 590    1024          638.9K         35/6/4   16 2  15  94
kmalloc-2k                 176    2048          360.4K          9/0/2   16 3   0 100
kmalloc-4k                  53    4096          229.3K          4/1/3    8 3  14  94
kmalloc-8k                  32    8192          262.1K          7/0/1    4 3   0 100
kmalloc-rcl-128            448     128           57.3K         13/0/1   32 0   0 100 a

 

API

할당 및 해제 관련한 주요 API는 다음과 같다.

  • kmalloc()
  • kfree()

 

kmalloc()에서 사용하는 GFP 플래그

  • GFP_KERNEL
    • kernel용 메모리 할당이며 sleep될 수 있다.
  • GFP_ATOMIC
    • sleep되면 안될 때 사용되며 인터럽트 핸들러 등에서 사용한다.
  • GFP_HIGHUSER
    • 유저용 highmem(high memory)에 우선 페이지 할당을 한다.
  • GFP_NOWAIT
    • 메모리 할당을 하는 동안 메모리 부족 시 kswapd를 깨워 reclaim을 하도록 허용한다.
  • __GFP_HIGH
    • 높은 우선 순위에서 처리되도록 요청할 때 사용한다.
  • __GFP_NOFAIL
    • 실패를 허용하지 않고, 메모리 할당 요청에 대해 성공할 때까지 처리하도록 요청할 때 사용한다.
  • __GFP_NORETRY
    • 메모리 할당 요청에 대해 실패 시 재시도 하지 않는다.
  • __GFP_NOWARN
    • 메모리 할당이 실패할 때 어떠한 경고도 처리하지 않도록 한다.
  • __GFP_RETRY_MAYFAIL
    • 메모리 할당이 처음 실패하는 경우 재시도 가능하나 실패 가능하다.

 

다음 그림은 kmalloc() 함수가 미리 준비된 kmalloc 슬랩 캐시 중 하나를 사용하여 object를 할당하는 모습을 보여준다.

 


Kmalloc 초기화

size_index[] 테이블

mm/slab_common.c”

/*
 * Conversion table for small slabs sizes / 8 to the index in the
 * kmalloc array. This is necessary for slabs < 192 since we have non power
 * of two cache sizes there. The size of larger slabs can be determined using
 * fls.
 */
static s8 size_index[24] = {
        3,      /* 8 */
        4,      /* 16 */
        5,      /* 24 */
        5,      /* 32 */
        6,      /* 40 */
        6,      /* 48 */
        6,      /* 56 */
        6,      /* 64 */
        1,      /* 72 */
        1,      /* 80 */
        1,      /* 88 */
        1,      /* 96 */
        7,      /* 104 */
        7,      /* 112 */
        7,      /* 120 */
        7,      /* 128 */
        2,      /* 136 */
        2,      /* 144 */
        2,      /* 152 */
        2,      /* 160 */
        2,      /* 168 */
        2,      /* 176 */
        2,      /* 184 */
        2       /* 192 */
};

슬랩 object 크기가 192 바이트 이하일 때 사용할 kmalloc 슬랩 캐시를 선택하기 위한 테이블이다.  괄호 안의 인덱스 값은 사이즈를8로 나눈 값 이고, 엔트리 값과 이에 따른 kmalloc 슬랩 캐시명은 다음과 같다.

  • 1 -> 72~96 사이즈를 사용하는 kmalloc-96
    • 아키텍처에서 지원하는 캐시 라인이 64 이상인 경우 kmalloc-96 생성을 포기하고 kmalloc-128으로 대치된다
  • 2 -> 136~192 사이즈를 사용하는kmalloc-192
    • 아키텍처에서 지원하는 캐시 라인이 128 이상인 경우 kmalloc-192 생성을 포기하고 kmalloc-256으로 대치된다.
  • 3 -> kmalloc-8
  • 4 -> kmalloc-16
  • 5 -> kmalloc-32
  • 6 -> kmalloc-64
  • 7 -> kmalloc-128

 

kmalloc_info[] 테이블

mm/slab_common.c

/*
 * kmalloc_info[] is to make slub_debug=,kmalloc-xx option work at boot time.
 * kmalloc_index() supports up to 2^26=64MB, so the final entry of the table is
 * kmalloc-67108864.
 */
const struct kmalloc_info_struct kmalloc_info[] __initconst = {
        {NULL,                      0},         {"kmalloc-96",             96},
        {"kmalloc-192",           192},         {"kmalloc-8",               8},
        {"kmalloc-16",             16},         {"kmalloc-32",             32},
        {"kmalloc-64",             64},         {"kmalloc-128",           128},
        {"kmalloc-256",           256},         {"kmalloc-512",           512},
        {"kmalloc-1k",           1024},         {"kmalloc-2k",           2048},
        {"kmalloc-4k",           4096},         {"kmalloc-8k",           8192},
        {"kmalloc-16k",         16384},         {"kmalloc-32k",         32768},
        {"kmalloc-64k",         65536},         {"kmalloc-128k",       131072},
        {"kmalloc-256k",       262144},         {"kmalloc-512k",       524288},
        {"kmalloc-1M",        1048576},         {"kmalloc-2M",        2097152},
        {"kmalloc-4M",        4194304},         {"kmalloc-8M",        8388608},
        {"kmalloc-16M",      16777216},         {"kmalloc-32M",      33554432},
        {"kmalloc-64M",      67108864}
};

kmalloc_info 테이블은 인덱스 26까지에 해당하는 kmalloc 캐시의 이름과 사이즈 정보로 초기 구성되어 있다.

  • 1번 kmalloc-96의경우 아키텍처의 캐시 라인이 64이상인 경우 운영되지 않는다.
    • ARM64는 운영하지 않는다.
  • 2번 kmalloc-192의경우 아키텍처의 캐시 라인이 128이상인 경우 운영되지 않는다.
    • ARM64는 운영하지 않는다.
  • 3~6번 kmalloc-8 ~ kmalloc-64의경우 캐시 라인의 크기 보다 작은 kmalloc은 운영되지 않는다.
    • ARM64는 모두 운영하지 않는다.
  • slub 캐시에서는 페이지 사이즈 2배 크기 만큼만 지원한다.
    • 예) ARM64, 4K 페이지의 경우 kmalloc-128 ~ kmalloc-8k까지 지원한다.

 

아래 그림은 size에따라 size_index[] 테이블을 통해 적절한 kmalloc 슬랩 캐시를 선택하는 모습을 보여준다.

  • size_index[] 테이블 값은 변경되기 전의 초기 상태이다.

 

kmalloc 캐시 인덱스 테이블 초기화

setup_kmalloc_cache_index_table()

mm/slab_common.c

/*
 * Patch up the size_index table if we have strange large alignment
 * requirements for the kmalloc array. This is only the case for
 * MIPS it seems. The standard arches will not generate any code here.
 *
 * Largest permitted alignment is 256 bytes due to the way we
 * handle the index determination for the smaller caches.
 *
 * Make sure that nothing crazy happens if someone starts tinkering
 * around with ARCH_KMALLOC_MINALIGN
 */
void __init setup_kmalloc_cache_index_table(void)
{
        unsigned int i;

        BUILD_BUG_ON(KMALLOC_MIN_SIZE > 256 ||
                (KMALLOC_MIN_SIZE & (KMALLOC_MIN_SIZE - 1)));

        for (i = 8; i < KMALLOC_MIN_SIZE; i += 8) {
                unsigned int elem = size_index_elem(i);

                if (elem >= ARRAY_SIZE(size_index))
                        break;
                size_index[elem] = KMALLOC_SHIFT_LOW;
        }

        if (KMALLOC_MIN_SIZE >= 64) {
                /*
                 * The 96 byte size cache is not used if the alignment
                 * is 64 byte.
                 */
                for (i = 64 + 8; i <= 96; i += 8)
                        size_index[size_index_elem(i)] = 7;

        }

        if (KMALLOC_MIN_SIZE >= 128) {
                /*
                 * The 192 byte sized cache is not used if the alignment
                 * is 128 byte. Redirect kmalloc to use the 256 byte cache
                 * instead.
                 */
                for (i = 128 + 8; i <= 192; i += 8)
                        size_index[size_index_elem(i)] = 8;
        }
}

kmalloc 캐시용 size_index 테이블을 초기화한다

  • 코드 라인8~14에서 KMALLOC_MIN_SIZE 사이즈 까지는 최저 사이즈 캐시를 통합하여 운영한다.
    • ARM32 rpi2의 경우 최하 사이즈는 kmalloc-64이다.
    • ARM32 rpi3의 경우 최하 사이즈는 kmalloc-128이다.
    • ARM64 rpi4의 경우 최하 사이즈는 kmalloc-128이다.
  • 코드 라인 16~24에서 KMALLOC_MIN_SIZE 사이즈가 64 이상인 경우 72~96까지의사이즈는 kmalloc-96을 만들지않고 kmalloc-128을사용하게 한다.
    • ARM32 rpi2의 경우 72~96까지의 사이즈에 대해 kmalloc-128을 사용한다
    • ARM32 rpi3의 경우 72~96까지의 사이즈에 대해 kmalloc-128을 사용한다
    • ARM64 rpi4의 경우 72~96까지의 사이즈에 대해 kmalloc-128을 사용한다.
  • 코드 라인 26~34에서 KMALLOC_MIN_SIZE 사이즈가 128 이상인 경우 140~192까지의사이즈는 kmalloc-192을만들지 않고 kmalloc-256을사용하게 한다.
    • ARM32 rpi2의 경우 72~96까지의 사이즈에 대해 kmalloc-192를 지원한다.
    • ARM32 rpi3의 경우 72~96까지의 사이즈에 대해 kmalloc-256을 사용한다
    • ARM64 rpi4의 경우 72~96까지의 사이즈에 대해 kmalloc-256을 사용한다.

 

아래 그림은 setup_kmalloc_cache_index_table() 함수에 의해 조정된 size_index[] 테이블을 통해 적절한 kmalloc 슬랩 캐시를 선택하는 모습을 보여준다

 

kmalloc 캐시 초기화

create_kmalloc_caches() – (slab, slub)

mm/slab_common.c

/*
 * Create the kmalloc array. Some of the regular kmalloc arrays
 * may already have been created because they were needed to
 * enable allocations for slab creation.
 */
void __init create_kmalloc_caches(slab_flags_t flags)
{
        int i, type;

        for (type = KMALLOC_NORMAL; type <= KMALLOC_RECLAIM; type++) {
                for (i = KMALLOC_SHIFT_LOW; i <= KMALLOC_SHIFT_HIGH; i++) {
                        if (!kmalloc_caches[type][i])
                                new_kmalloc_cache(i, type, flags);

                        /*
                         * Caches that are not of the two-to-the-power-of size.
                         * These have to be created immediately after the
                         * earlier power of two caches
                         */
                        if (KMALLOC_MIN_SIZE <= 32 && i == 6 &&
                                        !kmalloc_caches[type][1])
                                new_kmalloc_cache(1, type, flags);
                        if (KMALLOC_MIN_SIZE <= 64 && i == 7 &&
                                        !kmalloc_caches[type][2])
                                new_kmalloc_cache(2, type, flags);
                }
        }

        /* Kmalloc array is now usable */
        slab_state = UP;

#ifdef CONFIG_ZONE_DMA
        for (i = 0; i <= KMALLOC_SHIFT_HIGH; i++) {
                struct kmem_cache *s = kmalloc_caches[KMALLOC_NORMAL][i];

                if (s) {
                        unsigned int size = kmalloc_size(i);
                        const char *n = kmalloc_cache_name("dma-kmalloc", size);

                        BUG_ON(!n);
                        kmalloc_caches[KMALLOC_DMA][i] = create_kmalloc_cache(
                                n, size, SLAB_CACHE_DMA | flags, 0, 0);
                }
        }
#endif
}

kmalloc 슬랩 캐시를 타입 및 사이즈별로 미리 생성한다.

  • 코드 라인 5~22에서 normal 타입과reclaim 타입두 가지 타입으로 슬랩이 지원하는 사이즈에 대해 순회하며 kmalloc 캐시를 생성한다.
  • 코드 라인25에서 지금부터 kmalloc array 캐시가 사용될 수 있다.
  • 코드 라인 28~39에서 슬랩이지원하는 사이즈에 대해 순회하며 dma 타입의 kmalloc 캐시를 생성한다.

 

new_kmalloc_cache()

mm/slab_common.c

static void __init
new_kmalloc_cache(int idx, int type, slab_flags_t flags)
{
        const char *name;

        if (type == KMALLOC_RECLAIM) {
                flags |= SLAB_RECLAIM_ACCOUNT;
                name = kmalloc_cache_name("kmalloc-rcl",
                                                kmalloc_info[idx].size);
                BUG_ON(!name);
        } else {
                name = kmalloc_info[idx].name;
        }

        kmalloc_caches[type][idx] = create_kmalloc_cache(name,
                                        kmalloc_info[idx].size, flags, 0,
                                        kmalloc_info[idx].size);
}

kmalloc_infi[@idx] 정보를사용하여@type에 대한 kmalloc 슬랩 캐시를 생성한다.

  • 예) idx=8, type=KMALLOC_RECLAIM
    • kmalloc-rcl-256
  • 예) idx=10, type=KMALLOC_NORMAL
    • kmalloc-1k

 

size_index_elem()

mm/slab_common.c

static inline int size_index_elem(size_t bytes)
{
        return (bytes - 1) / 8;
}

인수 bytes에 대해 8바이트 단위로 사용되는 size_index[] 배열 엘레멘트 인덱스 값을 반환한다.

  • 예) 0~7=0, 8~15=1, 16~23=2, …

 

kmalloc_size()

include/linux/slab.h

/*      
 * Determine size used for the nth kmalloc cache.
 * return size or 0 if a kmalloc cache for that
 * size does not exist
 */             
static __always_inline unsigned int kmalloc_size(unsigned int n)
{
#ifndef CONFIG_SLOB
        if (n > 2)      
                return 1U << n;
        
        if (n == 1 && KMALLOC_MIN_SIZE <= 32)
                return 96;
        
        if (n == 2 && KMALLOC_MIN_SIZE <= 64)
                return 192;
#endif          
        return 0;
}

인수 n 값에 대응하는 사이즈를 반환한다.

  • 예) rpi2: 0->0,  1->0, 2->192, 3->8, 4->16, 5->32, 6->64, 7->128, …

 

create_kmalloc_cache()

mm/slab_common.c

struct kmem_cache *__init create_kmalloc_cache(const char *name,
                unsigned int size, slab_flags_t flags,
                unsigned int useroffset, unsigned int usersize)
{
        struct kmem_cache *s = kmem_cache_zalloc(kmem_cache, GFP_NOWAIT);

        if (!s)
                panic("Out of memory when creating slab %s\n", name);

        create_boot_cache(s, name, size, flags, useroffset, usersize);
        list_add(&s->list, &slab_caches);
        memcg_link_cache(s);
        s->refcount = 1;
        return s;
}

인수로 주어진 @name, @size 및 @flags로 슬랩 캐시를 생성한다.

  • 코드 라인 5~8에서 kmem_cache 캐시에서 슬랩 object를 할당 받아온다.
  • 코드 라인 10에서 kmem_cache 캐시에서 사용하는 per cpu 캐시를 per-cpu 자료형으로 할당 받고, per 노드에서 사용할 kmem_cache_node 구조체는 kmem_cache_node 캐시에서 slub object를 할당받아 partial 리스트에 등록한다
  • 코드 라인 11~14에서 전역 slab_caches 리스트에 생성한 캐시를 추가하고 memcg에도 추가한 후, 참조카운터에 1을 대입하고 생성한 슬랩 캐시를 반환한다.

 

mm/slab_common.c

struct kmem_cache *
kmalloc_caches[NR_KMALLOC_TYPES][KMALLOC_SHIFT_HIGH + 1] __ro_after_init;
EXPORT_SYMBOL(kmalloc_caches);

kmalloc 슬랩 캐시 리스트이다.

 


Kmalloc 할당자

커널에서 물리 및 가상 주소가 연속된 메모리의 할당이 필요한 경우 요청 사이즈에 따라 다음과 같은 메모리 할당자를 사용하여 할당한다.

  • slub 할당자
    • 2 페이지 이하 사이즈 요청 시 2의 배수로 미리 만들어 운영되는 kmalloc 슬랩 캐시를 사용하여 슬랩 object를 할당한다.
  • 페이지 할당자
    • 2 페이지를 초과하는 사이즈 요청 시 버디 시스템을 사용하는 페이지 할당자를 사용하여 order 단위의 페이지를 할당한다.

 

다음 그림은 kmalloc() 함수가 사이즈에 따라 호출되는 함수들을 보여준다. 할당 요청 사이즈가 클 때에는 페이지 할당자에 요구하고, 작을 때에는 미리 준비된 kmalloc 슬랩(slub) 캐시에 요청한다.

 

kmalloc()

include/mm/slab.h

/**
 * kmalloc - allocate memory
 * @size: how many bytes of memory are required.
 * @flags: the type of memory to allocate.
 *
 * kmalloc is the normal method of allocating memory
 * for objects smaller than page size in the kernel.
 *
 * The @flags argument may be one of the GFP flags defined at
 * include/linux/gfp.h and described at
 * :ref:`Documentation/core-api/mm-api.rst <mm-api-gfp-flags>`
 *
 * The recommended usage of the @flags is described at
 * :ref:`Documentation/core-api/memory-allocation.rst <memory-allocation>`
 *
 * Below is a brief outline of the most useful GFP flags
 *
 * %GFP_KERNEL
 *      Allocate normal kernel ram. May sleep.
 *
 * %GFP_NOWAIT
 *      Allocation will not sleep.
 *
 * %GFP_ATOMIC
 *      Allocation will not sleep.  May use emergency pools.
 *
 * %GFP_HIGHUSER
 *      Allocate memory from high memory on behalf of user.
 *
 * Also it is possible to set different flags by OR'ing
 * in one or more of the following additional @flags:
 *
 * %__GFP_HIGH
 *      This allocation has high priority and may use emergency pools.
 *
 * %__GFP_NOFAIL
 *      Indicate that this allocation is in no way allowed to fail
 *      (think twice before using).
 *
 * %__GFP_NORETRY
 *      If memory is not immediately available,
 *      then give up at once.
 *
 * %__GFP_NOWARN
 *      If allocation fails, don't issue any warnings.
 *
 * %__GFP_RETRY_MAYFAIL
 *      Try really hard to succeed the allocation but fail
 *      eventually.
 */
static __always_inline void *kmalloc(size_t size, gfp_t flags)
{
        if (__builtin_constant_p(size)) {
#ifndef CONFIG_SLOB
                unsigned int index;
#endif
                if (size > KMALLOC_MAX_CACHE_SIZE)
                        return kmalloc_large(size, flags);
#ifndef CONFIG_SLOB
                index = kmalloc_index(size);

                if (!index)
                        return ZERO_SIZE_PTR;

                return kmem_cache_alloc_trace(
                                kmalloc_caches[kmalloc_type(flags)][index],
                                flags, size);
#endif
        }
        return __kmalloc(size, flags);
}

커널로부터 메모리 할당 요청에 대해 할당 요청 사이즈가 2 페이지(slub 기준)를 초과하는 경우 버디 시스템을 사용하는 페이지 할당자에 요청하고, 그렇지 않은 경우에는 kmalloc 슬랩 캐시를 사용하여 요청한다.

  • 코드 라인 3~8에서 요청 사이즈가 상수이면서 KMALLOC_MAX_CACHE_SIZE(slub 기준 2 페이지)를 초과하는 경우 직접 버디 시스템을 사용하는 페이지 할당자로 요청한다.
    • KMALLOC_MAX_CACHE_SIZE
      • slub을 사용하는 경우 2 페이지 사이즈
  • 코드 라인 10~17에서 요청 사이즈가 상수이면 사이즈 및 타입에 따른 kmalloc 슬랩 캐시를 선택하여 슬랩 object를 할당 받는다.
    • kmalloc_index() 함수는 사이즈로 인덱스 값을 산출한다.
  • 코드 라인 20에서 size에 변수를 사용한 경우 __kmalloc() 함수를 호출한다.

 

kmalloc_large()

include/linux/slab.h

static __always_inline void *kmalloc_large(size_t size, gfp_t flags)
{
        unsigned int order = get_order(size);
        return kmalloc_order_trace(size, flags, order);
}

size에 필요한 order를 결정한 후 버디 시스템을 사용하는 페이지 할당자로부터 페이지를 할당 받는다.

 

kmalloc_order_trace()

mm/slab_common.c

#ifdef CONFIG_TRACING
void *kmalloc_order_trace(size_t size, gfp_t flags, unsigned int order)
{
        void *ret = kmalloc_order(size, flags, order);
        trace_kmalloc(_RET_IP_, ret, size, PAGE_SIZE << order, flags);
        return ret;
}
EXPORT_SYMBOL(kmalloc_order_trace);
#endif

버디 시스템을 사용하는 페이지 할당자로부터 order 만큼의 페이지를 할당 받는다.

 

kmalloc_order()

mm/slab_common.c

/*
 * To avoid unnecessary overhead, we pass through large allocation requests
 * directly to the page allocator. We use __GFP_COMP, because we will need to
 * know the allocation order to free the pages properly in kfree.
 */
void *kmalloc_order(size_t size, gfp_t flags, unsigned int order)
{
        void *ret;
        struct page *page;

        flags |= __GFP_COMP;
        page = alloc_pages(flags, order);
        ret = page ? page_address(page) : NULL;
        ret = kasan_kmalloc_large(ret, size, flags); 
        /* As ret might get tagged, call kmemleak hook after KASAN. */
        kmemleak_alloc(ret, size, 1, flags);
        return ret;
}
EXPORT_SYMBOL(kmalloc_order);

큰 페이지에 대한 할당 요청이므로 compound 페이지를 할당받기 위해 __GFP_COMP 플래그를 추가하고, 버디 시스템을 사용하는 페이지 할당자로부터 order 만큼의 페이지를 할당 받는다.

 

kmalloc_index()

include/linux/slab.h

#ifndef CONFIG_SLOB
/*
 * Figure out which kmalloc slab an allocation of a certain size
 * belongs to.
 * 0 = zero alloc
 * 1 =  65 .. 96 bytes
 * 2 = 120 .. 192 bytes
 * n = 2^(n-1) .. 2^n -1
 */
static __always_inline int kmalloc_index(size_t size)
{
        if (!size)
                return 0;

        if (size <= KMALLOC_MIN_SIZE)
                return KMALLOC_SHIFT_LOW;

        if (KMALLOC_MIN_SIZE <= 32 && size > 64 && size <= 96)
                return 1;
        if (KMALLOC_MIN_SIZE <= 64 && size > 128 && size <= 192)
                return 2;
        if (size <=          8) return 3;
        if (size <=         16) return 4;
        if (size <=         32) return 5;
        if (size <=         64) return 6;
        if (size <=        128) return 7;
        if (size <=        256) return 8;
        if (size <=        512) return 9;
        if (size <=       1024) return 10;
        if (size <=   2 * 1024) return 11;
        if (size <=   4 * 1024) return 12;
        if (size <=   8 * 1024) return 13;
        if (size <=  16 * 1024) return 14;
        if (size <=  32 * 1024) return 15;
        if (size <=  64 * 1024) return 16;
        if (size <= 128 * 1024) return 17;
        if (size <= 256 * 1024) return 18;
        if (size <= 512 * 1024) return 19;
        if (size <= 1024 * 1024) return 20;
        if (size <=  2 * 1024 * 1024) return 21;
        if (size <=  4 * 1024 * 1024) return 22;
        if (size <=  8 * 1024 * 1024) return 23;
        if (size <=  16 * 1024 * 1024) return 24;
        if (size <=  32 * 1024 * 1024) return 25;
        if (size <=  64 * 1024 * 1024) return 26;
        BUG();

        /* Will never be reached. Needed because the compiler may complain */
        return -1;
}
#endif /* !CONFIG_SLOB */ 

0~64M 이하의 요청 사이즈에 따른 index를 0~23 까지의 수로 반환한다. 64M를 초과하는 경우 에러로 -1을 반환한다.

  • 요청 사이즈에 따른 index 값
    • 0 = 할당 없음 (zero alloc)
    • 1 = 65 .. 96 bytes
    • 2 = 120 .. 192 bytes
    • n = 2^(n-1) .. 2^n -1
      • 3 = 1 .. 8
      • 4 = 9 .. 16
      • 5 = 17 .. 32
      • 6 = 33 .. 64
      • 26 = 32M-1 .. 64M
    • 단, size가 1~KMALLOC_MIN_SIZE인 경우 KMALLOC_SHIFT_LOW를 반환한다.
      • rpi2 예) KMALLOC_MIN_SIZE=64, KMALLOC_SHIFT_LOW=6
      • arm64예) KMALLOC_MIN_SIZE=128, KMALLOC_SHIFT_LOW=7

 

kmem_cache_alloc_node_trace()

mm/slub.c

#ifdef CONFIG_TRACING
void *kmem_cache_alloc_node_trace(struct kmem_cache *s,
                                    gfp_t gfpflags,
                                    int node, size_t size)
{
        void *ret = slab_alloc_node(s, gfpflags, node, _RET_IP_);

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

        ret = kasan_kmalloc(s, ret, size, gfpflags);
        return ret;
}
EXPORT_SYMBOL(kmem_cache_alloc_node_trace);
#endif

요청한 슬랩 캐시의 @node에서 슬랩 object를 할당 받는다.

 

__kmalloc()

mm/slub.c

void *__kmalloc(size_t size, gfp_t flags)
{
        struct kmem_cache *s;
        void *ret;

        if (unlikely(size > KMALLOC_MAX_CACHE_SIZE))
                return kmalloc_large(size, flags);

        s = kmalloc_slab(size, flags);

        if (unlikely(ZERO_OR_NULL_PTR(s)))
                return s;

        ret = slab_alloc(s, flags, _RET_IP_);

        trace_kmalloc(_RET_IP_, ret, size, s->size, flags);

        kasan_kmalloc(s, ret, size);

        return ret;
}
EXPORT_SYMBOL(__kmalloc);

@size 값이 변수인 경우 호출되어 다음과 같이 처리한다.

  • 코드 라인 6~7에서 size가 KMALLOC_MAX_CACHE_SIZE(slub의 경우 2 페이지)를 초과하는 경우 kmalloc_large() 함수를 통해 버디 시스템을 사용한 페이지 할당자로 부터 페이지를 할당 받는다.
  • 코드 라인 9~12에서 size에 및 타입에 따른 적절한 kmalloc 슬랩 캐시를 구해온다.
  • 코드 라인 14에서 구한 kmalloc 슬랩 캐시에서 슬랩 object를 할당 받는다.

 

kmalloc_slab()

mm/slab_common.c

/*
 * Find the kmem_cache structure that serves a given size of
 * allocation
 */
struct kmem_cache *kmalloc_slab(size_t size, gfp_t flags)
{
        unsigned int index;

        if (size <= 192) {
                if (!size)
                        return ZERO_SIZE_PTR;

                index = size_index[size_index_elem(size)];
        } else {
                if (WARN_ON_ONCE(size > KMALLOC_MAX_CACHE_SIZE))
                        return NULL;
                index = fls(size - 1);
        }

        return kmalloc_caches[kmalloc_type(flags)][index];
}

@size 및 @flags로 구한 타입을 통해 해당 kmalloc 슬랩 캐시를 구해온다.

  • 코드 라인 5~9에서 @size가 1 ~ 192인 경우 이미 만들어진 size_index[] 테이블을 활용하여 사이즈 값으로 인덱스를 산출한다.
  • 코드 라인 10~14에서 @size가 193 ~ KMALLOC_MAX_SIZE(slub인 경우 2 페이지)인 경우 필요 비트 수를 산출하여 반환한다.
    • 193 ~ 256 → 8
    • 257 ~ 512 → 9
    • 513 ~ 1024 10
    • 1025 ~ 2048  11
    • 2049 ~ 4096  12
    • 4097 ~ 8192  13
  • 코드 라인 16에서 @flags에서 타입을 추출하고, 산출한 인덱스로 얻은 kmallc 캐시를 반환한다.

 

size_index_elem()

mm/slab_common.c

static inline int size_index_elem(size_t bytes)
{
        return (bytes - 1) / 8;
}

size_index[] 테이블은 인덱스 당 8바이트 범위를 사용한다. 따라서 요청 bytes-1을 8로 나눈 몫을 반환한다.

  • 1 ~ 8 → 0
  • 9 ~ 16 → 1
  • 17 ~ 24 → 2
  • 25 ~ 32 → 3
  • 33 ~ 40 → 4
  • 41 ~ 48 → 5

 

참고

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 캐시가 만들어지지 않는다.

 

참고

ZONE 타입

<kernel v5.0>

리눅스에서 물리 메모리 주소 영역을 ZONE 단위로 구분하여 관리한다. 각각의 ZONE은 다음과 같다.

    • ZONE_DMA
    • ZONE_DMA32
    • ZONE_NORMAL
    • ZONE_HIGHMEM
  • ZONE_MOVABLE
  • ZONE_DEVICE

 

ZONE_DMA & ZONE_DMA32

DMA(Direct Memory Access)를 사용하여 메모리에 직접 접근하는 디바이스들을 사용하는 시스템에서 DMA를 사용하는 디바이스가 해당 시스템이 사용하는 주소 버스를 모두 커버하는 경우에는 별도로 ZONE_DMA를 구성할 필요 없이 기본 적으로 만들어지는 ZONE_NORMAL을 사용하여 메모리를 할당받아 사용할 수 있다. 그러나 DMA를 사용하는 디바이스가 시스템이 지원하는 물리 메모리 주소보다 작은 경우에는 별도로 ZONE_DMA를 구성해서 DMA 디바이스용으로 메모리 할당을 제한된 주소 미만으로 만들어놔야 그 DMA 디바이스가 접근할 수 있게된다. x86 및 arm의 경우 다음과 같은 조건에 따라 CONFIG_ZONE_DMA 및 CONFIG_ZONE_DMA32 사용 유무를 결정해서 사용한다.

  • DMA를 사용하는 디바이스 장치의 주소 버스가 시스템의 최대 물리 메모리 접근 주소까지 접근될 수 있게 설계된 경우 CONFIG_DMA를 설정할 필요 없다.
  • DMA를 사용하는 디바이스 장치의 주소 버스가 시스템의 최대 물리 메모리 접근 주소보다 접근할 수 없는 경우 CONFIG_DMA를 설정해서 사용한다.
    • 32bit x86:
      • CONFIG_ZONE_DMA 사용
    • 64bit x86:
      • CONFIG_ZONE_DMA 사용
      • 추가로 CONFIG_ZONE_DMA32 사용
        • 4G 영역(32bit 주소)을 사용하는 DMA 장치를 지원하기 위해 사용된다.
    • 32bit arm:
      • 회로(Circuit) 구성을 단순하게 할 필요성이 있는 경우 해당 아키텍처가 필요로 하는 만큼의 주소 버스를 사용하지 않고 줄여서 설계된 DMA 디바이스 장치를 사용하는 경우 사용한다.
  • DMA 컨트롤러 및 채널 참고

 

DMA 사이즈 설정

  • 32bit x86
    • 16MB로 고정되어 자동 구성된다.
    • 24bit address bus를 사용한 AT(80286) 시스템에 채용된 ISA 버스에서 동작하는 디바이스를 호환(backward compatible)시키기 위해 사용된다
  • 64bit x86
    • 4GB로 고정되어 자동 구성된다.
    • 32bit address만을 사용하는 기존 버스들(32bit EISA, PCI,  24bit ISA, …)에서 동작하는 디바이스를 호환시키기 위해 사용된다.
  • 32bit ARM
    • CONFIG_DMA를 사용하는 경우 DMA size가 flexible하게 구성된다.
    • 임베디드 시스템 회로(Circuit) 구성이 DMA 디바이스 장치에서 제한된 address bus 핀(pin)만 사용하는 경우 그 지정할 수 있는 주소만큼 영역을 제한시킨다.
      • 예) arm32에서 DMA 디바이스 장치가 28 bit address bus를 지원하는 경우
        • DMA size는 256MB로 설정해야 한다.
  • 64bit ARM
    • 물리 메모리 시작 주소를 기준으로 최대 4GB 까지의 물리 메모리 주소로 자동 구성된다.

 

다음 그림은 DMA를 사용하는 디바이스 장치가 어느 때 CONFIG_DMA를 설정해야 하는지를 나타낸다.

zone-dma-1

  • DMA를 사용하는 디바이스 장치가 32비트 주소를 지원하므로 특별히 ZONE_DMA를 설정할 필요 없다.

 

zone-dma-2

  • DMA를 사용하는 디바이스 장치가 제한된 24비트 주소만을 지원하는데 32비트 주소로 할당된 메모리는 디바이스 장치에서 접근할 수 없다.

 

zone-dma-3

  • DMA를 사용하는 디바이스 장치가 제한된 24비트 주소만을 지원하므로 관련 메모리 할당을 ZONE_DMA 영역에서 할당을 받아 사용한다.
    • ZONE_DMA 사이즈를 64MB(2^26)로 설정하여 사용한다.

 

ZONE_NORMAL

  • 시스템에서 지원하는 물리 메모리가 아키텍처의 가상 주소에 1:1로 미리 매핑되어 사용할 수 있는 만큼의 영역이다. 따라서 커널 메모리 할당 시 ZONE_DMA 영역과 함께 가장 빠르게 접근할 수 있는 메모리 영역이다.
    • 32bit 시스템에서는 4G의 공간을 user space와 kernel space로 나누어 사용하므로 user space는 비워두고 kernel space의 일부 영역을 사용하여 물리 메모리를 배치하는데 이에 사용된 물리 메모리 영역이 ZONE_NORMAL이다.
      • 32bit arm
        • 커널 크기 구성에 따라 다르다.
          • CONFIG_VMSPLIT_3G
            • normal zone의 최대 영역 크기=760MB
            • user space가 3G, kernel space가 1G
            • 대부분의 PC 리눅스와 임베디드 커널에서 사용한다.
          • CONFIG_VMSPLIT_2G
            • normal zone의 최대 영역 크기=1GB + 760MB
            • user space가 2G, kernel space가 2G
            • 라즈베리파이2 및 일부 임베디드 시스템이 사용하는 설정이다.
          • CONFIG_VMSPLIT_1G
            • normal zone의 최대 영역 크기=2GB + 760MB
            • user space가 1G, kernel space가 3G
            • 극단적인 설정이라 거의 사용되지 않는다.
      • 32bit x86
        • normal zone의 최대 영역 크기=896MB
    • 물론 ZONE_DMA 및 ZONE_DMA32도 가상 주소에 1:1로 매핑되어 있다.
    • 32 비트 시스템에서 normal zone은 크기가 제한되어 있으므로 최대한 커널 할당에 사용되는 비싼 영역이다.
    • 64bit ARM인 ARMv8 아키텍처에서는 최대 커널 주소 공간이 256T이고 그 중 절반의 공간인 128T 공간을 물리 메모리 배치로 사용할 수 있다. 즉 거대한 normal zone이며, 이 공간이 매우 크기 때문에 별도의 highmem zone을 생성해야할 이유가 없다.
      • ARMv8.2 아키텍처부터는 최대 52bit 물리 주소를 지원하여 4 Peta 공간을 지원한다.

 

다음 그림은 32bit 시스템에서의 ZONE_NORMAL 영역이 제한되어 있음을 보여준다.

zone-2a

 

ZONE_HIGHMEM

ZONE_NORMAL이 처리하지 못하는 메모리 영역이 모두 ZONE_HIGHMEM으로 구성된다.

  • 32bit 시스템에서는 1:1 매핑이 일부만 가능하기 때문에 ZONE_NORMAL을 초과하는 메모리가 이 영역을 사용한다.
  • 64bit 시스템에서는 모든 물리 메모리가 1:1 매핑이 가능하므로 ZONE_HIGHMEM을 사용하지 않는다.
  • user 메모리의 할당 시 우선적으로 highmem 영역의 메모리를 먼저 소모시키고, 이를 다 소모한 경우에 한해 normal zone 영역을 사용한다.

 

ZONE_MOVABLE

이 영역은 아래와 같이 두 가지 목적으로 사용한다.

  • 단편화 방지
    • 마지막 zone에 대해서 각 노드의  일부 영역을 할당하여 버디 시스템으로 구현된 페이지 할당자가 메모리 파편화를 막기 위해 이 영역을 전용으로 사용한다.
    • 이 영역을 구성하지 않아도 버디 시스템은 migration 타입으로 분류하여 최대한 파편화를 막는데, 이 영역을 지정하는 경우 이 영역 전체가 movable 페이지 구성이 되므로 파편화를 막는데 더욱 큰 효율을 보일 수 있다.
    • 마지막(last) zone이란 것은 시스템에 설치된 zone 중 가장 상위에 존재하는 zone으로 시스템에 따라 가장 다른데 아래와 같이 높은 순부터 낮은 순으로 나열한다.
      • ZONE_HIGHMEM -> ZONE_NORMAL -> ZONE_DMA(또는 ZONE_DMA32)
      • ZONE_HIGHMEM이 없는 64비트 시스템에서는 ZONE_NORMAL이 될 수 있고 그것도 없는 경우 ZONE_DMA(또는 ZONE_DMA32)가 될 수 있다.
  • 핫플러그 메모리 지원
    • 버디 시스템의 메모리 파편화도 막으면서 동시에 최근에 구현된 “메모리 Hotplug”를 지원하기 위해 메모리 Hotplug를 원하는 노드 전체를 ZONE_MOVABLE로 구성하여 사용한다.

 

다음 그림은 NUMA 32bit ARM 시스템에서의 ZONE_MOVABLE 구성을 보여준다.

zone-3

 

다음 그림은 64bit ARM 시스템에서의 ZONE_MOVABLE 구성을 보여준다.

  • 커널 v4.16-rc1 부터 ZONE_DMA 대신 ZONE_DMA32를 사용한다.

zone-4

 

ZONE_DEVICE

  • 커널 v4.3에 새롭게 추가되었으며 대용량 persistent memory 디바이스 및 heterogeneous memory 디바이스 등에서 사용하기 위한 영역이다.
    • ZONE_DMA는 디바이스가 cpu의 주 메모리에 대한 접근을 하고자 하는 것과 비교를 해보면 ZONE_DEVICE는 다음을 추구한다.
      • cpu가 대용량의 디바이스 메모리에 접근하고자 한다.
        • persistent memrory 예) 주 메모리가 8G, 고속 디바이스 메모리가 512G
        • heterogeneous memory 예) 주 메모리가 2G, GPU 디바이스 메모리가 6G
      • 고속 처리를 위해 CPU 및 디바이스 측에서 캐시를 사용하고자 한다.
      • 동시성
        • 양쪽에서 h/w cache coherent가 동작되고자 한다.
        • cpu 측에서 atomic operation을 사용하고자 한다.
  • 필요조건
    • 메모리 핫플러그 및 핫리무브가 지원되어야 하며 64비트에서만 사용하는 SPARSEMEM_VMEMMAP 설정이 필요하다.
  • 서버급인 x86_64 및 PPC_64 아키텍처부터 구현되고 있다.
  • persistent memory 디바이스에 유저스페이스를 이용하는 DAX 매핑을 사용할 수도 있다.
  • heterogeneous memory 디바이스에서 접근하는 메모리를 CPU에서도 접근할 수 있다. (CONFIG_HMM)
    • 커널 v4.14에 추가되었다.
    • 전체 영역 또는 일부 영역을 지정한다.
    • ZONE_DEVICE를 통해 메모리 swap 장치로 사용할 수 있다.
    • private(CONFIG_DEVICE_PRIVATE)으로 설정된 HMM 메모리는 디바이스에서만 접근할 수 있고, CPU에서는 접근할 때에는 커널의 일반 메모리 관련 API를 사용하지 못하고,  HMM 전용 API를 통해서만 접근할 수 있다.
    • public(CONFIG_DEVICE_PUBLIC)으로 설정된 HMM 메모리는 디바이스와 CPU 양쪽에서 접근할 수 있다.
      • 커널에서 사용하는 모든 메모리 API들을 사용하여 접근할 수 있다.
      • 양방향 매핑에 대한 동기화 및 cache coherent 문제를 해결하도록 h/w가 지원해야 한다.
  • 고속 인터페이스
    • 가장 빠른 것은 디바이스 메모리가 SoC에 임베드되어 내부 고속 버스에 통합되는 경우이다.
      • 임베드된 GPU가 사용하는 GDDR 메모리
    • 외부 버스 중 가장 빠른 DRAM 메모리 버스를 사용할 수도 있다
      • 인텔의 옵테인 메모리(고가이며 CPU가 지원해야한다.)
    • PCIe를 사용하는 가장 대중화된 외부(시스템이 아닌 칩 외부) 고속 인터페이스이다.
      • 고속 SSD 및 인텔의 옵테인 메모리(CPU가 지원하지 못할 때 사용할 수 있는 저가용)
        • 참고로 기존 SSD가 연결되는 인터페이스는 SATA부터 출발했지만 현재는 PCIe를 사용하는 AHCI와 NVMe(AHCI보다 더 빠른 프로토콜)가 있다.
      • pc 장착용 GPU들도 PCIe를 사용한다.
      • 메모리 접근 속도에 비해 수십배 느린 PCIe 버스를 사용해야 하므로 CPU에서 접근할 때에는 느린 제약이 있다.
      • 디바이스 메모리에 대한 atomic operation 및 cache coherent 등이 그동안 지원되지 않았으며 최근에는 가능해졌다.
  • 참고:

 

참고