How Does SLUB Allocate kmalloc() Memory? – Linux Device Drivers Coaching in Hyderabad

Linux Kernel Slab Allocator: Why kmalloc() Wastes Memory
A free, beginner-friendly lecture on kernel memory allocation, internal fragmentation, and the ksize() API — updated for modern SLUB-only kernels
Level
Beginner–Intermediate
Read Time
~14 minutes
Kernel Version
6.8+ (SLUB only)

If you have ever written a kernel module and called kmalloc(), you have probably assumed that asking for, say, 100 bytes gives you exactly 100 bytes. It doesn’t. The Linux kernel slab allocator almost always hands back more memory than you requested, and understanding why is one of the most practical lessons in kernel memory management. In this free lecture from EmbeddedPathashala’s Linux kernel programming course, we’ll break down how the Linux kernel slab allocator decides how much memory to actually give you, how to measure that “hidden” extra memory with ksize(), and what changed in recent kernels that makes this topic even more relevant today.

What You Will Learn

Slab allocator basics
kmalloc() internals
SLUB size classes
ksize() API
Internal fragmentation
Kernel module example
Performance tuning
Security implications

Prerequisites

  • Basic C programming knowledge
  • A working Linux kernel module build setup (kernel headers installed)
  • Familiarity with writing and inserting a simple “Hello World” kernel module
  • Comfort reading dmesg output

What Is the Linux Kernel Slab Allocator?

The kernel needs memory constantly — for file descriptors, network buffers, process structures, and thousands of other small objects. Handing out memory one full page (4 KB on most systems) at a time for a 20-byte structure would waste enormous amounts of RAM. The Linux kernel slab allocator solves this by carving full pages into smaller, fixed-size chunks and managing pools (“caches”) of those chunks so the kernel can allocate and free small objects quickly and efficiently.

Think of it like a bakery that only sells bread in whole loaves, but keeps a rack of pre-cut slices in fixed sizes (small, medium, large) for customers who only need a slice. You don’t get exactly the gram weight you asked for — you get the smallest slice that’s big enough to cover your request.

Memory Allocation Layers in the Kernel
Kernel Subsystem Requests Memory
↓
Slab Allocator
(small objects, kmalloc)
Page / Buddy Allocator
(page-sized+ blocks)
↓
Physical RAM (Pages)

Page Allocator vs Slab Allocator: When the Kernel Uses Each

As a rule of thumb, when an allocation request is close to or larger than a full page and is a near power-of-two size, the kernel tends to satisfy it straight from the page allocator. For everything smaller than a page — which describes the vast majority of kernel allocations — the slab layer, accessed through kmalloc(), is the right tool.

Allocation Size Recommended API Backing Layer
A few bytes to ~8 KB kmalloc() / kzalloc() Slab allocator (SLUB)
Multiple whole pages __get_free_pages() / alloc_pages() Page (buddy) allocator
Large, possibly non-contiguous vmalloc() Virtual memory allocator

Why kmalloc() Never Gives You Exactly What You Asked For

Here’s the key idea behind every Linux kernel slab allocator discussion: to keep allocation and freeing fast, the slab layer doesn’t create a custom-sized chunk for every request. Instead, it maintains a fixed ladder of “kmalloc caches,” each holding objects of one particular size. A typical x86_64 system exposes caches roughly following this pattern (exact sizes can vary slightly by kernel configuration):

Typical kmalloc Size Ladder (SLUB)
8
16
32
64
96
128
192
256
512
1024
2048
4096
8192

A request is always rounded up to the next rung on this ladder.

If your driver asks for 20 bytes, there is no “20-byte cache,” so the allocator rounds up to the next available rung — typically 32 bytes on most distro kernels. You receive a valid, usable 32-byte block, but 12 of those bytes are unused padding. Multiply that pattern across millions of allocations happening every second on a busy server, and you can see why understanding this rounding behaviour matters for both memory efficiency and cache-line-friendly design.

Kernel Update: SLUB Is Now the Only Slab Allocator

Older kernel textbooks and tutorials often describe three competing slab implementations — SLAB, SLOB, and SLUB — and explain how to choose between them. That advice is now outdated. The kernel community has spent the last few release cycles consolidating everything onto a single implementation:

Allocator Status
SLOB (low-memory systems) Removed in kernel 6.4
SLAB (the original general-purpose allocator) Deprecated in 6.5, removed by 6.8
SLUB (unqueued slab allocator) The sole allocator in current mainline kernels

In practical terms, this means every modern kmalloc() call you make today is serviced exclusively by SLUB. You no longer need to worry about CONFIG_SLAB vs CONFIG_SLUB build options — there’s only one road left, which actually makes the Linux kernel slab allocator easier to reason about than it was a few years ago.

Measuring Real Allocation Size With ksize()

So how do you actually see this rounding happen instead of just taking it on faith? The kernel gives you a small diagnostic API for exactly this purpose:

size_t ksize(const void *objp);

Pass it a pointer previously returned by kmalloc(), kzalloc(), or a similar slab-backed allocation function, and it returns the true usable size of that block — not the size you asked for, but the size of the cache slot you were actually given. A couple of important rules:

  • ksize() only works on live, slab-allocated memory. Calling it after kfree(), or on memory from the page allocator, is undefined behaviour.
  • The value it returns is safe to use — you are allowed to write into that extra padding — but doing so on purpose is fragile and not recommended for normal drivers.
  • It’s primarily a debugging and instrumentation tool, not something you’d call in a performance-critical hot path.

Hands-On: Writing a Kernel Module to Observe Slab Wastage

Let’s build a small original example. This module allocates memory in increasing steps and prints how much was actually reserved versus how much we asked for. Create a directory, place the following in slabwaste.c:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>

#define STEP_BYTES   200
#define MAX_REQUEST  4000

static int __init slabwaste_init(void)
{
    size_t requested = 50;
    void *block;
    size_t granted, extra, waste_pct;

    pr_info("slabwaste: requested : granted : extra : waste%%\n");

    while (requested <= MAX_REQUEST) {
        block = kmalloc(requested, GFP_KERNEL);
        if (!block) {
            pr_err("slabwaste: allocation failed at %zu bytes\n", requested);
            return -ENOMEM;
        }

        granted   = ksize(block);
        extra     = granted - requested;
        waste_pct = (extra * 100) / requested;

        pr_info("slabwaste: %6zu : %6zu : %5zu : %3zu%%\n",
                requested, granted, extra, waste_pct);

        kfree(block);
        requested += STEP_BYTES;
    }

    return 0;
}

static void __exit slabwaste_exit(void)
{
    pr_info("slabwaste: module removed\n");
}

module_init(slabwaste_init);
module_exit(slabwaste_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Demonstrates kmalloc rounding using ksize()");

Build it with a minimal Makefile:

obj-m += slabwaste.o

all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

Load it and watch the output:

sudo insmod slabwaste.ko
dmesg | tail -25
sudo rmmod slabwaste

Interpreting the Output: Reading Wastage Percentages

When you run the module above, you’ll see a pattern like this (actual numbers vary by kernel build and architecture, so treat this as an illustration of the trend rather than a fixed table):

Requested Granted Extra Bytes Waste %
50 64 14 28%
250 256 6 2%
450 512 62 14%
650 1024 374 58%
1050 2048 998 95%

Notice the pattern: waste is lowest right after you cross into a new cache size, and climbs steadily until you jump to the next rung, where it spikes again. This “sawtooth” behaviour is inherent to any allocator that uses fixed size classes, and it’s the single most important mental model to carry away from this lecture on the Linux kernel slab allocator.

Real-World Use Cases

  • Driver development: Sizing DMA-safe buffers or packet structures so they land cleanly on a cache boundary instead of just under one.
  • Embedded systems: On memory-constrained boards, knowing the real allocation size helps you budget RAM accurately instead of relying on the requested size.
  • Kernel debugging: Diagnosing unexpected memory pressure or slab cache growth using tools like /proc/slabinfo alongside ksize()-based instrumentation.
  • Custom kmem_cache design: Deciding when a subsystem should create its own dedicated kmem_cache instead of relying on the generic kmalloc caches, precisely to avoid this rounding waste for very high-frequency allocations.

Common Mistakes and Troubleshooting Tips

  • Calling ksize() on freed memory. This is a use-after-free bug. Always call it before kfree().
  • Calling ksize() on page-allocator memory. It only understands slab objects; using it on alloc_pages() results is invalid.
  • Assuming exact sizes. Never write code that depends on kmalloc(n) returning exactly n usable bytes for anything beyond what you requested — treat the extra space as accidental, not guaranteed.
  • Ignoring GFP flags. Passing the wrong allocation flag (e.g. using GFP_KERNEL inside an interrupt handler) causes far more serious bugs than any rounding waste ever will — always match the flag to the calling context.
  • Not checking the return value. Like any allocator, kmalloc() can return NULL under memory pressure; always check before dereferencing.

Best Practices

  • Prefer kzalloc() over kmalloc() plus manual zeroing when you need zero-initialised memory — it’s clearer and avoids uninitialised-memory bugs.
  • For structures allocated extremely frequently (thousands of times per second), consider a dedicated kmem_cache created with kmem_cache_create() so the size matches exactly and allocation/free performance improves.
  • Use ksize() during development and profiling, not as a permanent part of production allocation logic.
  • Free every successful allocation on every exit path, including error paths, to avoid kernel memory leaks.

Performance Considerations

The size-class rounding that causes “wastage” is actually a deliberate performance trade-off. By keeping a small, fixed number of object sizes, SLUB avoids the bookkeeping overhead of tracking arbitrary sizes, keeps allocation and free operations close to O(1), and improves CPU cache locality since same-sized objects sit together. In other words, the extra bytes you see are the price paid for speed — and for the vast majority of kernel code, that trade is worth it.

Security Considerations

The padding bytes revealed by ksize() are a well-known area of interest in kernel exploitation research, since heap-adjacent overflows can sometimes corrupt neighbouring objects within the same size-class slab. This is one reason modern kernels harden the slab layer with features such as randomized freelists and hardened usercopy checks. As a driver author, the practical takeaway is simple: never intentionally write beyond the size you requested, even though ksize() shows more room is technically available.

Summary / Key Takeaways

  • The Linux kernel slab allocator serves small, sub-page allocations through kmalloc(), while the page allocator handles page-sized-and-larger requests.
  • kmalloc() always rounds your request up to the nearest fixed cache size, which creates predictable “wastage.”
  • ksize() lets you measure the true allocated size of any live slab object.
  • As of recent mainline kernels (6.8 and later), SLUB is the only slab allocator left in the tree — SLOB and SLAB have both been removed.
  • This rounding behaviour is a performance feature, not a bug, and understanding it helps you write more memory-efficient drivers.

Conclusion

Understanding the Linux kernel slab allocator is one of those foundational skills that quietly improves everything else you do in kernel and driver development. Once you know why kmalloc(20) can silently hand you 32 bytes, you’ll write more deliberate allocation code, size your structures more thoughtfully, and read kernel memory diagnostics with far more confidence. Try the hands-on module above on your own machine, experiment with different request sizes, and see the sawtooth wastage pattern for yourself — it’s one of the best ways to internalise how kernel memory management really works.

Frequently Asked Questions

1. What is the Linux kernel slab allocator used for?
It efficiently manages small, sub-page memory allocations inside the kernel, avoiding the waste of handing out a full page for a tiny request.

2. Why does kmalloc() return more memory than I asked for?
Because the slab allocator only maintains a fixed set of cache sizes; your request is rounded up to the nearest available size.

3. Is SLAB still used in modern Linux kernels?
No. SLAB was deprecated in kernel 6.5 and removed by kernel 6.8. SLUB is now the only general-purpose slab allocator in mainline Linux.

4. Can I safely use the extra memory that ksize() reveals?
Technically the memory is valid to write to, but relying on it intentionally is considered fragile and is not recommended practice.

5. What happens if I call ksize() on freed memory?
It results in undefined behaviour, similar to any other use-after-free access. Always call it before freeing the block.

6. Should I always use kzalloc() instead of kmalloc()?
Use kzalloc() whenever you need zero-initialised memory; it is clearer and safer than allocating and manually zeroing afterward.

7. How can I avoid allocation wastage for very frequent allocations?
Create a dedicated kmem_cache sized exactly to your structure using kmem_cache_create() instead of relying on the generic kmalloc caches.

8. Is this tutorial part of a full free course?
Yes — this lecture is part of EmbeddedPathashala’s free Linux kernel programming and Linux device drivers course.

Continue Learning for Free

This lecture is part of EmbeddedPathashala’s free Linux kernel programming course, covering memory management, device drivers, and embedded Linux from first principles.

Explore the Free Course

2 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *