Skip to main content

Command Palette

Search for a command to run...

Understanding atomic_t vs refcount_t in the Linux Kernel

Updated
16 min readView as Markdown
Understanding atomic_t vs refcount_t in the Linux Kernel

atomic_t vs refcount_t in the Linux Kernel: Counters, References, and Object Lifetime

Introduction

When developing Linux kernel code, counters and object lifetime management can look deceptively similar.

Both may involve operations such as:

increment();
decrement();

But they solve fundamentally different problems.

Two important Linux kernel primitives are:

  • atomic_t

  • refcount_t

The distinction is simple:

atomic_t is primarily for atomic integer operations.

refcount_t is specifically for reference counting and object lifetime management.

Understanding this distinction becomes increasingly important when writing kernel modules that operate in concurrent environments.


1. The Core Idea

A kernel object can contain both an atomic counter and a reference counter:

struct demo_object {
    atomic_t  counter;
    refcount_t refs;
};

Although both fields contain numeric values, their meaning is different.

Field Purpose Typical Question
atomic_t counter Counts operations or events "How many operations occurred?"
refcount_t refs Tracks object references "How many users still reference this object?"

This semantic difference is the foundation of the entire demonstration.


2. What Is atomic_t?

atomic_t provides atomic operations on an integer value.

Common kernel APIs include:

API Purpose
atomic_set() Initialize or assign a value
atomic_read() Read the current value
atomic_inc() Increment the value
atomic_dec() Decrement the value
atomic_add() Add a value

For example:

atomic_inc(&obj->counter);

This performs an atomic increment of the counter.

The important word is:

atomic

The operation is designed for concurrent execution contexts so that the particular atomic update is performed as an atomic operation.

However, this does not mean that every operation involving the object automatically becomes thread-safe.


3. Typical Uses of atomic_t

atomic_t is useful when the value represents activity or statistics.

Examples include:

Example Meaning
Request counter Number of processed requests
Packet counter Number of processed packets
Event counter Number of generated events
Operation counter Number of operations performed
Statistics Runtime kernel statistics

For example:

atomic_inc(&obj->counter);

could mean:

"Another request has been processed."

The counter represents activity, not ownership.


4. What Is refcount_t?

refcount_t is intended for reference counting.

Instead of asking:

How many operations happened?

it asks:

How many references currently keep this object alive?

Common APIs include:

API Purpose
refcount_set() Initialize the reference count
refcount_read() Read the reference count
refcount_inc() Acquire a reference
refcount_dec() Release a reference
refcount_dec_and_test() Release a reference and test for zero

The key concept is object lifetime.


5. Reference Counting Lifecycle

Consider an object that starts with one reference.

Object created
      |
      v
refs = 1
      |
      | another user obtains reference
      v
refs = 2
      |
      | one user releases reference
      v
refs = 1
      |
      | final reference released
      v
refs = 0
      |
      v
Object can be freed

The critical transition is:

1 -> 0

When the final reference is released, the object may become eligible for cleanup.


6. atomic_t vs refcount_t

These primitives should not be treated as interchangeable.

atomic_t refcount_t
General-purpose atomic counter Reference-counting primitive
Counts operations/events Tracks object references
Useful for statistics Used for object lifetime
atomic_inc() refcount_inc()
atomic_dec() refcount_dec()
atomic_add() refcount_dec_and_test()

A useful mental model is:

Question Appropriate Type
How many requests happened? atomic_t
How many packets were processed? atomic_t
How many events occurred? atomic_t
How many active references exist? refcount_t
Can this object be freed yet? refcount_t

The most important distinction is:

                 What does the number mean?
                           |
             +-------------+-------------+
             |                           |
             v                           v
       Activity/Event              Object Ownership
             |                           |
             v                           v
         atomic_t                    refcount_t

7. Demonstration 1: Basic atomic_t

The first demonstration treats atomic_t as a simple operation counter.

The counter starts at zero:

0

Five increments produce:

0 -> 1 -> 2 -> 3 -> 4 -> 5

An additional atomic addition of 10 produces:

5 -> 15

A decrement then produces:

15 -> 14

The important concept is not the final number.

The important concept is what the number represents.

Here it represents:

Operations / Activity

It does not determine whether an underlying object is still alive.


8. Demonstration 2: Basic refcount_t

Now consider a reference counter.

The reference count starts with one reference:

refs = 1

That initial reference represents ownership of the object.

When another user needs the object:

1 -> 2

When that user finishes:

2 -> 1

The object remains alive because one reference still exists.

Eventually, when the final reference is released:

1 -> 0

the object can reach its cleanup state.

The important idea is:

refcount_t
    |
    v
Object lifetime
    |
    v
Can the object still be safely used?

9. Demonstration 3: Using Both Together

A kernel object can contain both an operation counter and a reference count.

For example:

struct demo_object {
    atomic_t   counter;
    refcount_t refs;
};

Their meanings are independent:

Member Meaning
counter Number of operations
refs Number of active references

Suppose the object is in this state:

State Value
Operations performed 5
Active references 2

There is no requirement for these values to match.

For example:

Operations = 5
References = 2

is perfectly meaningful.

Five operations do not imply five references.

Similarly:

References = 2

does not imply that exactly two operations have occurred.

They represent different aspects of the object's state.


10. Demonstration 4: Requests and Object Lifetime

Consider a kernel object that processes requests.

Every request can increment an operation counter:

atomic_inc(&object->counter);

The counter might evolve as follows:

Request Counter
First request 1
Second request 2
Third request 3

At the same time, another user may acquire a reference to the object.

This produces two independent forms of tracking:

Tracking Primitive
Request activity atomic_t
Object ownership refcount_t

Conceptually:

                  Kernel Object
                       |
             +---------+---------+
             |                   |
             v                   v
      Operation activity     Object lifetime
             |                   |
             v                   v
         atomic_t            refcount_t
             |                   |
             v                   v
       "What happened?"     "Who still needs it?"

This separation of responsibilities is important in concurrent kernel programming.


11. Demonstration 5: Multiple References

Suppose an object starts with:

refs = 1

Three additional references are acquired:

1 -> 2 -> 3 -> 4

The reference count is now:

refs = 4

Each additional reference represents another entity that currently needs the object to remain alive.

Those references must eventually be released.

After releasing the three temporary references:

4 -> 3 -> 2 -> 1

The original reference remains.

Finally, when that last reference is released:

1 -> 0

the object can be cleaned up.


12. The Reference Counting Rule

One of the most important rules when working with reference counting is:

Every acquired reference must eventually be released.

Conceptually:

Operation Effect
Acquire reference refs + 1
Release reference refs - 1
Final release refs == 0
Zero references Object may be freed

The basic lifecycle is:

Acquire
   |
   v
refs + 1
   |
   v
Use object
   |
   v
Release
   |
   v
refs - 1
   |
   +-------------------+
   |                   |
   | refs > 0          | refs == 0
   |                   |
   v                   v
Object remains       Cleanup
alive

An unbalanced reference can prevent an object from reaching its cleanup state.

This is one reason reference-counting bugs can be difficult to diagnose.


13. Object Lifetime

The demonstration dynamically allocates its object.

The lifecycle can be understood as:

Stage Action
Creation Allocate object
Initialization Initialize counter and references
Usage Perform operations
Reference acquisition Increment reference count
Reference release Decrement reference count
Final release Reference count reaches zero
Cleanup Free object

The overall lifecycle looks like this:

+----------------------+
| Object allocated     |
+----------+-----------+
           |
           v
+----------------------+
| Initialize counters  |
| and references       |
+----------+-----------+
           |
           v
+----------------------+
| Object is usable     |
+----------+-----------+
           |
           v
+----------------------+
| References exist     |
+----------+-----------+
           |
           v
+----------------------+
| References released  |
+----------+-----------+
           |
           v
+----------------------+
| refcount reaches 0   |
+----------+-----------+
           |
           v
+----------------------+
| Object can be freed  |
+----------------------+

The critical relationship is:

Object allocated
       |
       v
References exist
       |
       v
Object is usable
       |
       v
Final reference released
       |
       v
Reference count reaches zero
       |
       v
Object can be freed

14. Why Not Use atomic_t for Everything?

At first glance, it may seem reasonable to use atomic_t for both operation counters and object references.

For example:

atomic_t counter;

But compare it with:

refcount_t refs;

The second declaration communicates much more information.

atomic_t counter;
        |
        +--> This is an atomic integer counter.


refcount_t refs;
        |
        +--> This represents references to an object.

Reference counting has semantic requirements related to ownership and lifetime.

Using the appropriate kernel primitive therefore makes the code:

  • easier to understand

  • clearer to maintain

  • more explicit about ownership

  • better aligned with the intended kernel abstraction

The type itself communicates intent.


15. Atomic Does Not Mean the Entire Program Is Thread-Safe

This is an important concept.

An atomic operation does not automatically make an entire algorithm thread-safe.

Suppose a structure contains several fields:

struct demo_object {
    atomic_t counter;
    int state;
    void *data;
};

Making this operation atomic:

atomic_inc(&obj->counter);

does not automatically protect:

obj->state
obj->data

from concurrent access.

Atomicity applies to the particular atomic operation.

A larger algorithm may still require synchronization.


16. Kernel Synchronization Primitives

Linux provides multiple synchronization mechanisms, each intended for different problems.

Mechanism Typical Purpose
atomic_t Atomic counter operations
Spinlock Short critical sections
Mutex Sleepable mutual exclusion
Semaphore Resource synchronization
RCU Read-heavy concurrent access
Memory barriers Ordering and visibility

Conceptually:

                 Concurrency Problem
                         |
          +--------------+--------------+
          |              |              |
          v              v              v
      Counter        Critical       Read-heavy
      update         section        access
          |              |              |
          v              v              v
      atomic_t       Spinlock          RCU
                         |
                         v
                       Mutex

The correct primitive depends on what is being protected and what the execution context allows.


17. Kernel Headers

The demonstration uses several kernel headers.

Header Main APIs / Purpose
<linux/module.h> Module infrastructure
<linux/kernel.h> Kernel logging and core definitions
<linux/init.h> Initialization and cleanup annotations
<linux/atomic.h> atomic_t operations
<linux/refcount.h> refcount_t operations
<linux/slab.h> kmalloc() and kfree()

For example:

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

These headers provide the infrastructure required by the demonstration module.


18. Memory Management

The example dynamically allocates its demonstration object.

The relevant memory-management APIs are:

API Purpose
kmalloc() Allocate kernel memory
kfree() Release kernel memory

It is important to distinguish memory allocation from reference counting.

Mechanism Responsibility
kmalloc() Obtain memory
refcount_t Track references
kfree() Release memory

Therefore:

kmalloc()
   |
   v
Memory exists
   |
   v
refcount_t
   |
   v
Track who still references it
   |
   v
refs == 0
   |
   v
kfree()

A reference count does not allocate memory.

Likewise, kmalloc() does not track object ownership.

The code must connect these mechanisms correctly.


19. Module Lifecycle

A Linux kernel module normally has an initialization path and an exit path.

Module Event Purpose
Module initialization Allocate and initialize resources
Module operation Execute the demonstration
Module cleanup Release resources

The demonstration performs its examples during initialization.

The dynamically allocated object is eventually released during module cleanup after the reference count reaches its final state.

Conceptually:

module_init()
     |
     v
Allocate object
     |
     v
Initialize atomic_t
     |
     v
Initialize refcount_t
     |
     v
Run demonstrations
     |
     v
module_exit()
     |
     v
Release object

20. A Practical Mental Model

A simple way to remember the difference is to ask what the number means.

                    What does the number represent?
                                  |
                    +-------------+-------------+
                    |                           |
                    v                           v
             Activity / Events             Ownership / Lifetime
                    |                           |
                    v                           v
                atomic_t                    refcount_t
                    |                           |
                    v                           v
             "How many?"                 "Who still needs it?"

Examples:

How many packets were processed?
        |
        +--> atomic_t


How many requests occurred?
        |
        +--> atomic_t


How many references exist?
        |
        +--> refcount_t


Can the object be freed?
        |
        +--> refcount_t

21. Common Conceptual Mistakes

Mistake 1: Treating atomic_t as a lifetime mechanism

An atomic counter can count events, but that does not automatically mean it correctly models object ownership.

atomic_t counter;

does not communicate the same lifetime semantics as:

refcount_t refs;

Mistake 2: Assuming atomic means thread-safe

This:

atomic_inc(&obj->counter);

only makes that atomic operation atomic.

It does not automatically protect:

obj->state;
obj->data;
obj->other_field;

Mistake 3: Assuming counters should match

Consider:

Operations = 100
References = 2

There is nothing inherently wrong with this.

The values represent different concepts.

100 operations
      !=
2 references

Mistake 4: Forgetting to release references

If a reference is acquired:

refs + 1

it must eventually be released:

refs - 1

Otherwise the object may never reach:

refs == 0

and therefore may never reach its intended cleanup state.


22. atomic_t and refcount_t in One Object

A useful conceptual example is:

struct demo_object {
    atomic_t   operations;
    refcount_t refs;
};

The object now has two independent dimensions of state:

                    demo_object
                         |
              +----------+----------+
              |                     |
              v                     v
        operations                refs
              |                     |
              v                     v
          atomic_t              refcount_t
              |                     |
              v                     v
       Activity tracking      Lifetime tracking

For example:

operations = 500
refs       = 3

This means:

500 operations have been counted
3 references currently exist

It does not mean:

500 references

or:

3 operations

The two counters have independent meanings.


23. Key Lessons

The most important concepts from this project are:

Concept Lesson
atomic_t Use for atomic counter operations
refcount_t Use for object reference counting
Reference acquisition The object must remain alive while referenced
Reference release Every acquired reference needs a release
Zero references The object can reach its cleanup state
kmalloc() Allocates kernel memory
kfree() Releases kernel memory
Atomic operation Does not make an entire algorithm thread-safe
Synchronization May still be required around larger operations

The central distinction is:

atomic_t
   |
   +--> Atomic counting
   |
   +--> Operations / events / statistics


refcount_t
   |
   +--> Reference counting
   |
   +--> Object ownership / lifetime

24. Where to Go Next

This demonstration is intentionally small.

Once the basic concepts are clear, the next step is to move from isolated API demonstrations toward realistic Linux kernel concurrency and lifetime-management patterns.

A useful progression is:

Stage Topic
1 atomic_t
2 refcount_t
3 Object lifetime management
4 Kernel threads
5 Spinlocks
6 Mutexes
7 Character devices
8 Concurrent user-space access
9 kref
10 RCU
11 Memory ordering
12 Real kernel subsystem patterns

The progression can be visualized as:

atomic_t
   |
   v
refcount_t
   |
   v
Object Lifetime
   |
   v
Kernel Threads
   |
   v
Spinlocks
   |
   v
Mutexes
   |
   v
Character Devices
   |
   v
Concurrent User-Space Access
   |
   v
kref
   |
   v
RCU
   |
   v
Memory Ordering
   |
   v
Real Kernel Subsystem Patterns

This moves the learning process from simple primitives toward the concurrency and lifetime-management techniques used in larger kernel components.


25. Final Takeaway

The entire distinction can be summarized with two questions:

Primitive Think About
atomic_t "How many operations or events?"
refcount_t "How many references keep this object alive?"

Or, even more simply:

              atomic_t
                  |
                  v
       "How many operations?"
                  |
                  v
          Atomic counting


              refcount_t
                  |
                  v
       "Who still references it?"
                  |
                  v
          Object lifetime

A well-designed kernel component should use the primitive that matches the meaning of the data.

atomic_t
    =
atomic counting


refcount_t
    =
reference counting
    +
object lifetime

Understanding this distinction provides a strong foundation for more advanced Linux kernel topics such as:

  • concurrency

  • locking

  • reference ownership

  • character drivers

  • kref

  • RCU

  • memory ordering

  • lifetime management

The key lesson is therefore not simply how to call the APIs.

It is understanding why the kernel provides different primitives for different meanings.


GitHub Repository

The complete source code, build files, and module implementation are available here:

linux_kernel_atomic_refcount_2

Explore the repository to see the concepts demonstrated in an actual Linux kernel module.