๏ปฟ# 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 ์บ์ ํต๊ณ ํ์ธ
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_alloc โ slab_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 ๋น๊ต
| ํญ๋ชฉ | 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 ํต๊ณ | ์ ํ์ |
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 ๋ฑ ํ์ฒ๋ฆฌ
์กฐ๊ฑด๋ณ ๋น๊ต
ํ ๋น ๊ฒฝ๋ก ๋น๊ต
| ์กฐ๊ฑด | 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 ์ ์ฑ ๋ณ ์ฐจ์ด
| NUMA ์ ์ฑ | ํ ๋น ๋ ธ๋ ๊ฒฐ์ | remote ๋น์จ | ์ฑ๋ฅ ์ํฅ |
|---|---|---|---|
| default | local node ์ฐ์ | ๋ฎ์ | **์ต์ ** |
| strict_numa | local 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 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