refcount_t API in Linux Kernel: The Complete Reference Counting Guide-Linux Device Driver Training in Hyderabad

← Previous Lecture  |  Next Lecture →

refcount_t API in Linux Kernel: The Complete Reference Counting Guide
Free Linux Kernel Development Course · Kernel Synchronization Part 2 · Updated for Kernel 6.x

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_t vs refcount_t API comparison, function by function
  • How real kernel subsystems use reference counting to decide when to free memory
  • The valid value range for refcount_t and 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_t basics 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.

Why a Plain Counter Fails Under Concurrency
CPU 0

Reads counter = 1
Computes 1 + 1 = 2
Writes counter = 2

CPU 1 (same instant)

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.

atomic_t vs refcount_t — Operation Reference
Purpose atomic_t refcount_t
Header file<linux/atomic.h><linux/refcount.h>
Declare + initATOMIC_INIT(1)REFCOUNT_INIT(1)
Read valueatomic_read()refcount_read()
Set valueatomic_set()refcount_set()
Incrementatomic_inc()refcount_inc()
Decrementatomic_dec()refcount_dec()
Add / Subtractatomic_add() / atomic_sub()refcount_add() / refcount_sub()
Add unless zeroatomic_add_return()refcount_add_not_zero()
Test-and-free on drop to zeroatomic_sub_return() == 0refcount_sub_and_test()
Decrement + free checkmanual, no built-in helprefcount_dec_and_test()
Decrement + take a lock atomicallynot providedrefcount_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:

  1. Initialize the refcount to 1 when the object is created.
  2. Call a “get” function every time a new part of the kernel starts holding a pointer to the object — this calls refcount_inc() internally.
  3. 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.
Reference-Counted Object Lifetime
Object created
refcount = 1
→
Holder A calls get()
refcount = 2
→
Holder A calls put()
refcount = 1
→
Last holder calls put()
refcount = 0
Object 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.

What Happens When You Misuse refcount_t
Correct usage

Every get() is matched by a put(). Counter moves 1 → 2 → 1 → 0. Object freed exactly once.

Incorrect usage

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

MistakeWhy it’s a problem
Calling refcount_inc() on a counter that might already be zeroThe 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 fieldsBypasses all of the built-in overflow/underflow protection
Freeing an object without dropping its own bootstrap referenceLeaks memory, since the count never reaches zero

Best Practices

  • Always initialize with REFCOUNT_INIT(1) or refcount_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_t is 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() and refcount_dec_and_test(), is the standard idiom across the kernel.
  • Preferring refcount_t over raw atomic_t for 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.

free linux kernel development course free linux device drivers course free embedded systems course refcount_t api linux kernel

← Previous Lecture  |  Next Lecture →

2 Comments

Leave a Reply

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