percpu_init_late()

 

percpu_init_late()

mm/percpu.c

/*
 * First and reserved chunks are initialized with temporary allocation
 * map in initdata so that they can be used before slab is online.
 * This function is called after slab is brought up and replaces those
 * with properly allocated maps.
 */
void __init percpu_init_late(void)
{
        struct pcpu_chunk *target_chunks[] =
                { pcpu_first_chunk, pcpu_reserved_chunk, NULL };
        struct pcpu_chunk *chunk;
        unsigned long flags;
        int i;

        for (i = 0; (chunk = target_chunks[i]); i++) {
                int *map;
                const size_t size = PERCPU_DYNAMIC_EARLY_SLOTS * sizeof(map[0]);

                BUILD_BUG_ON(size > PAGE_SIZE);

                map = pcpu_mem_zalloc(size);
                BUG_ON(!map);

                spin_lock_irqsave(&pcpu_lock, flags);
                memcpy(map, chunk->map, size);
                chunk->map = map;
                spin_unlock_irqrestore(&pcpu_lock, flags);
        }
}

slab(slub) 메모리 관리자가 활성화된 이후 pcpu_first_chunk와 pcpu_reserved_chunk에서 사용한 할당 map을 새로 할당 받은 메모리에 복사한다.

 

pcpu_mem_zalloc()

mm/percpu.c

/**
 * pcpu_mem_zalloc - allocate memory
 * @size: bytes to allocate
 *
 * Allocate @size bytes.  If @size is smaller than PAGE_SIZE,
 * kzalloc() is used; otherwise, vzalloc() is used.  The returned
 * memory is always zeroed.
 *
 * CONTEXT:
 * Does GFP_KERNEL allocation.
 *
 * RETURNS:
 * Pointer to the allocated area on success, NULL on failure.
 */
static void *pcpu_mem_zalloc(size_t size)
{
        if (WARN_ON_ONCE(!slab_is_available()))
                return NULL;

        if (size <= PAGE_SIZE)
                return kzalloc(size, GFP_KERNEL);
        else
                return vzalloc(size);
}

요청 size로 메모리 할당 후 0으로 초기화한다.

  • 요청 size가 PAGE_SIZE보다 작은 경우 kzalloc() 함수를 사용하고
    • 물리적으로 연속된 메모리가 확보되며 빠르지만 파편화된 메모리로 인해 실패할 확률이 vzalloc보다 크다. 따라서 작은 사이즈의 메모리의 할당에 더 어울린다.
    • DMA 버퍼에 사용하는 메모리는 물리적으로 연속된 메모리의 할당이 요구된다.
  • 요청 size가 PAGE_SIZE보다 큰 경우 vzalloc() 함수를 사용한다.
    • 연속된 물리 주소는 필요하지 않고 연속된 가상 주소만 있으면 되는 상황에서 사용된다. kzalloc()보다 파편화되지 않는다. 내부적으로는 여러 번의 kzalloc()함수가 호출되어 관리되어 사용되므로 kzalloc()보다 느리다.

 

참고

 

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

 

참고