Skip to content

Commit

Permalink
rust/kernel/sync: Introduce BoxedMutex and BoxedCondVar
Browse files Browse the repository at this point in the history
Introduce `Boxed` flavours of `Mutex` and `CondVar`.

These flavours do not force users to keep them `Pin`ned. Which
means users no longer need to make complex `Pin` inferences,
or use `unsafe` blocks simply to use `Mutex` or `CondVar`.

Signed-off-by: Sven Van Asbroeck <[email protected]>
  • Loading branch information
Sven Van Asbroeck committed Jun 10, 2021
1 parent 5dd07d5 commit cdc5556
Show file tree
Hide file tree
Showing 4 changed files with 245 additions and 22 deletions.
117 changes: 117 additions & 0 deletions rust/kernel/sync/condvar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
use super::{Guard, Lock, NeedsLockClass};
use crate::bindings;
use crate::str::CStr;
use crate::Result;
use alloc::boxed::Box;
use core::{cell::UnsafeCell, marker::PhantomPinned, mem::MaybeUninit, pin::Pin};

extern "C" {
Expand Down Expand Up @@ -135,3 +137,118 @@ impl NeedsLockClass for CondVar {
unsafe { bindings::__init_waitqueue_head(self.wait_list.get(), name.as_char_ptr(), key) };
}
}

/// Exposes the kernel's [`struct wait_queue_head`] as a condition variable. It allows the caller to
/// atomically release the given lock and go to sleep. It reacquires the lock when it wakes up. And
/// it wakes up when notified by another thread (via [`CondVar::notify_one`] or
/// [`CondVar::notify_all`]) or because the thread received a signal.
///
/// [`struct wait_queue_head`]: ../../../include/linux/wait.h
///
/// # Invariants
///
/// `wait_list` never moves out of its [`Box`].
pub struct BoxedCondVar {
/// A `bindings::wait_queue_head` kernel object.
/// It contains a [`struct list_head`] that is self-referential, so
/// it cannot be safely moved once it is initialised.
/// We guarantee that it will never move, as per the invariant above.
wait_list: Box<UnsafeCell<bindings::wait_queue_head>>,
}

// SAFETY: `CondVar` only uses a `struct wait_queue_head`, which is safe to use on any thread.
unsafe impl Send for BoxedCondVar {}

// SAFETY: `CondVar` only uses a `struct wait_queue_head`, which is safe to use on multiple threads
// concurrently.
unsafe impl Sync for BoxedCondVar {}

impl BoxedCondVar {
/// Constructs a new condition variable.
///
/// # Safety
///
/// `key` must point to a valid memory location as it will be used by the kernel.
pub unsafe fn new_with_key(
name: &'static CStr,
key: *mut bindings::lock_class_key,
) -> Result<Self> {
let cv = Self {
wait_list: Box::try_new(UnsafeCell::new(bindings::wait_queue_head::default()))?,
};
unsafe {
bindings::__init_waitqueue_head(cv.wait_list.get(), name.as_char_ptr(), key);
}
Ok(cv)
}

/// Atomically releases the given lock (whose ownership is proven by the guard) and puts the
/// thread to sleep. It wakes up when notified by [`CondVar::notify_one`] or
/// [`CondVar::notify_all`], or when the thread receives a signal.
///
/// Returns whether there is a signal pending.
#[must_use = "wait returns if a signal is pending, so the caller must check the return value"]
pub fn wait<L: Lock>(&self, guard: &mut Guard<'_, L>) -> bool {
let lock = guard.lock;
let mut wait = MaybeUninit::<bindings::wait_queue_entry>::uninit();

// SAFETY: `wait` points to valid memory.
unsafe { rust_helper_init_wait(wait.as_mut_ptr()) };

// SAFETY: Both `wait` and `wait_list` point to valid memory.
unsafe {
bindings::prepare_to_wait_exclusive(
self.wait_list.get(),
wait.as_mut_ptr(),
bindings::TASK_INTERRUPTIBLE as _,
);
}

// SAFETY: The guard is evidence that the caller owns the lock.
unsafe { lock.unlock() };

// SAFETY: No arguments, switches to another thread.
unsafe { bindings::schedule() };

lock.lock_noguard();

// SAFETY: Both `wait` and `wait_list` point to valid memory.
unsafe { bindings::finish_wait(self.wait_list.get(), wait.as_mut_ptr()) };

super::signal_pending()
}

/// Calls the kernel function to notify the appropriate number of threads with the given flags.
fn notify(&self, count: i32, flags: u32) {
// SAFETY: `wait_list` points to valid memory.
unsafe {
bindings::__wake_up(
self.wait_list.get(),
bindings::TASK_NORMAL,
count,
flags as _,
)
};
}

/// Wakes a single waiter up, if any. This is not 'sticky' in the sense that if no thread is
/// waiting, the notification is lost completely (as opposed to automatically waking up the
/// next waiter).
pub fn notify_one(&self) {
self.notify(1, 0);
}

/// Wakes all waiters up, if any. This is not 'sticky' in the sense that if no thread is
/// waiting, the notification is lost completely (as opposed to automatically waking up the
/// next waiter).
pub fn notify_all(&self) {
self.notify(0, 0);
}

/// Wakes all waiters up. If they were added by `epoll`, they are also removed from the list of
/// waiters. This is useful when cleaning up a condition variable that may be waited on by
/// threads that use `epoll`.
pub fn free_waiters(&self) {
self.notify(1, bindings::POLLHUP | POLLFREE);
}
}
30 changes: 30 additions & 0 deletions rust/kernel/sync/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,11 @@ mod mutex;
mod spinlock;

pub use arc::{Ref, RefCount, RefCounted};
pub use condvar::BoxedCondVar;
pub use condvar::CondVar;
pub use guard::{Guard, Lock};
pub use locked_by::LockedBy;
pub use mutex::BoxedMutex;
pub use mutex::Mutex;
pub use spinlock::SpinLock;

Expand All @@ -58,6 +60,34 @@ macro_rules! init_with_lockdep {
}};
}

#[doc(hidden)]
#[macro_export]
macro_rules! boxed_mutex {
($obj:expr, $name:literal) => {{
static mut CLASS: core::mem::MaybeUninit<$crate::bindings::lock_class_key> =
core::mem::MaybeUninit::uninit();
// SAFETY: `CLASS` is never used by Rust code directly; the kernel may change it though.
#[allow(unused_unsafe)]
unsafe {
$crate::sync::BoxedMutex::new_with_key($obj, $crate::c_str!($name), CLASS.as_mut_ptr())
}
}};
}

#[doc(hidden)]
#[macro_export]
macro_rules! boxed_condvar {
($name:literal) => {{
static mut CLASS: core::mem::MaybeUninit<$crate::bindings::lock_class_key> =
core::mem::MaybeUninit::uninit();
// SAFETY: `CLASS` is never used by Rust code directly; the kernel may change it though.
#[allow(unused_unsafe)]
unsafe {
$crate::sync::BoxedCondVar::new_with_key($crate::c_str!($name), CLASS.as_mut_ptr())
}
}};
}

/// A trait for types that need a lock class during initialisation.
///
/// Implementers of this trait benefit from the [`init_with_lockdep`] macro that generates a new
Expand Down
84 changes: 84 additions & 0 deletions rust/kernel/sync/mutex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
use super::{Guard, Lock, NeedsLockClass};
use crate::bindings;
use crate::str::CStr;
use crate::Result;
use alloc::boxed::Box;
use core::{cell::UnsafeCell, marker::PhantomPinned, pin::Pin};

/// Safely initialises a [`Mutex`] with the given name, generating a new lock class.
Expand Down Expand Up @@ -99,3 +101,85 @@ impl<T: ?Sized> Lock for Mutex<T> {
&self.data
}
}

/// Exposes the kernel's [`struct mutex`]. When multiple threads attempt to lock the same mutex,
/// only one at a time is allowed to progress, the others will block (sleep) until the mutex is
/// unlocked, at which point another thread will be allowed to wake up and make progress.
///
/// Since it may block, [`Mutex`] needs to be used with care in atomic contexts.
///
/// [`struct mutex`]: ../../../include/linux/mutex.h
///
/// # Invariants
///
/// `mutex` never moves out of its [`Box`].
pub struct BoxedMutex<T: ?Sized> {
/// A `struct mutex` kernel object.
/// It contains a [`struct mutex`] that is self-referential, so it
/// cannot be safely moved once it is initialised. We guarantee that
/// it will never move, as per the invariant above.
mutex: Box<UnsafeCell<bindings::mutex>>,

/// The data protected by the mutex.
data: UnsafeCell<T>,
}

// SAFETY: `Mutex` can be transferred across thread boundaries iff the data it protects can.
unsafe impl<T: ?Sized + Send> Send for BoxedMutex<T> {}

// SAFETY: `Mutex` serialises the interior mutability it provides, so it is `Sync` as long as the
// data it protects is `Send`.
unsafe impl<T: ?Sized + Send> Sync for BoxedMutex<T> {}

impl<T> BoxedMutex<T> {
/// Constructs a new mutex.
///
/// # Safety
///
/// `key` must point to a valid memory location as it will be used by the kernel.
pub unsafe fn new_with_key(
t: T,
name: &'static CStr,
key: *mut bindings::lock_class_key,
) -> Result<Self> {
let m = Self {
mutex: Box::try_new(UnsafeCell::new(bindings::mutex::default()))?,
data: UnsafeCell::new(t),
};
unsafe {
bindings::__mutex_init(m.mutex.get(), name.as_char_ptr(), key);
}
Ok(m)
}
}

impl<T: ?Sized> Lock for BoxedMutex<T> {
type Inner = T;

fn lock_noguard(&self) {
// SAFETY: `mutex` points to valid memory.
unsafe {
rust_helper_mutex_lock(self.mutex.get());
}
}

unsafe fn unlock(&self) {
unsafe {
bindings::mutex_unlock(self.mutex.get());
}
}

fn locked_data(&self) -> &UnsafeCell<T> {
&self.data
}
}

impl<T: ?Sized> BoxedMutex<T> {
/// Locks the mutex and gives the caller access to the data protected by it. Only one thread at
/// a time is allowed to access the protected data.
pub fn lock(&self) -> Guard<'_, Self> {
self.lock_noguard();
// SAFETY: The mutex was just acquired.
unsafe { Guard::new(self) }
}
}
36 changes: 14 additions & 22 deletions samples/rust/rust_miscdev.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ use alloc::{boxed::Box, sync::Arc};
use core::pin::Pin;
use kernel::prelude::*;
use kernel::{
c_str,
boxed_condvar, boxed_mutex, c_str,
file::File,
file_operations::{FileOpener, FileOperations},
io_buffer::{IoBufferReader, IoBufferWriter},
miscdev,
sync::{CondVar, Mutex},
sync::{BoxedCondVar, BoxedMutex},
Error,
};

Expand All @@ -33,34 +33,26 @@ struct SharedStateInner {
}

struct SharedState {
state_changed: CondVar,
inner: Mutex<SharedStateInner>,
state_changed: BoxedCondVar,
inner: BoxedMutex<SharedStateInner>,
}

impl SharedState {
fn try_new() -> Result<Pin<Arc<Self>>> {
let state = Arc::try_pin(Self {
// SAFETY: `condvar_init!` is called below.
state_changed: unsafe { CondVar::new() },
// SAFETY: `mutex_init!` is called below.
inner: unsafe { Mutex::new(SharedStateInner { token_count: 0 }) },
})?;
// SAFETY: `state_changed` is pinned behind `Pin<Arc>`.
let state_changed = unsafe { Pin::new_unchecked(&state.state_changed) };
kernel::condvar_init!(state_changed, "SharedState::state_changed");
// SAFETY: `inner` is pinned behind `Pin<Arc>`.
let inner = unsafe { Pin::new_unchecked(&state.inner) };
kernel::mutex_init!(inner, "SharedState::inner");
Ok(state)
fn try_new() -> Result<Arc<Self>> {
let state = Self {
state_changed: boxed_condvar!("SharedState::state_changed")?,
inner: boxed_mutex!(SharedStateInner { token_count: 0 }, "SharedState::inner")?,
};
Ok(Arc::try_new(state)?)
}
}

struct Token {
shared: Pin<Arc<SharedState>>,
shared: Arc<SharedState>,
}

impl FileOpener<Pin<Arc<SharedState>>> for Token {
fn open(shared: &Pin<Arc<SharedState>>) -> Result<Self::Wrapper> {
impl FileOpener<Arc<SharedState>> for Token {
fn open(shared: &Arc<SharedState>) -> Result<Self::Wrapper> {
Ok(Box::try_new(Self {
shared: shared.clone(),
})?)
Expand Down Expand Up @@ -122,7 +114,7 @@ impl FileOperations for Token {
}

struct RustMiscdev {
_dev: Pin<Box<miscdev::Registration<Pin<Arc<SharedState>>>>>,
_dev: Pin<Box<miscdev::Registration<Arc<SharedState>>>>,
}

impl KernelModule for RustMiscdev {
Expand Down

0 comments on commit cdc5556

Please sign in to comment.