From 7ab1d2a310974d05389c9012d5b4ab88efd8af5e Mon Sep 17 00:00:00 2001 From: Yuekai Jia Date: Thu, 8 Aug 2024 13:18:28 +0800 Subject: [PATCH] [task]: Introduce namespaces io manage system resources --- Cargo.lock | 10 + Cargo.toml | 2 + modules/axns/Cargo.toml | 25 +++ modules/axns/src/lib.rs | 279 ++++++++++++++++++++++++ modules/axns/tests/test_global.rs | 71 ++++++ modules/axns/tests/test_thread_local.rs | 85 ++++++++ 6 files changed, 472 insertions(+) create mode 100644 modules/axns/Cargo.toml create mode 100644 modules/axns/src/lib.rs create mode 100644 modules/axns/tests/test_global.rs create mode 100644 modules/axns/tests/test_thread_local.rs diff --git a/Cargo.lock b/Cargo.lock index b9a7e7b0cd..28948ce37b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -464,6 +464,16 @@ dependencies = [ "spin", ] +[[package]] +name = "axns" +version = "0.1.0" +dependencies = [ + "axns", + "crate_interface", + "lazyinit", + "log", +] + [[package]] name = "axruntime" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 5ba69b3152..3b055a3975 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ members = [ "modules/axmm", "modules/axdma", "modules/axnet", + "modules/axns", "modules/axruntime", "modules/axsync", "modules/axtask", @@ -57,6 +58,7 @@ axhal = { path = "modules/axhal" } axlog = { path = "modules/axlog" } axmm = { path = "modules/axmm" } axnet = { path = "modules/axnet" } +axns = { path = "modules/axns" } axruntime = { path = "modules/axruntime" } axsync = { path = "modules/axsync" } axtask = { path = "modules/axtask" } diff --git a/modules/axns/Cargo.toml b/modules/axns/Cargo.toml new file mode 100644 index 0000000000..ba2b0b8a09 --- /dev/null +++ b/modules/axns/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "axns" +version.workspace = true +edition = "2021" +authors = ["Yuekai Jia "] +description = "ArceOS namespaces to control system resource sharing between threads" +license.workspace = true +homepage.workspace = true +repository = "https://github.com/arceos-org/arceos/tree/main/modules/axns" +documentation = "https://arceos-org.github.io/arceos/axns/index.html" + +[features] +default = [] + +# Each thread has its individual namespace field, instead of using the global +# namespace. +thread-local = [] + +[dependencies] +log = "0.4" +lazyinit = "0.2" +crate_interface = "0.1" + +[dev-dependencies] +axns = { workspace = true, features = ["thread-local"] } diff --git a/modules/axns/src/lib.rs b/modules/axns/src/lib.rs new file mode 100644 index 0000000000..1a9ed92834 --- /dev/null +++ b/modules/axns/src/lib.rs @@ -0,0 +1,279 @@ +//! [ArceOS](https://github.com/arceos-org/arceos) namespaces module. +//! +//! Namespaces are used to control system resource sharing between threads. This +//! module provides a unified interface to access system resources in different +//! scenarios. +//! +//! For a unikernel, there is only one global namespace, so all threads share +//! the same system resources, such as virtual address space, working directory, +//! and file descriptors, etc. +//! +//! For a monolithic kernel, each process corresponds to a namespace, all +//! threads in the same process share the same system resources. Different +//! processes have different namespaces and isolated resources. +//! +//! For further container support, some global system resources can also be +//! grouped into a namespace. +//! +//! See the examples of [`def_resource!`] for more usage. + +#![cfg_attr(not(test), no_std)] + +extern crate alloc; + +use alloc::sync::Arc; +use core::{alloc::Layout, fmt, ops::Deref}; + +use lazyinit::LazyInit; + +extern "C" { + fn __start_axns_resource(); + fn __stop_axns_resource(); +} + +/// A namespace that contains all user-defined resources. +/// +/// There are two types of namespaces: +/// +/// - Global namespace: this namespace is globally unique and all threads share +/// the resources in it. Resources are statically collected into the +/// `axns_resource` section, and the global namespace is constructed by the base +/// address of the section ([`AxNamespace::global`]). +/// - Thread-local namespace: this namespace is per-thread, each thread should +/// call [`AxNamespace::new_thread_local()`] to allocate a memory area to store +/// its own resources. Layout of resources in global and thread-local namespaces +/// is consistent. Each namespace has its own resources, which may be unique or +/// shared between threads by the [`Arc`] wrapper. +pub struct AxNamespace { + base: *mut u8, + alloc: bool, +} + +impl AxNamespace { + /// Returns the base address of the namespace, which points to the start of + /// all resources. + pub const fn base(&self) -> *mut u8 { + self.base + } + + /// Returns the size of the namespace (size of all resources). + pub fn size(&self) -> usize { + Self::section_size() + } + + /// Returns the size of the `axns_resource` section. + fn section_size() -> usize { + __stop_axns_resource as usize - __start_axns_resource as usize + } + + /// Returns the global namespace. + pub fn global() -> Self { + Self { + base: __start_axns_resource as *mut u8, + alloc: false, + } + } + + /// Constructs a new thread-local namespace. + /// + /// Each thread can have its own namespace instead of the global one, to + /// isolate resources between threads. + /// + /// This function allocates a memory area to store the thread-local resources, + /// and copies from the global namespace as the initial value. + #[cfg(feature = "thread-local")] + pub fn new_thread_local() -> Self { + let size = Self::section_size(); + let base = if size == 0 { + core::ptr::null_mut() + } else { + let layout = Layout::from_size_align(size, 64).unwrap(); + let dst = unsafe { alloc::alloc::alloc(layout) }; + let src = __start_axns_resource as *const u8; + unsafe { core::ptr::copy_nonoverlapping(src, dst, size) }; + dst + }; + Self { base, alloc: true } + } +} + +impl Drop for AxNamespace { + fn drop(&mut self) { + if self.alloc { + let size = Self::section_size(); + if size != 0 && !self.base.is_null() { + let layout = Layout::from_size_align(size, 64).unwrap(); + unsafe { alloc::alloc::dealloc(self.base, layout) }; + } + } + } +} + +/// A helper type to easily manage shared resources. +/// +/// It provides methods to lazily initialize the resource of the current thread, +/// or to share the resource with other threads. +pub struct AxResource(LazyInit>); + +impl AxResource { + /// Creates a new uninitialized resource. + pub const fn new() -> Self { + Self(LazyInit::new()) + } + + /// Returns a shared reference to the resource. + pub fn share(&self) -> Arc { + self.0.deref().clone() + } + + /// Initializes the resource and does not share with others. + pub fn init_new(&self, data: T) { + self.0.init_once(Arc::new(data)); + } + + /// Initializes the resource with the shared data. + pub fn init_shared(&self, data: Arc) { + self.0.init_once(data); + } + + /// Checks whether the value is initialized. + pub fn is_inited(&self) -> bool { + self.0.is_inited() + } +} + +impl Deref for AxResource { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.0.deref() + } +} + +impl fmt::Debug for AxResource { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + +/// The interfaces need to be implemented when enable thread-local namespaces. +#[cfg(feature = "thread-local")] +#[crate_interface::def_interface] +pub trait AxNamespaceIf { + /// Returns the pointer to the current namespace. + /// + /// It usually needs to be obtained from the thread local storage. + fn current_namespace_base() -> *mut u8; +} + +/// Returns the pointer to the current namespace. +/// +/// When `thread-local` feature is enabled, it returns the thread-local namespace +/// of the current thread. Otherwise, it returns the global namespace. +/// +/// # Safety +/// +/// This function is unsafe, the returned pointer should not outlive the current +/// thread. +pub unsafe fn current_namespace_base() -> *mut u8 { + #[cfg(feature = "thread-local")] + { + crate_interface::call_interface!(AxNamespaceIf::current_namespace_base) + } + #[cfg(not(feature = "thread-local"))] + { + AxNamespace::global().base() + } +} + +/// Defines a resource that managed by [`AxNamespace`]. +/// +/// Each resource will be collected into the `axns_resource` section. When +/// accessed, it is either dereferenced from the global namespace or the +/// thread-local namespace according to the `thread-local` feature. +/// +/// # Example +/// +/// ``` +/// use axns::AxResource; +/// +/// axns::def_resource! { +/// static FOO: u32 = 42; +/// static BAR: AxResource = AxResource::new(); +/// } +/// +/// BAR.init_new("hello world".to_string()); +/// assert_eq!(*FOO, 42); +/// assert_eq!(BAR.as_str(), "hello world"); +/// +/// mod imp { +/// use axns::{AxNamespace, AxNamespaceIf}; +/// +/// struct AxResourceImpl; +/// +/// #[crate_interface::impl_interface] +/// impl AxNamespaceIf for AxResourceImpl { +/// fn current_namespace_base() -> *mut u8 { +/// AxNamespace::global().base() +/// } +/// } +/// } +/// ``` +#[macro_export] +macro_rules! def_resource { + ( $( $(#[$attr:meta])* $vis:vis static $name:ident: $ty:ty = $default:expr; )+ ) => { + $( + $(#[$attr])* + $vis struct $name { __value: () } + + impl $name { + unsafe fn deref_from_base(&self, ns_base: *mut u8) -> &$ty { + extern { + fn __start_axns_resource(); + } + + #[link_section = "axns_resource"] + static RES: $ty = $default; + + let offset = &RES as *const _ as usize - __start_axns_resource as usize; + let ptr = ns_base.add(offset) as *const _; + &*ptr + } + + /// Dereference the resource from the given namespace. + pub fn deref_from(&self, ns: &$crate::AxNamespace) -> &$ty { + unsafe { self.deref_from_base(ns.base()) } + } + + /// Dereference the resource from the global namespace. + pub fn deref_global(&self) -> &$ty { + self.deref_from(&$crate::AxNamespace::global()) + } + + /// Dereference the resource automatically, according whether the + /// `thread-local` feature of the `axns` crate is enabled or not. + /// + /// When the feature is enabled, it dereferences from the + /// thread-local namespace of the current thread. Otherwise, it + /// dereferences from the global namespace. + pub fn deref_auto(&self) -> &$ty { + unsafe { self.deref_from_base($crate::current_namespace_base()) } + } + } + + impl core::ops::Deref for $name { + type Target = $ty; + + #[inline(never)] + fn deref(&self) -> &Self::Target { + self.deref_auto() + } + } + + #[used] + #[doc(hidden)] + $(#[$attr])* + $vis static $name: $name = $name { __value: () }; + )+ + }; +} diff --git a/modules/axns/tests/test_global.rs b/modules/axns/tests/test_global.rs new file mode 100644 index 0000000000..c65f1ffd36 --- /dev/null +++ b/modules/axns/tests/test_global.rs @@ -0,0 +1,71 @@ +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::{Barrier, Mutex}; +use std::thread; + +use axns::{def_resource, AxResource}; + +use self::imp::thread_init_namespace; + +def_resource! { + static FOO: AxResource = AxResource::new(); + static BAR: AxResource> = AxResource::new(); +} + +static BARRIER: Barrier = Barrier::new(3); + +fn thread_fn() { + FOO.fetch_add(1, Ordering::SeqCst); + BAR.lock().unwrap().push_str(" hello"); + + BARRIER.wait(); + println!("{:?} FOO: {:?}", std::thread::current().id(), *FOO); + println!("{:?} BAR: {:?}", std::thread::current().id(), BAR.lock()); + + // all threads share the same namespace + assert_eq!(FOO.load(Ordering::SeqCst), 103); + assert_eq!(BAR.lock().unwrap().as_str(), "one hello hello hello"); +} + +#[test] +fn test_namespace() { + thread_init_namespace(); + FOO.init_new(100.into()); + BAR.init_new(Mutex::new(String::from("one"))); + + let t1 = thread::spawn(|| { + thread_init_namespace(); + thread_fn(); + }); + let t2 = thread::spawn(|| { + thread_init_namespace(); + thread_fn(); + }); + + thread_fn(); + t1.join().unwrap(); + t2.join().unwrap(); +} + +mod imp { + use axns::{AxNamespace, AxNamespaceIf}; + use lazyinit::LazyInit; + + thread_local! { + static NS: LazyInit = LazyInit::new(); + } + + struct AxNamespaceImpl; + + #[crate_interface::impl_interface] + impl AxNamespaceIf for AxNamespaceImpl { + fn current_namespace_base() -> *mut u8 { + NS.with(|ns| ns.base()) + } + } + + pub fn thread_init_namespace() { + NS.with(|ns| { + ns.init_once(AxNamespace::global()); + }); + } +} diff --git a/modules/axns/tests/test_thread_local.rs b/modules/axns/tests/test_thread_local.rs new file mode 100644 index 0000000000..3135874fae --- /dev/null +++ b/modules/axns/tests/test_thread_local.rs @@ -0,0 +1,85 @@ +#![feature(thread_id_value)] + +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::{Barrier, Mutex}; +use std::thread; + +use axns::{def_resource, AxResource}; + +use self::imp::thread_init_namespace; + +def_resource! { + static FOO: AxResource = AxResource::new(); + static BAR: AxResource> = AxResource::new(); +} + +static BARRIER: Barrier = Barrier::new(3); + +fn thread_fn() { + FOO.fetch_add(1, Ordering::SeqCst); + BAR.lock().unwrap().push_str(" hello"); + + BARRIER.wait(); + println!("{:?} FOO: {:?}", std::thread::current().id(), *FOO); + println!("{:?} BAR: {:?}", std::thread::current().id(), BAR.lock()); + + let id: u64 = thread::current().id().as_u64().into(); + if id == 2 || id == 4 { + assert_eq!(FOO.load(Ordering::SeqCst), 102); + assert_eq!(BAR.lock().unwrap().as_str(), "one hello hello"); + } else if id == 3 { + assert_eq!(FOO.load(Ordering::SeqCst), 201); + assert_eq!(BAR.lock().unwrap().as_str(), "two hello"); + } +} + +#[test] +fn test_namespace() { + thread_init_namespace(); + FOO.init_new(100.into()); + BAR.init_new(Mutex::new(String::from("one"))); + + let t0_foo = FOO.share(); + let t0_bar = BAR.share(); + + let t1 = thread::spawn(|| { + thread_init_namespace(); + FOO.init_new(200.into()); // isolated from t0 + BAR.init_new(Mutex::new(String::from("two"))); // isolated from t0 + thread_fn(); + }); + let t2 = thread::spawn(|| { + thread_init_namespace(); + FOO.init_shared(t0_foo); // shared with t0 + BAR.init_shared(t0_bar); // shared with t0 + thread_fn(); + }); + + thread_fn(); + t1.join().unwrap(); + t2.join().unwrap(); +} + +mod imp { + use axns::{AxNamespace, AxNamespaceIf}; + use lazyinit::LazyInit; + + thread_local! { + static NS: LazyInit = LazyInit::new(); + } + + struct AxNamespaceImpl; + + #[crate_interface::impl_interface] + impl AxNamespaceIf for AxNamespaceImpl { + fn current_namespace_base() -> *mut u8 { + NS.with(|ns| ns.base()) + } + } + + pub fn thread_init_namespace() { + NS.with(|ns| { + ns.init_once(AxNamespace::new_thread_local()); + }); + } +}