# SLUB 할당자 (SLUB Allocator)
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는 Buddy Allocator 위에 커널 오브젝트를 캐싱하는 계층입니다. SLUB는 대형 물류창고의 소형 부품 서랍장과 비슷합니다. Buddy Allocator가 페이지라는 큰 상자를 가져오면, SLUB는 그 상자 안을 task_struct, inode, dentry, kmalloc-256 같은 같은 크기의 칸으로 나눠 자주 쓰는 부품을 CPU 가까이에 보관합니다. 빠른 경로에서는 CPU별 sheaf에서 바로 꺼내고, 비면 node barn과 partial slab을 거쳐 새 페이지를 가져옵니다.
# 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는 하나의 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는 하나의 슬래브 페이지(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; // 오브젝트 확장 메타데이터
};
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비트 값
};
};
각 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 관리 구조체
};
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 배치용
};
실제 오브젝트 포인터를 저장하는 배열 구조체입니다. 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[]; // 오브젝트 포인터 배열
};
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 수
};
/* 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_alloc → slab_alloc_node → fastpath(per-CPU sheaves) 또는 slowpath(___slab_alloc)
/* 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회로 할당 완료)
/* 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에 채우기
/* 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); // 페이지 할당자에 반환
}
/* 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 |
|---|---|---|---|
| 설계 방향 | 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 통계 | 제한적 |
| 요청 크기 | 대표 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 파일을 함께 확인해야 합니다.
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 등 후처리
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 등 후처리
| 조건 | fastpath | slowpath | 결과 |
|---|---|---|---|
| main sheaf에 오브젝트 존재 | lock 1회로 할당 | - | **최고 성능** |
| main 비어있음, barn에 empty sheaf | - | sheaf 교체 후 할당 | 중간 성능 |
| barn에도 empty 없음 | - | 새 slab 할당 | 느림 (Buddy 호출) |
| 메모리 부족 | - | slab_out_of_memory() | **할당 실패** |
| 조건 | fastpath | slowpath | 결과 |
|---|---|---|---|
| main sheaf에 여유 공간 | lock 1회로 해제 | - | **최고 성능** |
| main 가득참, spare 사용 | - | spare/main 교체 후 해제 | 중간 성능 |
| remote node 오브젝트 | - | barn 또는 direct free | 원격 노드 비용 발생 |
| slab 완전 해제 | - | remove_partial → discard_slab | 페이지 반환 |
| NUMA 정책 | 할당 노드 결정 | remote 비율 | 성능 영향 |
|---|---|---|---|
| default | local node 우선 | 낮음 | **최적** |
| strict_numa | local node만 | 0% | 원격 실패 시 폴백 없음 |
| mempolicy(BIND) | 지정 노드 | 0% | 지정 노드 부족 시 실패 |
| mempolicy(INTERLEAVE) | 순차 분산 | 높음 | 균등 분배, locality 감소 |
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;
}
문서 앞쪽의 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;
}
___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;
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);
}
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;
};
커널 문서 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 poisoning | use-after-free 탐지에 유용 |
| `U` | `store_user` | alloc/free 호출자 추적 | 메타데이터 증가, 디버그 빌드 필요 |
| `T` | `trace` | 단일 cache trace | 잘못 켜면 로그 폭증 가능 |
| `A` | `failslab` | failslab fault injection 대상 | 런타임에 0/1 쓰기 가능 |
mm/slub.c, mm/slab.h, include/linux/slab.h