What You Will Learn
- Why simple flag bits inside kernel and driver code still need protection from concurrent access
- How atomic bitwise operators in the Linux kernel let you set, clear, and test individual bits without taking a lock
- The complete set of atomic bit-test-and-modify APIs and when to reach for each one
- How to scan a bitmask efficiently using
find_first_bit(),find_next_bit(), and thefor_each_set_bit()family - Real driver-style code you can build and load on a current kernel
- Common mistakes, security pitfalls, and best practices around bitwise atomics
Prerequisites
Why a Single Bit Still Needs Protection
It’s tempting to think that flipping one bit inside a byte or a word is “too small” an operation to race. In reality, a bit-set instruction on most CPUs is still a read-modify-write (RMW) sequence at the hardware level: the processor reads the containing word, flips the target bit in a register, and writes the word back. If two CPU cores perform this sequence on the same word at the same time, one core’s update can silently overwrite the other’s, and a bit that should be set ends up cleared (or vice versa). This class of bug is brutal to reproduce because it depends on precise timing between cores.
Kernel code is full of small boolean-style state — “is this device ready,” “has this work item been queued,” “is this CPU currently idle” — and almost all of it is represented as individual bits packed into an unsigned long or an array of them (a bitmap). The kernel therefore provides a dedicated family of atomic bit operators so driver and subsystem authors never have to hand-roll locking just to flip a flag.
The Traditional Spinlock Approach (and Its Cost)
Before atomic bit operators existed in their current form, the safe way to update a shared flags word looked like this: acquire a spinlock, read the word, modify the bit in a local copy, write it back, then release the lock. Conceptually:
This works correctly, but it serializes every caller through the lock, even though the underlying hardware is fully capable of performing a single-bit RMW as one indivisible instruction on most architectures (via LOCK BTS/LOCK BTR on x86, or exclusive-load/store pairs on ARM). Taking a spinlock for that is unnecessary overhead: cache-line bouncing, potential contention, and extra instructions for something the CPU can already do atomically in hardware.
Meet the Kernel’s Atomic Bitwise Operators
The kernel exposes a compact, well-defined API (declared in <linux/bitops.h>) that maps directly onto atomic hardware bit instructions where the architecture supports them. Every one of these functions is safe to call concurrently from multiple CPUs without any additional locking.
| Function | Effect | Returns |
|---|---|---|
set_bit(nr, addr) | Atomically sets bit nr | void |
clear_bit(nr, addr) | Atomically clears bit nr | void |
change_bit(nr, addr) | Atomically toggles bit nr | void |
test_and_set_bit(nr, addr) | Sets bit, tells you what it was before | old value (0/1) |
test_and_clear_bit(nr, addr) | Clears bit, tells you what it was before | old value (0/1) |
test_and_change_bit(nr, addr) | Toggles bit, tells you what it was before | old value (0/1) |
Plain test_bit(nr, addr) (a read-only check, not in the table above) is also available and does not itself need to be atomic on most architectures since a single read of an aligned word is already indivisible — but it should still be used so the intent of the code is clear and portable.
Bit 7 (blue) and bit 2 (green) are set — each can be flipped independently and atomically with set_bit()/clear_bit(), without disturbing the rest of the byte.
A Modern Driver-Style Example
The example below models a small character-device driver that tracks per-device state — “device open,” “DMA busy,” and “firmware ready” — as bits in a single unsigned long. This pattern is common across real drivers in the current kernel tree. Build it against your running kernel’s headers (kernel 6.1 and later; APIs shown here are unchanged through 6.12+).
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/bitops.h>
#define pr_fmt(fmt) "epdrv: " fmt
/* Bit positions within dev_state */
#define ST_OPEN 0
#define ST_DMA_BUSY 1
#define ST_FW_READY 2
static unsigned long dev_state;
static int __init epdrv_init(void)
{
/* Atomically mark the device as open; find out if it was
* already open (useful for exclusive-open semantics) */
if (test_and_set_bit(ST_OPEN, &dev_state)) {
pr_warn("device already open, refusing\n");
return -EBUSY;
}
set_bit(ST_FW_READY, &dev_state);
pr_info("state after init: 0x%lx\n", dev_state);
return 0;
}
static void __exit epdrv_exit(void)
{
clear_bit(ST_DMA_BUSY, &dev_state);
clear_bit(ST_OPEN, &dev_state);
pr_info("state after exit: 0x%lx\n", dev_state);
}
module_init(epdrv_init);
module_exit(epdrv_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("EmbeddedPathashala atomic bit ops demo");
Notice there is no spinlock_t anywhere in this file. Every state transition is safe even if another CPU calls into the same driver concurrently, because test_and_set_bit(), set_bit(), and clear_bit() are individually atomic.
Efficiently Searching a Bitmask
Once you have many flags packed into a bitmap (an array of unsigned long), you often need to answer questions like “which CPUs are online?” or “which of these 256 work items are pending?” Scanning bit-by-bit in a loop works but wastes cycles, so the kernel provides fast bitmap-search primitives, declared in <linux/find.h> and used internally by the scheduler’s runqueue bitmaps and cpumask operations, among many other subsystems.
| API | Purpose |
|---|---|
find_first_bit(addr, size) | Index of the first set bit, or size if none are set |
find_first_zero_bit(addr, size) | Index of the first cleared bit |
find_next_bit(addr, size, offset) | Next set bit starting from offset |
for_each_set_bit(bit, addr, size) | Iterator macro that walks every set bit in a bitmap |
These routines are hand-optimized to work a full machine word at a time instead of bit-by-bit, which is why the kernel’s own CPU scheduler and IRQ subsystems rely on them for latency-sensitive lookups rather than writing manual loops.
Real-World Use Cases
- CPU topology masks —
cpumask_tbitmaps use these exact primitives to track online, present, and isolated CPUs. - Scheduler runqueues — priority bitmaps used by real-time scheduling classes rely on fast bit search to find the next runnable priority level.
- Block and network device queues — tag allocators track “in use” descriptors as bitmaps.
- Driver state machines — device-ready, suspended, and error flags, as shown in the example above.
Common Mistakes
| Mixing atomic and non-atomic access | Reading a flags word directly with if (state & (1 << bit)) while another path uses set_bit() defeats the safety atomics provide. Always use test_bit() for reads on shared state. |
| Assuming atomic bit ops replace all locking | They only protect the single bit operation itself, not a sequence of several related updates that must appear consistent together. |
| Wrong bitmap size for the search functions | Passing an incorrect size to find_next_bit() can read past the end of the bitmap array. |
Best Practices
- Prefer atomic bit operators over a spinlock whenever the critical section really is “flip one bit.”
- Use the
test_and_*variants when you need to know the previous value — they save you a separate read. - Reach for a proper lock (spinlock, mutex, or reader-writer lock) the moment you need to update more than one related field consistently.
- Document the meaning of each bit position with named constants, as shown in the example, rather than magic numbers.
Performance Considerations
Atomic bit operators avoid the cache-line contention and instruction overhead of acquiring and releasing a spinlock for a single-bit change. On multi-core systems under heavy concurrent access to the same flags word, this difference compounds quickly, since a spinlock forces full serialization while atomic bit instructions can often be retried by hardware with far less cross-core traffic.
Security Considerations
Bit flags frequently gate security-relevant decisions — “is this buffer validated,” “has this capability been granted.” Because test_and_set_bit() combines the check and the update into one atomic step, it closes the classic time-of-check-to-time-of-use (TOCTOU) race that a separate “read the flag, then set it” sequence would be vulnerable to.
Summary / Key Takeaways
- Even single-bit updates need atomicity on multi-core systems.
set_bit(),clear_bit(),change_bit(), and theirtest_and_*counterparts give you that safety without a lock.find_first_bit(),find_next_bit(), andfor_each_set_bit()let you scan large bitmaps efficiently.- Atomic bit ops complement, but don’t replace, spinlocks and other locks for multi-field updates.
Conclusion
Atomic bitwise operators are one of the simplest yet most widely used concurrency tools in the Linux kernel. Almost every subsystem — from the scheduler to block I/O to individual device drivers — leans on them to keep single-flag updates both fast and correct. Once you’re comfortable with set_bit()/clear_bit()/test_and_set_bit() and the bitmap-search family, you’ll start recognizing this pattern everywhere in kernel source, and you’ll know exactly when it’s the right tool instead of a heavier lock.
Frequently Asked Questions
They are declared in <linux/bitops.h>, with the fast bitmap-search functions in <linux/find.h>.
Yes. They map to a single atomic hardware instruction on most architectures, avoiding the acquire/release overhead and potential contention of a spinlock.
Whenever you need to update more than one related field together as a single consistent unit, or when the “bit” is only part of a larger critical section.
A single aligned word read is already indivisible on virtually all architectures, so test_bit() doesn’t need special atomic hardware support — but you should still use it for clarity and portability.
set_bit() just sets the bit. test_and_set_bit() sets it and also atomically returns what the bit’s value was immediately before the operation — useful for exclusive-access checks.
Widely — CPU topology masks (cpumask), scheduler priority bitmaps, IRQ management, and block/network descriptor tag allocators all rely on them.
Yes — the API is architecture-independent; each architecture supplies its own optimized low-level implementation underneath the same function names.
Continue Your Free Linux Kernel Development Course
This lecture is part of EmbeddedPathashala’s free Linux kernel programming and device driver course.
Previous Lecture Next Lecture
2 Comments