Skip to content

Commit

Permalink
ulib: call arceos_api in axstd (add sync)
Browse files Browse the repository at this point in the history
  • Loading branch information
equation314 committed Aug 6, 2023
1 parent 2304a61 commit 483e561
Show file tree
Hide file tree
Showing 4 changed files with 207 additions and 6 deletions.
1 change: 0 additions & 1 deletion Cargo.lock

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

5 changes: 2 additions & 3 deletions ulib/axstd/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ documentation = "https://rcore-os.github.io/arceos/axstd/index.html"
default = []

# Multicore
smp = ["axfeat/smp"]
smp = ["axfeat/smp", "spinlock/smp"]

# Floating point/SIMD
fp_simd = ["axfeat/fp_simd"]
Expand All @@ -36,7 +36,7 @@ alloc-buddy = ["axfeat/alloc-buddy"]
paging = ["axfeat/paging"]

# Multi-threading and scheduler
multitask = ["arceos_api/multitask", "axsync/multitask", "axfeat/multitask"]
multitask = ["arceos_api/multitask", "axfeat/multitask"]
sched_fifo = ["axfeat/sched_fifo"]
sched_rr = ["axfeat/sched_rr"]
sched_cfs = ["axfeat/sched_cfs"]
Expand Down Expand Up @@ -69,7 +69,6 @@ log-level-trace = ["axfeat/log-level-trace"]

[dependencies]
axfs = { path = "../../modules/axfs", optional = true }
axsync = { path = "../../modules/axsync", optional = true }
axfeat = { path = "../../api/axfeat" }
arceos_api = { path = "../../api/arceos_api" }
axio = { path = "../../crates/axio" }
Expand Down
9 changes: 7 additions & 2 deletions ulib/axstd/src/sync/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ pub use core::sync::atomic;
pub use alloc::sync::{Arc, Weak};

#[cfg(feature = "multitask")]
pub use axsync::{Mutex, MutexGuard};
mod mutex;

#[cfg(feature = "multitask")]
#[doc(cfg(feature = "multitask"))]
pub use self::mutex::{Mutex, MutexGuard};

#[cfg(not(feature = "multitask"))]
pub use spinlock::{SpinNoIrq as Mutex, SpinNoIrqGuard as MutexGuard};
#[doc(cfg(not(feature = "multitask")))]
pub use spinlock::{SpinRaw as Mutex, SpinRawGuard as MutexGuard}; // never used in IRQ context
198 changes: 198 additions & 0 deletions ulib/axstd/src/sync/mutex.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
//! A naïve sleeping mutex.
use core::cell::UnsafeCell;
use core::fmt;
use core::ops::{Deref, DerefMut};
use core::sync::atomic::{AtomicU64, Ordering};

use arceos_api::task::{self as api, AxWaitQueueHandle};

/// A mutual exclusion primitive useful for protecting shared data, similar to
/// [`std::sync::Mutex`](https://doc.rust-lang.org/std/sync/struct.Mutex.html).
///
/// When the mutex is locked, the current task will block and be put into the
/// wait queue. When the mutex is unlocked, all tasks waiting on the queue
/// will be woken up.
pub struct Mutex<T: ?Sized> {
wq: AxWaitQueueHandle,
owner_id: AtomicU64,
data: UnsafeCell<T>,
}

/// A guard that provides mutable data access.
///
/// When the guard falls out of scope it will release the lock.
pub struct MutexGuard<'a, T: ?Sized + 'a> {
lock: &'a Mutex<T>,
data: *mut T,
}

// Same unsafe impls as `std::sync::Mutex`
unsafe impl<T: ?Sized + Send> Sync for Mutex<T> {}
unsafe impl<T: ?Sized + Send> Send for Mutex<T> {}

impl<T> Mutex<T> {
/// Creates a new [`Mutex`] wrapping the supplied data.
#[inline(always)]
pub const fn new(data: T) -> Self {
Self {
wq: AxWaitQueueHandle::new(),
owner_id: AtomicU64::new(0),
data: UnsafeCell::new(data),
}
}

/// Consumes this [`Mutex`] and unwraps the underlying data.
#[inline(always)]
pub fn into_inner(self) -> T {
// We know statically that there are no outstanding references to
// `self` so there's no need to lock.
let Mutex { data, .. } = self;
data.into_inner()
}
}

impl<T: ?Sized> Mutex<T> {
/// Returns `true` if the lock is currently held.
///
/// # Safety
///
/// This function provides no synchronization guarantees and so its result should be considered 'out of date'
/// the instant it is called. Do not use it for synchronization purposes. However, it may be useful as a heuristic.
#[inline(always)]
pub fn is_locked(&self) -> bool {
self.owner_id.load(Ordering::Relaxed) != 0
}

/// Locks the [`Mutex`] and returns a guard that permits access to the inner data.
///
/// The returned value may be dereferenced for data access
/// and the lock will be dropped when the guard falls out of scope.
pub fn lock(&self) -> MutexGuard<T> {
let current_id = api::ax_current_task_id();
loop {
// Can fail to lock even if the spinlock is not locked. May be more efficient than `try_lock`
// when called in a loop.
match self.owner_id.compare_exchange_weak(
0,
current_id,
Ordering::Acquire,
Ordering::Relaxed,
) {
Ok(_) => break,
Err(owner_id) => {
assert_ne!(
owner_id, current_id,
"Thread({}) tried to acquire mutex it already owns.",
current_id,
);
// Wait until the lock looks unlocked before retrying
api::ax_wait_queue_wait(&self.wq, || !self.is_locked(), None);
}
}
}
MutexGuard {
lock: self,
data: unsafe { &mut *self.data.get() },
}
}

/// Try to lock this [`Mutex`], returning a lock guard if successful.
#[inline(always)]
pub fn try_lock(&self) -> Option<MutexGuard<T>> {
let current_id = api::ax_current_task_id();
// The reason for using a strong compare_exchange is explained here:
// https://github.com/Amanieu/parking_lot/pull/207#issuecomment-575869107
if self
.owner_id
.compare_exchange(0, current_id, Ordering::Acquire, Ordering::Relaxed)
.is_ok()
{
Some(MutexGuard {
lock: self,
data: unsafe { &mut *self.data.get() },
})
} else {
None
}
}

/// Force unlock the [`Mutex`].
///
/// # Safety
///
/// This is *extremely* unsafe if the lock is not held by the current
/// thread. However, this can be useful in some instances for exposing
/// the lock to FFI that doesn’t know how to deal with RAII.
pub unsafe fn force_unlock(&self) {
let owner_id = self.owner_id.swap(0, Ordering::Release);
let current_id = api::ax_current_task_id();
assert_eq!(
owner_id, current_id,
"Thread({}) tried to release mutex it doesn't own",
current_id,
);
// wake up one waiting thread.
api::ax_wait_queue_wake(&self.wq, 1);
}

/// Returns a mutable reference to the underlying data.
///
/// Since this call borrows the [`Mutex`] mutably, and a mutable reference is guaranteed to be exclusive in
/// Rust, no actual locking needs to take place -- the mutable borrow statically guarantees no locks exist. As
/// such, this is a 'zero-cost' operation.
#[inline(always)]
pub fn get_mut(&mut self) -> &mut T {
// We know statically that there are no other references to `self`, so
// there's no need to lock the inner mutex.
unsafe { &mut *self.data.get() }
}
}

impl<T: ?Sized + Default> Default for Mutex<T> {
#[inline(always)]
fn default() -> Self {
Self::new(Default::default())
}
}

impl<T: ?Sized + fmt::Debug> fmt::Debug for Mutex<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.try_lock() {
Some(guard) => write!(f, "Mutex {{ data: ")
.and_then(|()| (*guard).fmt(f))
.and_then(|()| write!(f, "}}")),
None => write!(f, "Mutex {{ <locked> }}"),
}
}
}

impl<'a, T: ?Sized> Deref for MutexGuard<'a, T> {
type Target = T;
#[inline(always)]
fn deref(&self) -> &T {
// We know statically that only we are referencing data
unsafe { &*self.data }
}
}

impl<'a, T: ?Sized> DerefMut for MutexGuard<'a, T> {
#[inline(always)]
fn deref_mut(&mut self) -> &mut T {
// We know statically that only we are referencing data
unsafe { &mut *self.data }
}
}

impl<'a, T: ?Sized + fmt::Debug> fmt::Debug for MutexGuard<'a, T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(&**self, f)
}
}

impl<'a, T: ?Sized> Drop for MutexGuard<'a, T> {
/// The dropping of the [`MutexGuard`] will release the lock it was created from.
fn drop(&mut self) {
unsafe { self.lock.force_unlock() }
}
}

0 comments on commit 483e561

Please sign in to comment.