asterinas/kernel/libs/xarray/src/lib.rs

256 lines
8.2 KiB
Rust

// SPDX-License-Identifier: MPL-2.0
//! This crate introduces a RCU-based [`XArray`] implementation.
//!
//! `XArray` is an abstract data type functioning like an expansive array of items
//! where each item is a [`NonNullPtr`], such as `Arc<T>` or `Box<T>`. It facilitates
//! efficient sequential access to adjacent entries, supporting multiple concurrent reads
//! and exclusively allowing one write operation at a time.
//!
//! In addition to directly manipulating the `XArray`, users can typically achieve more
//! flexible operations by creating a [`Cursor`]/[`CursorMut`] within the `XArray`. Since the
//! `XArray` enforces a single write operation at any given time, performing write operations
//! requires first acquiring a [`LockedXArray`] by calling its `lock` method.
//!
//! `XArray` also provides a convenient way to mark individual items (see [`XMark`]).
//!
//! # Example
//!
//! ```
//! use alloc::sync::Arc;
//!
//! use crare::rcu_xarray::*;
//! use crate::task::disable_preempt;
//!
//! let xarray_arc: XArray<Arc<i32>> = XArray::new();
//! let value = Arc::new(10);
//! xarray_arc.lock().store(10, value);
//!
//! let guard = disable_preempt();
//! assert_eq!(*xarray_arc.load(&guard, 10).unwrap().as_ref(), 10);
//!
//! // Usage of the cursor
//!
//! let locked_xarray = xarray_arc.lock();
//! let cursor_mut = locked_xarray.cursor_mut(100);
//!
//! let value = Arc::new(100);
//! cursor_mut.store(value);
//! assert_eq!(cursor_mut.load(10).unwrap().as_ref(), 100);
//! let cursor = xarray_arc.cursor(&guard, 100);
//! assert_eq!(cursor.load(10).unwrap().as_ref(), 100);
//! ```
//!
//! # Background
//!
//! The XArray concept was originally introduced by Linux, which keeps the data structure of
//! [Linux Radix Trees](https://lwn.net/Articles/175432/).
#![no_std]
#![deny(unsafe_code)]
extern crate alloc;
use core::marker::PhantomData;
pub use cursor::{Cursor, CursorMut, SetMarkError};
use entry::NodeEntry;
use mark::NoneMark;
pub use mark::XMark;
use ostd::{
sync::{
non_null::NonNullPtr, LocalIrqDisabled, PreemptDisabled, RcuOption, SpinGuardian, SpinLock,
SpinLockGuard,
},
task::atomic_mode::{AsAtomicModeGuard, InAtomicMode},
};
pub use range::Range;
mod cursor;
mod entry;
mod mark;
mod node;
mod range;
#[cfg(ktest)]
mod test;
const BITS_PER_LAYER: usize = 6;
const SLOT_SIZE: usize = 1 << BITS_PER_LAYER;
const SLOT_MASK: usize = SLOT_SIZE - 1;
/// A RCU-based `XArray` implementation.
///
/// `XArray` is used to store [`NonNullPtr`], with the additional requirement that user-stored
/// pointers must have a minimum alignment of 2 bytes.
///
/// `XArray` is RCU-based, which means:
/// - Multiple concurrent readers are permitted.
/// - Only one writer is allowed at a time.
/// - Simultaneous read operations are allowed while writing.
/// - Readers may see stale data (see [`Cursor`] and [`CursorMut`] for more information).
///
/// Interaction with `XArray` is generally through `Cursor` and `CursorMut`. Similar to
/// XArray's read-write properties, multiple `Cursor`s may coexist (shared read access) and
/// only one `CursorMut` may exist at a time (exclusive write access).
///
/// To create a `Cursor`, users can invoke [`XArray::cursor`] with an atomic-guard.
/// To create a `CursorMut`, users need to call [`XArray::lock`] or [`XArray::lock_irq_disabled`]
/// first to obtain a [`LockedXArray`] first.
///
/// `XArray` enables marking of individual items for user convenience. Items can have up to three
/// distinct marks by default, with each mark independently maintained. Users can use self-defined
/// types as marks by implementing the `From<Type>` trait for [`XMark`]. Marking is also applicable
/// to internal nodes, indicating marked descendant nodes, though such marking is not transparent
/// to users.
pub struct XArray<P, M = NoneMark>
where
P: NonNullPtr + Send + Sync,
{
head: RcuOption<NodeEntry<P>>,
xlock: SpinLock<()>,
_marker: PhantomData<M>,
}
/// A type that marks the [`XArray`] is locked.
#[derive(Clone, Copy)]
struct XLockGuard<'a>(&'a dyn InAtomicMode);
impl<P: NonNullPtr + Send + Sync, M> Default for XArray<P, M> {
fn default() -> Self {
Self::new()
}
}
impl<P: NonNullPtr + Send + Sync, M> XArray<P, M> {
/// Makes a new, empty `XArray`.
pub const fn new() -> Self {
Self {
head: RcuOption::new_none(),
xlock: SpinLock::new(()),
_marker: PhantomData,
}
}
/// Acquires the lock to perform mutable operations.
pub fn lock(&self) -> LockedXArray<P, M> {
LockedXArray {
xa: self,
guard: self.xlock.lock(),
_marker: PhantomData,
}
}
/// Acquires the lock with local IRQs disabled to perform mutable operations.
pub fn lock_irq_disabled(&self) -> LockedXArray<P, M, LocalIrqDisabled> {
LockedXArray {
xa: self,
guard: self.xlock.disable_irq().lock(),
_marker: PhantomData,
}
}
/// Creates a [`Cursor`] to perform read-related operations.
pub fn cursor<'a, G: AsAtomicModeGuard>(
&'a self,
guard: &'a G,
index: u64,
) -> Cursor<'a, P, M> {
Cursor::new(self, guard, index)
}
/// Creates a [`Range`] to immutably iterated over the specified `range`.
pub fn range<'a, G: AsAtomicModeGuard>(
&'a self,
guard: &'a G,
range: core::ops::Range<u64>,
) -> Range<'a, P, M> {
let cursor = self.cursor(guard, range.start);
Range::new(cursor, range.end)
}
/// Loads the `index`-th item.
///
/// If the target item exists, it will be returned with `Some(_)`,
/// otherwise, `None` will be returned.
pub fn load<'a, G: AsAtomicModeGuard>(
&'a self,
guard: &'a G,
index: u64,
) -> Option<P::Ref<'a>> {
let mut cursor = self.cursor(guard, index);
cursor.load()
}
}
impl<P: NonNullPtr + Sync + Send, M> Drop for XArray<P, M> {
fn drop(&mut self) {
self.lock().clear();
}
}
/// The locked [`XArray`] which obtains its inner spinlock.
///
/// The locked `XArray` is able to create `CursorMut` and do mutable operations.
/// There can only be one locked `XArray` at the same time.
pub struct LockedXArray<'a, P, M = NoneMark, G = PreemptDisabled>
where
P: NonNullPtr + Send + Sync,
G: SpinGuardian,
{
xa: &'a XArray<P, M>,
guard: SpinLockGuard<'a, (), G>,
_marker: PhantomData<(P, M)>,
}
impl<P: NonNullPtr + Send + Sync, M, G: SpinGuardian> LockedXArray<'_, P, M, G> {
/// Clears the corresponding [`XArray`].
pub fn clear(&mut self) {
if let Some(head) = self.xa.head.read_with(&self.guard) {
// Having a `LockedXArray` means that the `XArray` is locked.
head.clear_parent(XLockGuard(self.guard.as_atomic_mode_guard()));
}
self.xa.head.update(None);
}
/// Creates a [`CursorMut`] to perform read- and write-related operations.
pub fn cursor_mut(&mut self, index: u64) -> CursorMut<'_, P, M> {
CursorMut::new(self.xa, &self.guard, index)
}
/// Stores the provided item at the target index.
pub fn store(&mut self, index: u64, item: P) {
let mut cursor = self.cursor_mut(index);
cursor.store(item)
}
/// Removes the item at the target index.
///
/// Returns the removed item if some item was previously stored in the same position.
pub fn remove(&mut self, index: u64) -> Option<P::Ref<'_>> {
let mut cursor = self.cursor_mut(index);
cursor.remove()
}
/// Creates a [`Cursor`] to perform read-related operations.
pub fn cursor(&self, index: u64) -> Cursor<'_, P, M> {
Cursor::new(self.xa, &self.guard, index)
}
/// Creates a [`Range`] to immutably iterated over the specified `range`.
pub fn range(&self, range: core::ops::Range<u64>) -> Range<'_, P, M> {
let cursor = self.cursor(range.start);
Range::new(cursor, range.end)
}
/// Loads the `index`-th item.
///
/// If the target item exists, it will be returned with `Some(_)`, otherwise, `None` will be
/// returned.
pub fn load(&self, index: u64) -> Option<P::Ref<'_>> {
let mut cursor = self.cursor(index);
cursor.load()
}
}