On this page
Slab and kmalloc
Coverage: SLUB allocator → kmem_cache → kmalloc/kfree → slab merging → debugging (KASAN/SLUB_DEBUG) → SLOB retirement (6.4) Kernel versions: 2.6 ~ 6.x
Overview
The Buddy allocator's minimum allocation unit is 1 page (4KB). However, most allocations in the kernel are much smaller than 4KB—task_struct (~4KB), inode (~600B), dentry (~200B). If every small object occupied an entire page, the waste would be enormous.
The SLUB allocator solves this problem: it divides an entire page (or several contiguous pages) into multiple small objects of the same size, managing free and allocated objects using a freelist. SLUB is the current default slab implementation (SLOB was removed in 6.4). It originated from SLAB (Solaris → Linux 2.2) → SLUB (Linux 2.6.23, C. Lameter).
SLUB Architecture
kmem_cache
// include/linux/slub_def.h
;
Per-CPU Hot Slab
flowchart TD
START["SLUB allocation/free request"]
START --> OP{"Operation type?"}
OP -->|"Allocate"| ALLOC["Pop object from freelist head<br/>(atomic: this_cpu_cmpxchg)"]
ALLOC --> FULL{"Is current slab<br/>full?"}
FULL -->|"No"| FAST_ALLOC["✅ Return object<br/>(lockless, extremely fast)"]
FULL -->|"Yes"| PART{"Are there partial<br/>slabs?"}
PART -->|"Yes"| SWITCH["Switch to next partial slab"]
SWITCH --> FAST_ALLOC
PART -->|"No"| BUDDY_ALLOC["Allocate new slab from buddy"]
BUDDY_ALLOC --> FAST_ALLOC
OP -->|"Free"| FREE["Return object to freelist head"]
FREE --> EMPTY{"Does slab become completely empty?"}
EMPTY -->|"Yes"| RETURN["Possibly return to buddy"]
EMPTY -->|"No"| FAST_FREE["✅ Free complete<br/>(lockless, extremely fast)"]
classDef fast fill:#e8f5e9,stroke:#2e7d32
classDef slow fill:#ffebee,stroke:#c62828
classDef decision fill:#fff3e0,stroke:#ef6c00
class START,ALLOC,FREE fast
class FULL,PART,EMPTY decision
class SWITCH,BUDDY_ALLOC,RETURN slow
class FAST_ALLOC,FAST_FREE fast
Allocation and Free Paths
// mm/slub.c
// Allocation (fast path → slow path):
→ →
├─ fast path: Pop from cpu_slab's freelist
│ object = this_cpu_read(s->cpu_slab->freelist);
│ if (likely(object))
│ // Update freelist pointer (cmpxchg)
│ return object;
│
└─ slow path: __slab_alloc()
├─ Try slabs in CPU partial list
├─ Try slabs in node partial list
├─ If both fail → new_slab() → allocate_slab()
│ → alloc_pages(GFP_KERNEL, order) → buddy allocator
│ → Construct freelist
└─ Return first object
// Free (fast path):
kmem_cache_free(s, object)
→ slab_free()
├─ fast path: Return object to cpu_slab's freelist
│ ;
│
└─ slow path:
├─ Slab becomes completely free? → If per-node partial count is low → convert to partial
│ → Otherwise → release back to buddy allocator
└─ Update node statistics
kmalloc: General Allocation Interface
// include/linux/slab.h
// kmalloc is actually a wrapper around predefined kmem_caches
// Predefined general caches in the kernel:
// 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(size, flags):
// 1. Look up the corresponding kmem_cache based on size (via kmalloc_caches table)
// 2. Call kmem_cache_alloc(s, flags)
// 3. Return object pointer
// kfree(ptr):
// 1. Find which kmem_cache the object belongs to via object metadata
// → via page->slab_cache (field in struct page)
// 2. kmem_cache_free(cache, ptr)
Slab Merging
// mm/slab_common.c
// To save memory, kmem_caches with identical or similar sizes can be merged:
// "dentry" (256B) and "inode_cache" (592B)
// → Both allocated from kmalloc-512? No merge: they have different sizes
// → But if two caches have similar object_size (both close to 256B)
// → They may share the same underlying slab
//
// You can see which caches are merged under /sys/kernel/slab/
SLOB Retirement (6.4)
Historically, Linux had three slab implementations:
SLAB: Original implementation (ported from Solaris → Linux 2.2), most complex
SLUB: Simplified implementation (2.6.23), current default
SLOB: Minimal implementation, used for embedded systems (< 16MB RAM)
SLOB was removed in 6.4:
- SLUB has been optimized for years, and its code size is already small enough
- Embedded systems can use SLUB (CONFIG_SLUB_TINY)
- Maintaining three allocators imposes too high a maintenance burden
SLOB's design philosophy is worth mentioning:
- No per-CPU slabs
- All allocations come from a single free list
- First-fit allocation (finds the first block large enough)
- Extremely simple, but suffers from fragmentation and poor performance
kmemleak: Memory Leak Detection
// mm/kmemleak.c
// Similar to valgrind's memcheck in user space
// Periodically scans all allocated kernel objects to find unreferenced ones → reports leaks
// Enable:
// CONFIG_DEBUG_KMEMLEAK=y
// boot: kmemleak=on
// Manual trigger:
echo scan > /sys/kernel/debug/kmemleak
cat /sys/kernel/debug/kmemleak # Leak report
KASAN: Out-of-bounds/UAF Detection
// mm/kasan/
// Kernel Address Sanitizer — compile-time instrumentation
// Detects slab out-of-bounds, use-after-free, double-free
// Three modes:
// Generic KASAN: Shadow memory (1 byte tag per 8 bytes) → 1/8 memory overhead
// Software Tag-Based KASAN: Uses TBI (Top Byte Ignore) on ARM64 → low overhead
// Hardware Tag-Based KASAN: Uses ARM64 MTE → hardware checks → for production environments
Debugging and Observation
# Slab usage
# Columns: name, active_objs, num_objs, objsize, objperslab, pagesperslab
# More human-readable version
# Detailed info for a specific cache
# See who uses the most slabs (kmem trace)
|
# Memory leak detection
|
References and Further Reading
- Kernel Documentation:
Documentation/mm/slub.rst,Documentation/dev-tools/kmemleak.rst,Documentation/dev-tools/kasan.rst - LWN:
- "The SLUB allocator" (lwn.net/Articles/229984/)
- "Removing SLOB" (lwn.net/Articles/967178/)
- Source Files:
mm/slub.c— SLUB implementationmm/slab_common.c— Slab merging, sysfs interfaceinclude/linux/slub_def.h— struct kmem_cachemm/kmemleak.c— Leak detectionmm/kasan/— KASAN
Keywords: SLUB, kmem_cache, kmalloc, per-CPU slab, freelist, slab merging, SLOB retirement, kmemleak, KASAN