diff --git a/Cargo.lock b/Cargo.lock index 33021e148..83c6b893d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,19 @@ dependencies = [ "tock-registers", ] +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "const-random", + "once_cell", + "version_check", + "zerocopy 0.7.32", +] + [[package]] name = "aho-corasick" version = "1.0.4" @@ -263,7 +276,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.29", + "syn 2.0.32", "which", ] @@ -445,6 +458,26 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b396d1f76d455557e1218ec8066ae14bba60b4b36ecd55577ba979f5db7ecaa" +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom", + "once_cell", + "tiny-keccak", +] + [[package]] name = "core-foundation-sys" version = "0.8.4" @@ -463,7 +496,7 @@ version = "0.1.1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.32", ] [[package]] @@ -551,6 +584,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "defmt" version = "0.3.5" @@ -571,7 +610,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.32", ] [[package]] @@ -1168,7 +1207,7 @@ version = "0.1.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.32", ] [[package]] @@ -1212,7 +1251,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c64d9ba0963cdcea2e1b2230fbae2bab30eb25a174be395c41e764bfb65dd62" dependencies = [ "proc-macro2", - "syn 2.0.29", + "syn 2.0.32", ] [[package]] @@ -1518,6 +1557,20 @@ dependencies = [ "ruxtask", ] +[[package]] +name = "ruxfutex" +version = "0.1.0" +dependencies = [ + "ahash", + "axerrno", + "bitflags 2.4.0", + "lazy_static", + "log", + "memory_addr", + "ruxconfig", + "ruxtask", +] + [[package]] name = "ruxhal" version = "0.1.0" @@ -1607,12 +1660,14 @@ dependencies = [ "axnet", "axsync", "bindgen", + "bitflags 2.4.0", "flatten_objects", "lazy_static", "memory_addr", "ruxconfig", "ruxfeat", "ruxfs", + "ruxfutex", "ruxhal", "ruxruntime", "ruxtask", @@ -1627,6 +1682,7 @@ dependencies = [ "axalloc", "axlog", "axnet", + "axsync", "cfg-if", "crate_interface", "dtb", @@ -1638,6 +1694,7 @@ dependencies = [ "ruxdisplay", "ruxdriver", "ruxfs", + "ruxfutex", "ruxhal", "ruxtask", ] @@ -1746,7 +1803,7 @@ checksum = "dc59dfdcbad1437773485e0367fea4b090a2e0a16d9ffc46af47764536a298ec" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.32", ] [[package]] @@ -1848,9 +1905,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.29" +version = "2.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" +checksum = "239814284fd6f1a4ffe4ca893952cdd93c224b6a1571c9a9eadd670295c0c9e2" dependencies = [ "proc-macro2", "quote", @@ -1874,7 +1931,7 @@ checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.32", ] [[package]] @@ -1892,6 +1949,15 @@ dependencies = [ name = "timer_list" version = "0.1.0" +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tinybmp" version = "0.5.0" @@ -1940,7 +2006,7 @@ version = "0.1.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.32", ] [[package]] @@ -1974,7 +2040,7 @@ source = "git+https://github.com/syswonder/virtio-drivers.git?rev=256ec4c#256ec4 dependencies = [ "bitflags 1.3.2", "log", - "zerocopy", + "zerocopy 0.6.3", ] [[package]] @@ -2044,7 +2110,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.32", "wasm-bindgen-shared", ] @@ -2066,7 +2132,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.32", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2256,7 +2322,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3b9c234616391070b0b173963ebc65a9195068e7ed3731c6edac2ec45ebe106" dependencies = [ "byteorder", - "zerocopy-derive", + "zerocopy-derive 0.6.3", +] + +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive 0.7.32", ] [[package]] @@ -2267,5 +2342,16 @@ checksum = "8f7f3a471f98d0a61c34322fbbfd10c384b07687f680d4119813713f72308d91" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.32", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.32", ] diff --git a/Cargo.toml b/Cargo.toml index c53e5c188..c7f752423 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,6 +50,7 @@ members = [ "modules/ruxhal", "modules/ruxruntime", "modules/ruxtask", + "modules/ruxfutex", "api/ruxfeat", "api/arceos_api", diff --git a/Makefile b/Makefile index 7e60f6ab7..48d5a1a6e 100644 --- a/Makefile +++ b/Makefile @@ -198,6 +198,9 @@ debug: build -ex 'continue' \ -ex 'disp /16i $$pc' +debug_no_attach: build + $(call run_qemu_debug) + clippy: ifeq ($(origin ARCH), command line) $(call cargo_clippy,--target $(TARGET)) @@ -245,4 +248,4 @@ clean_musl: rm -rf ulib/ruxmusl/build_* rm -rf ulib/ruxmusl/install -.PHONY: all build disasm run justrun debug clippy fmt fmt_c test test_no_fail_fast clean clean_c clean_musl doc disk_image +.PHONY: all build disasm run justrun debug clippy fmt fmt_c test test_no_fail_fast clean clean_c clean_musl doc disk_image debug_no_attach diff --git a/api/ruxos_posix_api/Cargo.toml b/api/ruxos_posix_api/Cargo.toml index ed00a274a..3d7a4bfce 100644 --- a/api/ruxos_posix_api/Cargo.toml +++ b/api/ruxos_posix_api/Cargo.toml @@ -19,7 +19,7 @@ default = [] smp = ["ruxfeat/smp"] alloc = ["dep:axalloc", "ruxfeat/alloc"] -multitask = ["ruxfeat/multitask", "ruxtask/multitask"] +multitask = ["ruxfeat/multitask", "ruxtask/multitask", "dep:ruxfutex"] fd = ["alloc"] fs = ["dep:ruxfs", "ruxfeat/fs", "fd"] net = ["dep:axnet", "ruxfeat/net", "fd"] @@ -41,6 +41,7 @@ ruxconfig = { path = "../../modules/ruxconfig" } axlog = { path = "../../modules/axlog" } ruxhal = { path = "../../modules/ruxhal" } axsync = { path = "../../modules/axsync" } +ruxfutex = { path = "../../modules/ruxfutex", optional = true } axalloc = { path = "../../modules/axalloc", optional = true } ruxtask = { path = "../../modules/ruxtask", optional = true } ruxfs = { path = "../../modules/ruxfs", optional = true } @@ -55,5 +56,7 @@ spin = { version = "0.9" } lazy_static = { version = "1.4", features = ["spin_no_std"] } flatten_objects = { path = "../../crates/flatten_objects" } +bitflags = "2.2" + [build-dependencies] -bindgen ={ version = "0.66" } +bindgen = { version = "0.66" } diff --git a/api/ruxos_posix_api/src/imp/pthread/futex.rs b/api/ruxos_posix_api/src/imp/pthread/futex.rs index cd314b4e4..05cb6ee11 100644 --- a/api/ruxos_posix_api/src/imp/pthread/futex.rs +++ b/api/ruxos_posix_api/src/imp/pthread/futex.rs @@ -7,151 +7,124 @@ * See the Mulan PSL v2 for more details. */ -use alloc::collections::{BTreeMap, VecDeque}; -use core::{ffi::c_int, time::Duration}; +use core::{ + ffi::{c_int, c_uint}, + time::Duration, +}; -use axerrno::LinuxError; -use axsync::Mutex; -use memory_addr::VirtAddr; -use ruxtask::{current, AxTaskRef, TaskState, WaitQueue}; +use axerrno::{ax_err, ax_err_type, AxResult, LinuxError}; +use bitflags::bitflags; +use ruxfutex::{futex_wait, futex_wait_bitset, futex_wake, futex_wake_bitset}; use crate::ctypes; -#[derive(Debug)] -enum FutexFlags { - Wait, - Wake, - Requeue, - Unsupported, +const FUTEX_OP_MASK: u32 = 0x0000_000F; +const FUTEX_FLAGS_MASK: u32 = u32::MAX ^ FUTEX_OP_MASK; + +#[derive(PartialEq, Debug)] +#[repr(u32)] +#[allow(non_camel_case_types)] +pub enum FutexOp { + FUTEX_WAIT = 0, + FUTEX_WAKE = 1, + FUTEX_FD = 2, + FUTEX_REQUEUE = 3, + FUTEX_CMP_REQUEUE = 4, + FUTEX_WAKE_OP = 5, + FUTEX_LOCK_PI = 6, + FUTEX_UNLOCK_PI = 7, + FUTEX_TRYLOCK_PI = 8, + FUTEX_WAIT_BITSET = 9, + FUTEX_WAKE_BITSET = 10, } -impl FutexFlags { - pub fn from(val: c_int) -> Self { - match val & 0x7f { - 0 => FutexFlags::Wait, - 1 => FutexFlags::Wake, - 3 => FutexFlags::Requeue, - _ => FutexFlags::Unsupported, +bitflags! { + pub struct FutexFlags : u32 { + const FUTEX_PRIVATE = 128; + const FUTEX_CLOCK_REALTIME = 256; + } +} + +impl FutexOp { + pub fn from_u32(bits: u32) -> AxResult { + match bits { + 0 => Ok(FutexOp::FUTEX_WAIT), + 1 => Ok(FutexOp::FUTEX_WAKE), + 2 => Ok(FutexOp::FUTEX_FD), + 3 => Ok(FutexOp::FUTEX_REQUEUE), + 4 => Ok(FutexOp::FUTEX_CMP_REQUEUE), + 5 => Ok(FutexOp::FUTEX_WAKE_OP), + 6 => Ok(FutexOp::FUTEX_LOCK_PI), + 7 => Ok(FutexOp::FUTEX_UNLOCK_PI), + 8 => Ok(FutexOp::FUTEX_TRYLOCK_PI), + 9 => Ok(FutexOp::FUTEX_WAIT_BITSET), + 10 => Ok(FutexOp::FUTEX_WAKE_BITSET), + _ => ax_err!(InvalidInput, "unknown futex op: {}", bits), } } } -pub static FUTEX_WAIT_TASK: Mutex>> = - Mutex::new(BTreeMap::new()); +impl FutexFlags { + pub fn from_u32(bits: u32) -> AxResult { + FutexFlags::from_bits(bits) + .ok_or_else(|| ax_err_type!(InvalidInput, "unknown futex flags: {}", bits)) + } +} -pub static WAIT_FOR_FUTEX: WaitQueue = WaitQueue::new(); +pub fn futex_op_and_flags_from_u32(bits: u32) -> AxResult<(FutexOp, FutexFlags)> { + let op = { + let op_bits = bits & FUTEX_OP_MASK; + FutexOp::from_u32(op_bits)? + }; + let flags = { + let flags_bits = bits & FUTEX_FLAGS_MASK; + FutexFlags::from_u32(flags_bits)? + }; + Ok((op, flags)) +} -/// `Futex` implementation inspired by Starry +/// `Futex` implementation inspired by occlum pub fn sys_futex( uaddr: usize, - op: c_int, + op: c_uint, val: c_int, // timeout value, should be struct timespec pointer to: usize, - // used by Requeue - _uaddr2: c_int, - // not supported - _val3: c_int, + // used by Requeue, unused for now + #[allow(unused_variables)] uaddr2: c_int, + // bitset + val3: c_int, ) -> c_int { - debug!( - "sys_futex <= addr: {:#x}, op: {}, val: {}, to: {}", - uaddr, op, val, to - ); - check_dead_wait(); - let flag = FutexFlags::from(op); - let current_task = current(); - let timeout = if to != 0 && to > 0xffff000000000000usize { - let dur = unsafe { Duration::from(*(to as *const ctypes::timespec)) }; - dur.as_nanos() as u64 - } else { - 0 - }; - syscall_body!(sys_futex, { - match flag { - FutexFlags::Wait => { - let real_futex_val = unsafe { (uaddr as *const c_int).read_volatile() }; - trace!("real_futex_val: {}, expect: {}", real_futex_val, val); - if real_futex_val != val { - return Err(LinuxError::EAGAIN); - } - let mut futex_wait_task = FUTEX_WAIT_TASK.lock(); - let wait_list = if let alloc::collections::btree_map::Entry::Vacant(e) = - futex_wait_task.entry(uaddr.into()) - { - e.insert(VecDeque::new()); - futex_wait_task.get_mut(&(uaddr.into())).unwrap() - } else { - futex_wait_task.get_mut(&(uaddr.into())).unwrap() - }; + let futex_addr = uaddr as *const i32; + let bitset = val3 as _; + let max_count = val as _; + let futex_val = val as _; - let next = current_task.as_task_ref().clone(); - wait_list.push_back((next, val)); - drop(futex_wait_task); + syscall_body!(sys_futex, { + let (op, _flag) = futex_op_and_flags_from_u32(op).map_err(LinuxError::from)?; + let timeout = to as *const ctypes::timespec; + let timeout = if !timeout.is_null() + && matches!(op, FutexOp::FUTEX_WAIT | FutexOp::FUTEX_WAIT_BITSET) + { + let dur = unsafe { Duration::from(*timeout) }; + Some(dur) + } else { + None + }; + debug!( + "sys_futex <= addr: {:#x}, op: {:?}, val: {}, to: {:?}", + uaddr, op, val, timeout, + ); - // TODO: check signals - if timeout == 0 { - ruxtask::yield_now(); - } else { - #[cfg(feature = "irq")] - { - let timeout = WAIT_FOR_FUTEX.wait_timeout(Duration::from_nanos(timeout)); - if !timeout { - // TODO: should check signals - return Err(LinuxError::EINTR); - } - } - } - Ok(0) + let ret = match op { + FutexOp::FUTEX_WAIT => futex_wait(futex_addr, futex_val, timeout).map(|_| 0), + FutexOp::FUTEX_WAIT_BITSET => { + futex_wait_bitset(futex_addr, futex_val, timeout, bitset).map(|_| 0) } - FutexFlags::Wake => { - trace!( - "thread id: {}, wake addr: {:#x}", - current_task.id().as_u64(), - uaddr - ); - let mut futex_wait_task = FUTEX_WAIT_TASK.lock(); - if futex_wait_task.contains_key(&(uaddr.into())) { - let wait_list = futex_wait_task.get_mut(&(uaddr.into())).unwrap(); - loop { - if let Some((task, _)) = wait_list.pop_front() { - // wake up a waiting task - if !task.is_blocked() { - continue; - } - trace!("Wake task: {}", task.id().as_u64()); - drop(futex_wait_task); - WAIT_FOR_FUTEX.notify_task(false, &task); - } else { - drop(futex_wait_task); - } - break; - } - } else { - drop(futex_wait_task); - } - ruxtask::yield_now(); - Ok(val) - } - FutexFlags::Requeue => { - debug!("unimplemented for REQUEUE"); - Ok(0) - } - _ => Err(LinuxError::EFAULT), - } + FutexOp::FUTEX_WAKE => futex_wake(futex_addr, max_count), + FutexOp::FUTEX_WAKE_BITSET => futex_wake_bitset(futex_addr, max_count, bitset), + _ => ax_err!(Unsupported, "unsupported futex option: {:?}", op), + }; + ret.map_err(LinuxError::from) }) } - -fn check_dead_wait() { - let mut futex_wait_tast = FUTEX_WAIT_TASK.lock(); - for (vaddr, wait_list) in futex_wait_tast.iter_mut() { - let real_futex_val = unsafe { ((*vaddr).as_usize() as *const u32).read_volatile() }; - for (task, val) in wait_list.iter() { - if real_futex_val as i32 != *val && task.state() == TaskState::Blocked { - WAIT_FOR_FUTEX.notify_task(false, task); - } - } - wait_list.retain(|(task, val)| { - real_futex_val as i32 == *val && task.state() == TaskState::Blocked - }); - } -} diff --git a/api/ruxos_posix_api/src/imp/pthread/mod.rs b/api/ruxos_posix_api/src/imp/pthread/mod.rs index a7625a00f..74ce409c7 100644 --- a/api/ruxos_posix_api/src/imp/pthread/mod.rs +++ b/api/ruxos_posix_api/src/imp/pthread/mod.rs @@ -18,9 +18,10 @@ use spin::RwLock; use crate::ctypes; pub mod condvar; -pub mod futex; pub mod mutex; +pub mod futex; + #[cfg(feature = "musl")] pub mod dummy; #[cfg(not(feature = "musl"))] @@ -209,9 +210,15 @@ pub fn sys_pthread_exit(retval: *mut c_void) -> ! { debug!("sys_pthread_exit <= {:#x}", retval as usize); #[cfg(feature = "musl")] { + use core::sync::atomic::Ordering; + let id = ruxtask::current().as_task_ref().id().as_u64(); + // if current task is not `main` if id != 2u64 { - ruxtask::current().as_task_ref().free_thread_list_lock(); + let current = ruxtask::current(); + let current = current.as_task_ref(); + current.free_thread_list_lock(); + let _ = ruxfutex::futex_wake(current.tl().load(Ordering::Relaxed) as usize as _, 1); } // retval is exit code for musl Pthread::exit_musl(retval as usize); diff --git a/modules/ruxfutex/Cargo.toml b/modules/ruxfutex/Cargo.toml new file mode 100644 index 000000000..a3c5e50ed --- /dev/null +++ b/modules/ruxfutex/Cargo.toml @@ -0,0 +1,33 @@ + +[package] +name = "ruxfutex" +version = "0.1.0" +edition = "2021" +authors = ["Zhi Zhou "] +description = "Ruxos futex implementation" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/syswonder/ruxos" +repository = "https://github.com/syswonder/ruxos/tree/main/modules/ruxfutex" + +[features] +default = [] + +irq = ["ruxtask/irq"] + +[dependencies] +# Ruxos modules +axerrno = { path = "../../crates/axerrno" } + +ruxconfig = { path = "../ruxconfig" } +ruxtask = { path = "../ruxtask", features = ["multitask"] } + +# Other crates +log = "0.4" +lazy_static = { version = "1.4", features = ["spin_no_std"] } +bitflags = "2.2" +ahash = { version = "0.8.7", default-features = false, features = [ + "compile-time-rng", +] } + +[dev-dependencies] +memory_addr = { path = "../../crates/memory_addr" } diff --git a/modules/ruxfutex/src/api.rs b/modules/ruxfutex/src/api.rs new file mode 100644 index 000000000..fc52bb6ce --- /dev/null +++ b/modules/ruxfutex/src/api.rs @@ -0,0 +1,215 @@ +/* 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::time::Duration; + +use axerrno::{ax_err, AxResult}; +use log::{debug, trace}; + +use super::{ + types::{FutexBucket, FutexKey}, + FUTEX_BUCKETS, +}; + +/// The bitset that matches any task, +/// used by [`futex_wake_bitset`] and [`futex_wait_bitset`]. +pub const FUTEX_BITSET_MATCH_ANY: u32 = u32::MAX; + +fn futex_wait_timeout( + futex_addr: *const i32, + futex_val: i32, + timeout: Option, + bitset: u32, + #[allow(unused_variables)] is_relative: bool, +) -> AxResult<()> { + // Get the futex bucket + let futex_key = FutexKey::new(futex_addr, bitset); + let (_, futex_bucket) = FUTEX_BUCKETS.get_bucket(futex_key); + + let condition = || { + // Check the futex value + let actual_val = futex_key.load_val(); + trace!("futex_wait_bitset actual_val: {}", actual_val); + if actual_val != futex_val { + return ax_err!( + WouldBlock, + "futex value does not match: expected {}, found {}", + futex_val, + actual_val + ); + } + + Ok(()) + }; + + // Lock the queue before checking futex value. + match timeout { + Some(timeout) => { + #[cfg(feature = "irq")] + let wait_timeout = if is_relative { + FutexBucket::wait_timeout_meta_if + } else { + FutexBucket::wait_timeout_absolutely_meta_if + }; + #[cfg(not(feature = "irq"))] + let wait_timeout = FutexBucket::wait_timeout_absolutely_meta_if; + let _is_timeout = wait_timeout(futex_bucket, timeout, futex_key, condition)?; + Ok(()) + } + None => futex_bucket.wait_meta_if(futex_key, condition), + } +} + +/// This operation tests that the value at the futex word +/// pointed to by the address `futex_addr` still contains the +/// expected value val, and if so, then sleeps waiting for a +/// [`futex_wake`] operation on the futex word. The load of the +/// value of the futex word is an atomic memory access (i.e., +/// using atomic machine instructions of the respective +/// architecture). This load, the comparison with the +/// expected value, and starting to sleep are performed +/// atomically and totally ordered with respect to other futex +/// operations on the same futex word. If the thread starts +/// to sleep, it is considered a waiter on this futex word. +/// If the futex value does not match val, then the call fails +/// immediately with the error [`AxError::WouldBlock`]. +/// +/// The purpose of the comparison with the expected value is +/// to prevent lost wake-ups. If another thread changed the +/// value of the futex word after the calling thread decided +/// to block based on the prior value, and if the other thread +/// executed a [`futex_wake`] operation after +/// the value change and before this [`futex_wait`] operation, +/// then the calling thread will observe the value change and +/// will not start to sleep. +/// +/// If the timeout is not [`None`], it specifies a timeout for the wait. +/// If timeout is NULL, the call blocks indefinitely. +/// +/// Note that `timeout` is interpreted as a relative +/// value. This differs from other futex operations, where +/// timeout is interpreted as an absolute value. To obtain +/// the equivalent of [`futex_wait`] with an absolute timeout, +/// call [`futex_wait_bitset`] with `bitset` specified as +/// [`FUTEX_BITSET_MATCH_ANY`]. +/// +/// [`AxError::WouldBlock`]: axerrno::AxError::WouldBlock +pub fn futex_wait( + futex_addr: *const i32, + futex_val: i32, + timeout: Option, +) -> AxResult<()> { + debug!( + "futex_wait addr: {:#x}, val: {}, timeout: {:?}", + futex_addr as usize, futex_val, timeout + ); + futex_wait_timeout(futex_addr, futex_val, timeout, FUTEX_BITSET_MATCH_ANY, true) +} + +/// This operation is like [`futex_wait`] except that `bitset` +/// is used to provide a 32-bit bit mask to the kernel. This bit +/// mask, in which at least one bit must be set, is stored in +/// the kernel-internal state of the waiter. See the +/// description of [`futex_wake_bitset`] for further details. +/// +/// If timeout is not [`None`], it specifies an absolute timeout +/// for the wait operation. If timeout is [`None`], the operation can +/// block indefinitely. +pub fn futex_wait_bitset( + futex_addr: *const i32, + futex_val: i32, + timeout: Option, + bitset: u32, +) -> AxResult<()> { + debug!( + "futex_wait_bitset addr: {:#x}, val: {}, timeout: {:?}, bitset: {:#x}", + futex_addr as usize, futex_val, timeout, bitset + ); + futex_wait_timeout(futex_addr, futex_val, timeout, bitset, false) +} + +/// This operation wakes at most `max_count` of the waiters that are +/// waiting (e.g., inside [`futex_wait`]) on the futex word at the +/// address `futex_addr`. Most commonly, `max_count` is specified as either +/// 1 (wake up a single waiter) or [`usize::MAX`] (wake up all +/// waiters). No guarantee is provided about which waiters +/// are awoken (e.g., a waiter with a higher scheduling +/// priority is not guaranteed to be awoken in preference to a +/// waiter with a lower priority). +pub fn futex_wake(futex_addr: *const i32, max_count: usize) -> AxResult { + futex_wake_bitset(futex_addr, max_count, FUTEX_BITSET_MATCH_ANY) +} + +/// This operation is the same as [`futex_wake`] except that the +/// `btiset` argument is used to provide a 32-bit bit mask to the +/// kernel. This bit mask, in which at least one bit must be +/// set, is used to select which waiters should be woken up. +/// The selection is done by a bitwise AND of the "wake" bit +/// mask (i.e., the value in `bitset`) and the bit mask which is +/// stored in the kernel-internal state of the waiter (the +/// "wait" bit mask that is set using [`futex_wait_bitset`]). All +/// of the waiters for which the result of the AND is nonzero +/// are woken up; the remaining waiters are left sleeping. +/// +/// The effect of [`futex_wait_bitset`] and [`futex_wake_bitset`] is +/// to allow selective wake-ups among multiple waiters that +/// are blocked on the same futex. However, note that, +/// depending on the use case, employing this bit-mask +/// multiplexing feature on a futex can be less efficient than +/// simply using multiple futexes, because employing bit-mask +/// multiplexing requires the kernel to check all waiters on a +/// futex, including those that are not interested in being +/// woken up (i.e., they do not have the relevant bit set in +/// their "wait" bit mask). In the current implementation, this does +/// not make a significant difference. +/// +/// The constant [`FUTEX_BITSET_MATCH_ANY`], which corresponds to +/// all 32 bits set in the bit mask, can be used as the `bitset` +/// argument for [`futex_wait_bitset`] and [`futex_wake_bitset`]. +/// Other than differences in the handling of the timeout +/// argument, the [`futex_wait`] operation is equivalent to +/// [`futex_wait_bitset`] with `bitset` specified as +/// [`FUTEX_BITSET_MATCH_ANY`]; that is, allow a wake-up by any +/// waker. The [`futex_wake`] operation is equivalent to +/// [`futex_wake_bitset`] with `bitset` specified as +/// [`FUTEX_BITSET_MATCH_ANY`]; that is, wake up any waiter(s). +pub fn futex_wake_bitset(futex_addr: *const i32, max_count: usize, bitset: u32) -> AxResult { + debug!( + "futex_wake_bitset addr: {:#x}, max_count: {}, bitset: {:#x}", + futex_addr as usize, max_count, bitset + ); + + let futex_key = FutexKey::new(futex_addr, bitset); + let (_, futex_bucket) = FUTEX_BUCKETS.get_bucket(futex_key); + + let mut count = 0; + + // Wake up the tasks in the bucket + let task_count = futex_bucket.notify_task_if(false, |task, &key| { + trace!( + "futex wake: count: {}, key: {:?}, futex_key: {:?}, bitset: {}, is_notified: {}, task: {:?}", + count, + key, + futex_key, + bitset, + !(count >= max_count || futex_key != key || (bitset & key.bitset()) == 0), + task, + ); + if !task.is_blocked() { + return true; + } + if count >= max_count || futex_key != key || (bitset & key.bitset()) == 0 { + false + } else { + count += 1; + true + } + }); + Ok(task_count) +} diff --git a/modules/ruxfutex/src/lib.rs b/modules/ruxfutex/src/lib.rs new file mode 100644 index 000000000..532d4d56e --- /dev/null +++ b/modules/ruxfutex/src/lib.rs @@ -0,0 +1,49 @@ +/* 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. + */ + +//! [Ruxos] futex implementation, provides a set of functions for implementing user-space synchronization primitives. +//! +//! See [futex manpage] for more details. This implementation separates different futex operations into different functions, +//! corresponding their `futex_op`s. +//! +//! [futex manpage]: https://man7.org/linux/man-pages/man2/futex.2.html +//! [Ruxos]: https://github.com/syswonder/ruxos +//! [cargo test]: https://doc.rust-lang.org/cargo/guide/tests.html + +#![no_std] +#![feature(doc_auto_cfg)] + +extern crate alloc; +extern crate log; + +mod api; +mod types; + +pub use api::{ + futex_wait, futex_wait_bitset, futex_wake, futex_wake_bitset, FUTEX_BITSET_MATCH_ANY, +}; + +use types::FutexVec; + +use core::ops::Deref; + +use lazy_static::lazy_static; + +// Use the same count as linux kernel to keep the same performance +const BUCKET_COUNT: usize = ((1 << 8) * (ruxconfig::SMP)).next_power_of_two(); +const BUCKET_MASK: usize = BUCKET_COUNT - 1; + +lazy_static! { + static ref FUTEX_BUCKETS: FutexVec = FutexVec::new(BUCKET_COUNT); +} + +/// Inits the futex module. +pub fn init_futex() { + let _ = FUTEX_BUCKETS.deref(); +} diff --git a/modules/ruxfutex/src/types.rs b/modules/ruxfutex/src/types.rs new file mode 100644 index 000000000..0f7a09ac1 --- /dev/null +++ b/modules/ruxfutex/src/types.rs @@ -0,0 +1,101 @@ +/* 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, + hash::{Hash, Hasher}, + sync::atomic::{self, AtomicI32}, +}; + +use ahash::AHasher; +use alloc::vec::Vec; + +use ruxtask::WaitQueueWithMetadata; + +use super::BUCKET_MASK; + +#[derive(Clone, Copy)] +pub(crate) struct FutexKey { + key: usize, + bitset: u32, +} + +impl Debug for FutexKey { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("FutexKey") + .field("key", &format_args!("{:#x}", self.key)) + .field("bitset", &format_args!("{:#x}", self.bitset)) + .finish() + } +} + +pub(crate) type FutexBucket = WaitQueueWithMetadata; + +pub(crate) struct FutexVec { + pub(crate) buckets: Vec, +} + +impl FutexKey { + /// Create futex key from its address and a bitset. + /// + /// Note that `addr` or `self.key` is actually a [`PhysAddr`](memory_addr::PhysAddr) pointing to a [`i32`] + /// but while RuxOS only supports single addr space, we'd like to treat it as a normal pointer. + pub fn new(addr: *const i32, bitset: u32) -> Self { + Self { + key: addr as usize, + bitset, + } + } + + /// Load the key value, atomically. + #[inline] + pub fn load_val(&self) -> i32 { + let ptr = self.key as *const AtomicI32; + unsafe { (*ptr).load(atomic::Ordering::SeqCst) } + } + + /// Return the address that this futex key references. + #[inline] + pub fn addr(&self) -> usize { + self.key + } + + #[inline] + pub fn bitset(&self) -> u32 { + self.bitset + } +} + +impl PartialEq for FutexKey { + fn eq(&self, other: &Self) -> bool { + self.key == other.key + } +} + +impl FutexVec { + pub fn new(size: usize) -> Self { + let buckets = (0..size) + .map(|_| WaitQueueWithMetadata::new()) + .collect::>(); + Self { buckets } + } + + pub fn get_bucket(&self, key: FutexKey) -> (usize, &FutexBucket) { + let hash = { + // this addr should be aligned as a `*const u32`, which is this multiples of 4, + // so ignoring the last 2 bits is fine + let addr = key.addr() >> 2; + let mut hasher = AHasher::default(); + addr.hash(&mut hasher); + hasher.finish() as usize + }; + let idx = BUCKET_MASK & hash; + (idx, &self.buckets[idx]) + } +} diff --git a/modules/ruxruntime/Cargo.toml b/modules/ruxruntime/Cargo.toml index 73273b90c..f0615e5e1 100644 --- a/modules/ruxruntime/Cargo.toml +++ b/modules/ruxruntime/Cargo.toml @@ -23,7 +23,7 @@ alloc = ["axalloc", "dtb"] paging = ["ruxhal/paging", "lazy_init"] rtc = ["ruxhal/rtc"] -multitask = ["ruxtask/multitask"] +multitask = ["ruxtask/multitask", "dep:ruxfutex"] fs = ["ruxdriver", "ruxfs"] blkfs = ["fs"] virtio-9p = ["fs", "rux9p"] @@ -32,7 +32,7 @@ net = ["ruxdriver", "axnet"] display = ["ruxdriver", "ruxdisplay"] signal = [] -musl = [] +musl = ["dep:ruxfutex"] [dependencies] cfg-if = "1.0" @@ -46,9 +46,11 @@ rux9p = { path = "../rux9p", optional = true } axnet = { path = "../axnet", optional = true } ruxdisplay = { path = "../ruxdisplay", optional = true } ruxtask = { path = "../ruxtask", optional = true } +axsync = { path = "../axsync", optional = true } +ruxfutex = { path = "../ruxfutex", optional = true } crate_interface = { path = "../../crates/crate_interface" } percpu = { path = "../../crates/percpu", optional = true } kernel_guard = { path = "../../crates/kernel_guard", optional = true } lazy_init = { path = "../../crates/lazy_init", optional = true } -dtb = { path = "../../crates/dtb", optional = true} +dtb = { path = "../../crates/dtb", optional = true } diff --git a/modules/ruxruntime/src/lib.rs b/modules/ruxruntime/src/lib.rs index 2c72d6729..80701e284 100644 --- a/modules/ruxruntime/src/lib.rs +++ b/modules/ruxruntime/src/lib.rs @@ -199,7 +199,11 @@ pub extern "C" fn rust_main(cpu_id: usize, dtb: usize) -> ! { ruxhal::platform_init(); #[cfg(feature = "multitask")] - ruxtask::init_scheduler(); + { + ruxtask::init_scheduler(); + #[cfg(feature = "musl")] + ruxfutex::init_futex(); + } #[cfg(any(feature = "fs", feature = "net", feature = "display"))] { diff --git a/modules/ruxtask/src/api.rs b/modules/ruxtask/src/api.rs index 9011add5b..67476e271 100644 --- a/modules/ruxtask/src/api.rs +++ b/modules/ruxtask/src/api.rs @@ -18,7 +18,7 @@ pub use crate::task::{CurrentTask, TaskId, TaskInner}; #[cfg(not(feature = "musl"))] use crate::tsd; #[doc(cfg(feature = "multitask"))] -pub use crate::wait_queue::WaitQueue; +pub use crate::wait_queue::{WaitQueue, WaitQueueWithMetadata}; /// The reference type of a task. pub type AxTaskRef = Arc; diff --git a/modules/ruxtask/src/task.rs b/modules/ruxtask/src/task.rs index 92bee4ad0..31950e583 100644 --- a/modules/ruxtask/src/task.rs +++ b/modules/ruxtask/src/task.rs @@ -116,6 +116,12 @@ impl TaskInner { self.id } + /// Gets the clear tid of the task. + #[cfg(feature = "musl")] + pub const fn tl(&self) -> &AtomicU64 { + &self.tl + } + /// Gets the name of the task. pub fn name(&self) -> &str { self.name.as_str() @@ -138,13 +144,11 @@ impl TaskInner { /// set 0 to thread_list_lock #[cfg(feature = "musl")] pub fn free_thread_list_lock(&self) { - unsafe { - let addr = self.tl.load(Ordering::Relaxed); - if addr == 0 { - return; - } - (addr as *mut core::ffi::c_int).write_volatile(0) + let addr = self.tl.load(Ordering::Relaxed); + if addr == 0 { + return; } + unsafe { &*(addr as *const AtomicI32) }.store(0, Ordering::Release) } } @@ -184,7 +188,7 @@ impl TaskInner { fn new_common_tls( id: TaskId, name: String, - tls: usize, + #[cfg_attr(not(feature = "tls"), allow(unused_variables))] tls: usize, set_tid: AtomicU64, tl: AtomicU64, ) -> Self { diff --git a/modules/ruxtask/src/wait_queue.rs b/modules/ruxtask/src/wait_queue.rs index 61072071a..9f48b1236 100644 --- a/modules/ruxtask/src/wait_queue.rs +++ b/modules/ruxtask/src/wait_queue.rs @@ -13,7 +13,7 @@ use spinlock::SpinRaw; use crate::{AxRunQueue, AxTaskRef, CurrentTask, RUN_QUEUE}; -/// A queue to store sleeping tasks. +/// A queue to store sleeping tasks, each with its metadata. /// /// # Examples /// @@ -35,11 +35,14 @@ use crate::{AxRunQueue, AxTaskRef, CurrentTask, RUN_QUEUE}; /// WQ.wait(); // block until `notify()` is called /// assert_eq!(VALUE.load(Ordering::Relaxed), 1); /// ``` -pub struct WaitQueue { - queue: SpinRaw>, // we already disabled IRQs when lock the `RUN_QUEUE` +pub struct WaitQueueWithMetadata { + queue: SpinRaw>, // we already disabled IRQs when lock the `RUN_QUEUE` } -impl WaitQueue { +/// A wait queue with no metadata. +pub type WaitQueue = WaitQueueWithMetadata<()>; + +impl WaitQueueWithMetadata { /// Creates an empty wait queue. pub const fn new() -> Self { Self { @@ -61,7 +64,7 @@ impl WaitQueue { // wake up by timer (timeout). // `RUN_QUEUE` is not locked here, so disable IRQs. let _guard = kernel_guard::IrqSave::new(); - self.queue.lock().retain(|t| !curr.ptr_eq(t)); + self.queue.lock().retain(|(t, _)| !curr.ptr_eq(t)); curr.set_in_wait_queue(false); } #[cfg(feature = "irq")] @@ -73,97 +76,109 @@ impl WaitQueue { /// Blocks the current task and put it into the wait queue, until other task /// notifies it. - pub fn wait(&self) { + pub fn wait_meta(&self, meta: Meta) { RUN_QUEUE.lock().block_current(|task| { task.set_in_wait_queue(true); - self.queue.lock().push_back(task) + self.queue.lock().push_back((task, meta)) }); self.cancel_events(crate::current()); } - /// Blocks the current task and put it into the wait queue, until the given - /// `condition` becomes true. - /// - /// Note that even other tasks notify this task, it will not wake up until - /// the condition becomes true. - pub fn wait_until(&self, condition: F) + /// If `condition` returns [`Ok`], blocks the current task and put it into the wait queue, + /// until other task notifies it. + pub fn wait_meta_if(&self, meta: Meta, mut condition: F) -> Result<(), R> where - F: Fn() -> bool, + F: FnMut() -> Result<(), R>, { - loop { - let mut rq = RUN_QUEUE.lock(); - if condition() { - break; - } - rq.block_current(|task| { - task.set_in_wait_queue(true); - self.queue.lock().push_back(task); - }); - } + let mut wq = self.queue.lock(); + condition()?; + + RUN_QUEUE.lock().block_current(|task| { + task.set_in_wait_queue(true); + wq.push_back((task, meta)); + drop(wq); + }); self.cancel_events(crate::current()); + + Ok(()) } /// Blocks the current task and put it into the wait queue, until other tasks /// notify it, or the given duration has elapsed. #[cfg(feature = "irq")] - pub fn wait_timeout(&self, dur: core::time::Duration) -> bool { + pub fn wait_timeout_meta(&self, dur: core::time::Duration, meta: Meta) -> bool { + let deadline = dur + ruxhal::time::current_time(); + self.wait_timeout_absolutely_meta(deadline, meta) + } + + /// If `condition` returns [`Ok`], blocks the current task and put it into the wait queue, + /// until other tasks notify it, or the given duration has elapsed. + #[cfg(feature = "irq")] + pub fn wait_timeout_meta_if( + &self, + dur: core::time::Duration, + meta: Meta, + condition: F, + ) -> Result + where + F: FnMut() -> Result<(), R>, + { let deadline = dur + ruxhal::time::current_time(); - self.wait_timeout_absolutely(deadline) + self.wait_timeout_absolutely_meta_if(deadline, meta, condition) } + /// Blocks the current task and put it into the wait queue, until other tasks - /// notify it, or the given deadling has elapsed. - pub fn wait_timeout_absolutely(&self, _deadline: core::time::Duration) -> bool { + /// notify it, or the given deadline has elapsed. + pub fn wait_timeout_absolutely_meta(&self, deadline: core::time::Duration, meta: Meta) -> bool { let curr = crate::current(); debug!( "task wait_timeout: {} deadline={:?}", curr.id_name(), - _deadline + deadline ); #[cfg(feature = "irq")] - crate::timers::set_alarm_wakeup(_deadline, curr.clone()); + crate::timers::set_alarm_wakeup(deadline, curr.clone()); RUN_QUEUE.lock().block_current(|task| { task.set_in_wait_queue(true); - self.queue.lock().push_back(task) + self.queue.lock().push_back((task, meta)) }); let timeout = curr.in_wait_queue(); // still in the wait queue, must have timed out self.cancel_events(curr); timeout } - /// Blocks the current task and put it into the wait queue, until the given - /// `condition` becomes true, or the given duration has elapsed. - /// - /// Note that even other tasks notify this task, it will not wake up until - /// the above conditions are met. - #[cfg(feature = "irq")] - pub fn wait_timeout_until(&self, dur: core::time::Duration, condition: F) -> bool + /// If `condition` returns [`Ok`], blocks the current task and put it into the wait queue, + /// until other tasks notify it, or the given deadline has elapsed. + pub fn wait_timeout_absolutely_meta_if( + &self, + deadline: core::time::Duration, + meta: Meta, + mut condition: F, + ) -> Result where - F: Fn() -> bool, + F: FnMut() -> Result<(), R>, { let curr = crate::current(); - let deadline = ruxhal::time::current_time() + dur; + let mut wq = self.queue.lock(); + condition()?; + debug!( - "task wait_timeout: {}, deadline={:?}", + "task wait_timeout: {} deadline={:?}", curr.id_name(), deadline ); + #[cfg(feature = "irq")] crate::timers::set_alarm_wakeup(deadline, curr.clone()); - let mut timeout = true; - while ruxhal::time::current_time() < deadline { - let mut rq = RUN_QUEUE.lock(); - if condition() { - timeout = false; - break; - } - rq.block_current(|task| { - task.set_in_wait_queue(true); - self.queue.lock().push_back(task); - }); - } + RUN_QUEUE.lock().block_current(|task| { + task.set_in_wait_queue(true); + wq.push_back((task, meta)); + drop(wq); + }); + let timeout = curr.in_wait_queue(); // still in the wait queue, must have timed out self.cancel_events(curr); - timeout + Ok(timeout) } /// Wakes up one task in the wait queue, usually the first one. @@ -186,7 +201,7 @@ impl WaitQueue { pub fn notify_all(&self, resched: bool) { loop { let mut rq = RUN_QUEUE.lock(); - if let Some(task) = self.queue.lock().pop_front() { + if let Some((task, _)) = self.queue.lock().pop_front() { task.set_in_wait_queue(false); rq.unblock_task(task, resched); } else { @@ -203,17 +218,44 @@ impl WaitQueue { pub fn notify_task(&self, resched: bool, task: &AxTaskRef) -> bool { let mut rq = RUN_QUEUE.lock(); let mut wq = self.queue.lock(); - if let Some(index) = wq.iter().position(|t| Arc::ptr_eq(t, task)) { + if let Some(index) = wq.iter().position(|(t, _)| Arc::ptr_eq(t, task)) { task.set_in_wait_queue(false); - rq.unblock_task(wq.remove(index).unwrap(), resched); + rq.unblock_task(wq.remove(index).unwrap().0, resched); true } else { false } } + /// Wake up all corresponding tasks that `filter` returns true. + /// + /// Returns number of tasks awaken. + /// + /// If `resched` is true, the current task will be preempted when the + /// preemption is enabled. + pub fn notify_task_if(&self, resched: bool, mut filter: F) -> usize + where + F: FnMut(&AxTaskRef, &Meta) -> bool, + { + let mut rq = RUN_QUEUE.lock(); + let mut wq = self.queue.lock(); + let len_before = wq.len(); + + wq.retain(|(task, meta)| { + if filter(task, meta) { + task.set_in_wait_queue(false); + rq.unblock_task(task.clone(), resched); + false + } else { + true + } + }); + + len_before - wq.len() + } + pub(crate) fn notify_one_locked(&self, resched: bool, rq: &mut AxRunQueue) -> bool { - if let Some(task) = self.queue.lock().pop_front() { + if let Some((task, _)) = self.queue.lock().pop_front() { task.set_in_wait_queue(false); rq.unblock_task(task, resched); true @@ -223,9 +265,217 @@ impl WaitQueue { } pub(crate) fn notify_all_locked(&self, resched: bool, rq: &mut AxRunQueue) { - while let Some(task) = self.queue.lock().pop_front() { + while let Some((task, _)) = self.queue.lock().pop_front() { task.set_in_wait_queue(false); rq.unblock_task(task, resched); } } + + /// Queue a given task with its metadata given. + /// + /// It is marked as unsafe as it does nothing other than queueing the task, + /// so one might want to modify the state of the task appropriately before/after preforming + /// this operation. + /// + /// # Safety + ///This function does not cause memory/concurrent safety issues directly, but may lead + /// to resource leak or break assumptions in scheduler when used incautiously. + pub unsafe fn queue_task_meta(&self, task: AxTaskRef, meta: Meta) { + self.queue.lock().push_back((task, meta)); + } + + /// Dequeue the given task, returning its metadata if this task is actually inside this queue. + /// + /// It is marked as unsafe as it does nothing other than dequeueing the task, + /// so one might want to modify the state of the task appropriately before/after preforming + /// this operation. + /// + /// # Safety + ///This function does not cause memory/concurrent safety issues directly, but may lead + /// to resource leak or break assumptions in scheduler when used incautiously. + pub unsafe fn dequeue_task(&self, task: &AxTaskRef) -> Option { + let mut wq = self.queue.lock(); + let pos = wq.iter().position(|(t, _)| Arc::ptr_eq(t, task)); + if let Some(pos) = pos { + let (_, meta) = wq.swap_remove_back(pos).unwrap(); + Some(meta) + } else { + None + } + } + + /// Dequeue all corresponding tasks that `filter` returns true. + /// + /// It is marked as unsafe as it does nothing other than dequeueing the tasks, + /// so one might want to modify the state of the task appropriately before/after preforming + /// this operation. + /// + /// # Safety + ///This function does not cause memory/concurrent safety issues directly, but may lead + /// to resource leak or break assumptions in scheduler when used incautiously. + pub unsafe fn dequeue_tasks_if(&self, mut filter: F, mut dequeue_op: Op) + where + F: FnMut(&AxTaskRef, &Meta) -> bool, + Op: FnMut(AxTaskRef, Meta), + { + let mut wq = self.queue.lock(); + let first_false = partition_deque(&mut wq, |(t, m)| filter(t, m)); + + // `..first_false` represents the range that `filter` returns true for all tasks within. + wq.drain(..first_false).for_each(|(t, m)| dequeue_op(t, m)); + } +} + +impl WaitQueueWithMetadata { + /// Blocks the current task and put it into the wait queue, until the given + /// `condition` becomes true. + /// + /// Note that even other tasks notify this task, it will not wake up until + /// the condition becomes true. + pub fn wait_until_meta(&self, mut condition: F, meta: Meta) + where + F: FnMut() -> bool, + { + loop { + let mut rq = RUN_QUEUE.lock(); + if condition() { + break; + } + rq.block_current(|task| { + task.set_in_wait_queue(true); + self.queue.lock().push_back((task, meta.clone())); + }); + } + self.cancel_events(crate::current()); + } + + /// Blocks the current task and put it into the wait queue, until the given + /// `condition` becomes true, or the given duration has elapsed. + /// + /// Note that even other tasks notify this task, it will not wake up until + /// the above conditions are met. + #[cfg(feature = "irq")] + pub fn wait_timeout_until_meta( + &self, + dur: core::time::Duration, + mut condition: F, + meta: Meta, + ) -> bool + where + F: FnMut() -> bool, + { + let curr = crate::current(); + let deadline = ruxhal::time::current_time() + dur; + debug!( + "task wait_timeout: {}, deadline={:?}", + curr.id_name(), + deadline + ); + crate::timers::set_alarm_wakeup(deadline, curr.clone()); + + let mut timeout = true; + while ruxhal::time::current_time() < deadline { + let mut rq = RUN_QUEUE.lock(); + if condition() { + timeout = false; + break; + } + rq.block_current(|task| { + task.set_in_wait_queue(true); + self.queue.lock().push_back((task, meta.clone())); + }); + } + self.cancel_events(curr); + timeout + } +} + +impl WaitQueueWithMetadata { + /// Blocks the current task and put it into the wait queue, until other task + /// notifies it. + pub fn wait(&self) { + self.wait_meta(Default::default()) + } + + /// Blocks the current task and put it into the wait queue, until other tasks + /// notify it, or the given duration has elapsed. + #[cfg(feature = "irq")] + pub fn wait_timeout(&self, dur: core::time::Duration) -> bool { + self.wait_timeout_meta(dur, Default::default()) + } + + /// Blocks the current task and put it into the wait queue, until other tasks + /// notify it, or the given deadling has elapsed. + pub fn wait_timeout_absolutely(&self, deadline: core::time::Duration) -> bool { + self.wait_timeout_absolutely_meta(deadline, Default::default()) + } + + /// Queue a given task with "default" metadata. + /// + /// It is marked as unsafe as it does nothing other than queueing the task, + /// so one might want to modify the state of the task appropriately before/after preforming + /// this operation. + /// + /// # Safety + /// This function does not cause memory/concurrent safety issues directly, but may lead + /// to resource leak or break assumptions in scheduler when used incautiously. + pub unsafe fn queue_task(&self, task: AxTaskRef) { + self.queue.lock().push_back((task, Default::default())); + } +} + +impl WaitQueueWithMetadata { + /// Blocks the current task and put it into the wait queue, until the given + /// `condition` becomes true. + /// + /// Note that even other tasks notify this task, it will not wake up until + /// the condition becomes true. + pub fn wait_until(&self, condition: F) + where + F: FnMut() -> bool, + { + self.wait_until_meta(condition, Default::default()) + } + + /// Blocks the current task and put it into the wait queue, until the given + /// `condition` becomes true, or the given duration has elapsed. + /// + /// Note that even other tasks notify this task, it will not wake up until + /// the above conditions are met. + #[cfg(feature = "irq")] + pub fn wait_timeout_until(&self, dur: core::time::Duration, condition: F) -> bool + where + F: FnMut() -> bool, + { + self.wait_timeout_until_meta(dur, condition, Default::default()) + } +} + +/// Partition a [`VecDeque`] in-place so that it contains all elements for +/// which `predicate(e)` is `true`, followed by all elements for which +/// `predicate(e)` is `false`. +/// Returns the index of the first element which returned false. +/// Returns 0 if all elements returned false. +/// Returns `data.len()` if all elements returned true. +fn partition_deque(data: &mut VecDeque, mut pred: F) -> usize +where + F: FnMut(&T) -> bool, +{ + let len = data.len(); + if len == 0 { + return 0; + } + let (mut l, mut r) = (0, len - 1); + loop { + while l < len && pred(&data[l]) { + l += 1; + } + while r > 0 && !pred(&data[r]) { + r -= 1; + } + if l >= r { + return l; + } + data.swap(l, r); + } } diff --git a/scripts/debug/rust-gdb.sh b/scripts/debug/rust-gdb.sh new file mode 100755 index 000000000..dc1f1506b --- /dev/null +++ b/scripts/debug/rust-gdb.sh @@ -0,0 +1,31 @@ +#!/bin/sh +# Copied from https://github.com/rust-lang/rust/blob/master/src/etc/rust-gdb +# Exit if anything fails +set -e + +# Prefer rustc in the same directory as this script +DIR="$(dirname "$0")" +if [ -x "$DIR/rustc" ]; then + RUSTC="$DIR/rustc" +else + RUSTC="rustc" +fi + +# Find out where the pretty printer Python module is +RUSTC_SYSROOT="$("$RUSTC" --print=sysroot)" +GDB_PYTHON_MODULE_DIRECTORY="$RUSTC_SYSROOT/lib/rustlib/etc" +# Get the commit hash for path remapping +RUSTC_COMMIT_HASH="$("$RUSTC" -vV | sed -n 's/commit-hash: \([a-zA-Z0-9_]*\)/\1/p')" + +echo "set substitute-path /rustc/$RUSTC_COMMIT_HASH $RUSTC_SYSROOT/lib/rustlib/src/rust" + +# Run GDB with the additional arguments that load the pretty printers +# Set the environment variable `RUST_GDB` to overwrite the call to a +# different/specific command (defaults to `gdb-multiarch`). +RUST_GDB="${RUST_GDB:-gdb-multiarch}" +PYTHONPATH="$PYTHONPATH:$GDB_PYTHON_MODULE_DIRECTORY" exec ${RUST_GDB} \ + --directory="$GDB_PYTHON_MODULE_DIRECTORY" \ + -iex "add-auto-load-safe-path $GDB_PYTHON_MODULE_DIRECTORY" \ + -iex "set substitute-path /rustc/$RUSTC_COMMIT_HASH $RUSTC_SYSROOT/lib/rustlib/src/rust" \ + # -iex "source $GDB_PYTHON_MODULE_DIRECTORY/gdb_load_rust_pretty_printers.py" \ + "$@" \ No newline at end of file diff --git a/scripts/make/build_musl.mk b/scripts/make/build_musl.mk index e97747b5a..03d1e316c 100644 --- a/scripts/make/build_musl.mk +++ b/scripts/make/build_musl.mk @@ -16,6 +16,8 @@ LDFLAGS += -nostdlib -static -no-pie --gc-sections -T$(LD_SCRIPT) ifeq ($(MODE), release) CFLAGS += -O3 +else + CFLAGS += -Og -g endif ifeq ($(ARCH), x86_64) @@ -37,7 +39,7 @@ else endif endif -build_musl: +build_musl: ifeq ($(wildcard $(build_dir)),) ifeq ($(wildcard $(musl_dir)),) @echo "Download musl-1.2.3 source code" @@ -45,7 +47,7 @@ ifeq ($(wildcard $(build_dir)),) tar -zxvf $(muslibc_dir)/musl-1.2.3.tar.gz -C $(muslibc_dir) && rm -f $(muslibc_dir)/musl-1.2.3.tar.gz endif mkdir -p $(build_dir) - cd $(build_dir) && ../musl-1.2.3/configure --prefix=../install --exec-prefix=../ --syslibdir=../install/lib --disable-shared ARCH=$(RUX_ARCH) CC=$(CC) CROSS_COMPILE=$(CROSS_COMPILE) CFLAGS=$(CFLAGS) + cd $(build_dir) && ../musl-1.2.3/configure --prefix=../install --exec-prefix=../ --syslibdir=../install/lib --disable-shared ARCH=$(RUX_ARCH) CC=$(CC) CROSS_COMPILE=$(CROSS_COMPILE) CFLAGS='$(CFLAGS)' cd $(build_dir) && $(MAKE) -j && $(MAKE) install endif diff --git a/ulib/ruxmusl/src/aarch64/mod.rs b/ulib/ruxmusl/src/aarch64/mod.rs index ca04bb619..186d8c147 100644 --- a/ulib/ruxmusl/src/aarch64/mod.rs +++ b/ulib/ruxmusl/src/aarch64/mod.rs @@ -173,11 +173,11 @@ pub fn syscall(syscall_id: SyscallId, args: [usize; 6]) -> isize { #[cfg(feature = "multitask")] SyscallId::FUTEX => ruxos_posix_api::sys_futex( args[0], - args[1] as c_int, - args[2] as c_int, + args[1] as _, + args[2] as _, args[3], - args[4] as c_int, - args[5] as c_int, + args[4] as _, + args[5] as _, ) as _, SyscallId::NANO_SLEEP => ruxos_posix_api::sys_nanosleep( args[0] as *const ctypes::timespec, diff --git a/ulib/ruxmusl/src/x86_64/mod.rs b/ulib/ruxmusl/src/x86_64/mod.rs index 74a4284a8..77f6fc45c 100644 --- a/ulib/ruxmusl/src/x86_64/mod.rs +++ b/ulib/ruxmusl/src/x86_64/mod.rs @@ -358,11 +358,11 @@ pub fn syscall(syscall_id: SyscallId, args: [usize; 6]) -> isize { #[cfg(feature = "multitask")] SyscallId::FUTEX => ruxos_posix_api::sys_futex( args[0], - args[1] as c_int, - args[2] as c_int, + args[1] as _, + args[2] as _, args[3], - args[4] as c_int, - args[5] as c_int, + args[4] as _, + args[5] as _, ) as _, #[cfg(feature = "epoll")]