From 1618725966fc8d181bc82dd133ed07c897248952 Mon Sep 17 00:00:00 2001 From: Josh Junon Date: Tue, 22 Oct 2024 16:44:59 -0600 Subject: [PATCH] sync: re-introduce `oro-sync` with spinlock and ticket spinlock implementations --- .cargo/config.toml | 2 +- Cargo.lock | 9 +- Cargo.toml | 2 + oro-sync/Cargo.toml | 21 +++++ oro-sync/src/lib.rs | 216 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 248 insertions(+), 2 deletions(-) create mode 100644 oro-sync/Cargo.toml create mode 100644 oro-sync/src/lib.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index de934be3..7a25966f 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -7,7 +7,7 @@ limine-x86_64 = "build --target oro-arch-x86_64/x86_64-unknown-oro.json --featur limine-aarch64 = "build --target oro-arch-aarch64/aarch64-unknown-oro.json --features oro-debug/pl011 --bin oro-limine-aarch64 -Zunstable-options -Zbuild-std=core,compiler_builtins,alloc -Zbuild-std-features=compiler-builtins-mem" -oro-clippy = "clippy --target ./oro-arch-x86_64/x86_64-unknown-oro.json --target oro-arch-aarch64/aarch64-unknown-oro.json -p oro-boot-protocol -p oro-mem -p oro-debug -p oro-elf -p oro-kernel -p oro-macro -p oro-macro-proc --all-features -Zunstable-options -Zbuild-std=core,compiler_builtins,alloc -Zbuild-std-features=compiler-builtins-mem" +oro-clippy = "clippy --target ./oro-arch-x86_64/x86_64-unknown-oro.json --target oro-arch-aarch64/aarch64-unknown-oro.json -p oro-boot-protocol -p oro-mem -p oro-debug -p oro-elf -p oro-kernel -p oro-macro -p oro-macro-proc -p oro-sync --all-features -Zunstable-options -Zbuild-std=core,compiler_builtins,alloc -Zbuild-std-features=compiler-builtins-mem" oro-clippy-x86_64 = "clippy --target ./oro-arch-x86_64/x86_64-unknown-oro.json --features oro-debug/uart16550 --bin oro-kernel-x86_64 --bin oro-limine-x86_64 --all-features -Zunstable-options -Zbuild-std=core,compiler_builtins,alloc -Zbuild-std-features=compiler-builtins-mem" diff --git a/Cargo.lock b/Cargo.lock index 5d333e6b..0f669786 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "aho-corasick" @@ -346,6 +346,13 @@ dependencies = [ "oro-macro", ] +[[package]] +name = "oro-sync" +version = "0.0.0" +dependencies = [ + "oro-dbgutil", +] + [[package]] name = "oro-type" version = "0.0.0" diff --git a/Cargo.toml b/Cargo.toml index 94f68571..c7149853 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ members = [ "oro-dtb", "oro-dbgutil", "oro-id", + "oro-sync", ] [workspace.dependencies] @@ -48,6 +49,7 @@ oro-type.path = "oro-type" oro-dtb.path = "oro-dtb" oro-dbgutil.path = "oro-dbgutil" oro-id.path = "oro-id" +oro-sync.path = "oro-sync" limine = "0.2.0" uart_16550 = "0.3.0" diff --git a/oro-sync/Cargo.toml b/oro-sync/Cargo.toml new file mode 100644 index 00000000..7ac9ddd9 --- /dev/null +++ b/oro-sync/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "oro-sync" +version.workspace = true +description = "Synchronization primitives for the Oro kernel" +publish = false +edition = "2021" +authors = [ + "Josh Junon (https//github.com/qix-)" +] +homepage = "https://oro.sh" +repository = "https://github.com/oro-os/kernel" +license = "MPL-2.0" + +[lib] +doctest = false + +[dependencies] +oro-dbgutil.workspace = true + +[lints] +workspace = true \ No newline at end of file diff --git a/oro-sync/src/lib.rs b/oro-sync/src/lib.rs new file mode 100644 index 00000000..91ef4ba8 --- /dev/null +++ b/oro-sync/src/lib.rs @@ -0,0 +1,216 @@ +//! Synchronization primitives for the Oro Kernel. +#![cfg_attr(not(test), no_std)] + +use core::{ + cell::UnsafeCell, + ops::{Deref, DerefMut}, + sync::atomic::{ + AtomicBool, AtomicUsize, + Ordering::{AcqRel, Acquire, Relaxed, Release}, + }, +}; + +/// The number of iterations to wait for a stale ticket mutex lock. +const TICKET_MUTEX_TIMEOUT: usize = 1000; + +/// Standardized lock interface implemented for all lock types. +pub trait Lock { + /// The lock guard type used by the lock implementation. + type Guard<'a>: Drop + Deref + DerefMut + where + Self: 'a; + + /// Acquires a lock, blocking until it's available. + fn lock(&self) -> Self::Guard<'_>; +} + +/// A simple unfair, greedy spinlock. The most efficient spinlock +/// available in this library. +pub struct Mutex { + /// The guarded value. + value: UnsafeCell, + /// Whether or not the lock is taken. + locked: AtomicBool, +} + +// SAFETY: We are implementing a safe interface around a mutex so we can assert `Sync`. +unsafe impl Sync for Mutex {} + +impl Mutex { + /// Creates a new spinlock mutex for the given value. + pub const fn new(value: T) -> Self { + Self { + value: UnsafeCell::new(value), + locked: AtomicBool::new(false), + } + } +} + +impl Lock for Mutex { + type Guard<'a> = MutexGuard<'a, T>; + + fn lock(&self) -> Self::Guard<'_> { + loop { + if !self.locked.swap(true, Acquire) { + return MutexGuard { lock: self }; + } + + ::core::hint::spin_loop(); + } + } +} + +/// A mutex guard for the simple [`Mutex`] type. +pub struct MutexGuard<'a, T: Send + 'static> +where + Self: 'a, +{ + /// A reference to the lock for which we have a guard. + lock: &'a Mutex, +} + +impl Drop for MutexGuard<'_, T> { + fn drop(&mut self) { + self.lock.locked.store(false, Release); + } +} + +impl Deref for MutexGuard<'_, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + // SAFETY: We have guaranteed singular access as we're locked. + unsafe { &*self.lock.value.get() } + } +} + +impl DerefMut for MutexGuard<'_, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + // SAFETY: We have guaranteed singular access as we're locked. + unsafe { &mut *self.lock.value.get() } + } +} + +/// A ticketed, fair mutex implementation. +pub struct TicketMutex { + /// The guarded value. + value: UnsafeCell, + /// The currently served ticket. + now_serving: AtomicUsize, + /// The next ticket. + next_ticket: AtomicUsize, + /// Whether or not we've locked the lock. + locked: AtomicBool, +} + +// SAFETY: We are implementing a safe interface around a mutex so we can assert `Sync`. +unsafe impl Sync for TicketMutex {} + +impl TicketMutex { + /// Creates a new ticket mutex. + pub const fn new(value: T) -> Self { + Self { + value: UnsafeCell::new(value), + now_serving: AtomicUsize::new(0), + next_ticket: AtomicUsize::new(0), + locked: AtomicBool::new(false), + } + } +} + +impl Lock for TicketMutex { + type Guard<'a> = TicketMutexGuard<'a, T>; + + fn lock(&self) -> Self::Guard<'_> { + 'new_ticket: loop { + let ticket = self.next_ticket.fetch_add(1, Relaxed); + let mut old_now_serving = self.now_serving.load(Acquire); + + // NOTE(qix-): The wrapping is intentional and desirable. + #[allow(clippy::cast_possible_wrap)] + { + debug_assert!((ticket.wrapping_sub(old_now_serving) as isize) >= 0); + } + + let mut timeout = TICKET_MUTEX_TIMEOUT; + + loop { + let now_serving = self.now_serving.load(Acquire); + + // NOTE(qix-): The wrapping is intentional and desirable. + #[expect(clippy::cast_possible_wrap)] + let position = ticket.wrapping_sub(now_serving) as isize; + + if position == 0 && !self.locked.swap(true, Release) { + return TicketMutexGuard { lock: self }; + } + + if position < 0 { + // We've been forcibly skipped; obtain a new ticket + // and start over. + continue 'new_ticket; + } + + // If the ticket has been advanced, then reset the timeout. + if now_serving != old_now_serving { + old_now_serving = now_serving; + timeout = TICKET_MUTEX_TIMEOUT; + } else if !self.locked.load(Acquire) { + // NOTE(qix-): Only wraps in the case of a logic bug. + // NOTE(qix-): Once we compare-exchange the new ticket number, + // NOTE(qix-): the ticket should be changed on the next iteration, + // NOTE(qix-): resetting the timer. If this invariant is false for + // NOTE(qix-): some reason, then timeout will wrap which will cause + // NOTE(qix-): a debug assertion indicating a bug. + timeout -= 1; + + if timeout == 0 { + // The existing ticket has timed out; forcibly un-deadlock it. + // We don't care about the result here; if another thread already + // updated it, we honor that; otherwise ours is guaranteed to succeed. + let _ = self.now_serving.compare_exchange( + now_serving, + now_serving.wrapping_add(1), + AcqRel, + Relaxed, + ); + } + } + + ::core::hint::spin_loop(); + } + } + } +} + +/// A lock guard for a [`TicketMutex`]. +pub struct TicketMutexGuard<'a, T: Send + 'static> +where + Self: 'a, +{ + /// The lock we are guarding. + lock: &'a TicketMutex, +} + +impl Drop for TicketMutexGuard<'_, T> { + fn drop(&mut self) { + self.lock.now_serving.fetch_add(1, Release); + self.lock.locked.store(false, Release); + } +} + +impl Deref for TicketMutexGuard<'_, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + // SAFETY: We have guaranteed singular access as we're locked. + unsafe { &*self.lock.value.get() } + } +} + +impl DerefMut for TicketMutexGuard<'_, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + // SAFETY: We have guaranteed singular access as we're locked. + unsafe { &mut *self.lock.value.get() } + } +}