⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/sync/
hazard.rs

1//! Hazard Pointer Registry
2//!
3//! Hazard pointers provide safe memory reclamation for lock-free data
4//! structures. When a thread reads a shared pointer, it publishes the
5//! address in a per-CPU hazard pointer slot. Before freeing memory,
6//! the reclaimer checks all hazard pointers to ensure no thread is
7//! actively referencing the object.
8//!
9//! This is simpler than RCU for single-object protection and is used
10//! alongside RCU for different access patterns:
11//! - RCU: read-heavy data structures with infrequent updates
12//! - Hazard pointers: fine-grained per-object protection in lock-free
13//!   containers (queues, stacks, hash maps)
14
15use core::sync::atomic::{AtomicUsize, Ordering};
16
17// ---------------------------------------------------------------------------
18// Configuration
19// ---------------------------------------------------------------------------
20
21/// Maximum number of CPUs (matches smp::MAX_CPUS).
22const MAX_CPUS: usize = 16;
23
24/// Number of hazard pointer slots per CPU.
25const SLOTS_PER_CPU: usize = 4;
26
27/// Total number of hazard pointer slots.
28const TOTAL_SLOTS: usize = MAX_CPUS * SLOTS_PER_CPU;
29
30/// Sentinel value indicating an unused hazard pointer slot.
31const HP_EMPTY: usize = 0;
32
33// ---------------------------------------------------------------------------
34// Global Hazard Pointer Array
35// ---------------------------------------------------------------------------
36
37/// Global hazard pointer slots. Each slot holds the address of a memory
38/// object that a thread is actively referencing. Set to HP_EMPTY when
39/// not in use.
40#[allow(clippy::declare_interior_mutable_const)]
41static HAZARD_POINTERS: [AtomicUsize; TOTAL_SLOTS] = {
42    const INIT: AtomicUsize = AtomicUsize::new(HP_EMPTY);
43    [INIT; TOTAL_SLOTS]
44};
45
46// ---------------------------------------------------------------------------
47// API
48// ---------------------------------------------------------------------------
49
50/// A guard that holds a hazard pointer slot. When dropped, the slot is
51/// cleared (set to HP_EMPTY).
52pub struct HazardGuard {
53    slot_index: usize,
54}
55
56impl Drop for HazardGuard {
57    fn drop(&mut self) {
58        HAZARD_POINTERS[self.slot_index].store(HP_EMPTY, Ordering::Release);
59    }
60}
61
62/// Protect a pointer by publishing it in a hazard pointer slot.
63///
64/// Returns a `HazardGuard` that clears the slot when dropped. The caller
65/// must keep the guard alive as long as the pointer is being accessed.
66///
67/// `slot` must be 0..SLOTS_PER_CPU-1 (per-CPU local slot index).
68pub fn protect(ptr: usize, slot: usize) -> HazardGuard {
69    debug_assert!(slot < SLOTS_PER_CPU, "slot index out of range");
70    let cpu = crate::sched::smp::current_cpu_id() as usize;
71    let global_slot = cpu * SLOTS_PER_CPU + slot;
72
73    HAZARD_POINTERS[global_slot].store(ptr, Ordering::Release);
74
75    // Memory barrier ensures the hazard pointer is visible to reclaimers
76    // before we proceed to access the protected object.
77    core::sync::atomic::fence(Ordering::SeqCst);
78
79    HazardGuard {
80        slot_index: global_slot,
81    }
82}
83
84/// Check whether a given address is currently protected by any hazard pointer.
85///
86/// Used by reclaimers before freeing memory to ensure no thread is actively
87/// referencing the object.
88pub fn is_protected(addr: usize) -> bool {
89    for slot in &HAZARD_POINTERS {
90        if slot.load(Ordering::Acquire) == addr {
91            return true;
92        }
93    }
94    false
95}
96
97/// Scan all hazard pointer slots and return a list of currently protected
98/// addresses. Used for batch reclamation: collect a retire list, then filter
99/// out any addresses that appear in the hazard set.
100pub fn collect_protected() -> [usize; TOTAL_SLOTS] {
101    let mut result = [HP_EMPTY; TOTAL_SLOTS];
102    for (i, slot) in HAZARD_POINTERS.iter().enumerate() {
103        result[i] = slot.load(Ordering::Acquire);
104    }
105    result
106}
107
108/// Clear all hazard pointer slots for a given CPU.
109///
110/// Called during CPU shutdown or thread exit to release all protections.
111pub fn clear_cpu_slots(cpu_id: u8) {
112    let base = (cpu_id as usize) * SLOTS_PER_CPU;
113    for i in 0..SLOTS_PER_CPU {
114        HAZARD_POINTERS[base + i].store(HP_EMPTY, Ordering::Release);
115    }
116}
117
118/// Get the number of active (non-empty) hazard pointer slots.
119pub fn active_count() -> usize {
120    HAZARD_POINTERS
121        .iter()
122        .filter(|slot| slot.load(Ordering::Relaxed) != HP_EMPTY)
123        .count()
124}