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_trefcount_t
The distinction is simple:
atomic_tis primarily for atomic integer operations.
refcount_tis 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
krefRCU
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.



