Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Slab Allocator (SLAB/SLUB/SLOB)

Introduction

The Linux kernel frequently needs to allocate small, fixed-size objects — data structures like struct task_struct, struct inode, struct dentry, etc. Allocating these directly from the page allocator (which works in units of pages) would be extremely wasteful. The slab allocator solves this by pre-allocating pages and carving them into fixed-size objects, providing fast allocation and freeing with minimal overhead.

Linux has three slab allocator implementations:

  • SLAB: The original, feature-rich allocator (being phased out)
  • SLUB: The default allocator since Linux 2.6.22 — simpler, more scalable, better NUMA performance
  • SLOB: A minimal allocator for embedded systems with very limited memory

Slab Allocator Architecture

Design Principles

  1. Object pooling: Pre-allocate objects and reuse them without returning to the page allocator.
  2. Per-CPU caches: Reduce lock contention by caching objects per CPU.
  3. NUMA awareness: Allocate objects from the local NUMA node when possible.
  4. Slab merging: Combine caches with similar object sizes to reduce fragmentation.
flowchart TB
    subgraph "Application / Kernel Code"
        KMALLOC["kmalloc(size, gfp)"]
        CACHE_ALLOC["kmem_cache_alloc(cache, gfp)"]
    end
    subgraph "SLUB Allocator"
        PCPU["Per-CPU Page Cache<br>(kmem_cache_cpu)"]
        SLAB["Slab Page<br>(contiguous pages)"]
        PARTIAL["Partial Slabs List<br>(kmem_cache_node)"]
    end
    subgraph "Page Allocator"
        BUDDY["Buddy System"]
    end

    KMALLOC --> PCPU
    CACHE_ALLOC --> PCPU
    PCPU -->|"Empty"| PARTIAL
    PARTIAL -->|"All partial empty"| BUDDY
    BUDDY -->|"New slab page"| SLAB
    SLAB --> PARTIAL
    PARTIAL --> PCPU

SLUB Allocator (Default)

Key Data Structures

/* include/linux/slub_def.h */

/* Per-CPU cache: fast path for allocation */
struct kmem_cache_cpu {
    union {
        struct {
            unsigned long tid;    /* Transaction ID for lockless fast path */
            freelist_aba_t freelist; /* Pointer to first free object */
        };
        freelist_aba_t freelist_aba;
    };
    struct slab *slab;     /* Slab from which objects are being allocated */
#ifdef CONFIG_SLUB_CPU_PARTIAL
    struct slab *partial;  /* Per-CPU partial slab list */
#endif
    /* ... stat counters ... */
};

/* Per-node structure: manages partial slab lists */
struct kmem_cache_node {
    spinlock_t list_lock;        /* Protects the lists */
    unsigned long nr_partial;    /* Number of partial slabs */
    struct list_head partial;    /* List of partial slabs */
    /* ... */
};

/* The slab cache descriptor */
struct kmem_cache {
    struct kmem_cache_cpu __percpu *cpu_slab; /* Per-CPU caches */
    slab_flags_t flags;            /* Cache flags */
    unsigned long min_partial;     /* Minimum partial slabs to keep */
    unsigned int size;             /* Object size including metadata */
    unsigned int object_size;      /* Actual object size */
    struct reciprocal_value reciprocal_size;
    unsigned int offset;           /* Free pointer offset within object */
    struct kmem_cache_node *node[MAX_NUMNODES]; /* Per-node data */

    const char *name;              /* Cache name (shown in /proc/slabinfo) */
    struct list_head list;         /* List of all caches */

    /* Constructor/destructor */
    void (*ctor)(void *object);

    /* ... sizing, debugging, sysfs fields ... */
};

Slab Page Layout

A slab is one or more contiguous pages that contain objects. The metadata (free list pointer) is stored either within the objects themselves or in a separate area:

/* mm/slub.c */
struct slab {
    unsigned long __page_flags;
    struct kmem_cache *slab_cache;  /* Back-pointer to cache */
    union {
        struct {
            union {
                struct list_head slab_list;  /* Node partial list */
                struct rcu_head rcu_head;
            };
            struct {
                void *freelist;     /* First free object */
                union {
                    unsigned long counters;
                    struct {
                        unsigned inuse:16;
                        unsigned objects:15;
                        unsigned frozen:1;
                    };
                };
            };
        };
    };
    /* ... */
};

Free objects are linked via an embedded free list. The free pointer is stored at object + cache->offset:

/* Simplified: free pointer stored within the object */
object[0] -> next_free = object[1]
object[1] -> next_free = object[2]
object[2] -> next_free = NULL
/* freelist points to object[0] */

Allocation Fast Path

/* mm/slub.c (simplified) */
static __always_inline void *slab_alloc_node(struct kmem_cache *s,
                                              gfp_t gfpflags,
                                              int node, unsigned long addr)
{
    void *object;
    struct kmem_cache_cpu *c;
    unsigned long tid;

    /* Get per-CPU slab */
    c = raw_cpu_ptr(s->cpu_slab);

    /* Lockless fast path: check freelist */
    if (likely(c->freelist)) {
        object = c->freelist;
        c->freelist = get_freepointer(s, object);
        return object;
    }

    /* Slow path: try partial list, allocate new slab */
    return __slab_alloc(s, gfpflags, node, addr);
}

The fast path is extremely efficient — just a pointer dereference and update, no locks needed (uses transaction-ID-based optimistic concurrency).

Allocation Slow Path

/* mm/slub.c (simplified) */
static void *__slab_alloc(struct kmem_cache *s, gfp_t gfpflags,
                           int node, unsigned long addr)
{
    struct kmem_cache_cpu *c = raw_cpu_ptr(s->cpu_slab);

    /* 1. Check per-CPU partial list */
    if (c->partial) {
        c->slab = c->partial;
        c->partial = c->slab->next;
        goto new_slab;
    }

    /* 2. Check per-node partial list */
    if (kmem_cache_has_cpu_partial(s)) {
        /* Transfer partial slabs from node to CPU */
        /* ... */
    }

    /* 3. Allocate a new slab from page allocator */
    c->slab = allocate_slab(s, gfpflags, node);
    if (!c->slab)
        return NULL;

new_slab:
    /* Initialize slab and return first object */
    c->freelist = c->slab->freelist;
    object = c->freelist;
    c->freelist = get_freepointer(s, object);
    return object;
}

Free Path

/* mm/slub.c (simplified) */
static __always_inline void slab_free(struct kmem_cache *s,
                                       struct slab *slab,
                                       void *object,
                                       unsigned long addr)
{
    /* Fast path: put back on per-CPU freelist */
    if (slab == c->slab) {
        set_freepointer(s, object, c->freelist);
        c->freelist = object;
        return;
    }

    /* Slow path: returning to a different slab */
    __slab_free(s, slab, object, addr);
}

SLAB Allocator (Legacy)

SLAB was the original Linux slab allocator. It uses per-CPU object arrays (array caches) and per-node shared caches. SLAB is more complex than SLUB but was the default for many years:

/* mm/slab.c (simplified SLAB structure) */
/*
 * Per-CPU array cache: holds recently freed objects
 * Per-node shared cache: overflow from array caches
 * Per-node alien cache: objects from other NUMA nodes
 * Slab pages: contain the actual objects
 */

SLAB was removed from the kernel in Linux 6.8. SLUB is now the only full-featured allocator.

SLOB Allocator

SLOB is a minimal allocator for embedded systems:

/* mm/slob.c */
/*
 * SLOB uses a simple first-fit algorithm:
 * - Maintains free lists sorted by size
 * - Very small code footprint (~6KB)
 * - No per-CPU caches
 * - Poor performance under heavy load
 * - Suitable for systems with <64MB RAM
 */
# Select slab allocator at compile time
# CONFIG_SLAB=y    (legacy, removed in 6.8)
# CONFIG_SLUB=y    (default)
# CONFIG_SLOB=y    (embedded)

kmem_cache: Creating Object Caches

Defining a Cache

/* Kernel example: creating a slab cache */
#include <linux/slab.h>

/* Define a cache for struct my_object */
struct kmem_cache *my_cache;

static int __init my_init(void)
{
    my_cache = kmem_cache_create(
        "my_object_cache",     /* Name (appears in /proc/slabinfo) */
        sizeof(struct my_object), /* Object size */
        0,                     /* Alignment (0 = default) */
        SLAB_HWCACHE_ALIGN,    /* Flags */
        NULL                   /* Constructor */
    );
    if (!my_cache)
        return -ENOMEM;

    return 0;
}

/* Allocate an object */
struct my_object *obj = kmem_cache_alloc(my_cache, GFP_KERNEL);

/* Free an object */
kmem_cache_free(my_cache, obj);

/* Destroy the cache (all objects must be freed first) */
kmem_cache_destroy(my_cache);

Cache Flags

/* include/linux/slab.h */
#define SLAB_HWCACHE_ALIGN   0x00002000  /* Align to hardware cache line */
#define SLAB_CACHE_DMA       0x00004000  /* Allocate from DMA zone */
#define SLAB_PANIC           0x00040000  /* Panic on failure */
#define SLAB_TYPESAFE_BY_RCU 0x00080000  /* RCU-safe freeing */
#define SLAB_MEM_SPREAD      0x00100000  /* Spread allocations across nodes */
#define SLAB_TRACE           0x00200000  /* Trace allocations */

kmalloc API

kmalloc() is the primary small-object allocator in the Linux kernel. It allocates physically contiguous memory backed by size-indexed slab caches (e.g., kmalloc-8, kmalloc-16, …, kmalloc-8k). For allocations larger than KMALLOC_MAX_CACHE_SIZE, kmalloc falls back directly to the page allocator.

Core Functions

#include <linux/slab.h>

/* Allocate size bytes. Returns NULL on failure. */
void *kmalloc(size_t size, gfp_t flags);

/* Free kmalloc'd memory. NULL is safe. */
void kfree(const void *ptr);

/* Allocate zero-initialized memory */
void *kzalloc(size_t size, gfp_t flags);

/* Allocate an array (overflow-safe multiplication) */
void *kmalloc_array(size_t n, size_t size, gfp_t flags);

/* Allocate zero-initialized array */
void *kcalloc(size_t n, size_t size, gfp_t flags);

/* Reallocate to new size (may copy data) */
void *krealloc(const void *p, size_t new_size, gfp_t flags);

/* Allocate with NUMA node affinity */
void *kmalloc_node(size_t size, gfp_t flags, int node);

/* Allocate with vmalloc fallback for large sizes */
void *kvmalloc(size_t size, gfp_t flags);
void *kvmalloc_node(size_t size, gfp_t flags, int node);
void *kvfree(const void *addr);

/* Track actual caller (for debugging) */
void *kmalloc_track_caller(size_t size, gfp_t flags);

Size Classes and Rounding

kmalloc rounds the requested size up to the nearest power-of-2 size class. On a typical system:

Requested SizeCache UsedActual Allocation
1–8 byteskmalloc-88 bytes
9–16 byteskmalloc-1616 bytes
17–32 byteskmalloc-3232 bytes
33–64 byteskmalloc-6464 bytes
65–128 byteskmalloc-128128 bytes
129–256 byteskmalloc-256256 bytes
257–512 byteskmalloc-512512 bytes
513–1024 byteskmalloc-1k1024 bytes
1025–2048 byteskmalloc-2k2048 bytes
2049–4096 byteskmalloc-4k4096 bytes
4097–8192 byteskmalloc-8k8192 bytes
>8192 bytesPage allocatorOrder-N pages

The kmalloc_index() function maps sizes to cache indices. The maximum kmalloc size is KMALLOC_MAX_SIZE (typically 8192 or 32768 depending on config).

GFP Flags for kmalloc

/* Common GFP flags */
GFP_KERNEL      /* May sleep, may reclaim — normal kernel allocation */
GFP_ATOMIC      /* Cannot sleep — interrupt/atomic context */
GFP_NOWAIT      /* Don't wait at all */
GFP_NOIO        /* May sleep, no I/O initiated (avoid recursion) */
GFP_NOFS        /* May sleep, no filesystem operations */
GFP_DMA         /* Allocate from DMA zone (< 16MB on x86) */
GFP_DMA32       /* Allocate from DMA32 zone (< 4GB) */
GFP_USER        /* For user-space allocations */
GFP_HIGHUSER    /* Like GFP_USER but from high memory */
GFP_ZERO        /* Zero-fill the allocation (combined with above) */

kvmalloc — Flexible Allocation

kvmalloc() tries kmalloc first and falls back to vmalloc for large allocations:

void *kvmalloc(size_t size, gfp_t flags)
{
    void *ret;

    /* Try physically contiguous first */
    ret = kmalloc(size, flags | __GFP_NOWARN);
    if (ret)
        return ret;

    /* Fall back to virtually contiguous */
    if (size > PAGE_SIZE)
        return vmalloc(size);

    return NULL;
}

Use kvmalloc() when:

  • The allocation might be large but you’d prefer contiguous memory
  • You don’t need physically contiguous memory (just virtually contiguous)
  • You want to avoid vmalloc overhead for small allocations

Always free with kvfree() — it detects whether the pointer was kmalloc’d or vmalloc’d.

kmalloc — The Primary Small Allocator

kmalloc() is the most common kernel memory allocation function. It allocates physically contiguous memory from a size-indexed slab cache:

/* include/linux/slab.h */
void *kmalloc(size_t size, gfp_t flags);
void kfree(const void *ptr);

/* Size-specific variants for known sizes (compile-time optimized) */
void *kmalloc_track_caller(size_t size, gfp_t flags);  /* Tracks actual caller */
void *kvmalloc_node(size_t size, gfp_t flags, int node); /* vmalloc fallback */

/* Array allocation */
void *kmalloc_array(size_t n, size_t size, gfp_t flags);
void *kcalloc(size_t n, size_t size, gfp_t flags); /* Zero-initialized */

Size Classes

SLUB maintains multiple caches for different size ranges. On a typical system:

$ cat /proc/slabinfo | grep -E "^kmalloc" | head -20
# name            <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab>
kmalloc-8k           256    256   8192    4    8
kmalloc-4k          1024   1024   4096    8    8
kmalloc-2k          2048   2048   2048   16    8
kmalloc-1k          4096   4096   1024   16    4
kmalloc-512         8192   8192    512   16    2
kmalloc-256        16384  16384    256   16    1
kmalloc-128        32768  32768    128   32    1
kmalloc-64         65536  65536     64   64    1
kmalloc-32        131072 131072     32  128    1
kmalloc-16        262144 262144     16  256    1
kmalloc-8         524288 524288      8  512    1

How kmalloc Works

/* mm/slub.c (simplified) */
static __always_inline void *__kmalloc(size_t size, gfp_t flags)
{
    struct kmem_cache *s;
    void *ret;

    /* Find the appropriate size-indexed cache */
    if (size <= KMALLOC_MAX_CACHE_SIZE) {
        s = kmalloc_caches[kmalloc_index(size)][kmalloc_type(flags)];
        ret = slab_alloc_node(s, flags, NUMA_NO_NODE, _RET_IP_);
    } else {
        /* Large allocation: go directly to page allocator */
        ret = __kmalloc_large(size, flags);
    }

    return ret;
}

/* Map size to cache index (power-of-2 rounding) */
static __always_inline unsigned int kmalloc_index(size_t size)
{
    if (!size) return 0;
    if (size <= 8) return 3;       /* kmalloc-8 */
    if (size <= 16) return 4;      /* kmalloc-16 */
    if (size <= 32) return 5;      /* kmalloc-32 */
    if (size <= 64) return 6;      /* kmalloc-64 */
    if (size <= 128) return 7;     /* kmalloc-128 */
    if (size <= 256) return 8;     /* kmalloc-256 */
    if (size <= 512) return 9;     /* kmalloc-512 */
    if (size <= 1024) return 10;   /* kmalloc-1k */
    if (size <= 2048) return 11;   /* kmalloc-2k */
    if (size <= 4096) return 12;   /* kmalloc-4k */
    if (size <= 8192) return 13;   /* kmalloc-8k */
    /* ... up to KMALLOC_MAX_SIZE (usually 8192 or 32768) */
    return 0; /* Should not reach here */
}

kfree

/* mm/slub.c */
void kfree(const void *x)
{
    struct page *page;
    void *object = (void *)x;

    page = virt_to_head_page(x);
    if (unlikely(!PageSlab(page))) {
        /* Large kmalloc: free pages directly */
        free_pages((unsigned long)x, compound_order(page));
        return;
    }

    /* Return to slab cache */
    slab_free(page->slab_cache, page, object, _RET_IP_);
}

Slab Merging

The Problem

Different subsystems may create caches with similar object sizes. To reduce waste, SLUB can merge compatible caches:

/* mm/slab_common.c */
/*
 * Two caches can be merged if:
 * 1. Same object size
 * 2. Same alignment
 * 3. Compatible flags
 * 4. No custom constructor/destructor
 */

Merge Control

# Disable slab merging (for debugging)
$ cat /proc/cmdline | tr ' ' '\n' | grep slub
slub_nomerge

# Or via sysfs
$ cat /sys/kernel/slab/kmalloc-64/merge
kmalloc-64

# View merged caches
$ ls /sys/kernel/slab/
kmalloc-8        kmalloc-128    kmalloc-1k     ...
dentry           inode_cache    ext4_inode_cache ...

Debugging and Monitoring

/proc/slabinfo

$ cat /proc/slabinfo
slabinfo - version: 2.1
# name            <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> : tunables <limit> <batchcount> <sharedfactor> : slabdata <active_slabs> <num_slabs> <sharedavail>
# Global stats:
slabinfo - version: 2.1
# name            <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab>
kmalloc-8         1024   1024     8  512    1
dentry             8192  8192   192   21    1
inode_cache        4096  4096   640   25    4
ext4_inode_cache   2048  2048  1088   15    4
task_struct         256   256  6016    5    8
signal_cache        128   128  1152   14    4
mm_struct            64    64  1664   19    8

/sys/kernel/slab/

# Per-cache detailed statistics
$ cat /sys/kernel/slab/kmalloc-64/alloc_fastpath
12345678

$ cat /sys/kernel/slab/kmalloc-64/alloc_slowpath
12345

$ cat /sys/kernel/slab/kmalloc-64/free_fastpath
12340000

$ cat /sys/kernel/slab/kmalloc-64/object_size
64

$ cat /sys/kernel/slab/kmalloc-64/slab_size
64

$ cat /sys/kernel/slab/kmalloc-64/objs_per_slab
64

$ cat /sys/kernel/slab/kmalloc-64/order
1

Slab Memory in /proc/meminfo

$ grep -E "Slab|SReclaim|SUnreclaim" /proc/meminfo
Slab:            1048576 kB    # Total slab memory
SReclaimable:     786432 kB    # Reclaimable (dentry, inode caches)
SUnreclaim:       262144 kB    # Unreclaimable (kmalloc, task_struct)

slabtop

$ slabtop -o | head -20
 Active / Total Objects (% used)    : 123456 / 134567 (91.7%)
 Active / Total Slabs (% used)      : 3456 / 3456 (100.0%)
 Active / Total Caches              : 78 / 120
 Active / Total Size (% used)       : 45678.90K / 52345.67K (87.3%)
 Minimum / Average / Maximum Object : 0.01K / 0.37K / 8.00K

  OBJS ACTIVE  USE OBJ SIZE  SLABS OBJ/SLAB CACHE SIZE NAME
 65536  62345  95%    0.06K   1024       64      4096K kmalloc-64
 32768  31200  95%    0.19K    512       64      6144K dentry
 16384  15200  92%    0.58K    512       32      9216K radix_tree_node
  8192   7600  92%    1.00K    256       32      8192K kmalloc-1k
  4096   3800  92%    0.63K    128       32      4096K inode_cache
  2048   1900  92%    0.50K     64       32      2048K kmalloc-512

SLUB Allocator Internals: The Transaction ID

SLUB uses a lockless fast path based on a transaction ID (TID) to avoid CMPXCHG:

/* mm/slub.c */
static __always_inline void *slab_alloc_node(struct kmem_cache *s,
                                              gfp_t gfpflags,
                                              int node, unsigned long addr)
{
    void *object;
    struct kmem_cache_cpu *c;
    unsigned long tid;
    freelist_aba_t freelist;

    c = raw_cpu_ptr(s->cpu_slab);

    do {
        tid = this_cpu_read(s->cpu_slab->tid);
        freelist = this_cpu_read(s->cpu_slab->freelist);

        if (!freelist.freelist)
            goto slowpath;

        /* Optimistic: assume freelist won't change */
        object = freelist.freelist;
        freelist.freelist = get_freepointer(s, object);

    } while (!this_cpu_try_cmpxchg_double(s->cpu_slab->freelist,
                                            s->cpu_slab->tid,
                                            freelist, tid));

    return object;

slowpath:
    return __slab_alloc(s, gfpflags, node, addr);
}

kmem_cache Flags and NUMA Behavior

Per-Node vs Global Caches

By default, each NUMA node has its own set of slab pages. Objects are preferentially allocated from the local node:

/* Allocate from specific node */
void *obj = kmem_cache_alloc_node(cache, GFP_KERNEL, node_id);

/* Allocate from current node (default) */
void *obj = kmem_cache_alloc(cache, GFP_KERNEL);

SLAB_TYPESAFE_BY_RCU

This flag allows objects to be accessed after kfree() under RCU protection — the physical memory is not immediately returned to the page allocator:

my_cache = kmem_cache_create("my_cache", sizeof(struct my_obj),
                              0, SLAB_TYPESAFE_BY_RCU, NULL);

/* Objects freed with kfree_rcu() or call_rcu() */
kfree_rcu(obj, rcu_head);

Allocator Comparison

FeatureSLUB (default)SLAB (removed 6.8)SLOB
Per-CPU cachesYes (freelist)Yes (array cache)No
NUMA supportYesYesNo
Lockless fast pathYes (TID-based)NoNo
Slab mergingYesYesNo
Code sizeMediumLargeSmall
DebuggingExtensiveExtensiveMinimal
PerformanceBest overallGoodWorst
Use caseGeneralLegacyEmbedded (<64MB)

Code Example: Kernel Module with Custom Cache

#include <linux/module.h>
#include <linux/slab.h>
#include <linux/list.h>

struct widget {
    int id;
    char name[32];
    struct list_head list;
};

static struct kmem_cache *widget_cache;
static LIST_HEAD(widget_list);

static struct widget *create_widget(int id, const char *name)
{
    struct widget *w;

    w = kmem_cache_alloc(widget_cache, GFP_KERNEL);
    if (!w)
        return NULL;

    w->id = id;
    strscpy(w->name, name, sizeof(w->name));
    list_add(&w->list, &widget_list);
    return w;
}

static void destroy_widget(struct widget *w)
{
    list_del(&w->list);
    kmem_cache_free(widget_cache, w);
}

static int __init widget_init(void)
{
    widget_cache = kmem_cache_create("widget_cache",
                                      sizeof(struct widget),
                                      0, SLAB_HWCACHE_ALIGN, NULL);
    if (!widget_cache)
        return -ENOMEM;

    create_widget(1, "alpha");
    create_widget(2, "beta");
    create_widget(3, "gamma");

    pr_info("Created 3 widgets, cache: %s\n",
            kmem_cache_name(widget_cache));

    return 0;
}

static void __exit widget_exit(void)
{
    struct widget *w, *tmp;

    list_for_each_entry_safe(w, tmp, &widget_list, list)
        destroy_widget(w);

    kmem_cache_destroy(widget_cache);
    pr_info("All widgets destroyed\n");
}

module_init(widget_init);
module_exit(widget_exit);
MODULE_LICENSE("GPL");

zsmalloc Allocator

zsmalloc is a specialized slab-like allocator designed specifically for use with zram (compressed RAM block devices). Unlike the general-purpose slab allocators (SLUB/SLOB), zsmalloc is optimized for storing compressed objects under low-memory conditions.

Design Goals

  • No higher-order allocations: zsmalloc never attempts order > 0 page allocations, which are very likely to fail under memory pressure.
  • Minimize fragmentation: Instead of using single pages (which would waste space for objects ≥ PAGE_SIZE/2), zsmalloc links multiple order-0 pages together into a zspage — a chain of physical pages that acts as a single contiguous region.
  • Objects span page boundaries: An object can start on one physical page and end on another within the same zspage.

Key Characteristics

PropertyDetail
Max object sizePAGE_SIZE (typically 4 KiB)
Return typeOpaque unsigned long handle (not a dereferenceable pointer)
Size classes255 classes, with merging of similar classes
Statistics/sys/kernel/debug/zsmalloc/<user>/classes (with CONFIG_ZSMALLOC_STAT)

Why Opaque Handles?

zs_malloc() returns an opaque handle rather than a pointer because zsmalloc does not keep zspages permanently mapped — this avoids consuming precious kernel virtual address space on 32-bit systems. Access to allocated memory must go through zs_map_object() / zs_unmap_object() APIs.

zspage Structure

A zspage is a chain of up to ZSMALLOC_CHAIN_SIZE order-0 physical pages linked via struct page fields. Each zspage belongs to a size class, and the optimal chain length is calculated at pool creation time to maximize space utilization.

/* Simplified zspage layout */
struct zspage {
    unsigned int class_idx;    /* Size class index */
    unsigned int fullness;     /* Fullness group (0-100%) */
    unsigned int objects;      /* Total objects that fit */
    unsigned int inuse;        /* Objects currently allocated */
    /* Linked list of pages in the chain */
    struct page *first_page;
};

Usage Example

#include <linux/zsmalloc.h>

/* Create a zsmalloc pool */
struct zs_pool *pool = zs_create_pool("my_pool", GFP_KERNEL);

/* Allocate an object (returns opaque handle) */
unsigned long handle = zs_malloc(pool, size, GFP_KERNEL);

/* Map the object for access */
void *obj = zs_map_object(pool, handle, ZS_MM_RW);

/* Use obj[0..size-1] */
memcpy(obj, data, size);

/* Unmap when done */
zs_unmap_object(pool, handle);

/* Free */
zs_free(pool, handle, size);

/* Destroy pool */
zs_destroy_pool(pool);

Size Class Merging

zsmalloc merges size classes with similar characteristics (same pages per zspage and similar object counts) to reduce overhead. For example, classes #95–99 may be merged into class #100 if their optimal zspage configurations are similar. The tradeoff is slightly wasted bytes per object versus better overall utilization.

Statistics Interface

With CONFIG_ZSMALLOC_STAT, the file /sys/kernel/debug/zsmalloc/<user>/classes shows per-class statistics:

class size 10% 20% 30% 40% 50% 60% 70% 80% 90% 99% 100% obj_allocated obj_used pages_used pages_per_zspage freeable
 30  512  0  12   4   1   0   1   0   0   1   0    414        3464      3346       433           1       14

The percentage columns show the number of zspages at each fullness level, helping identify fragmentation and utilization issues.


References