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
struct kmem_cache {
    unsigned int        size;           // Actual size of each object (including alignment)
    unsigned int        object_size;    // Original object size (excluding metadata)
    unsigned int        offset;         // Pointer offset to the next free object
    unsigned int        cpu_partial;    // Number of per-CPU partial slabs

    const char          *name;          // Name visible in /proc/slabinfo

    // Constructor (rarely used; Linux prefers zero-initialization + manual init)
    void (*ctor)(void *);

    struct kmem_cache_node *node[MAX_NUMNODES];  // Per-NUMA node
    struct kmem_cache_cpu __percpu *cpu_slab;    // Per-CPU hot slab (lockless)
};

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):
kmem_cache_alloc(s, flags)slab_alloc()slab_alloc_node()
    ├─ 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
    │     this_cpu_cmpxchg(s->cpu_slab->freelist, old, object);
    │
    └─ slow path: __slab_free()
        ├─ Slab becomes completely free? → If per-node partial count is low → convert to partial
        │                → Otherwise free_slab() → 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
cat /proc/slabinfo
# Columns: name, active_objs, num_objs, objsize, objperslab, pagesperslab

# More human-readable version
slabtop -o       # Sort by number of active objects
slabtop -s c     # Sort by cache size

# Detailed info for a specific cache
cat /sys/kernel/slab/dentry/order       # Slab order
cat /sys/kernel/slab/dentry/object_size # Object size
cat /sys/kernel/slab/dentry/objects     # Number of allocated objects
cat /sys/kernel/slab/dentry/slab_size   # Size of each slab

# See who uses the most slabs (kmem trace)
echo 1 > /sys/kernel/debug/tracing/events/kmem/kmalloc/enable
echo 1 > /sys/kernel/debug/tracing/events/kmem/kfree/enable
cat /sys/kernel/debug/tracing/trace_pipe | head -100

# Memory leak detection
echo scan > /sys/kernel/debug/kmemleak
cat /sys/kernel/debug/kmemleak | head -50

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 implementation
    • mm/slab_common.c — Slab merging, sysfs interface
    • include/linux/slub_def.h — struct kmem_cache
    • mm/kmemleak.c — Leak detection
    • mm/kasan/ — KASAN

Keywords: SLUB, kmem_cache, kmalloc, per-CPU slab, freelist, slab merging, SLOB retirement, kmemleak, KASAN