← Previous Lecture | Next Lecture →
In the previous lectures of this free Linux kernel development course, we covered the atomic_t and refcount_t APIs for protecting a plain 32-bit integer from concurrent read-modify-write races. In this lecture we extend that idea to 64-bit atomic integers using atomic64_t, which every Linux device driver author eventually needs once counters, byte totals, or timestamps grow beyond the 32-bit range.
What You Will Learn
- Why 64-bit atomic integers exist alongside
atomic_t - The
atomic64_tdata type and how it maps to the 32-bit API - Common
atomic64_*operations with a real, original driver example - Why
refcount_tdeliberately has no 64-bit variant - How atomic operations stay architecture-independent internally
Prerequisites
- Comfortable with the
atomic_tAPI (previous lecture in this series) - Basic C pointers and unsigned integer types
- A Linux kernel 6.x source tree or VM to try the examples
Why Do We Need a 64-bit Atomic Integer Type?
A regular atomic_t internally wraps a 32-bit signed integer. That is perfectly fine for small counters — device open counts, simple flags, small reference tallies. But several real driver scenarios overflow a 32-bit counter fast:
- A network or storage driver counting total bytes transferred — a busy 10 Gbps link can wrap a 32-bit byte counter in a matter of seconds.
- A high-resolution packet or interrupt counter on a system running for months.
- Any statistic that must survive long-uptime embedded systems without silently wrapping around to zero.
For all such cases the kernel provides an identical family of atomic operators built on a 64-bit integer, so the driver author gets the exact same race-free guarantees, just with more headroom.
The atomic64_t Type and Naming Convention
Switching from 32-bit to 64-bit atomics in kernel 6.x is mostly a naming exercise — the logic and safety guarantees are identical:
- Declare the variable as
atomic64_tinstead ofatomic_t. - Every function gets an
atomic64_prefix instead ofatomic_. - Initialize with
ATOMIC64_INIT()instead ofATOMIC_INIT().
| 32-bit atomic_t API | 64-bit atomic64_t API | Purpose |
|---|---|---|
ATOMIC_INIT(0) | ATOMIC64_INIT(0) | Static initialization |
atomic_read() | atomic64_read() | Atomic read |
atomic_set() | atomic64_set() | Atomic write |
atomic_add() | atomic64_add() | Add value |
atomic_inc() | atomic64_inc() | Increment by 1 |
atomic_dec_and_test() | atomic64_dec_and_test() | Decrement, test for zero |
atomic_add_return() | atomic64_add_return() | Add and return new value |
atomic_cmpxchg() | atomic64_cmpxchg() | Compare-and-swap |
32-bit signed
atomic_read()atomic_inc()Range: ~ ±2.1 billion
64-bit signed
atomic64_read()atomic64_inc()Range: ~ ±9.2 quintillion
Note: On 32-bit CPU architectures, the kernel still implements atomic64_t correctly and atomically — internally it may use architecture-specific tricks (such as locked double-word instructions) so your driver code never has to worry about the underlying CPU word size. The API itself is completely arch-independent.
Original Example: A 64-bit Byte Counter in a Misc Driver
Below is an original demo driver (not copied from any book) that exposes a misc device and keeps a race-free 64-bit running total of bytes written to it — exactly the kind of counter that would overflow a plain atomic_t on a busy system.
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/atomic.h>
static atomic64_t ep_total_bytes = ATOMIC64_INIT(0);
static ssize_t ep_dev_write(struct file *filp, const char __user *buf,
size_t count, loff_t *ppos)
{
/* Pretend to consume the data; we only track byte volume here */
atomic64_add((s64)count, &ep_total_bytes);
return count;
}
static ssize_t ep_dev_read(struct file *filp, char __user *buf,
size_t count, loff_t *ppos)
{
char tmp[32];
int len;
if (*ppos > 0)
return 0;
len = scnprintf(tmp, sizeof(tmp), "%lld\n",
atomic64_read(&ep_total_bytes));
if (copy_to_user(buf, tmp, len))
return -EFAULT;
*ppos += len;
return len;
}
static const struct file_operations ep_dev_fops = {
.owner = THIS_MODULE,
.write = ep_dev_write,
.read = ep_dev_read,
};
static struct miscdevice ep_dev_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "ep_atomic64_demo",
.fops = &ep_dev_fops,
};
static int __init ep_dev_init(void)
{
return misc_register(&ep_dev_misc);
}
static void __exit ep_dev_exit(void)
{
misc_deregister(&ep_dev_misc);
}
module_init(ep_dev_init);
module_exit(ep_dev_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("EmbeddedPathashala atomic64_t byte counter demo");
Every write() to /dev/ep_atomic64_demo from multiple concurrent user-space processes safely adds to ep_total_bytes without any explicit lock, and a read() reports the running total — even past the point where a 32-bit counter would have wrapped around.
Why refcount_t Does Not Have a 64-bit Variant
You might expect a refcount64_t to exist alongside refcount_t, but it does not, and this is intentional. refcount_t is designed purely for object lifetime tracking (get/put reference counts), and no real object in the kernel is referenced anywhere near 2^32 times simultaneously. Extending its range would add overhead without any practical safety benefit, so the kernel community deliberately kept refcount_t 32-bit only, even on 64-bit systems.
Best Practices
- Use
atomic64_tonly when a counter can realistically exceed 32-bit range — otherwise plainatomic_tis lighter and just as safe. - Never read or write the underlying integer directly; always go through the
atomic64_*accessor functions, including for initialization. - Prefer
atomic64_add_return()over separate add-then-read calls when you need the post-update value, to avoid a race between the two steps.
Common Mistakes
- Mixing
atomic_tandatomic64_tmacros/functions on the same variable — this will not compile correctly and defeats the purpose of the type-safe API. - Assuming
atomic64_tis only useful on 64-bit CPUs — it is equally usable and correct on 32-bit architectures. - Using a 64-bit atomic where a spinlock-protected structure was really needed (atomics only protect a single scalar, not multiple related fields).
Summary / Key Takeaways
atomic64_tgives you the same race-free guarantees asatomic_t, just with a 64-bit range.- The API mirrors
atomic_tone-to-one, just with anatomic64_prefix. - It is arch-independent — correct on both 32-bit and 64-bit CPUs.
refcount_tintentionally stays 32-bit only, since object lifetimes never need that range.
Frequently Asked Questions (FAQ)
Q1. When should I use atomic64_t instead of atomic_t in a Linux driver?
Use atomic64_t when a counter can realistically exceed roughly 2.1 billion within the device’s uptime — for example cumulative byte counters on high-throughput drivers.
Q2. Is atomic64_t slower than atomic_t?
On 64-bit CPUs the cost is essentially identical to atomic_t. On some older 32-bit CPUs it can be marginally more expensive due to how 64-bit atomicity is implemented, but this is rarely noticeable in practice.
Q3. Can I use atomic64_t inside interrupt context?
Yes. Like atomic_t, all atomic64_* operations are safe to call from interrupt (atomic) context since they never sleep.
Q4. Does atomic64_t replace the need for spinlocks?
Only for a single 64-bit scalar value. If you need to update multiple related fields together, you still need a spinlock or mutex to protect the whole critical section.
Q5. Why doesn’t refcount_t have a 64-bit version?
Because no kernel object needs a reference count anywhere close to the 32-bit limit; adding 64-bit support would add overhead with no real-world benefit.
Q6. Is atomic64_t part of the C11/C++11 atomic standard?
No, it is the Linux kernel’s own internal API, predating widespread C11/C++11 atomic support. The kernel still uses its own atomic_t/atomic64_t types rather than the C standard library atomics.
This lecture is part of EmbeddedPathashala’s free Linux kernel development course, free Linux device drivers course, and free embedded systems course.
Previous Lecture Next Lecture
2 Comments