Skip to content

Commit

Permalink
Merge pull request thesofproject#113 from Rust-for-Linux/sync
Browse files Browse the repository at this point in the history
Add `LockedBy` to the `sync` module.
  • Loading branch information
ojeda authored Mar 19, 2021
2 parents 35b9d14 + 9c4b05e commit 8482d37
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 11 deletions.
8 changes: 4 additions & 4 deletions rust/kernel/sync/condvar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ impl CondVar {
/// [`CondVar::notify_all`], or when the thread receives a signal.
///
/// Returns whether there is a signal pending.
pub fn wait<L: Lock>(&self, g: &Guard<L>) -> bool {
let l = g.lock;
pub fn wait<L: Lock>(&self, guard: &Guard<L>) -> bool {
let lock = guard.lock;
let mut wait = MaybeUninit::<bindings::wait_queue_entry>::uninit();

// SAFETY: `wait` points to valid memory.
Expand All @@ -81,12 +81,12 @@ impl CondVar {
}

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

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

l.lock_noguard();
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()) };
Expand Down
6 changes: 1 addition & 5 deletions rust/kernel/sync/guard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,5 @@ pub trait Lock {
unsafe fn unlock(&self);

/// Returns the data protected by the lock.
///
/// # Safety
///
/// It must only be called by the current owner of the lock.
unsafe fn locked_data(&self) -> &core::cell::UnsafeCell<Self::Inner>;
fn locked_data(&self) -> &core::cell::UnsafeCell<Self::Inner>;
}
112 changes: 112 additions & 0 deletions rust/kernel/sync/locked_by.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// SPDX-License-Identifier: GPL-2.0

//! A wrapper for data protected by a lock that does not wrap it.

use super::{Guard, Lock};
use core::{cell::UnsafeCell, ops::Deref, ptr};

/// Allows access to some data to be serialised by a lock that does not wrap it.
///
/// In most cases, data protected by a lock is wrapped by the appropriate lock type, e.g.,
/// [`super::Mutex`] or [`super::SpinLock`]. [`LockedBy`] is meant for cases when this is not
/// possible. For example, if a container has a lock and some data in the contained elements needs
/// to be protected by the same lock.
///
/// [`LockedBy`] wraps the data in lieu of another locking primitive, and only allows access to it
/// when the caller shows evidence that 'external' lock is locked.
///
/// # Example
///
/// The following is an example for illustrative purposes: `InnerDirectory::bytes_used` is an
/// aggregate of all `InnerFile::bytes_used` and must be kept consistent; so we wrap `InnerFile` in
/// a `LockedBy` so that it shares a lock with `InnerDirectory`. This allows us to enforce at
/// compile-time that access to `InnerFile` is only granted when an `InnerDirectory` is also
/// locked; we enforce at run time that the right `InnerDirectory` is locked.
///
/// ```
/// use super::Mutex;
/// use alloc::{string::String, vec::Vec};
///
/// struct InnerFile {
/// bytes_used: u64,
/// }
///
/// struct File {
/// name: String,
/// inner: LockedBy<InnerFile, Mutex<InnerDirectory>>,
/// }
///
/// struct InnerDirectory {
/// /// The sum of the bytes used by all files.
/// bytes_used: u64,
/// files: Vec<File>,
/// }
///
/// struct Directory {
/// name: String,
/// inner: Mutex<InnerDirectory>,
/// }
/// ```
pub struct LockedBy<T: ?Sized, L: Lock + ?Sized> {
owner: *const L::Inner,
data: UnsafeCell<T>,
}

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

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

impl<T, L: Lock + ?Sized> LockedBy<T, L> {
/// Constructs a new instance of [`LockedBy`].
///
/// It stores a raw pointer to the owner that is never dereferenced. It is only used to ensure
/// that the right owner is being used to access the protected data. If the owner is freed, the
/// data becomes inaccessible; if another instance of the owner is allocated *on the same
/// memory location*, the data becomes accessible again: none of this affects memory safety
/// because in any case at most one thread (or CPU) can access the protected data at a time.
pub fn new(owner: &L, data: T) -> Self {
Self {
owner: owner.locked_data().get(),
data: UnsafeCell::new(data),
}
}
}

impl<T: ?Sized, L: Lock + ?Sized> LockedBy<T, L> {
/// Returns a reference to the protected data when the caller provides evidence (via a
/// [`Guard`]) that the owner is locked.
pub fn access<'a>(&'a self, guard: &'a Guard<L>) -> &'a T {
if !ptr::eq(guard.deref(), self.owner) {
panic!("guard does not match owner");
}

// SAFETY: `guard` is evidence that the owner is locked.
unsafe { &mut *self.data.get() }
}

/// Returns a mutable reference to the protected data when the caller provides evidence (via a
/// mutable [`Guard`]) that the owner is locked mutably.
pub fn access_mut<'a>(&'a self, guard: &'a mut Guard<L>) -> &'a mut T {
if !ptr::eq(guard.deref().deref(), self.owner) {
panic!("guard does not match owner");
}

// SAFETY: `guard` is evidence that the owner is locked.
unsafe { &mut *self.data.get() }
}

/// Returns a mutable reference to the protected data when the caller provides evidence (via a
/// mutable owner) that the owner is locked mutably. Showing a mutable reference to the owner
/// is sufficient because we know no other references can exist to it.
pub fn access_from_mut<'a>(&'a self, owner: &'a mut L::Inner) -> &'a mut T {
if !ptr::eq(owner, self.owner) {
panic!("mismatched owners");
}

// SAFETY: `owner` is evidence that there is only one reference to the owner.
unsafe { &mut *self.data.get() }
}
}
2 changes: 2 additions & 0 deletions rust/kernel/sync/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@ use core::pin::Pin;

mod condvar;
mod guard;
mod locked_by;
mod mutex;
mod spinlock;

pub use condvar::CondVar;
pub use guard::{Guard, Lock};
pub use locked_by::LockedBy;
pub use mutex::Mutex;
pub use spinlock::SpinLock;

Expand Down
2 changes: 1 addition & 1 deletion rust/kernel/sync/mutex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ impl<T: ?Sized> Lock for Mutex<T> {
bindings::mutex_unlock(self.mutex.get());
}

unsafe fn locked_data(&self) -> &UnsafeCell<T> {
fn locked_data(&self) -> &UnsafeCell<T> {
&self.data
}
}
2 changes: 1 addition & 1 deletion rust/kernel/sync/spinlock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ impl<T: ?Sized> Lock for SpinLock<T> {
rust_helper_spin_unlock(self.spin_lock.get());
}

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

0 comments on commit 8482d37

Please sign in to comment.