← Previous Lecture | Next Lecture →
If you have ever wondered how the Linux kernel knows exactly when it is safe to free a task_struct, a network buffer, or a driver’s private data structure, the answer almost always involves the refcount_t api linux kernel developers rely on for safe reference counting. In this lecture of our free Linux kernel development course, you will learn how refcount_t works, how it differs from the older atomic_t interface, and how to use it correctly inside your own kernel modules and device drivers on kernel 6.x.
What You Will Learn
- Why raw integers are unsafe for tracking object lifetime in the kernel
- The complete
atomic_tvsrefcount_tAPI comparison, function by function - How real kernel subsystems use reference counting to decide when to free memory
- The valid value range for
refcount_tand what happens when you break it - How the kernel’s fault-injection and dump-test tooling helps validate refcounting code
- A hands-on, original misc-driver example using the refcount_t api linux kernel pattern safely
- Common mistakes, best practices, performance, and security considerations
Prerequisites
- Comfort writing a basic Linux kernel module (module_init/module_exit)
- Familiarity with
atomic_tbasics from the previous lecture in this free Linux device drivers course - A kernel 6.x build environment (any recent distribution kernel or a custom-built kernel works fine)
Why Plain Integers Cannot Track Object Lifetime
Many kernel objects — a task, an open file, a network device, a piece of driver-allocated memory — are shared between multiple threads of execution. Before anyone can safely free that object, the kernel needs to know that nobody else is still using it. A plain int counter looks tempting, but incrementing or decrementing an ordinary integer is not a single CPU instruction on most architectures; it is a read, a modify, and a write. On a multi-core system, two CPUs can perform this sequence at the same time and silently lose an update, which either frees an object that is still in use (a dangerous use-after-free) or leaks memory forever. This is exactly the gap that the refcount_t api linux kernel tooling closes.
Reads counter = 1
Computes 1 + 1 = 2
Writes counter = 2
Reads counter = 1
Computes 1 + 1 = 2
Writes counter = 2
Result: counter ends at 2 instead of 3 — one increment is lost, and an object may be freed too early.
atomic_t vs refcount_t: Full API Comparison
The kernel actually offers two related but distinct tools here. atomic_t gives you a generic, lock-free 32-bit integer for any counting job. refcount_t is a purpose-built wrapper on top of the same atomic primitives, designed specifically for object lifetime tracking, with built-in protection against underflow, overflow, and use-after-free bugs. The table below lists the operations you will use most often when working with the refcount_t api linux kernel subsystems expose.
| Purpose | atomic_t | refcount_t |
|---|---|---|
| Header file | <linux/atomic.h> | <linux/refcount.h> |
| Declare + init | ATOMIC_INIT(1) | REFCOUNT_INIT(1) |
| Read value | atomic_read() | refcount_read() |
| Set value | atomic_set() | refcount_set() |
| Increment | atomic_inc() | refcount_inc() |
| Decrement | atomic_dec() | refcount_dec() |
| Add / Subtract | atomic_add() / atomic_sub() | refcount_add() / refcount_sub() |
| Add unless zero | atomic_add_return() | refcount_add_not_zero() |
| Test-and-free on drop to zero | atomic_sub_return() == 0 | refcount_sub_and_test() |
| Decrement + free check | manual, no built-in help | refcount_dec_and_test() |
| Decrement + take a lock atomically | not provided | refcount_dec_and_lock(), refcount_dec_and_mutex_lock() |
Notice the last two rows. refcount_t deliberately does not give you a raw “subtract and return the new value” primitive the way atomic_t does. Instead it forces you to ask a yes/no question — “did this just become the last reference?” — through refcount_dec_and_test(). That design choice removes an entire class of bugs where a driver author reads the returned count and makes the wrong decision about it.
How the Kernel Uses refcount_t to Decide When to Free an Object
To make the refcount_t api linux kernel concept concrete, imagine any kernel object that can be referenced from more than one place at a time — a process, a file, a networking socket, or your own driver’s private context structure. The pattern is always the same three steps:
- Initialize the refcount to 1 when the object is created.
- Call a “get” function every time a new part of the kernel starts holding a pointer to the object — this calls
refcount_inc()internally. - Call a “put” function every time a holder is done with the object — this calls
refcount_dec_and_test(), and only the caller that sees it return true is responsible for actually freeing the object.
refcount = 1refcount = 2refcount = 1refcount = 0Object freed
Original Example: A Reference-Counted Object in a Misc Driver
Below is an original, self-contained example (not taken from any book or external source) showing this exact pattern inside a simple misc character device. The driver wraps a small heap-allocated context structure in a refcount_t, growing and shrinking its lifetime as user-space processes open and close the device node.
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/refcount.h>
#include <linux/slab.h>
#include <linux/fs.h>
struct ep_object {
refcount_t refcnt;
int payload;
};
static struct ep_object *ep_obj;
/* "get" - called whenever a new holder needs the object */
static struct ep_object *ep_object_get(struct ep_object *obj)
{
refcount_inc(&obj->refcnt);
return obj;
}
/* "put" - called whenever a holder is finished with the object */
static void ep_object_put(struct ep_object *obj)
{
if (refcount_dec_and_test(&obj->refcnt)) {
pr_info("ep_refobj: last reference dropped, freeing object\n");
kfree(obj);
}
}
static int ep_refobj_open(struct inode *inode, struct file *filp)
{
filp->private_data = ep_object_get(ep_obj);
pr_info("ep_refobj: open(), refcount now %u\n",
refcount_read(&ep_obj->refcnt));
return 0;
}
static int ep_refobj_release(struct inode *inode, struct file *filp)
{
ep_object_put(filp->private_data);
return 0;
}
static const struct file_operations ep_refobj_fops = {
.owner = THIS_MODULE,
.open = ep_refobj_open,
.release = ep_refobj_release,
};
static struct miscdevice ep_refobj_miscdev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "ep_refobj",
.fops = &ep_refobj_fops,
};
static int __init ep_refobj_init(void)
{
int ret;
ep_obj = kzalloc(sizeof(*ep_obj), GFP_KERNEL);
if (!ep_obj)
return -ENOMEM;
refcount_set(&ep_obj->refcnt, 1);
ret = misc_register(&ep_refobj_miscdev);
if (ret) {
kfree(ep_obj);
return ret;
}
pr_info("ep_refobj: loaded, initial refcount 1\n");
return 0;
}
static void __exit ep_refobj_exit(void)
{
misc_deregister(&ep_refobj_miscdev);
ep_object_put(ep_obj); /* drop the module's own initial reference */
}
module_init(ep_refobj_init);
module_exit(ep_refobj_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("EmbeddedPathashala original refcount_t demo driver");
Every open() call takes a reference with ep_object_get(), and every release() call drops it with ep_object_put(). The object is only freed once every holder — including the module’s own bootstrap reference dropped in ep_refobj_exit() — has released its hold. This mirrors, in a simplified original form, the same “get on acquire, put on release” idiom that real kernel subsystems use for tasks, files, and network structures.
The Valid refcount_t Range and Why It Matters for Security
Unlike atomic_t, which happily lets you set a counter to any signed 32-bit value including zero or negative numbers, refcount_t is deliberately restrictive. A healthy, in-use object must always sit in the range of one up to just under the unsigned integer maximum. On modern kernels (6.x), full range-and-saturation checking is compiled in for every architecture, so any attempt to increment a refcount that has already reached zero, or to decrement it below zero, does not silently wrap around — it triggers a kernel WARN() and the counter is “saturated” (pinned) at its current safe value instead of corrupting further.
This matters more than it might first appear. Historically, integer-overflow bugs in reference counting were a well-known class of Linux kernel security vulnerability: an attacker who could drive a raw atomic_t counter past its maximum value could wrap it back to a small number, trigger a premature free, and then exploit the resulting use-after-free. The saturation and warning behaviour built into the refcount_t api linux kernel hardening work exists specifically to turn that class of silent, exploitable overflow into a loud, easily-caught bug during testing.
Every get() is matched by a put(). Counter moves 1 → 2 → 1 → 0. Object freed exactly once.
An extra put() is called after the count already hit zero. The kernel fires WARN(), prints a call trace, and refuses to let the counter go negative.
Validating Your Refcounting Code with the Kernel’s Fault-Injection Tooling
Because refcounting bugs are timing-dependent and easy to miss during normal testing, the kernel ships its own internal test infrastructure for deliberately exercising these edge cases, as part of its wider fault-injection framework. This lets kernel developers intentionally simulate an out-of-range refcount operation in a controlled way and confirm that the WARN() and saturation logic actually fire as expected, rather than waiting to discover a bug in production. As a driver author, the practical takeaway is simpler: always exercise your driver’s open/close, get/put, and error paths under stress and under a debug kernel build before shipping, so any refcounting mistake surfaces as a clear warning during development rather than a mysterious crash later.
Common Mistakes When Using refcount_t
| Mistake | Why it’s a problem |
|---|---|
Calling refcount_inc() on a counter that might already be zero | The object may already be scheduled for freeing; you need refcount_inc_not_zero() in racy lookup paths |
Ignoring the boolean return of refcount_dec_and_test() | You will either free the object twice or never free it at all |
Mixing plain integer math with refcount_t fields | Bypasses all of the built-in overflow/underflow protection |
| Freeing an object without dropping its own bootstrap reference | Leaks memory, since the count never reaches zero |
Best Practices
- Always initialize with
REFCOUNT_INIT(1)orrefcount_set(&counter, 1)at creation time, never zero. - Use
refcount_dec_and_test()(or the lock-combined variants) instead of a manual read-then-decrement sequence. - In lookup paths where the object might already be on its way out, use
refcount_inc_not_zero()so you never resurrect a dying object. - Pair every “get” with exactly one “put” — treat this as strictly as you would treat matching a lock with its unlock.
- Test your driver’s error and cleanup paths on a debug kernel so a misuse triggers a visible
WARN()during development.
Performance Considerations
refcount_t is built directly on the same lock-free atomic CPU instructions as atomic_t, so on the fast path (a simple get or put with no contention) the extra range checking adds only a small, effectively negligible overhead compared to a raw atomic operation. The real performance win comes from correctness: a driver that gets refcounting wrong doesn’t just crash — it can force costly debugging sessions and, in the worst case, security patches, both of which are far more expensive than the tiny CPU cost of the safety checks.
Security Considerations
As covered above, unchecked reference-counting overflow has historically been a real-world source of Linux kernel privilege-escalation vulnerabilities. Preferring refcount_t over a raw atomic_t for any object-lifetime counter is now considered a kernel hardening best practice, precisely because it converts a potentially silent, exploitable overflow into a loud WARN() that a developer or a fuzzing pipeline will catch quickly.
Summary / Key Takeaways
refcount_tis a specialised wrapper around atomic operations, purpose-built for tracking object lifetime.- It restricts values to a safe range and saturates instead of overflowing, triggering a
WARN()on misuse. - The “get” and “put” pattern, backed by
refcount_inc()andrefcount_dec_and_test(), is the standard idiom across the kernel. - Preferring
refcount_tover rawatomic_tfor lifetime counters closes off a known class of security bugs.
Conclusion
The refcount_t api linux kernel developers reach for today is a direct response to years of hard-earned lessons about reference-counting bugs in production systems. By replacing raw integer math with a purpose-built type that enforces a safe range and fails loudly rather than silently, the kernel eliminates an entire category of use-after-free and memory-leak bugs at the API level. Everything covered here — atomic_t, its 32-bit limitation — leads naturally into the next question: what if you need a counter larger than 32 bits? That is exactly where 64-bit atomic operations come in, which is the subject of our next lecture in this free Linux kernel development course.
FAQ
Q1. What is the difference between atomic_t and refcount_t in the Linux kernel?
atomic_t is a general-purpose lock-free integer for any counting task, while refcount_t is a specialised type built on top of it, restricted to a safe non-negative range and designed specifically for tracking object lifetime.
Q2. Why can’t I just use a plain int for reference counting?
A plain int’s increment or decrement is not a single atomic CPU operation, so two CPUs updating it at the same time can lose an update, causing a premature free or a memory leak.
Q3. What does refcount_dec_and_test() actually do?
It atomically decrements the counter and returns true only if that decrement brought the count to zero, so the caller knows it is safe — and its responsibility — to free the object.
Q4. What happens if I decrement a refcount_t below zero?
On kernel 6.x, this triggers a WARN() and the counter saturates instead of wrapping around, which prevents the underflow from being silently exploitable.
Q5. Is refcount_t slower than atomic_t?
The overhead is negligible on the normal fast path since both are built on the same underlying atomic CPU instructions; the extra range checking is a small price for closing off a serious class of bugs.
Q6. When should I use refcount_inc_not_zero() instead of refcount_inc()?
Use it whenever you look up an object that another thread might already be freeing, so you never resurrect an object whose count has already reached zero.
Q7. Can refcount_t be used on 32-bit and 64-bit kernels?
Yes, refcount_t works identically on both 32-bit and 64-bit kernel builds, unlike the older atomic_t which is limited to 32-bit counting.
Q8. Where is refcount_t used inside the real kernel?
It is used throughout the kernel wherever an object’s lifetime needs tracking across multiple holders, such as process, file, and per-user resource structures.

2 Comments