Add mutex primitive and replace mutex in Jinux

This commit is contained in:
ClawSeven
2023-04-04 01:24:15 -04:00
committed by Tate, Hongliang Tian
parent 5277886b5d
commit 888853a6de
14 changed files with 281 additions and 212 deletions

View File

@ -90,7 +90,7 @@ impl UserContextApiInternal for UserContext {
call_irq_callback_functions(&self.into_trap_frame()); call_irq_callback_functions(&self.into_trap_frame());
} }
crate::arch::irq::enable_interrupts(); crate::arch::irq::enable_local();
if self.user_context.trap_num as u16 != SYSCALL_TRAPNUM { if self.user_context.trap_num as u16 != SYSCALL_TRAPNUM {
self.trap_information = TrapInformation { self.trap_information = TrapInformation {
cr2: unsafe { x86::controlregs::cr2() }, cr2: unsafe { x86::controlregs::cr2() },

View File

@ -20,6 +20,14 @@ pub(crate) fn init() {
IRQ_LIST.call_once(|| list); IRQ_LIST.call_once(|| list);
} }
pub(crate) fn enable_interrupts() { pub(crate) fn enable_local() {
x86_64::instructions::interrupts::enable(); x86_64::instructions::interrupts::enable();
} }
pub(crate) fn disable_local() {
x86_64::instructions::interrupts::disable();
}
pub(crate) fn is_local_enabled() -> bool {
x86_64::instructions::interrupts::are_enabled()
}

View File

@ -1,8 +1,100 @@
//! CPU. //! CPU.
use crate::trap::disable_local;
use core::cell::UnsafeCell;
use core::ops::Deref;
cfg_if::cfg_if! { cfg_if::cfg_if! {
if #[cfg(target_arch = "x86_64")]{ if #[cfg(target_arch = "x86_64")]{
pub use trapframe::GeneralRegs; pub use trapframe::GeneralRegs;
pub use crate::arch::x86::cpu::*; pub use crate::arch::x86::cpu::*;
} }
} }
/// Defines a CPU-local variable.
///
/// # Example
///
/// ```rust
/// use crate::cpu_local;
/// use core::cell::RefCell;
///
/// cpu_local! {
/// static FOO: RefCell<u32> = RefCell::new(1);
///
/// #[allow(unused)]
/// pub static BAR: RefCell<f32> = RefCell::new(1.0);
/// }
/// CpuLocal::borrow_with(&FOO, |val| {
/// println!("FOO VAL: {:?}", *val);
/// })
///
/// ```
#[macro_export]
macro_rules! cpu_local {
// empty
() => {};
// multiple declarations
($(#[$attr:meta])* $vis:vis static $name:ident: $t:ty = $init:expr; $($rest:tt)*) => {
$(#[$attr])* $vis static $name: CpuLocal<$t> = unsafe { CpuLocal::new($init) };
crate::cpu_local!($($rest)*);
};
// single declaration
($(#[$attr:meta])* $vis:vis static $name:ident: $t:ty = $init:expr) => (
// TODO: reimplement cpu-local variable to support multi-core
$(#[$attr])* $vis static $name: CpuLocal<$t> = CpuLocal::new($init);
);
}
/// CPU-local objects.
///
/// A CPU-local object only gives you immutable references to the underlying value.
/// To mutate the value, one can use atomic values (e.g., `AtomicU32`) or internally mutable
/// objects (e.g., `RefCell`).
///
/// The `CpuLocal<T: Sync>` can be used directly.
/// Otherwise, the `CpuLocal<T>` must be used through `CpuLocal::borrow_with`.
///
/// TODO: re-implement `CpuLocal`
pub struct CpuLocal<T>(UnsafeCell<T>);
// Safety. At any given time, only one task can access the inner value T of a cpu-local variable.
unsafe impl<T> Sync for CpuLocal<T> {}
impl<T> CpuLocal<T> {
/// Initialize CPU-local object
/// Developer cannot construct a valid CpuLocal object arbitrarily
pub const unsafe fn new(val: T) -> Self {
Self(UnsafeCell::new(val))
}
/// Borrow an immutable reference to the underlying value and feed it to a closure.
///
/// During the execution of the closure, local IRQs are disabled. This ensures that
/// the CPU-local object is only accessed by the current task or IRQ handler.
/// As local IRQs are disabled, one should keep the closure as short as possible.
pub fn borrow_with<U, F: FnOnce(&T) -> U>(this: &Self, f: F) -> U {
// FIXME: implement disable preemption
// Disable interrupts when accessing cpu-local variable
let _guard = disable_local();
// Safety. Now that the local IRQs are disabled, this CPU-local object can only be
// accessed by the current task/thread. So it is safe to get its immutable reference
// regardless of whether `T` implements `Sync` or not.
let val_ref = unsafe { this.do_borrow() };
f(val_ref)
}
unsafe fn do_borrow(&self) -> &T {
&*self.0.get()
}
}
impl<T: Sync> Deref for CpuLocal<T> {
type Target = T;
fn deref(&self) -> &T {
unsafe { &*self.0.get() }
}
}

View File

@ -29,6 +29,7 @@ pub mod user;
mod util; mod util;
pub mod vm; pub mod vm;
pub use self::cpu::CpuLocal;
pub use self::error::Error; pub use self::error::Error;
pub use self::prelude::Result; pub use self::prelude::Result;
use alloc::vec::Vec; use alloc::vec::Vec;

View File

@ -1,95 +0,0 @@
use super::percpu::PerCpu;
use crate::cpu_local;
use core::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use x86_64::instructions::interrupts;
/// Disable all IRQs on the current CPU (i.e., locally).
///
/// This function returns a guard object, which will automatically enable local IRQs again when
/// it is dropped. This function works correctly even when it is called in a _nested_ way.
/// The local IRQs shall only be re-enabled when the most outer guard is dropped.
///
/// This function can play nicely with `SpinLock` as the type uses this function internally.
/// One can invoke this function even after acquiring a spin lock. And the reversed order is also ok.
///
/// # Example
///
/// ``rust
/// use jinux_frame::irq;
///
/// {
/// let _ = irq::disable_local();
/// todo!("do something when irqs are disabled");
/// }
/// ```
#[must_use]
pub fn disable_local() -> DisabledLocalIrqGuard {
DisabledLocalIrqGuard::new()
}
/// A guard for disabled local IRQs.
pub struct DisabledLocalIrqGuard {
// Having a private field prevents user from constructing values of this type directly.
private: (),
}
impl !Send for DisabledLocalIrqGuard {}
cpu_local! {
static IRQ_OFF_COUNT: IrqInfo = IrqInfo::new();
}
impl DisabledLocalIrqGuard {
fn new() -> Self {
IRQ_OFF_COUNT.borrow().inc();
Self { private: () }
}
}
impl Drop for DisabledLocalIrqGuard {
fn drop(&mut self) {
IRQ_OFF_COUNT.borrow().dec();
}
}
#[derive(Debug, Default)]
struct IrqInfo {
// number of interrupt counterpart
off_num: AtomicU32,
// interrupt state before calling dec()/inc()
interrupt_enable: AtomicBool,
}
impl IrqInfo {
const fn new() -> Self {
Self {
off_num: AtomicU32::new(0),
interrupt_enable: AtomicBool::new(false),
}
}
fn inc(&self) {
let enabled = interrupts::are_enabled();
let off_num = self.off_num.load(Ordering::Relaxed);
if off_num == 0 {
self.interrupt_enable.store(enabled, Ordering::Relaxed);
}
if enabled {
interrupts::disable();
}
self.off_num.fetch_add(1, Ordering::Relaxed);
}
fn dec(&self) {
let off_num = self.off_num.load(Ordering::Relaxed);
if off_num < 1 {
// disable_interrupt/inc() should be called before enable_interrupt/dec()
panic!("The enable_interrupt and disable_interrupt are the counterpart");
}
let update_num = self.off_num.fetch_sub(1, Ordering::Relaxed) - 1;
if update_num == 0 && self.interrupt_enable.load(Ordering::Relaxed) {
interrupts::enable();
}
}
}

View File

@ -1,9 +1,11 @@
mod atomic_bits; mod atomic_bits;
mod mutex;
mod rcu; mod rcu;
mod spin; mod spin;
mod wait; mod wait;
pub use self::atomic_bits::AtomicBits; pub use self::atomic_bits::AtomicBits;
pub use self::mutex::{Mutex, MutexGuard};
pub use self::rcu::{pass_quiescent_state, OwnerPtr, Rcu, RcuReadGuard, RcuReclaimer}; pub use self::rcu::{pass_quiescent_state, OwnerPtr, Rcu, RcuReadGuard, RcuReclaimer};
pub use self::spin::{SpinLock, SpinLockGuard}; pub use self::spin::{SpinLock, SpinLockGuard};
pub use self::wait::WaitQueue; pub use self::wait::WaitQueue;

View File

@ -0,0 +1,60 @@
use super::spin::{SpinLock, SpinLockGuard};
use core::ops::{Deref, DerefMut};
use core::fmt;
pub struct Mutex<T> {
inner: SpinLock<T>,
}
impl<T> Mutex<T> {
#[inline(always)]
pub const fn new(val: T) -> Self {
Self {
inner: SpinLock::new(val),
}
}
pub fn lock(&self) -> MutexGuard<T> {
MutexGuard {
lock: self.inner.lock(),
}
}
}
impl<T: fmt::Debug> fmt::Debug for Mutex<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(&self.inner, f)
}
}
unsafe impl<T: Send> Send for Mutex<T> {}
unsafe impl<T: Send> Sync for Mutex<T> {}
pub struct MutexGuard<'a, T> {
lock: SpinLockGuard<'a, T>,
}
impl<'a, T> Deref for MutexGuard<'a, T> {
type Target = T;
fn deref(&self) -> &T {
self.lock.deref()
}
}
impl<'a, T> DerefMut for MutexGuard<'a, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.lock.deref_mut()
}
}
impl<'a, T: 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> !Send for MutexGuard<'a, T> {}
unsafe impl<T: Sync> Sync for MutexGuard<'_, T> {}

View File

@ -1,96 +0,0 @@
use crate::sync::disable_local;
use core::cell::UnsafeCell;
/// Defines a CPU-local variable.
/// # Example
///
/// ```rust
/// use crate::cpu_local;
/// use core::cell::RefCell;
/// {
/// cpu_local! {
/// static FOO: RefCell<u32> = RefCell::new(1);
///
/// #[allow(unused)]
/// pub static BAR: RefCell<f32> = RefCell::new(1.0);
/// }
///
/// FOO.borrow_with(|val| {
/// println!("FOO VAL: {:?}", *val);
/// });
/// }
/// ```
#[macro_export]
macro_rules! cpu_local {
// empty
() => {};
($(#[$attr:meta])* $vis:vis static $name:ident: $t:ty = const { $init:expr }; $($rest:tt)*) => {
$(#[$attr])* $vis static $name: PerCpu<$t> = PerCpu::new(const $init);
crate::cpu_local!($($rest)*);
};
($(#[$attr:meta])* $vis:vis static $name:ident: $t:ty = const { $init:expr }) => (
$(#[$attr])* $vis static $name: PerCpu<$t> = PerCpu::new(const $init);
);
// multiple declarations
($(#[$attr:meta])* $vis:vis static $name:ident: $t:ty = $init:expr; $($rest:tt)*) => {
$(#[$attr])* $vis static $name: PerCpu<$t> = PerCpu::new($init);
crate::cpu_local!($($rest)*);
};
// single declaration
($(#[$attr:meta])* $vis:vis static $name:ident: $t:ty = $init:expr) => (
// TODO: reimplement per-cpu variable to support multi-core
$(#[$attr])* $vis static $name: PerCpu<$t> = PerCpu::new($init);
);
}
/// Per-CPU objects.
///
/// A per-CPU object only gives you immutable references to the underlying value.
/// To mutate the value, one can use atomic values (e.g., `AtomicU32`) or internally mutable
/// objects (e.g., `RefCell`).
///
/// TODO: reimplement PerCpu inner UnsafeCell with access function as thread_local! macro,
/// because different CPUs access static per-cpu variables in different positions.
pub struct PerCpu<T>(UnsafeCell<T>);
// safety
unsafe impl<T> Sync for PerCpu<T> {}
impl<T> PerCpu<T> {
/// Initialize per-CPU object
/// unsafe
pub const fn new(val: T) -> Self {
Self(UnsafeCell::new(val))
}
/// Borrow an immutable reference to the underlying value and feed it to a closure.
///
/// During the execution of the closure, local IRQs are disabled. This ensures that
/// the per-CPU object is only accessed by the current task or IRQ handler.
/// As local IRQs are disabled, one should keep the closure as short as possible.
pub fn borrow_with<U, F: FnOnce(&T) -> U>(&self, f: F) -> U {
// FIXME: implement disable preemption
// Disable interrupts when accessing per-cpu variable
let _guard = disable_local();
// Safety. Now that the local IRQs are disabled, this per-CPU object can only be
// accessed by the current task/thread. So it is safe to get its immutable reference
// regardless of whether `T` implements `Sync` or not.
let val_ref = unsafe { self.do_borrow() };
f(val_ref)
}
unsafe fn do_borrow(&self) -> &T {
&*self.0.get()
}
}
impl<T: Sync> PerCpu<T> {
/// Gets an immutable reference to the value inside the per-CPU object.
pub fn borrow(&self) -> &T {
// Safety. Since the value of `T` is `Sync`, it is ok for multiple tasks or IRQ handlers
// executing on the current CPU to have immutable references.
unsafe { self.do_borrow() }
}
}

View File

@ -1,13 +1,10 @@
use core::cell::UnsafeCell; use core::cell::UnsafeCell;
use core::sync::atomic::Ordering;
use core::{
ops::{Deref, DerefMut},
sync::atomic::AtomicBool,
};
use crate::sync::disable_local;
use crate::sync::irq::DisabledLocalIrqGuard;
use core::fmt; use core::fmt;
use core::ops::{Deref, DerefMut};
use core::sync::atomic::{AtomicBool, Ordering};
use crate::trap::disable_local;
use crate::trap::DisabledLocalIrqGuard;
/// A spin lock. /// A spin lock.
pub struct SpinLock<T> { pub struct SpinLock<T> {
@ -31,21 +28,35 @@ impl<T> SpinLock<T> {
pub fn lock(&self) -> SpinLockGuard<T> { pub fn lock(&self) -> SpinLockGuard<T> {
// FIXME: add disable_preemption // FIXME: add disable_preemption
let guard = disable_local(); let guard = disable_local();
self.access_lock(); self.acquire_lock();
SpinLockGuard { SpinLockGuard {
lock: &self, lock: &self,
irq_guard: guard, irq_guard: guard,
} }
} }
/// Try Acquire the spin lock immedidately.
pub fn try_lock(&self) -> Option<SpinLockGuard<T>> {
// FIXME: add disable_preemption
let irq_guard = disable_local();
if self.try_acquire_lock() {
let lock_guard = SpinLockGuard {
lock: &self,
irq_guard,
};
return Some(lock_guard);
}
return None;
}
/// Access the spin lock, otherwise busy waiting /// Access the spin lock, otherwise busy waiting
fn access_lock(&self) { fn acquire_lock(&self) {
while !self.try_access_lock() { while !self.try_acquire_lock() {
core::hint::spin_loop(); core::hint::spin_loop();
} }
} }
fn try_access_lock(&self) -> bool { fn try_acquire_lock(&self) -> bool {
self.lock self.lock
.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst) .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
.is_ok() .is_ok()
@ -98,7 +109,6 @@ impl<'a, T: fmt::Debug> fmt::Debug for SpinLockGuard<'a, T> {
} }
} }
// SpinLockGuard cannot be sent between tasks/threads
impl<'a, T> !Send for SpinLockGuard<'a, T> {} impl<'a, T> !Send for SpinLockGuard<'a, T> {}
// Safety. SpinLockGuard can be shared between tasks/threads in same CPU. // Safety. SpinLockGuard can be shared between tasks/threads in same CPU.

View File

@ -1,10 +1,13 @@
use crate::arch::irq;
use crate::arch::irq::{IRQ_LIST, NOT_USING_IRQ}; use crate::arch::irq::{IRQ_LIST, NOT_USING_IRQ};
use crate::cpu::CpuLocal;
use crate::cpu_local;
use crate::util::recycle_allocator::RecycleAllocator;
use crate::{prelude::*, Error}; use crate::{prelude::*, Error};
use crate::util::recycle_allocator::RecycleAllocator;
use core::fmt::Debug; use core::fmt::Debug;
use core::sync::atomic::{AtomicBool, AtomicU32, Ordering::Relaxed};
use spin::{Mutex, MutexGuard}; use spin::{Mutex, MutexGuard};
// use crate::sync::{Mutex, MutexGuard};
use trapframe::TrapFrame; use trapframe::TrapFrame;
pub fn allocate_irq() -> Result<IrqAllocateHandle> { pub fn allocate_irq() -> Result<IrqAllocateHandle> {
@ -167,3 +170,88 @@ impl Drop for IrqCallbackHandle {
ID_ALLOCATOR.lock().dealloc(self.id); ID_ALLOCATOR.lock().dealloc(self.id);
} }
} }
/// Disable all IRQs on the current CPU (i.e., locally).
///
/// This function returns a guard object, which will automatically enable local IRQs again when
/// it is dropped. This function works correctly even when it is called in a _nested_ way.
/// The local IRQs shall only be re-enabled when the most outer guard is dropped.
///
/// This function can play nicely with `SpinLock` as the type uses this function internally.
/// One can invoke this function even after acquiring a spin lock. And the reversed order is also ok.
///
/// # Example
///
/// ``rust
/// use jinux_frame::irq;
///
/// {
/// let _ = irq::disable_local();
/// todo!("do something when irqs are disabled");
/// }
/// ```
#[must_use]
pub fn disable_local() -> DisabledLocalIrqGuard {
DisabledLocalIrqGuard::new()
}
/// A guard for disabled local IRQs.
pub struct DisabledLocalIrqGuard {
// Having a private field prevents user from constructing values of this type directly.
private: (),
}
impl !Send for DisabledLocalIrqGuard {}
cpu_local! {
static IRQ_OFF_COUNT: IrqInfo = IrqInfo::new();
}
impl DisabledLocalIrqGuard {
fn new() -> Self {
IRQ_OFF_COUNT.inc();
Self { private: () }
}
}
impl Drop for DisabledLocalIrqGuard {
fn drop(&mut self) {
IRQ_OFF_COUNT.dec();
}
}
#[derive(Debug, Default)]
struct IrqInfo {
// interrupt disabling level
level: AtomicU32,
// interrupt state before calling dec()/inc()
is_irq_enabled: AtomicBool,
}
impl IrqInfo {
const fn new() -> Self {
Self {
level: AtomicU32::new(0),
is_irq_enabled: AtomicBool::new(false),
}
}
fn inc(&self) {
let enabled = irq::is_local_enabled();
let level = self.level.load(Relaxed);
if level == 0 {
self.is_irq_enabled.store(enabled, Relaxed);
}
if enabled {
irq::disable_local();
}
self.level.fetch_add(1, Relaxed);
}
fn dec(&self) {
let level = self.level.fetch_sub(1, Relaxed) - 1;
if level == 0 && self.is_irq_enabled.load(Relaxed) {
irq::enable_local();
}
}
}

View File

@ -2,7 +2,7 @@ mod handler;
mod irq; mod irq;
pub(crate) use self::handler::call_irq_callback_functions; pub(crate) use self::handler::call_irq_callback_functions;
pub use self::irq::{allocate_irq, IrqAllocateHandle}; pub use self::irq::{allocate_irq, disable_local, DisabledLocalIrqGuard, IrqAllocateHandle};
pub(crate) use self::irq::{allocate_target_irq, IrqCallbackHandle, IrqLine}; pub(crate) use self::irq::{allocate_target_irq, IrqCallbackHandle, IrqLine};
pub use trapframe::TrapFrame; pub use trapframe::TrapFrame;

View File

@ -1,7 +1,6 @@
use crate::prelude::*; use crate::prelude::*;
use alloc::string::String; use alloc::string::String;
use core::time::Duration; use core::time::Duration;
use spin::MutexGuard;
use super::{InodeMode, InodeType, Metadata, Vnode, NAME_MAX}; use super::{InodeMode, InodeType, Metadata, Vnode, NAME_MAX};

View File

@ -15,11 +15,12 @@ pub(crate) use alloc::vec::Vec;
pub(crate) use bitflags::bitflags; pub(crate) use bitflags::bitflags;
pub(crate) use core::ffi::CStr; pub(crate) use core::ffi::CStr;
pub(crate) use jinux_frame::config::PAGE_SIZE; pub(crate) use jinux_frame::config::PAGE_SIZE;
pub(crate) use jinux_frame::sync::{Mutex, MutexGuard};
pub(crate) use jinux_frame::vm::Vaddr; pub(crate) use jinux_frame::vm::Vaddr;
pub(crate) use jinux_frame::{print, println}; pub(crate) use jinux_frame::{print, println};
pub(crate) use log::{debug, error, info, trace, warn}; pub(crate) use log::{debug, error, info, trace, warn};
pub(crate) use pod::Pod; pub(crate) use pod::Pod;
pub(crate) use spin::{Mutex, RwLock}; pub(crate) use spin::RwLock;
/// return current process /// return current process
#[macro_export] #[macro_export]

View File

@ -15,7 +15,6 @@ mod static_cap;
pub use options::{VmoChildOptions, VmoOptions}; pub use options::{VmoChildOptions, VmoOptions};
pub use pager::Pager; pub use pager::Pager;
use spin::Mutex;
/// Virtual Memory Objects (VMOs) are a type of capability that represents a /// Virtual Memory Objects (VMOs) are a type of capability that represents a
/// range of memory pages. /// range of memory pages.