Ryotta's Linux 7.0 MM

메모리 관리 서브시스템 완전 분석

# SLUB 할당자 (SLUB Allocator)

개요 (Overview)

Linux 커널의 SLUB(SUnqueued Lockless Allocator) 할당자는 커널 오브젝트의 효율적인 메모리 할당/해제를 담당하는 slab 할당자입니다. SLUB는 기존 SLAB 할당자의 복잡한 구조를 단순화하면서 lockless fastpath를 통해 높은 성능을 달성했습니다. 모든 커널 오브젝트(task_struct, inode, dentry 등)는 SLUB를 통해 할당되며, kmalloc/kfree도 내부적으로 SLUB를 사용합니다.

SLUB의 핵심 설계 철학은 per-CPU 캐시(sheaves)를 통한 fastpath 처리와 NUMA 노드별 partial list를 통한 slowpath 분리입니다. 이를 통해 대부분의 할당/해제가 lock 없이 처리되어 멀티소켓 시스템에서도 높은 확장성을 제공합니다.

소스 파일 경로

mm/slub.c          — SLUB 할당자 핵심 구현 (약 9,800줄)
mm/slab.h          — 내부 구조체 정의 (struct slab, kmem_cache_node 등)
include/linux/slab.h — 외부 API (kmalloc, kfree, kmem_cache_create 등)

SLUB 할당자 개요

SLUB는 Buddy Allocator 위에 커널 오브젝트를 캐싱하는 계층입니다. SLUB는 대형 물류창고의 소형 부품 서랍장과 비슷합니다. Buddy Allocator가 페이지라는 큰 상자를 가져오면, SLUB는 그 상자 안을 task_struct, inode, dentry, kmalloc-256 같은 같은 크기의 칸으로 나눠 자주 쓰는 부품을 CPU 가까이에 보관합니다. 빠른 경로에서는 CPU별 sheaf에서 바로 꺼내고, 비면 node barn과 partial slab을 거쳐 새 페이지를 가져옵니다.


시각적 개요

SLUB 할당자 구조도

SLUB 구조도

SLUB 할당/해제 흐름도

할당/해제 흐름

빠른 점검 명령

# SLUB 캐시 통계 확인
cat /proc/slabinfo | head -20

# 특정 캐시 상세 정보
cat /sys/kernel/slab/task_struct/alloc_fastpath
cat /sys/kernel/slab/task_struct/alloc_slowpath

# SLUB 디버그 모드 확인 (부팅 파라미터)
cat /proc/cmdline | grep slub_debug

# 메모리에서 SLAB 사용량 확인
cat /proc/meminfo | grep -E 'Slab|SReclaimable|SUnreclaim'

# 특정 캐시의 partial slab 수 확인
cat /sys/kernel/slab/<cache_name>/partial

# SLUB 통계 리셋 및 확인
echo 1 > /sys/kernel/slab/<cache_name>/alloc_fastpath
echo 0 > /sys/kernel/slab/<cache_name>/alloc_fastpath

# 캐시별 소비량 기준 상위 항목
slabtop -s c

# kmalloc 크기 클래스와 주요 캐시 확인
grep -E '^(kmalloc-|task_struct|dentry|inode_cache)' /proc/slabinfo | head -20

# 캐시별 객체 크기, slab당 객체 수, order 확인
cat /sys/kernel/slab/<cache_name>/object_size
cat /sys/kernel/slab/<cache_name>/objs_per_slab
cat /sys/kernel/slab/<cache_name>/order

# SLUB 디버그 옵션 파일 확인 (CONFIG_SLUB_DEBUG 필요)
for f in sanity_checks red_zone poison store_user trace failslab; do cat /sys/kernel/slab/<cache_name>/$f 2>/dev/null; done

# 부팅 옵션 예: 전체 sanity/redzone/poison/user tracking 또는 kmalloc-256만 적용
# slab_debug=FZPU
# slab_debug=FZPU,kmalloc-256

핵심 자료구조

struct kmem_cache — 슬래브 캐시 관리자

struct kmem_cache는 하나의 slab 캐시(예: task_struct용 캐시)를 관리하는 최상위 구조체입니다. 각 캐시는 고정된 크기의 오브젝트를 할당하며, per-CPU sheaves와 NUMA 노드별 partial list를 가집니다.

/* mm/slab.h:197-251 */
struct kmem_cache {
    struct slub_percpu_sheaves __percpu *cpu_sheaves;  // per-CPU 캐시
    slab_flags_t flags;                                // 캐시 플래그
    unsigned long min_partial;                         // 최소 partial slab 수
    unsigned int size;                                 // 오브젝트 크기 (메타데이터 포함)
    unsigned int object_size;                          // 오브젝트 크기 (메타데이터 미포함)
    struct reciprocal_value reciprocal_size;           // 역수 나눗셈용
    unsigned int offset;                               // free pointer 오프셋
    unsigned int sheaf_capacity;                       // sheaf 최대 용량
    struct kmem_cache_order_objects oo;                // 최적 order/오브젝트 수
    struct kmem_cache_order_objects min;               // 최소 order/오브젝트 수
    gfp_t allocflags;                                 // 페이지 할당 시 GFP 플래그
    int refcount;                                     // 참조 카운트
    void (*ctor)(void *object);                       // 오브젝트 생성자
    unsigned int inuse;                               // 메타데이터까지의 오프셋
    unsigned int align;                               // 정렬
    const char *name;                                 // 캐시 이름
    struct list_head list;                            // 전체 캐시 리스트
    struct kmem_cache_node *node[MAX_NUMNODES];       // NUMA 노드별 관리 구조체
};

struct slab — 단일 슬래브 페이지

struct slab는 하나의 슬래브 페이지(4KB 이상의 compound page)를 나타냅니다. struct page와 메모리를 공유하여 overhead를 최소화합니다.

/* mm/slab.h:74-92 */
struct slab {
    memdesc_flags_t flags;                    // 슬래브 플래그
    struct kmem_cache *slab_cache;            // 소속 캐시
    union {
        struct {
            struct list_head slab_list;       // partial/full 리스트
            struct freelist_counters;         // freelist + 카운터 (cmpxchg)
        };
        struct rcu_head rcu_head;             // RCU 해제용
    };
    unsigned int __page_type;                 // 페이지 타입
    atomic_t __page_refcount;                 // 참조 카운트
    unsigned long obj_exts;                   // 오브젝트 확장 메타데이터
};

freelist_counters — Lockless freelist 관리

SLUB의 핵심 혁신은 freelist, inuse, objects, frozen을 하나의 64비트/128비트 값으로 묶어 cmpxchg로 원자적 업데이트하는 것입니다.

/* mm/slab.h:41-71 */
struct freelist_counters {
    union {
        struct {
            void *freelist;           // 다음 빈 오브젝트 포인터
            union {
                unsigned long counters;  // 64비트 카운터
                struct {
                    unsigned inuse:16;   // 사용 중인 오브젝트 수
                    unsigned objects:15; // 전체 오브젝트 수
                    unsigned frozen:1;   // frozen 상태 (디버그용)
                };
            };
        };
        freelist_full_t freelist_counters;  // cmpxchg용 128비트 값
    };
};

struct kmem_cache_node — NUMA 노드별 관리

각 NUMA 노드마다 하나씩 존재하며, partial slab 리스트와 barn을 관리합니다.

/* mm/slab.h:430-440 */
struct kmem_cache_node {
    spinlock_t list_lock;              // 리스트 보호용 스핀락
    unsigned long nr_partial;          // partial slab 수
    struct list_head partial;          // partial slab 리스트
    atomic_long_t nr_slabs;            // 전체 slab 수 (디버그용)
    atomic_long_t total_objects;       // 전체 오브젝트 수 (디버그용)
    struct list_head full;             // full slab 리스트 (디버그용)
    struct node_barn *barn;            // barn 관리 구조체
};

struct slub_percpu_sheaves — per-CPU 캐시

SLUB의 fastpath를 담당하는 per-CPU 구조체입니다. 각 CPU는 두 개의 sheaf(main, spare)를 가지며, 할당/해제의 대부분이 여기서 처리됩니다.

/* mm/slub.c:420-425 */
struct slub_percpu_sheaves {
    local_trylock_t lock;              // per-CPU 잠금
    struct slab_sheaf *main;           // 메인 sheaf (할당/해제 주력)
    struct slab_sheaf *spare;          // 스페어 sheaf (main 교체용)
    struct slab_sheaf *rcu_free;       // kfree_rcu 배치용
};

struct slab_sheaf — 오브젝트 캐시 단위

실제 오브젝트 포인터를 저장하는 배열 구조체입니다. main sheaf에서 오브젝트를 할당하고, 해제 시에도 main sheaf에 추가합니다.

/* mm/slub.c:404-418 */
struct slab_sheaf {
    union {
        struct rcu_head rcu_head;
        struct list_head barn_list;       // barn 리스트 연결
        struct {
            unsigned int capacity;        // 최대 용량
            bool pfmemalloc;              // PF_MEMALLOC 할당 여부
        };
    };
    struct kmem_cache *cache;             // 소속 캐시
    unsigned int size;                    // 현재 저장된 오브젝트 수
    int node;                             // NUMA 노드 (rcu_sheaf용)
    void *objects[];                      // 오브젝트 포인터 배열
};

struct node_barn — NUMA 노드별 barn

barn(barnyard)은 NUMA 노드에서 empty/full sheaf를 교환하는 공간입니다. CPU 간 sheaf 교환의 중간 다리 역할을 합니다.

/* mm/slub.c:396-402 */
struct node_barn {
    spinlock_t lock;                   // barn 보호용 스핀락
    struct list_head sheaves_full;     // 가득 찬 sheaf 리스트
    struct list_head sheaves_empty;    // 빈 sheaf 리스트
    unsigned int nr_full;              // full sheaf 수
    unsigned int nr_empty;             // empty sheaf 수
};

핵심 함수

kmem_cache_alloc_noprof() — 메인 할당 API

/* mm/slub.c:4871-4880 */
void *kmem_cache_alloc_noprof(struct kmem_cache *s, gfp_t gfpflags)
{
    void *ret = slab_alloc_node(s, NULL, gfpflags, NUMA_NO_NODE, _RET_IP_,
                                s->object_size);
    trace_kmem_cache_alloc(_RET_IP_, ret, s, gfpflags, NUMA_NO_NODE);
    return ret;
}

역할: 사용자가 호출하는 주요 할당 API입니다. slab_alloc_node()를 호출하여 실제 할당을 수행합니다.

흐름: kmem_cache_allocslab_alloc_node → fastpath(per-CPU sheaves) 또는 slowpath(___slab_alloc)

slab_alloc_node() — fastpath/slowpath 분기

/* mm/slub.c:4750-4868 (요약) */
static __always_inline void *slab_alloc_node(struct kmem_cache *s,
        struct list_lru *lru, gfp_t gfpflags, int node, unsigned long addr,
        size_t orig_size)
{
    struct slub_percpu_sheaves *pcs;
    void *object;

    /* 1단계: pre-allocation hook (KFENCE, KASAN 등) */
    s = slab_pre_alloc_hook(s, gfpflags);
    if (!s) return NULL;

    /* 2단계: per-CPU sheaves에서 할당 시도 (fastpath) */
    pcs = this_cpu_ptr(s->cpu_sheaves);
    local_lock(&pcs->lock);

    if (pcs->main->size > 0) {
        /* main sheaf에서 오브젝트 반환 */
        object = pcs->main->objects[pcs->main->size - 1];
        pcs->main->size--;
        goto success;
    }

    /* 3단계: main이 비어있으면 spare와 교체 또는 slowpath */
    object = ___slab_alloc(s, gfpflags, node, addr, orig_size);

success:
    slab_post_alloc_hook(s, lru, gfpflags, 1, &object, ...);
    return object;
}

fastpath 조건: main sheaf에 빈 오브젝트가 있을 때 (lock 1회로 할당 완료)

___slab_alloc() — slowpath 처리

/* mm/slub.c:4374-4451 */
static void *___slab_alloc(struct kmem_cache *s, gfp_t gfpflags, int node,
                           unsigned long addr, unsigned int orig_size)
{
    struct partial_context pc;
    void *object;
    struct slab *slab;

    /* 1단계: partial list에서 할당 시도 */
    pc.flags = gfpflags;
    object = get_from_partial(s, node, &pc);
    if (object) goto success;

    /* 2단계: 새 slab 할당 */
    slab = new_slab(s, pc.flags, node);
    if (!slab) {
        slab_out_of_memory(s, gfpflags, node);
        return NULL;
    }

    /* 3단계: 새 slab에서 오브젝트 할당 */
    object = alloc_from_new_slab(s, slab, &object, 1, allow_spin);

success:
    if (kmem_cache_debug_flags(s, SLAB_STORE_USER))
        set_track(s, object, TRACK_ALLOC, addr, gfpflags);
    return object;
}

분기 로직:

1. get_from_partial() 성공 → partial slab에서 할당

2. new_slab() → 페이지 할당자에서 새 slab 페이지 할당

3. alloc_from_new_slab() → 새 slab에서 오브젝트 할당 후 sheaf에 채우기

__slab_free() — slowpath 해제

/* mm/slub.c:5470-5574 */
static void __slab_free(struct kmem_cache *s, struct slab *slab,
                        void *head, void *tail, int cnt, unsigned long addr)
{
    struct freelist_counters old, new;
    struct kmem_cache_node *n = NULL;
    bool was_full;

    do {
        old.freelist = slab->freelist;
        old.counters = slab->counters;
        was_full = (old.freelist == NULL);

        /* freelist에 오브젝트 추가 */
        set_freepointer(s, tail, old.freelist);
        new.freelist = head;
        new.counters = old.counters;
        new.inuse -= cnt;

        /* slab이 비어있거나 full에서 변환될 때 list_lock 필요 */
        if (!new.inuse || was_full) {
            n = get_node(s, slab_nid(slab));
            spin_lock_irqsave(&n->list_lock, flags);
        }
    } while (!slab_update_freelist(s, slab, &old, &new, "__slab_free"));

    /* list_lock 획득 후 처리 */
    if (!was_full && !on_node_partial) return;  // 이미 partial에 있음

    if (unlikely(!new.inuse && n->nr_partial >= s->min_partial))
        goto slab_empty;  // slab이 비었고 partial이 충분히 많음

    if (unlikely(was_full)) {
        add_partial(n, slab, ADD_TO_TAIL);  // full → partial 전환
    }

    return;

slab_empty:
    remove_partial(n, slab);  // partial에서 제거
    discard_slab(s, slab);    // 페이지 할당자에 반환
}

allocate_slab() — 새 slab 페이지 할당

/* mm/slub.c:3455-3530 */
static struct slab *allocate_slab(struct kmem_cache *s, gfp_t flags, int node)
{
    struct kmem_cache_order_objects oo = s->oo;
    gfp_t alloc_gfp;
    struct slab *slab;

    /* 높은 order 할당 실패 시 min order로 폴백 */
    alloc_gfp = (flags | __GFP_NOWARN | __GFP_NORETRY) & ~__GFP_NOFAIL;
    slab = alloc_slab_page(alloc_gfp, node, oo, allow_spin);
    if (unlikely(!slab)) {
        oo = s->min;
        slab = alloc_slab_page(alloc_gfp, node, oo, allow_spin);
        if (unlikely(!slab)) return NULL;
    }

    /* slab 초기화 */
    slab->objects = oo_objects(oo);
    slab->inuse = 0;
    slab->frozen = 0;
    slab->slab_cache = s;

    /* freelist 초기화 */
    kasan_poison_slab(slab);
    setup_slab_debug(s, slab, start);
    alloc_slab_obj_exts_early(s, slab);

    /* freelist 연결 (또는 shuffle) */
    shuffle = shuffle_freelist(s, slab, allow_spin);
    if (!shuffle) {
        start = fixup_red_left(s, start);
        slab->freelist = start;
        for (idx = 0; idx < slab->objects - 1; idx++) {
            next = p + s->size;
            set_freepointer(s, p, next);
            p = next;
        }
    }
    return slab;
}

SLAB / SLUB / SLOB 비교

항목SLABSLUBSLOB
설계 방향magazine/per-CPU array 중심의 전통 구현단순한 per-CPU fastpath와 slab metadata 통합작은 시스템용 단순 first-fit 계열
Linux 7.0 관점과거 비교 배경기본 slab allocator 구현(`mm/slub.c`)legacy 비교 배경
작은 객체 처리cache별 객체 재사용`kmalloc-*` cache와 `kmem_cache_create()` cache 재사용작은 footprint 우선
NUMA지원node partial list, barn, mempolicy 연동제한적
디버깅지원`slab_debug`, red zone, poison, user tracking, sysfs 통계제한적

kmalloc 크기 반올림과 내부 단편화

요청 크기대표 cache index결과의미
1-8B`kmalloc-8`최소 크기 cache 사용너무 작은 객체도 최소 align 비용 발생
65-96B`kmalloc-96`128B보다 작은 특수 bucket내부 단편화 감소 목적
129-192B`kmalloc-192`256B보다 작은 특수 bucket중간 크기 낭비 완화
193-256B`kmalloc-256`256B bucket나머지 공간은 내부 단편화
4KB 초과, 8KB 이하`kmalloc-8k`order-1까지 SLUB cache그보다 큰 요청은 page allocator 성격 강화

kmem_cache_create()는 같은 크기의 객체를 자주 만들 때 유리합니다. ctor, SLAB_NO_MERGE, debug flag, usercopy region이 있으면 merge가 막히므로 /sys/kernel/slab/에서 alias와 debug 파일을 함께 확인해야 합니다.


호출 흐름

할당 흐름 (Allocation Flow)

kmem_cache_alloc(s, gfp)
  │
  ├─→ slab_alloc_node()
  │     │
  │     ├─→ [FASTPATH] per-CPU main sheaf에서 할당
  │     │     └─ local_trylock(cpu_sheaves) → main->objects[--size] → unlock
  │     │
  │     └─→ [SLOWPATH] ___slab_alloc()
  │           │
  │           ├─→ get_from_partial()
  │           │     ├─→ get_from_partial_node()  ← partial list 탐색
  │           │     └─→ get_node()로 NUMA 노드 관리
  │           │
  │           ├─→ new_slab() → allocate_slab()
  │           │     └─→ alloc_slab_page()  ← 페이지 할당자(Buddy) 호출
  │           │
  │           └─→ alloc_from_new_slab()  ← 새 slab에서 sheaf 채우기
  │
  └─→ slab_post_alloc_hook()  ← KFENCE, memcg 등 후처리

해제 흐름 (Free Flow)

kmem_cache_free(s, obj)
  │
  ├─→ slab_free()
  │     │
  │     ├─→ [FASTPATH] per-CPU main sheaf에 추가
  │     │     └─ free_to_pcs() → per-CPU main/spare sheaf 또는 barn 교환
  │     │
  │     └─→ [SLOWPATH] __slab_free()
  │           │
  │           ├─→ cmpxchg로 freelist 원자적 업데이트
  │           │
  │           ├─→ [full→partial] add_partial()  ← node->list_lock 획득
  │           │
  │           └─→ [slab empty] remove_partial() → discard_slab()
  │                 └─→ free_slab() → __free_slab()  ← 페이지 반환
  │
  └─→ memcg 등 후처리

조건별 비교

할당 경로 비교

조건fastpathslowpath결과
main sheaf에 오브젝트 존재lock 1회로 할당-**최고 성능**
main 비어있음, barn에 empty sheaf-sheaf 교체 후 할당중간 성능
barn에도 empty 없음-새 slab 할당느림 (Buddy 호출)
메모리 부족-slab_out_of_memory()**할당 실패**

해제 경로 비교

조건fastpathslowpath결과
main sheaf에 여유 공간lock 1회로 해제-**최고 성능**
main 가득참, spare 사용-spare/main 교체 후 해제중간 성능
remote node 오브젝트-barn 또는 direct free원격 노드 비용 발생
slab 완전 해제-remove_partial → discard_slab페이지 반환

NUMA 정책별 차이

NUMA 정책할당 노드 결정remote 비율성능 영향
defaultlocal node 우선낮음**최적**
strict_numalocal node만0%원격 실패 시 폴백 없음
mempolicy(BIND)지정 노드0%지정 노드 부족 시 실패
mempolicy(INTERLEAVE)순차 분산높음균등 분배, locality 감소

Linux 7.0 원문 상세

kmalloc 크기 클래스와 최대 캐시 크기

kmalloc()은 요청 크기를 가장 가까운 slab cache index로 반올림합니다. Linux 7.0 SLUB는 order-1 페이지까지는 slab cache를 직접 사용하고, 그보다 큰 요청은 page allocator로 넘깁니다.

/* include/linux/slab.h:580-593 */
/*
 * SLUB directly allocates requests fitting in to an order-1 page
 * (PAGE_SIZE*2).  Larger requests are passed to the page allocator.
 */
#define KMALLOC_SHIFT_HIGH	(PAGE_SHIFT + 1)
#define KMALLOC_SHIFT_MAX	(MAX_PAGE_ORDER + PAGE_SHIFT)
#ifndef KMALLOC_SHIFT_LOW
#define KMALLOC_SHIFT_LOW	3
#endif

/* 최대 할당 가능 크기 */
#define KMALLOC_MAX_SIZE	(1UL << KMALLOC_SHIFT_MAX)
/* 실제로 slab 캐시를 사용하는 최대 크기 */
#define KMALLOC_MAX_CACHE_SIZE	(1UL << KMALLOC_SHIFT_HIGH)
/* include/linux/slab.h:710-733 */
static __always_inline unsigned int __kmalloc_index(size_t size,
						    bool size_is_constant)
{
	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;
/* mm/slab_common.c:861-883 */
const struct kmalloc_info_struct kmalloc_info[] __initconst = {
	INIT_KMALLOC_INFO(0, 0),
	INIT_KMALLOC_INFO(96, 96),
	INIT_KMALLOC_INFO(192, 192),
	INIT_KMALLOC_INFO(8, 8),
	INIT_KMALLOC_INFO(16, 16),
	INIT_KMALLOC_INFO(32, 32),
	INIT_KMALLOC_INFO(64, 64),
	INIT_KMALLOC_INFO(128, 128),
	INIT_KMALLOC_INFO(256, 256),
	INIT_KMALLOC_INFO(512, 512),
	INIT_KMALLOC_INFO(1024, 1k),
	INIT_KMALLOC_INFO(2048, 2k),
	INIT_KMALLOC_INFO(4096, 4k),
	INIT_KMALLOC_INFO(8192, 8k),
	INIT_KMALLOC_INFO(16384, 16k),
	INIT_KMALLOC_INFO(32768, 32k),
	INIT_KMALLOC_INFO(65536, 64k),
	INIT_KMALLOC_INFO(131072, 128k),
	INIT_KMALLOC_INFO(262144, 256k),
	INIT_KMALLOC_INFO(524288, 512k),
	INIT_KMALLOC_INFO(1048576, 1M),
	INIT_KMALLOC_INFO(2097152, 2M)
};

캐시 병합 조건

minzkn이 언급한 cache merging은 slab_nomerge, 디버그/RCU/usercopy/ctor 조건, alignment와 size compatibility에 의해 결정됩니다.

/* mm/slab_common.c:155-174 */
int slab_unmergeable(struct kmem_cache *s)
{
	if (slab_nomerge || (s->flags & SLAB_NEVER_MERGE))
		return 1;

	if (s->ctor)
		return 1;

#ifdef CONFIG_HARDENED_USERCOPY
	if (s->usersize)
		return 1;
#endif

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

	return 0;
}
/* mm/slab_common.c:194-228 */
static struct kmem_cache *find_mergeable(unsigned int size, slab_flags_t flags,
		const char *name, struct kmem_cache_args *args)
{
	struct kmem_cache *s;
	unsigned int align;

	flags = kmem_cache_flags(flags, name);
	if (slab_args_unmergeable(args, flags))
		return NULL;

	size = ALIGN(size, sizeof(void *));
	align = calculate_alignment(flags, args->align, size);
	size = ALIGN(size, align);

	list_for_each_entry_reverse(s, &slab_caches, list) {
		if (slab_unmergeable(s))
			continue;

		if (size > s->size)
			continue;

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

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

		return s;
	}

Linux 7.0 fastpath 할당 원문

문서 앞쪽의 slab_alloc_node() 요약은 이해를 돕기 위한 흐름입니다. 실제 Linux 7.0 fastpath는 alloc_from_pcs()에서 main->objects[main->size - 1]를 직접 꺼내고, 비어 있으면 __pcs_replace_empty_main()으로 spare/barn 교체를 시도합니다.

/* mm/slub.c:4711-4743 */
	if (!local_trylock(&s->cpu_sheaves->lock))
		return NULL;

	pcs = this_cpu_ptr(s->cpu_sheaves);

	if (unlikely(pcs->main->size == 0)) {
		pcs = __pcs_replace_empty_main(s, pcs, gfp);
		if (unlikely(!pcs))
			return NULL;
	}

	object = pcs->main->objects[pcs->main->size - 1];

	if (unlikely(node_requested)) {
		/*
		 * Verify that the object was from the node we want. This could
		 * be false because of cpu migration during an unlocked part of
		 * the current allocation or previous freeing process.
		 */
		if (page_to_nid(virt_to_page(object)) != node) {
			local_unlock(&s->cpu_sheaves->lock);
			stat(s, ALLOC_NODE_MISMATCH);
			return NULL;
		}
	}

	pcs->main->size--;

	local_unlock(&s->cpu_sheaves->lock);

	stat(s, ALLOC_FASTPATH);

	return object;
/* mm/slub.c:4837-4868 */
static __fastpath_inline void *slab_alloc_node(struct kmem_cache *s, struct list_lru *lru,
		gfp_t gfpflags, int node, unsigned long addr, size_t orig_size)
{
	void *object;
	bool init = false;

	s = slab_pre_alloc_hook(s, gfpflags);
	if (unlikely(!s))
		return NULL;

	object = kfence_alloc(s, orig_size, gfpflags);
	if (unlikely(object))
		goto out;

	object = alloc_from_pcs(s, gfpflags, node);

	if (!object)
		object = __slab_alloc_node(s, gfpflags, node, addr, orig_size);

	maybe_wipe_obj_freeptr(s, object);
	init = slab_want_init_on_alloc(gfpflags, s);

out:
	/*
	 * When init equals 'true', like for kzalloc() family, only
	 * @orig_size bytes might be zeroed instead of s->object_size
	 * In case this fails due to memcg_slab_post_alloc_hook(),
	 * object is set to NULL
	 */
	slab_post_alloc_hook(s, lru, gfpflags, 1, &object, init, orig_size);

	return object;
}

Linux 7.0 slowpath 할당 원문

___slab_alloc()은 preferred NUMA node가 지정됐지만 __GFP_THISNODE가 없는 경우 먼저 target node를 opportunistic하게 시도하고, 실패하면 원래 gfpflags로 다른 node partial list까지 허용합니다.

/* mm/slub.c:4383-4450 */
	stat(s, ALLOC_SLOWPATH);

new_objects:

	pc.flags = gfpflags;
	/*
	 * When a preferred node is indicated but no __GFP_THISNODE
	 *
	 * 1) try to get a partial slab from target node only by having
	 *    __GFP_THISNODE in pc.flags for get_from_partial()
	 * 2) if 1) failed, try to allocate a new slab from target node with
	 *    GPF_NOWAIT | __GFP_THISNODE opportunistically
	 * 3) if 2) failed, retry with original gfpflags which will allow
	 *    get_from_partial() try partial lists of other nodes before
	 *    potentially allocating new page from other nodes
	 */
	if (unlikely(node != NUMA_NO_NODE && !(gfpflags & __GFP_THISNODE)
		     && try_thisnode)) {
		if (unlikely(!allow_spin))
			/* 더 제한적인 모드에서 NOWAIT로 gfp를 업그레이드하지 않음 */
			pc.flags = gfpflags | __GFP_THISNODE;
		else
			pc.flags = GFP_NOWAIT | __GFP_THISNODE;
	}

	pc.orig_size = orig_size;
	object = get_from_partial(s, node, &pc);
	if (object)
		goto success;

	slab = new_slab(s, pc.flags, node);

	if (unlikely(!slab)) {
		if (node != NUMA_NO_NODE && !(gfpflags & __GFP_THISNODE)
		    && try_thisnode) {
			try_thisnode = false;
			goto new_objects;
		}
		slab_out_of_memory(s, gfpflags, node);
		return NULL;
	}

	stat(s, ALLOC_SLAB);

	if (IS_ENABLED(CONFIG_SLUB_TINY) || kmem_cache_debug(s)) {
		object = alloc_single_from_new_slab(s, slab, orig_size, gfpflags);

		if (likely(object))
			goto success;
	} else {
		alloc_from_new_slab(s, slab, &object, 1, allow_spin);

		/* we don't need to check SLAB_STORE_USER here */
		if (likely(object))
			return object;
	}

	if (allow_spin)
		goto new_objects;

	/* 무한 루프를 유발할 수 있음. 대신 실패 반환 */
	return NULL;

success:
	if (kmem_cache_debug_flags(s, SLAB_STORE_USER))
		set_track(s, object, TRACK_ALLOC, addr, gfpflags);

	return object;

Linux 7.0 해제 fastpath 원문

kmem_cache_free()virt_to_slab()로 slab을 찾은 뒤 slab_free()로 들어갑니다. 같은 NUMA node이고 pfmemalloc slab이 아니면 free_to_pcs()가 per-CPU sheaf에 되돌리는 fastpath입니다.

/* mm/slub.c:6158-6175 */
static __fastpath_inline
void slab_free(struct kmem_cache *s, struct slab *slab, void *object,
	       unsigned long addr)
{
	memcg_slab_free_hook(s, slab, &object, 1);
	alloc_tagging_slab_free_hook(s, slab, &object, 1);

	if (unlikely(!slab_free_hook(s, object, slab_want_init_on_free(s), false)))
		return;

	if (likely(!IS_ENABLED(CONFIG_NUMA) || slab_nid(slab) == numa_mem_id())
	    && likely(!slab_test_pfmemalloc(slab))) {
		if (likely(free_to_pcs(s, object, true)))
			return;
	}

	__slab_free(s, slab, object, object, 1, addr);
	stat(s, FREE_SLOWPATH);
}

kmem_cache_node 실제 위치 정정

struct kmem_cache_node는 Linux 7.0에서 mm/slub.c에 정의됩니다. NUMA node별 partial/full list와 barn 포인터가 여기서 연결됩니다.

/* mm/slub.c:430-440 */
struct kmem_cache_node {
	spinlock_t list_lock;
	unsigned long nr_partial;
	struct list_head partial;
#ifdef CONFIG_SLUB_DEBUG
	atomic_long_t nr_slabs;
	atomic_long_t total_objects;
	struct list_head full;
#endif
	struct node_barn *barn;
};

slab_debug 옵션 매핑

커널 문서 Documentation/admin-guide/mm/slab.rst 기준으로 slab_debug=FZPU는 sanity check, red zone, poison, alloc/free user tracking을 켭니다.

옵션sysfs 파일의미주의점
`F``sanity_checks`객체/프리리스트 일관성 검사최소 디버깅으로도 성능 영향 가능
`Z``red_zone`객체 양끝 red zone 검사객체 배치가 바뀌어 order 증가 가능
`P``poison`해제 객체와 padding poisoninguse-after-free 탐지에 유용
`U``store_user`alloc/free 호출자 추적메타데이터 증가, 디버그 빌드 필요
`T``trace`단일 cache trace잘못 켜면 로그 폭증 가능
`A``failslab`failslab fault injection 대상런타임에 0/1 쓰기 가능

할당자 비교

관련 문서

  • 00-overview.html — 메모리 관리 개요 (Buddy Allocator 위의 slab 계층 위치)
  • 01-page_alloc.html — Buddy Allocator (allocate_slab에서 alloc_slab_page 호출)
  • 03-vma_mmap.html — VMA / mmap (kmalloc 사용)
  • 05-page_reclaim.html — 페이지 회수 (slab shrinker 연동)
  • 14-memcontrol.html — Memory Cgroup (memcg slab 할당 계정)
  • 27-debug.html — 디버그 도구 (SLUB_DEBUG, KASAN, KFENCE)

  • 참조

  • Linux 7.0 소스: mm/slub.c, mm/slab.h, include/linux/slab.h
  • minzkn.com: 메모리 관리 개요 — Slab 섹션
  • Documentation/mm/slub.rst