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.
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?”
detected
registered shrinkers
reports how many
can be freed
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 forscan_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.
More free lessons on Linux kernel programming, device drivers, and embedded systems at EmbeddedPathashala.
