What Is the Modern Linux Shrinker API? – Free Linux Device Drivers Training Online

 

Linux Kernel Slab Shrinkers Explained (Modern Shrinker API)
Free Linux Kernel Development Course — Memory Management Module

Every driver in this free Linux kernel development course that owns a custom slab cache has an unwritten obligation: cooperate when the system is running low on memory. That cooperation happens through a slab shrinker. In this lesson we explain what a shrinker actually does, why the registration API you’ll find in most books is now deprecated, and how to register one correctly on a modern kernel.

slab shrinker
memory reclamation
shrinker_register
kswapd
free linux device drivers course
kernel memory pressure

What You Will Learn

  • What a slab shrinker is and why the kernel calls into your driver at all
  • The two callbacks every shrinker must implement, and what each one is responsible for
  • Why the old register_shrinker() call pattern is deprecated
  • How to register a shrinker with the modern, name-based shrinker API
  • How to observe shrinker activity for debugging

Prerequisites

  • Comfort creating a custom slab cache with kmem_cache_create() — covered in the previous lecture of this free Linux kernel development course
  • Basic understanding of virtual memory and why the kernel needs to reclaim memory at all

Why Shrinkers Exist

Caching is a trade-off. Every cache your driver keeps around — including a custom slab cache — is memory that isn’t available for something else. As long as the system has plenty of free RAM this doesn’t matter. But under memory pressure, background kernel threads (commonly named kswapd) actively look for memory to reclaim, and they expect every well-behaved subsystem to offer some of its cached memory back. A shrinker is simply the interface your driver implements so the reclaim machinery can ask, “how much of your cache can you give back right now?”

Where a Shrinker Sits in the Reclaim Path
Memory pressure
detected
→
kswapd walks
registered shrinkers
→
count_objects()
reports how many
can be freed
→
scan_objects()
actually frees them

The Two Callbacks You Must Implement

Callback Responsibility
count_objects() Reports an estimate of how many objects could be freed right now. Returning 0 tells the kernel “don’t bother scanning me this round.”
scan_objects() Does the real work of freeing objects, up to the batch size the kernel requests, and returns how many were actually freed (or a special “stop” value if it could not safely make progress).

Why the Old register_shrinker() Pattern Is Deprecated

If you look at older kernel programming references, you’ll see a pattern where a driver fills in a plain struct shrinker on the stack or as a static variable and passes it directly to register_shrinker(). That approach has been replaced. Recent kernels moved to a dynamically allocated, named shrinker object instead, for two practical reasons:

  • Debuggability — a named shrinker shows up with a readable label under the shrinker debug directory, instead of an anonymous function pointer.
  • Lifetime safety — allocating the shrinker structure through the kernel’s own helper avoids a class of bugs where a shrinker could still be referenced after its owning driver had already started tearing down.
Old style (deprecated) Modern style (current kernels)
Static struct shrinker with callbacks set directly Shrinker allocated dynamically through a dedicated allocation helper
register_shrinker(&my_shrinker) Allocate with a name, set callbacks, then register the object explicitly
unregister_shrinker(&my_shrinker) Free the dynamically allocated shrinker object on teardown

Implementing the Callbacks

Let’s continue with the netbuf_ctx cache from the previous lecture and imagine we keep a simple linked list of objects that are safe to drop under pressure.

static unsigned long netbuf_count_objects(struct shrinker *sh,
                                            struct shrink_control *sc)
{
    unsigned long reclaimable = netbuf_reclaimable_count();

    if (reclaimable == 0)
        return SHRINK_EMPTY;

    return reclaimable;
}

static unsigned long netbuf_scan_objects(struct shrinker *sh,
                                           struct shrink_control *sc)
{
    unsigned long freed = netbuf_release_objects(sc->nr_to_scan);

    return freed ? freed : SHRINK_STOP;
}

Registering the Shrinker (Modern API)

static struct shrinker *netbuf_shrinker;

static int __init netbuf_shrinker_init(void)
{
    netbuf_shrinker = shrinker_alloc(0, "netbuf-demo-cache");
    if (!netbuf_shrinker)
        return -ENOMEM;

    netbuf_shrinker->count_objects = netbuf_count_objects;
    netbuf_shrinker->scan_objects  = netbuf_scan_objects;

    shrinker_register(netbuf_shrinker);

    return 0;
}

static void __exit netbuf_shrinker_exit(void)
{
    shrinker_free(netbuf_shrinker);
}

Notice the readable name string passed to the allocation helper — that name is exactly what shows up when you inspect registered shrinkers for debugging, which we cover next.

Observing Shrinker Activity

To confirm your shrinker is registered and see how the kernel is calling into it, check the shrinker debug interface (requires debugfs mounted):

$ sudo ls /sys/kernel/debug/shrinker/ | grep netbuf

$ sudo cat /sys/kernel/debug/shrinker/*netbuf*/count

You can also artificially trigger memory reclaim to test your callbacks under controlled conditions, rather than waiting for real memory pressure:

$ echo 1 | sudo tee /proc/sys/vm/drop_caches

Tip: log a line at the top of both callbacks during development so you can confirm in dmesg that the kernel is actually invoking them before you trust any deeper logic.

Common Mistakes and Troubleshooting

Mistake Why it hurts you
Using the old static-struct register_shrinker() pattern in new code Deprecated; new kernels expect the allocate-then-register pattern with a debug name.
Doing expensive or blocking work inside count_objects() This callback can be invoked frequently under pressure; it must be cheap and fast.
Forgetting to free the shrinker on module exit Leaves a stale shrinker reference registered against memory your module no longer owns.
Returning 0 from count_objects() when objects exist but are unsafe to free right now Use the dedicated “nothing reclaimable” return value instead of an ordinary zero where the API distinguishes them, so the kernel’s accounting stays accurate.

Best Practices

  • Give every shrinker a clear, unique debug name — you will thank yourself the first time you debug memory pressure on a live system.
  • Keep count_objects() lock-free or very cheap; save the real work for scan_objects().
  • Respect sc->nr_to_scan — never free more than requested in a single call.
  • Always pair shrinker registration with proper cleanup in your module’s exit path.

Performance Considerations

A slow count_objects() callback can be called repeatedly during reclaim storms and directly slow down the very memory pressure relief the kernel is trying to achieve. Keep the count path a simple read of an already-maintained counter rather than something that walks a large data structure.

Security Considerations

Be careful about what your scan_objects() callback frees and when. Freeing objects that are still referenced elsewhere in your driver — just because the kernel asked for memory back — is a fast path to use-after-free bugs. Only release objects your own accounting genuinely marks as safe to drop.

Summary / Key Takeaways

  • A shrinker is how your driver participates in system-wide memory reclamation.
  • count_objects() estimates reclaimable memory; scan_objects() actually frees it.
  • The old static register_shrinker() pattern is deprecated in favor of a named, dynamically allocated shrinker object.
  • Use the shrinker debugfs interface to confirm registration and observe real behaviour.

Frequently Asked Questions

Q1. What is a slab shrinker in simple terms?
It’s a callback interface your driver registers so the kernel can ask it to release some cached memory when the system is under memory pressure.

Q2. Why was the shrinker registration API changed?
Mainly for better debuggability through named shrinkers and safer lifetime handling compared to the old static-struct approach.

Q3. What happens if count_objects() always returns 0?
The kernel will treat your cache as having nothing to reclaim and will skip calling scan_objects() for it.

Q4. Can scan_objects() refuse to free anything?
Yes, it can return a special value indicating it could not make progress right now, for example due to lock contention.

Q5. Do I need a shrinker for every custom slab cache?
Only if your cache can genuinely grow large and hold objects that are safe to discard and later reconstruct or refetch.

Q6. How do I trigger memory pressure for testing?
You can use kernel-provided interfaces such as dropping caches, though real-world testing under actual memory pressure is still recommended before trusting the behaviour.

Conclusion

Slab shrinkers are the missing piece that turns a custom slab cache from “memory that just sits there” into a well-behaved citizen of the kernel’s memory management system. With the modern, named shrinker API you now know how to register cleanly, debug easily, and free memory safely under pressure. This wraps up the custom slab cache mini-module in our free Linux kernel development course — from here, the natural next step is exploring how the page cache and other kernel-wide caches use the very same reclaim philosophy at a larger scale.

Continue the Free Linux Kernel Development Course

More free lessons on Linux kernel programming, device drivers, and embedded systems at EmbeddedPathashala.

Explore All Free Courses

 

Leave a Reply

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