|
| 1 | +// SPDX-License-Identifier: GPL-2.0 |
| 2 | + |
| 3 | +//! A simple mutex implementation. |
| 4 | +//! |
| 5 | +//! Differently from [`super::Mutex`], this implementation does not require pinning, so the |
| 6 | +//! ergonomics are much improved, though the implementation is not as feature-rich as the C-based |
| 7 | +//! one. The main advantage is that it doesn't impose unsafe blocks on callers. |
| 8 | +//! |
| 9 | +//! The mutex is made up of 2 words in addition to the data it protects. The first one is accessed |
| 10 | +//! concurrently by threads trying to acquire and release the mutex, it contains a "stack" of |
| 11 | +//! waiters and a "locked" bit; the second one is only accessible by the thread holding the mutex, |
| 12 | +//! it contains a queue of waiters. Waiters are moved from the stack to the queue when the mutex is |
| 13 | +//! next unlocked while the stack is non-empty and the queue is empty. A single waiter is popped |
| 14 | +//! from the wait queue when the owner of the mutex unlocks it. |
| 15 | +//! |
| 16 | +//! The initial state of the mutex is `<locked=0, stack=[], queue=[]>`, meaning that it isn't |
| 17 | +//! locked and both the waiter stack and queue are empty. |
| 18 | +//! |
| 19 | +//! A lock operation transitions the mutex to state `<locked=1, stack=[], queue=[]>`. |
| 20 | +//! |
| 21 | +//! An unlock operation transitions the mutex back to the initial state, however, an attempt to |
| 22 | +//! lock the mutex while it's already locked results in a waiter being created (on the stack) and |
| 23 | +//! pushed onto the stack, so the state is `<locked=1, stack=[W1], queue=[]>`. |
| 24 | +//! |
| 25 | +//! Another thread trying to lock the mutex results in another waiter being pushed onto the stack, |
| 26 | +//! so the state becomes `<locked=1, stack=[W2, W1], queue=[]>`. |
| 27 | +//! |
| 28 | +//! In such states (queue is empty but stack is non-empty), the unlock operation is performed in |
| 29 | +//! three steps: |
| 30 | +//! 1. The stack is popped (but the mutex remains locked), so the state is: |
| 31 | +//! `<locked=1, stack=[], queue=[]>` |
| 32 | +//! 2. The stack is turned into a queue by reversing it, so the state is: |
| 33 | +//! `<locked=1, stack=[], queue=[W1, W2]> |
| 34 | +//! 3. Finally, the lock is released, and the first waiter is awakened, so the state is: |
| 35 | +//! `<locked=0, stack=[], queue=[W2]>` |
| 36 | +//! |
| 37 | +//! The mutex remains accessible to any threads attempting to lock it in any of the intermediate |
| 38 | +//! states above. For example, while it is locked, other threads may add waiters to the stack |
| 39 | +//! (which is ok because we want to release the ones on the queue first); another example is that |
| 40 | +//! another thread may acquire the mutex before waiter W1 in the example above, this makes the |
| 41 | +//! mutex unfair but this is desirable because the thread is running already and may in fact |
| 42 | +//! release the lock before W1 manages to get scheduled -- it also mitigates the lock convoy |
| 43 | +//! problem when the releasing thread wants to immediately acquire the lock again: it will be |
| 44 | +//! allowed to do so (as long as W1 doesn't get to it first). |
| 45 | +//! |
| 46 | +//! When the waiter queue is non-empty, unlocking the mutex always results in the first waiter being |
| 47 | +//! popped form the queue and awakened. |
| 48 | +
|
| 49 | +use super::{mutex::EmptyGuardContext, CreatableLock, Guard, Lock}; |
| 50 | +use crate::{bindings, str::CStr, Opaque}; |
| 51 | +use core::sync::atomic::{AtomicUsize, Ordering}; |
| 52 | +use core::{cell::UnsafeCell, pin::Pin}; |
| 53 | + |
| 54 | +/// The value that is OR'd into the [`Mutex::waiter_stack`] when the mutex is locked. |
| 55 | +const LOCKED: usize = 1; |
| 56 | + |
| 57 | +/// A simple mutex. |
| 58 | +/// |
| 59 | +/// This is mutual-exclusion primitive. It guarantees that only one thread at a time may access the |
| 60 | +/// data it protects. When multiple threads attempt to lock the same mutex, only one at a time is |
| 61 | +/// allowed to progress, the others will block (sleep) until the mutex is unlocked, at which point |
| 62 | +/// another thread will be allowed to wake up and make progress. |
| 63 | +/// |
| 64 | +/// # Examples |
| 65 | +/// |
| 66 | +/// ``` |
| 67 | +/// # use kernel::{Result, sync::Ref, sync::smutex::Mutex}; |
| 68 | +/// |
| 69 | +/// struct Example { |
| 70 | +/// a: u32, |
| 71 | +/// b: u32, |
| 72 | +/// } |
| 73 | +/// |
| 74 | +/// static EXAMPLE: Mutex<Example> = Mutex::new(Example{ a: 10, b: 20 }); |
| 75 | +/// |
| 76 | +/// fn inc_a(example: &Mutex<Example>) { |
| 77 | +/// let mut guard = example.lock(); |
| 78 | +/// guard.a += 1; |
| 79 | +/// } |
| 80 | +/// |
| 81 | +/// fn sum(example: &Mutex<Example>) -> u32 { |
| 82 | +/// let guard = example.lock(); |
| 83 | +/// guard.a + guard.b |
| 84 | +/// } |
| 85 | +/// |
| 86 | +/// fn try_new(a: u32, b: u32) -> Result<Ref<Mutex<Example>>> { |
| 87 | +/// Ref::try_new(Mutex::new(Example {a, b})) |
| 88 | +/// } |
| 89 | +/// ``` |
| 90 | +pub struct Mutex<T: ?Sized> { |
| 91 | + /// A stack of waiters. |
| 92 | + /// |
| 93 | + /// It is accessed atomically by threads lock/unlocking the mutex. Additionally, the |
| 94 | + /// least-significant bit is used to indicate whether the mutex is locked or not. |
| 95 | + waiter_stack: AtomicUsize, |
| 96 | + |
| 97 | + /// A queue of waiters. |
| 98 | + /// |
| 99 | + /// This is only accessible to the holder of the mutex. When the owner of the mutex is |
| 100 | + /// unlocking it, it will move waiters from the stack to the queue when the queue is empty and |
| 101 | + /// the stack non-empty. |
| 102 | + waiter_queue: UnsafeCell<*mut Waiter>, |
| 103 | + |
| 104 | + /// The data protected by the mutex. |
| 105 | + data: UnsafeCell<T>, |
| 106 | +} |
| 107 | + |
| 108 | +// SAFETY: `Mutex` can be transferred across thread boundaries iff the data it protects can. |
| 109 | +#[allow(clippy::non_send_fields_in_send_ty)] |
| 110 | +unsafe impl<T: ?Sized + Send> Send for Mutex<T> {} |
| 111 | + |
| 112 | +// SAFETY: `Mutex` serialises the interior mutability it provides, so it is `Sync` as long as the |
| 113 | +// data it protects is `Send`. |
| 114 | +unsafe impl<T: ?Sized + Send> Sync for Mutex<T> {} |
| 115 | + |
| 116 | +impl<T> Mutex<T> { |
| 117 | + /// Creates a new instance of the mutex. |
| 118 | + pub const fn new(data: T) -> Self { |
| 119 | + Self { |
| 120 | + waiter_stack: AtomicUsize::new(0), |
| 121 | + waiter_queue: UnsafeCell::new(core::ptr::null_mut()), |
| 122 | + data: UnsafeCell::new(data), |
| 123 | + } |
| 124 | + } |
| 125 | +} |
| 126 | + |
| 127 | +impl<T: ?Sized> Mutex<T> { |
| 128 | + /// Locks the mutex and gives the caller access to the data protected by it. Only one thread at |
| 129 | + /// a time is allowed to access the protected data. |
| 130 | + pub fn lock(&self) -> Guard<'_, Self> { |
| 131 | + let ctx = self.lock_noguard(); |
| 132 | + // SAFETY: The mutex was just acquired. |
| 133 | + unsafe { Guard::new(self, ctx) } |
| 134 | + } |
| 135 | +} |
| 136 | + |
| 137 | +impl<T> CreatableLock for Mutex<T> { |
| 138 | + type CreateArgType = T; |
| 139 | + |
| 140 | + unsafe fn new_lock(data: Self::CreateArgType) -> Self { |
| 141 | + Self::new(data) |
| 142 | + } |
| 143 | + |
| 144 | + unsafe fn init_lock( |
| 145 | + self: Pin<&mut Self>, |
| 146 | + _name: &'static CStr, |
| 147 | + _key: *mut bindings::lock_class_key, |
| 148 | + ) { |
| 149 | + } |
| 150 | +} |
| 151 | + |
| 152 | +// SAFETY: The mutex implementation ensures mutual exclusion. |
| 153 | +unsafe impl<T: ?Sized> Lock for Mutex<T> { |
| 154 | + type Inner = T; |
| 155 | + type GuardContext = EmptyGuardContext; |
| 156 | + |
| 157 | + fn lock_noguard(&self) -> EmptyGuardContext { |
| 158 | + loop { |
| 159 | + // Try the fast path: the caller owns the mutex if we manage to set the `LOCKED` bit. |
| 160 | + // |
| 161 | + // The `acquire` order matches with one of the `release` ones in `unlock`. |
| 162 | + if self.waiter_stack.fetch_or(LOCKED, Ordering::Acquire) & LOCKED == 0 { |
| 163 | + return EmptyGuardContext; |
| 164 | + } |
| 165 | + |
| 166 | + // Slow path: we'll likely need to wait, so initialise a local waiter struct. |
| 167 | + let mut waiter = Waiter { |
| 168 | + completion: Opaque::uninit(), |
| 169 | + next: core::ptr::null_mut(), |
| 170 | + }; |
| 171 | + |
| 172 | + // SAFETY: The completion object was just allocated on the stack and is valid for |
| 173 | + // writes. |
| 174 | + unsafe { bindings::init_completion(waiter.completion.get()) }; |
| 175 | + |
| 176 | + // Try to enqueue the waiter by pushing into onto the waiter stack. We want to do it |
| 177 | + // only while the mutex is locked by another thread. |
| 178 | + loop { |
| 179 | + // We use relaxed here because we're just reading the value we'll CAS later (which |
| 180 | + // has a stronger ordering on success). |
| 181 | + let mut v = self.waiter_stack.load(Ordering::Relaxed); |
| 182 | + if v & LOCKED == 0 { |
| 183 | + // The mutex was released by another thread, so try to acquire it. |
| 184 | + // |
| 185 | + // The `acquire` order matches with one of the `release` ones in `unlock`. |
| 186 | + v = self.waiter_stack.fetch_or(LOCKED, Ordering::Acquire); |
| 187 | + if v & LOCKED == 0 { |
| 188 | + return EmptyGuardContext; |
| 189 | + } |
| 190 | + } |
| 191 | + |
| 192 | + waiter.next = (v & !LOCKED) as _; |
| 193 | + |
| 194 | + // The `release` order matches with `acquire` in `unlock` when the stack is swapped |
| 195 | + // out. We use release order here to ensure that the other thread can see our |
| 196 | + // waiter fully initialised. |
| 197 | + if self |
| 198 | + .waiter_stack |
| 199 | + .compare_exchange( |
| 200 | + v, |
| 201 | + (&mut waiter as *mut _ as usize) | LOCKED, |
| 202 | + Ordering::Release, |
| 203 | + Ordering::Relaxed, |
| 204 | + ) |
| 205 | + .is_ok() |
| 206 | + { |
| 207 | + break; |
| 208 | + } |
| 209 | + } |
| 210 | + |
| 211 | + // Wait for the owner to lock to wake this thread up. |
| 212 | + // |
| 213 | + // SAFETY: Completion object was previously initialised with `init_completion` and |
| 214 | + // remains valid. |
| 215 | + unsafe { bindings::wait_for_completion(waiter.completion.get()) }; |
| 216 | + } |
| 217 | + } |
| 218 | + |
| 219 | + unsafe fn unlock(&self, _: &mut EmptyGuardContext) { |
| 220 | + // SAFETY: The caller owns the mutex, so it is safe to manipulate the local wait queue. |
| 221 | + let mut waiter = unsafe { *self.waiter_queue.get() }; |
| 222 | + loop { |
| 223 | + // If we have a non-empty local queue of waiters, pop the first one, release the mutex, |
| 224 | + // and wake it up (the popped waiter). |
| 225 | + if !waiter.is_null() { |
| 226 | + // SAFETY: The caller owns the mutex, so it is safe to manipulate the local wait |
| 227 | + // queue. |
| 228 | + unsafe { *self.waiter_queue.get() = (*waiter).next }; |
| 229 | + |
| 230 | + // The `release` order matches with one of the `acquire` ones in `lock_noguard`. |
| 231 | + self.waiter_stack.fetch_and(!LOCKED, Ordering::Release); |
| 232 | + |
| 233 | + // Wake up the first waiter. |
| 234 | + // |
| 235 | + // SAFETY: The completion object was initialised before being added to the wait |
| 236 | + // stack and is only removed above, when called completed. So it is safe for |
| 237 | + // writes. |
| 238 | + unsafe { bindings::complete_all((*waiter).completion.get()) }; |
| 239 | + return; |
| 240 | + } |
| 241 | + |
| 242 | + // Try the fast path when there are no local waiters. |
| 243 | + // |
| 244 | + // The `release` order matches with one of the `acquire` ones in `lock_noguard`. |
| 245 | + if self |
| 246 | + .waiter_stack |
| 247 | + .compare_exchange(LOCKED, 0, Ordering::Release, Ordering::Relaxed) |
| 248 | + .is_ok() |
| 249 | + { |
| 250 | + return; |
| 251 | + } |
| 252 | + |
| 253 | + // We don't have a local queue, so pull the whole stack off, reverse it, and use it as a |
| 254 | + // local queue. Since we're manipulating this queue, we need to keep ownership of the |
| 255 | + // mutex. |
| 256 | + // |
| 257 | + // The `acquire` order matches with the `release` one in `lock_noguard` where a waiter |
| 258 | + // is pushed onto the stack. It ensures that we see the fully-initialised waiter. |
| 259 | + let mut stack = |
| 260 | + (self.waiter_stack.swap(LOCKED, Ordering::Acquire) & !LOCKED) as *mut Waiter; |
| 261 | + while !stack.is_null() { |
| 262 | + // SAFETY: The caller still owns the mutex, so it is safe to manipulate the |
| 263 | + // elements of the wait queue, which will soon become that wait queue. |
| 264 | + let next = unsafe { (*stack).next }; |
| 265 | + |
| 266 | + // SAFETY: Same as above. |
| 267 | + unsafe { (*stack).next = waiter }; |
| 268 | + |
| 269 | + waiter = stack; |
| 270 | + stack = next; |
| 271 | + } |
| 272 | + } |
| 273 | + } |
| 274 | + |
| 275 | + fn locked_data(&self) -> &UnsafeCell<T> { |
| 276 | + &self.data |
| 277 | + } |
| 278 | +} |
| 279 | + |
| 280 | +struct Waiter { |
| 281 | + completion: Opaque<bindings::completion>, |
| 282 | + next: *mut Waiter, |
| 283 | +} |
0 commit comments