Hazard pointers for the kernel [LWN.net]
LWN<br>.net<br>News from the source
Content Weekly Edition<br>Archives<br>Search<br>Kernel<br>Security<br>Events calendar<br>Unread comments
LWN FAQ<br>Write for us
Edition Return to the Front page
User:<br>Password: |
Log in /<br>Subscribe /<br>Register
Hazard pointers for the kernel
By Jonathan Corbet<br>July 27, 2026
The kernel's read-copy-update (RCU) subsystem ensures that data will not be<br>deleted until it is known that there are no threads holding references to<br>it. RCU works well and is widely used throughout the kernel, but it can<br>increase memory use and add significant delays before unused kernel objects<br>are cleaned up. Hazard pointers are<br>an alternative approach to lockless data updates that offers better<br>performance, for some situations at least. The kernel community is<br>currently considering a<br>hazard-pointer implementation by Mathieu Desnoyers and Paul McKenney.
Like RCU, hazard pointers are meant to be a way to hold a short-lived<br>reference to an immutable object that may disappear once all references are<br>gone. Code holding references to RCU-protected data must disable<br>preemption; hazard pointers, instead, appear to be designed to allow<br>preemption, though such use may not be entirely optimal.
$ sudo subscribe today
Subscribe today and elevate your LWN privileges. You’ll have<br>access to all of LWN’s high-quality articles as soon as they’re<br>published, and help support LWN in the process. Act now and you can start with a free trial subscription.
The hazard-pointer API
At the API level, code using hazard pointers must allocate a context<br>(struct hazptr_ctx) for each pointer that will be in use at the<br>same time; this structure is normally placed on the stack. If there is a<br>pointer (we'll call it resource) to an object to be protected by a<br>hazard pointer, and code needs to access that object, the pointer must<br>first be acquired with a call to:
void *hazptr_acquire(struct hazptr_ctx *ctx, void * const *address);
Where ctx is the above-described context, and address, in<br>this case, would be the address of resource. This call will<br>return the current value of resource (the address of the protected<br>object) and ensure that this object will not be changed or deleted for as<br>long as the reference remains. When work with the protected object is<br>complete, the code must call:
void hazptr_release(struct hazptr_ctx *ctx, void *address);
Where address is the address of the old copy of the resource; after<br>this call, the previously protected object can no longer be used.
Normally, a hazard pointer must be released in the same execution context<br>in which it was obtained — in the same thread or interrupt handler, in<br>other words. There may be times when it is necessary to release the<br>pointer in a different setting, though. That can be done, but only if the<br>hazard-pointer context is passed to this function:
void hazptr_detach(struct hazptr_ctx *ctx);
This call must, clearly, be made before the pointer is actually released.
On the producer side, when the time comes to replace the protected object<br>with a new one, code should create and initialize the new object, aim the<br>resource pointer at this new copy, then call:
void hazptr_synchronize(void *address);
This call, which must be made in a preemptible context, will wait until<br>there are no more hazard-pointer references to the given address,<br>then return to the caller. At that point, the object at that address can<br>be freed.
The implementation
The core idea behind hazard pointers is relatively simple: a call to<br>hazptr_acquire() adds the pointer to a special list, while<br>hazptr_release() removes it from that list. When a call to<br>hazptr_synchronize() is made, that list is scanned for the address<br>in question; if the address is found there, the function will wait until it<br>is removed. This algorithm could be implemented with a simple linked list<br>protected by a lock, but the whole purpose is to maximize performance, so<br>the actual implementation is somewhat more complicated.
The hazard-pointer code maintains a global per-CPU array, with four slots<br>on each CPU. The oversimplified explanation of the algorithm is that, on a<br>call to hazptr_acquire(), an empty slot is found, and the relevant<br>address is stored there. Calls to hazptr_synchronize() can then<br>simply scan those slots (on each CPU) and wait until none of them contain<br>the protected address. But, once again, there are complications.
One of those is an ordering problem. hazptr_acquire() must read<br>the pointer to acquire, then store it into the slot. On the synchronize<br>side, that pointer must be changed, then the slots searched for the<br>previous value. If the synchronization code runs between the two acquire<br>operations — after the pointer is read, but before it is stored into a slot<br>— it will conclude that there are no references and release an object that<br>is still in use. That is not the sort of hazard the authors of this code<br>care to face.
To address this problem, the slots are maintained in three...