diff --git a/Cargo.lock b/Cargo.lock index 859e50f6a..905076383 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1403,6 +1403,15 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rand_xoshiro" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" +dependencies = [ + "rand_core", +] + [[package]] name = "ratio" version = "0.1.0" @@ -1795,6 +1804,18 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "ruxrand" +version = "0.1.0" +dependencies = [ + "crate_interface", + "lazy_init", + "percpu", + "rand", + "rand_xoshiro", + "spinlock", +] + [[package]] name = "ruxruntime" version = "0.1.0" @@ -1816,6 +1837,7 @@ dependencies = [ "ruxfutex", "ruxhal", "ruxnet", + "ruxrand", "ruxtask", "tty", ] @@ -1836,6 +1858,7 @@ dependencies = [ "ruxconfig", "ruxfdtable", "ruxhal", + "ruxrand", "ruxtask", "scheduler", "spinlock", diff --git a/Cargo.toml b/Cargo.toml index c1500efeb..2f381d05b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,6 +50,7 @@ members = [ "modules/ruxruntime", "modules/ruxtask", "modules/ruxfutex", + "modules/ruxrand", "api/ruxfeat", "api/arceos_api", diff --git a/crates/spinlock/Cargo.toml b/crates/spinlock/Cargo.toml index 4cd97499d..855e7fa96 100644 --- a/crates/spinlock/Cargo.toml +++ b/crates/spinlock/Cargo.toml @@ -2,7 +2,10 @@ name = "spinlock" version = "0.1.0" edition = "2021" -authors = ["Yuekai Jia "] +authors = [ + "Yuekai Jia ", + "Igna ", +] description = "`no_std` spin lock implementation that can disable kernel local IRQs or preemption while locking" license = "GPL-3.0-or-later OR Apache-2.0" homepage = "https://github.com/rcore-os/arceos" diff --git a/crates/spinlock/src/base.rs b/crates/spinlock/src/base.rs index 1dd6e5e3a..f6669201a 100644 --- a/crates/spinlock/src/base.rs +++ b/crates/spinlock/src/base.rs @@ -24,17 +24,23 @@ use core::sync::atomic::{AtomicBool, Ordering}; use kernel_guard::BaseGuard; +use crate::{strategy, Strategy}; + +/// The default strategy used in spinlocks. +pub type DefaultStrategy = strategy::Once; + /// A [spin lock](https://en.m.wikipedia.org/wiki/Spinlock) providing mutually /// exclusive access to data. /// /// This is a base struct, the specific behavior depends on the generic /// parameter `G` that implements [`BaseGuard`], such as whether to disable -/// local IRQs or kernel preemption before acquiring the lock. +/// local IRQs or kernel preemption before acquiring the lock. The parameter `S` +/// that implements [`Strategy`] defines the behavior when encountering contention. /// /// For single-core environment (without the "smp" feature), we remove the lock /// state, CPU can always get the lock if we follow the proper guard in use. -pub struct BaseSpinLock { - _phantom: PhantomData, +pub struct BaseSpinLock { + _phantom: PhantomData<(DG, S)>, #[cfg(feature = "smp")] lock: AtomicBool, data: UnsafeCell, @@ -52,10 +58,10 @@ pub struct BaseSpinLockGuard<'a, G: BaseGuard, T: ?Sized + 'a> { } // Same unsafe impls as `std::sync::Mutex` -unsafe impl Sync for BaseSpinLock {} -unsafe impl Send for BaseSpinLock {} +unsafe impl Sync for BaseSpinLock {} +unsafe impl Send for BaseSpinLock {} -impl BaseSpinLock { +impl BaseSpinLock { /// Creates a new [`BaseSpinLock`] wrapping the supplied data. #[inline(always)] pub const fn new(data: T) -> Self { @@ -77,16 +83,20 @@ impl BaseSpinLock { } } -impl BaseSpinLock { - /// Locks the [`BaseSpinLock`] and returns a guard that permits access to the inner data. +impl BaseSpinLock { + /// Locks the [`BaseSpinLock`] using the given guard type and backoff strategy, + /// 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. - #[inline(always)] - pub fn lock(&self) -> BaseSpinLockGuard { - let irq_state = G::acquire(); + pub fn lock_as(&self) -> BaseSpinLockGuard { + let irq_state = GT::acquire(); + #[cfg(feature = "smp")] { + use crate::strategy::{Backoff, Relax}; + + let mut backoff = ::new_backoff(); // Can fail to lock even if the spinlock is not locked. May be more efficient than `try_lock` // when called in a loop. while self @@ -94,9 +104,12 @@ impl BaseSpinLock { .compare_exchange_weak(false, true, Ordering::Acquire, Ordering::Relaxed) .is_err() { + backoff.backoff(); + let mut relax = ::new_relax(); + // Wait until the lock looks unlocked before retrying while self.is_locked() { - core::hint::spin_loop(); + relax.relax(); } } } @@ -109,6 +122,16 @@ impl BaseSpinLock { } } + /// Locks the [`BaseSpinLock`] using the "default" strategy specified by lock type, + /// 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. + #[inline(always)] + pub fn lock(&self) -> BaseSpinLockGuard { + self.lock_as::() + } + /// Returns `true` if the lock is currently held. /// /// # Safety @@ -183,14 +206,14 @@ impl BaseSpinLock { } } -impl Default for BaseSpinLock { +impl Default for BaseSpinLock { #[inline(always)] fn default() -> Self { Self::new(Default::default()) } } -impl fmt::Debug for BaseSpinLock { +impl fmt::Debug for BaseSpinLock { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self.try_lock() { Some(guard) => write!(f, "SpinLock {{ data: ") diff --git a/crates/spinlock/src/lib.rs b/crates/spinlock/src/lib.rs index 2ce00ebaf..32582d720 100644 --- a/crates/spinlock/src/lib.rs +++ b/crates/spinlock/src/lib.rs @@ -16,15 +16,23 @@ //! environment (without this feature), the lock state is unnecessary and //! optimized out. CPU can always get the lock if we follow the proper guard //! in use. By default, this feature is disabled. +//! - `rand`: Provide extra contention-alleviating strategy using exponential +//! backoff algorithm. The user is responsible for providing the random number +//! generator implementation. #![cfg_attr(not(test), no_std)] mod base; -use kernel_guard::{NoOp, NoPreempt, NoPreemptIrqSave}; +/// Defines the strategies used when encountering lock contention. +pub mod strategy; + +use kernel_guard::{NoPreempt, NoPreemptIrqSave}; pub use self::base::{BaseSpinLock, BaseSpinLockGuard}; +pub use self::strategy::*; + /// A spin lock that disables kernel preemption while trying to lock, and /// re-enables it after unlocking. /// @@ -48,7 +56,7 @@ pub type SpinNoIrqGuard<'a, T> = BaseSpinLockGuard<'a, NoPreemptIrqSave, T>; /// /// It must be used in the preemption-disabled and local IRQ-disabled context, /// or never be used in interrupt handlers. -pub type SpinRaw = BaseSpinLock; +pub type SpinRaw = BaseSpinLock; /// A guard that provides mutable data access for [`SpinRaw`]. -pub type SpinRawGuard<'a, T> = BaseSpinLockGuard<'a, NoOp, T>; +pub type SpinRawGuard<'a, T> = BaseSpinLockGuard<'a, kernel_guard::NoOp, T>; diff --git a/crates/spinlock/src/strategy.rs b/crates/spinlock/src/strategy.rs new file mode 100644 index 000000000..a3ec70bc8 --- /dev/null +++ b/crates/spinlock/src/strategy.rs @@ -0,0 +1,161 @@ +/* Copyright (c) [2023] [Syswonder Community] + * [Ruxos] is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +use core::marker::PhantomData; + +// Re-exports extra strategies when feature enabled. +#[cfg(feature = "rand")] +pub use crate::rand_strategy::*; + +#[inline(always)] +fn exp_backoff(current_limit: &mut u32, max: u32) { + let limit = *current_limit; + *current_limit = max.max(limit << 1); + for _ in 0..limit { + core::hint::spin_loop(); + } +} + +/// Defines the backoff behavior of a spinlock. +pub trait Backoff { + /// Backoff behavior when failed to acquire the lock. + fn backoff(&mut self); +} + +/// Defines the relax behavior of a spinlock. +pub trait Relax { + /// Relax behavior when the lock seemed still held. + fn relax(&mut self); +} + +/// Defines the lock behavior when encountering contention. +/// [`Backoff::backoff`] is called when failed to acquire the lock, and +/// [`Relax::relax`]` is called when the lock seemed still held. +/// +/// One can easily define a new [`Strategy`] impl that +/// combines existing backoff/relax behaviors. +pub trait Strategy { + /// The type that defines the relax behavior. + type Relax: Relax; + + /// The type that defines the backoff behavior. + type Backoff: Backoff; + + /// Create a new relax state every time after failed to acquire the lock. + fn new_relax() -> Self::Relax; + + /// Create a new backoff state every time after the locking procedure began. + fn new_backoff() -> Self::Backoff; +} + +impl Strategy for T { + type Relax = T; + type Backoff = T; + + #[inline(always)] + fn new_relax() -> Self::Relax { + T::default() + } + + #[inline(always)] + fn new_backoff() -> Self::Backoff { + T::default() + } +} + +/// Do nothing when backoff/relax is required. +/// It can be used as a baseline, or under rare circumstances be used as a +/// performance improvement. +/// +/// Note that under most modern CPU design, not using any backoff/relax strategy +/// would normally make things slower. +#[derive(Debug, Default)] +pub struct NoOp; + +/// Call [`core::hint::spin_loop`] once when backoff/relax is required. +/// +/// This may improve performance by said, reducing bus traffic. The exact +/// behavior and benefits depend on the machine. +#[derive(Debug, Default)] +pub struct Once; + +/// Call [`core::hint::spin_loop`] with exponentially increased time when +/// backoff/relax is required. +/// +/// This would generally increase performance when the lock is highly contended. +#[derive(Debug)] +pub struct Exp(u32); + +/// Combines a [`Relax`] and a [`Backoff`] into a strategy. +#[derive(Debug, Default)] +pub struct Combine(PhantomData<(R, B)>); + +impl Relax for NoOp { + #[inline(always)] + fn relax(&mut self) {} +} + +impl Backoff for NoOp { + #[inline(always)] + fn backoff(&mut self) {} +} + +impl Relax for Once { + #[inline(always)] + fn relax(&mut self) { + core::hint::spin_loop(); + } +} + +impl Backoff for Once { + #[inline(always)] + fn backoff(&mut self) { + core::hint::spin_loop(); + } +} + +impl Relax for Exp { + #[inline(always)] + fn relax(&mut self) { + exp_backoff(&mut self.0, N); + } +} + +impl Backoff for Exp { + #[inline(always)] + fn backoff(&mut self) { + exp_backoff(&mut self.0, N); + } +} + +impl Default for Exp { + #[inline(always)] + fn default() -> Self { + Self(1) + } +} + +impl Strategy for Combine +where + R: Relax + Default, + B: Backoff + Default, +{ + type Relax = R; + type Backoff = B; + + #[inline(always)] + fn new_relax() -> Self::Relax { + R::default() + } + + #[inline(always)] + fn new_backoff() -> Self::Backoff { + B::default() + } +} diff --git a/modules/ruxrand/Cargo.toml b/modules/ruxrand/Cargo.toml new file mode 100644 index 000000000..85bd04f63 --- /dev/null +++ b/modules/ruxrand/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "ruxrand" +version = "0.1.0" +edition = "2021" +authors = ["Sssssalty Fish "] +description = "RNG support for RuxOS" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/syswonder/ruxos" +repository = "https://github.com/rcore-os/arceos/tree/main/modules/ruxrand" + +[features] +default = [] + +easy-spin = [] + +[dependencies] +crate_interface = "0.1.1" +rand = { version = "0.8.5", default-features = false } +rand_xoshiro = { version = "0.6.0", default-features = false } + +spinlock = { version = "0.1.0", path = "../../crates/spinlock" } +percpu = { version = "0.1.0", path = "../../crates/percpu" } +lazy_init = { version = "0.1.0", path = "../../crates/lazy_init", default-features = false } + +[dev-dependencies] +rand = { version = "0.8.5" } diff --git a/modules/ruxrand/src/lib.rs b/modules/ruxrand/src/lib.rs new file mode 100644 index 000000000..4c3f31380 --- /dev/null +++ b/modules/ruxrand/src/lib.rs @@ -0,0 +1,39 @@ +/* Copyright (c) [2023] [Syswonder Community] + * [Ruxos] is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +//! Runtime library of [Ruxos](https://github.com/syswonder/ruxos). +//! +//! This module provides the implementation of kernel-level random +//! number generators (RNGs), especially the per-CPU RNG type. It also +//! enables the usage of random exponential backoff strategy in spinlocks. +//! +//! # Cargo Features +//! +//! - `easy-spin`: Use a alternate, extremely simple RNG for backoff in +//! spinlocks, instead of the default per-CPU RNG. This may increase +//! performance when the lock is not highly contended. +//! +//! All the features are optional and disabled by default. + +#![cfg_attr(not(test), no_std)] +#![feature(doc_cfg)] +#![feature(doc_auto_cfg)] + +/// Defines the per-CPU RNG. +pub mod rng; + +mod spin_rand; + +pub use rng::{percpu_rng, random, PercpuRng}; +pub use spin_rand::ExpRand; + +/// Initializes the per-CPU RNGs on the given CPU. +pub fn init(cpuid: usize) { + rng::init(cpuid); +} diff --git a/modules/ruxrand/src/rng.rs b/modules/ruxrand/src/rng.rs new file mode 100644 index 000000000..bbd40a60e --- /dev/null +++ b/modules/ruxrand/src/rng.rs @@ -0,0 +1,157 @@ +/* Copyright (c) [2023] [Syswonder Community] + * [Ruxos] is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +use lazy_init::LazyInit; +use percpu::def_percpu; +use rand::{distributions::Standard, prelude::*}; +use rand_xoshiro::Xoshiro256StarStar; + +#[allow(clippy::unusual_byte_groupings)] +const RNG_SEED: u64 = 0xBAD_C0FFEE_0DD_F00D; + +type PercpuRngType = Xoshiro256StarStar; + +#[def_percpu] +pub(crate) static PERCPU_RNG: LazyInit = LazyInit::new(); + +/// Initializes the per-CPU random number generator (RNG). +/// +/// This function seeds the RNG with a hard-coded seed value and then performs a +/// series of long jumps so that the random number sequence on each CPU is guaranteed to +/// not overlap with each other. +/// +/// A single [`PercpuRngType::long_jump`] skips 2^192 random numbers, providing sufficient +/// space for randomness on each CPU. +pub(crate) fn init(cpuid: usize) { + PERCPU_RNG.with_current(|percpu_ref| { + let mut rng = PercpuRngType::seed_from_u64(RNG_SEED); + for _ in 0..cpuid { + rng.long_jump(); + } + + percpu_ref.init_by(rng); + }); +} + +// Rationale for using a raw pointer in `PercpuRng`: +// +// Just like the case in `rand::thread_rng`, there will only +// ever be one mutable reference generated from the mutable pointer, because +// we only have such a reference inside `next_u32`, `next_u64`, etc. Within a +// single processor (which is the definition of `PercpuRng`), there will only ever +// be one of these methods active at a time. +// +// A possible scenario where there could be multiple mutable references is if +// `PercpuRng` is used inside `next_u32` and co. But the implementation is +// completely under our control. We just have to ensure none of them use +// `PercpuRng` internally, which is nonsensical anyway. We should also never run +// `PercpuRng` in destructors of its implementation, which is also nonsensical. +// +// Another possible scenario is that an interrupt happens at the middle of `next_u32` +// or so, and the interrupt handler uses `PercpuRng`. This is indeed a violation of the +// Rust aliasing model, but can hardly lead to any true hazard I think. It can be easily +// fixed by requiring no IRQ using `kernel_guard` when implementing the functions provided +// by `RngCore`. + +/// A RNG wrapper that's local to the calling CPU. The actual RNG type is +/// `PercpuRngType`, which is currently [`Xoshiro256StarStar`]. +/// +/// This type is ! [`Send`] and ! [`Sync`], preventing potential misuse under +/// SMP environments. Construct this type using [`Default::default`], or just +/// a call to [`percpu_rng`]. +#[derive(Clone, Debug)] +pub struct PercpuRng { + rng: *mut PercpuRngType, +} + +impl PercpuRng { + fn get_rng(&mut self) -> &mut PercpuRngType { + unsafe { &mut *self.rng } + } +} + +impl Default for PercpuRng { + fn default() -> Self { + percpu_rng() + } +} + +impl RngCore for PercpuRng { + #[inline(always)] + fn next_u32(&mut self) -> u32 { + self.get_rng().next_u32() + } + + #[inline(always)] + fn next_u64(&mut self) -> u64 { + self.get_rng().next_u64() + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + self.get_rng().fill_bytes(dest) + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> { + self.get_rng().try_fill_bytes(dest) + } +} + +impl CryptoRng for PercpuRng {} + +/// Retrieves the per-CPU RNG [`PercpuRng`] that points to the CPU-local RNG states. +/// +/// The RNGs were initialized by the same seed, but jumped to different locations in +/// the pseudo-random sequence, effectively making the RNG independently and identically +/// distributed on all CPUs. +pub fn percpu_rng() -> PercpuRng { + // It is unsafe to return mutable pointer to a global data structure + // without preemption disabled, but the baddest thing that can happen whatsoever + // here is the rng being put into some "random" state, which I think is not fatal. + let rng = unsafe { PERCPU_RNG.current_ref_mut_raw().get_mut_unchecked() as *mut _ }; + PercpuRng { rng } +} + +/// Generates a random value using the per-CPU random number generator. +/// +/// This is simply a shortcut for `percpu_rng().gen()`. See [`percpu_rng`] for +/// documentation of the entropy source and [`Standard`] for documentation of +/// distributions and type-specific generation. +/// +/// # Provided implementations +/// +/// The following types have provided implementations that +/// generate values with the following ranges and distributions: +/// +/// * Integers (`i32`, `u32`, `isize`, `usize`, etc.): Uniformly distributed +/// over all values of the type. +/// * `char`: Uniformly distributed over all Unicode scalar values, i.e. all +/// code points in the range `0...0x10_FFFF`, except for the range +/// `0xD800...0xDFFF` (the surrogate code points). This includes +/// unassigned/reserved code points. +/// * `bool`: Generates `false` or `true`, each with probability 0.5. +/// * Floating point types (`f32` and `f64`): Uniformly distributed in the +/// half-open range `[0, 1)`. See notes below. +/// * Wrapping integers (`Wrapping`), besides the type identical to their +/// normal integer variants. +/// +/// Also supported is the generation of the following +/// compound types where all component types are supported: +/// +/// * Tuples (up to 12 elements): each element is generated sequentially. +/// * Arrays (up to 32 elements): each element is generated sequentially; +/// see also [`Rng::fill`] which supports arbitrary array length for integer +/// types and tends to be faster for `u32` and smaller types. +/// * `Option` first generates a `bool`, and if true generates and returns +/// `Some(value)` where `value: T`, otherwise returning `None`. +pub fn random() -> T +where + Standard: Distribution, +{ + percpu_rng().gen() +} diff --git a/modules/ruxrand/src/spin_rand.rs b/modules/ruxrand/src/spin_rand.rs new file mode 100644 index 000000000..0d39d0aa0 --- /dev/null +++ b/modules/ruxrand/src/spin_rand.rs @@ -0,0 +1,109 @@ +/* Copyright (c) [2023] [Syswonder Community] + * [Ruxos] is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +use core::{ + fmt::Debug, + sync::atomic::{AtomicUsize, Ordering}, +}; + +use rand::RngCore; +use spinlock::{Backoff, Relax}; + +#[cfg(feature = "easy-spin")] +type SpinRng = EasyRng; + +#[cfg(not(feature = "easy-spin"))] +type SpinRng = crate::rng::PercpuRng; + +#[inline(always)] +fn exp_rand_backoff(current_limit: &mut u32, max: u32) { + let limit = *current_limit; + *current_limit = max.max(limit); + + let mut rng = SpinRng::default(); + // It is more "correct" to use `rng.gen_range(0..limit)`, + // but since `limit` would only be powers of two, a simple + // modulo would also keep the distribution uniform as long + // as `rng.next_u32()` keeps a uniform distribution on `u32`. + let delay = rng.next_u32() % limit; + for _ in 0..delay { + core::hint::spin_loop(); + } +} + +/// Calls [`core::hint::spin_loop`] random times within an exponentially grown limit +/// when backoff/relax is required. The random number is generated using [`RngCore::next_u32`], +/// and the actual rng used is controlled by the `easy-spin` feature. +/// +/// This would generally increase performance when the lock is highly contended. +#[derive(Debug)] +pub struct ExpRand(u32); + +impl Relax for ExpRand { + #[inline(always)] + fn relax(&mut self) { + exp_rand_backoff(&mut self.0, N); + } +} + +impl Backoff for ExpRand { + #[inline(always)] + fn backoff(&mut self) { + exp_rand_backoff(&mut self.0, N); + } +} + +impl Default for ExpRand { + #[inline(always)] + fn default() -> Self { + Self(1) + } +} + +#[derive(Clone, Default)] +struct EasyRng; + +impl Debug for EasyRng { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let state = EASY_RNG_STATE.load(Ordering::Relaxed); + f.debug_struct("EasyRng").field("state", &state).finish() + } +} + +impl RngCore for EasyRng { + fn next_u32(&mut self) -> u32 { + easy_rng() + } + + fn next_u64(&mut self) -> u64 { + easy_rng() as _ + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + dest.fill_with(|| easy_rng() as _) + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> { + dest.fill_with(|| easy_rng() as _); + Ok(()) + } +} + +static EASY_RNG_STATE: AtomicUsize = AtomicUsize::new(0); + +fn easy_rng() -> u32 { + const RANDOM_RANDOM_LIST: [u8; 64] = [ + 9, 7, 13, 0, 15, 2, 14, 1, 14, 14, 11, 3, 13, 11, 12, 10, 3, 6, 8, 1, 2, 0, 12, 12, 13, 2, + 9, 5, 3, 10, 6, 1, 15, 9, 6, 12, 9, 7, 4, 7, 4, 8, 11, 7, 0, 1, 2, 10, 15, 6, 5, 3, 0, 5, + 14, 4, 4, 13, 15, 8, 5, 10, 8, 11, + ]; + + let idx = EASY_RNG_STATE.fetch_add(1, Ordering::Relaxed) % RANDOM_RANDOM_LIST.len(); + RANDOM_RANDOM_LIST[idx] as _ +} diff --git a/modules/ruxruntime/Cargo.toml b/modules/ruxruntime/Cargo.toml index 0555efbaf..dc2e36855 100644 --- a/modules/ruxruntime/Cargo.toml +++ b/modules/ruxruntime/Cargo.toml @@ -23,7 +23,8 @@ alloc = ["axalloc", "dtb"] paging = ["ruxhal/paging", "lazy_init"] rtc = ["ruxhal/rtc"] -multitask = ["ruxtask/multitask", "dep:ruxfutex"] +multitask = ["ruxtask/multitask", "dep:ruxfutex", "rand"] +rand = ["dep:ruxrand"] fs = ["ruxdriver", "ruxfs"] blkfs = ["fs"] virtio-9p = ["fs", "rux9p"] @@ -49,6 +50,7 @@ ruxdisplay = { path = "../ruxdisplay", optional = true } ruxtask = { path = "../ruxtask", optional = true } axsync = { path = "../axsync", optional = true } ruxfutex = { path = "../ruxfutex", optional = true } +ruxrand = { path = "../ruxrand", optional = true } crate_interface = "0.1.1" percpu = { path = "../../crates/percpu", optional = true } diff --git a/modules/ruxruntime/src/lib.rs b/modules/ruxruntime/src/lib.rs index f666eb5a6..73b80371b 100644 --- a/modules/ruxruntime/src/lib.rs +++ b/modules/ruxruntime/src/lib.rs @@ -201,6 +201,9 @@ pub extern "C" fn rust_main(cpu_id: usize, dtb: usize) -> ! { info!("Initialize platform devices..."); ruxhal::platform_init(); + #[cfg(feature = "rand")] + ruxrand::init(cpu_id); + #[cfg(feature = "multitask")] { ruxtask::init_scheduler(); diff --git a/modules/ruxruntime/src/mp.rs b/modules/ruxruntime/src/mp.rs index 7b6025541..c9bdc6444 100644 --- a/modules/ruxruntime/src/mp.rs +++ b/modules/ruxruntime/src/mp.rs @@ -48,6 +48,9 @@ pub extern "C" fn rust_main_secondary(cpu_id: usize) -> ! { ruxhal::platform_init_secondary(); + #[cfg(feature = "rand")] + ruxrand::init(cpu_id); + #[cfg(feature = "multitask")] ruxtask::init_scheduler_secondary(); diff --git a/modules/ruxtask/Cargo.toml b/modules/ruxtask/Cargo.toml index 1b31fef41..0a7c1daa8 100644 --- a/modules/ruxtask/Cargo.toml +++ b/modules/ruxtask/Cargo.toml @@ -2,10 +2,7 @@ name = "ruxtask" version = "0.1.0" edition = "2021" -authors = [ - "Yuekai Jia ", - "AuYang261 ", -] +authors = ["Yuekai Jia ", "AuYang261 "] description = "Ruxos task management module" license = "GPL-3.0-or-later OR Apache-2.0" homepage = "https://github.com/syswonder/ruxos" @@ -15,8 +12,16 @@ repository = "https://github.com/syswonder/ruxos/tree/main/modules/ruxtask" default = [] multitask = [ - "dep:ruxconfig", "dep:percpu", "dep:spinlock", "dep:lazy_init", "dep:memory_addr", - "dep:scheduler", "dep:timer_list", "kernel_guard", "dep:crate_interface", + "dep:ruxconfig", + "dep:ruxrand", + "dep:percpu", + "dep:spinlock", + "dep:lazy_init", + "dep:memory_addr", + "dep:scheduler", + "dep:timer_list", + "dep:crate_interface", + "dep:kernel_guard", ] irq = [] tls = ["ruxhal/tls"] @@ -34,8 +39,10 @@ cfg-if = "1.0" log = "0.4" axerrno = { path = "../../crates/axerrno" } ruxhal = { path = "../ruxhal" } -ruxconfig = { path = "../ruxconfig", optional = true } ruxfdtable = { path = "../ruxfdtable" } +ruxconfig = { path = "../ruxconfig", optional = true } +ruxrand = { path = "../ruxrand", optional = true } + percpu = { path = "../../crates/percpu", optional = true } spinlock = { path = "../../crates/spinlock", optional = true } lazy_init = { path = "../../crates/lazy_init", optional = true } diff --git a/modules/ruxtask/src/run_queue.rs b/modules/ruxtask/src/run_queue.rs index 5e98942de..869363710 100644 --- a/modules/ruxtask/src/run_queue.rs +++ b/modules/ruxtask/src/run_queue.rs @@ -10,16 +10,22 @@ use alloc::collections::VecDeque; use alloc::sync::Arc; use axerrno::{LinuxError, LinuxResult}; +use kernel_guard::NoPreemptIrqSave; use lazy_init::LazyInit; use ruxfdtable::{FD_TABLE, RUX_FILE_LIMIT}; +use ruxrand::ExpRand; use scheduler::BaseScheduler; -use spinlock::SpinNoIrq; +use spinlock::{BaseSpinLock, Combine, SpinNoIrq}; use crate::task::{CurrentTask, TaskState}; use crate::{AxTaskRef, Scheduler, TaskInner, WaitQueue}; +pub(crate) const BACKOFF_LIMIT: u32 = 8; +pub(crate) type DefaultStrategy = Combine, spinlock::NoOp>; +pub(crate) type RQLock = BaseSpinLock; + // TODO: per-CPU -pub(crate) static RUN_QUEUE: LazyInit> = LazyInit::new(); +pub(crate) static RUN_QUEUE: LazyInit> = LazyInit::new(); // TODO: per-CPU static EXITED_TASKS: SpinNoIrq> = SpinNoIrq::new(VecDeque::new()); @@ -34,11 +40,11 @@ pub(crate) struct AxRunQueue { } impl AxRunQueue { - pub fn new() -> SpinNoIrq { + pub fn new() -> RQLock { let gc_task = TaskInner::new(gc_entry, "gc".into(), ruxconfig::TASK_STACK_SIZE); let mut scheduler = Scheduler::new(); scheduler.add_task(gc_task); - SpinNoIrq::new(Self { scheduler }) + RQLock::new(Self { scheduler }) } pub fn add_task(&mut self, task: AxTaskRef) {