64-bit Atomic Integers in Linux Kernel: atomic64_t Explained-Free Linux Device Driver Training in Hyderabad

64-bit Atomic Integers in Linux Kernel: atomic64_t Explained
Free Linux Kernel Development Course — Kernel Synchronization Part 2
Chapter: Kernel Synchronization
Level: Beginner to Intermediate
Kernel Version: 6.x

← 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.

Focus Keyphrase: atomic64_t linux kernel
free linux kernel development course free linux device drivers course free embedded systems course kernel synchronization

What You Will Learn

  • Why 64-bit atomic integers exist alongside atomic_t
  • The atomic64_t data type and how it maps to the 32-bit API
  • Common atomic64_* operations with a real, original driver example
  • Why refcount_t deliberately has no 64-bit variant
  • How atomic operations stay architecture-independent internally

Prerequisites

  • Comfortable with the atomic_t API (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_t instead of atomic_t.
  • Every function gets an atomic64_ prefix instead of atomic_.
  • Initialize with ATOMIC64_INIT() instead of ATOMIC_INIT().
32-bit atomic_t API64-bit atomic64_t APIPurpose
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 vs 64-bit Atomic API — Same Shape, Different Width
atomic_t
32-bit signed
atomic_read()
atomic_inc()
Range: ~ ±2.1 billion
atomic64_t
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_t only when a counter can realistically exceed 32-bit range — otherwise plain atomic_t is 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_t and atomic64_t macros/functions on the same variable — this will not compile correctly and defeats the purpose of the type-safe API.
  • Assuming atomic64_t is 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_t gives you the same race-free guarantees as atomic_t, just with a 64-bit range.
  • The API mirrors atomic_t one-to-one, just with an atomic64_ prefix.
  • It is arch-independent — correct on both 32-bit and 64-bit CPUs.
  • refcount_t intentionally 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.

Continue Learning Linux Kernel Synchronization

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

Leave a Reply

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