Skip to content

Commit

Permalink
sync: re-introduce oro-sync with spinlock and ticket spinlock imple…
Browse files Browse the repository at this point in the history
…mentations
  • Loading branch information
Qix- committed Oct 22, 2024
1 parent d695053 commit 1618725
Show file tree
Hide file tree
Showing 5 changed files with 248 additions and 2 deletions.
2 changes: 1 addition & 1 deletion .cargo/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
9 changes: 8 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ members = [
"oro-dtb",
"oro-dbgutil",
"oro-id",
"oro-sync",
]

[workspace.dependencies]
Expand All @@ -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"
Expand Down
21 changes: 21 additions & 0 deletions oro-sync/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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
216 changes: 216 additions & 0 deletions oro-sync/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<T: Send + 'static> {
/// 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<T: Send + 'static> {
/// The guarded value.
value: UnsafeCell<T>,
/// 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<T: Send + 'static> Sync for Mutex<T> {}

impl<T: Send + 'static> Mutex<T> {
/// 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<T: Send + 'static> Lock<T> for Mutex<T> {
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<T>,
}

impl<T: Send + 'static> Drop for MutexGuard<'_, T> {
fn drop(&mut self) {
self.lock.locked.store(false, Release);
}
}

impl<T: Send + 'static> 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<T: Send + 'static> 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<T: Send + 'static> {
/// The guarded value.
value: UnsafeCell<T>,
/// 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<T: Send + 'static> Sync for TicketMutex<T> {}

impl<T: Send + 'static> TicketMutex<T> {
/// 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<T: Send + 'static> Lock<T> for TicketMutex<T> {
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<T>,
}

impl<T: Send + 'static> Drop for TicketMutexGuard<'_, T> {
fn drop(&mut self) {
self.lock.now_serving.fetch_add(1, Release);
self.lock.locked.store(false, Release);
}
}

impl<T: Send + 'static> 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<T: Send + 'static> 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() }
}
}

0 comments on commit 1618725

Please sign in to comment.