From ef4b2444e30e8f3c4684ba78104373752b57a808 Mon Sep 17 00:00:00 2001 From: ken4647 Date: Tue, 19 Mar 2024 17:56:22 +0800 Subject: [PATCH 1/8] fix problem in `random-hw`, `9p` and `pl011`. --- Cargo.lock | 2 +- api/ruxos_posix_api/src/imp/getrandom.rs | 13 +- crates/driver_pci/Cargo.toml | 2 +- crates/driver_virtio/Cargo.toml | 2 +- modules/rux9p/src/drv.rs | 12 +- modules/rux9p/src/fs.rs | 180 +++++++++--------- .../src/platform/aarch64_common/pl011.rs | 78 ++++++-- 7 files changed, 175 insertions(+), 114 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 33021e148..d083c4aa9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1970,7 +1970,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "virtio-drivers" version = "0.4.0" -source = "git+https://github.com/syswonder/virtio-drivers.git?rev=256ec4c#256ec4ca669a40719090aeaec858f707e8259183" +source = "git+https://github.com/syswonder/virtio-drivers.git?rev=62dbe5a#62dbe5a09aff0cac00037c8fe6528d94665286b8" dependencies = [ "bitflags 1.3.2", "log", diff --git a/api/ruxos_posix_api/src/imp/getrandom.rs b/api/ruxos_posix_api/src/imp/getrandom.rs index 49186e8e3..8187bccc9 100644 --- a/api/ruxos_posix_api/src/imp/getrandom.rs +++ b/api/ruxos_posix_api/src/imp/getrandom.rs @@ -22,6 +22,9 @@ use crate::ctypes::{size_t, ssize_t}; use axerrno::LinuxError; +#[cfg(all(target_arch = "x86_64", feature = "random-hw"))] +use core::arch::x86_64::__cpuid; + static SEED: AtomicU64 = AtomicU64::new(0xae_f3); /// Returns a 32-bit unsigned pseudo random interger using LCG. @@ -54,15 +57,7 @@ fn srand_lcg(seed: u64) { fn has_rdrand() -> bool { #[cfg(target_arch = "x86_64")] { - let mut ecx: u32; - unsafe { - core::arch::asm!( - "mov eax, 1", - "cpuid", - out("ecx") ecx - ) - } - ecx & (1 << 30) != 0 + unsafe { __cpuid(1).ecx & (1 << 30) != 0 } } #[cfg(target_arch = "aarch64")] { diff --git a/crates/driver_pci/Cargo.toml b/crates/driver_pci/Cargo.toml index ba037cc7f..dec497669 100644 --- a/crates/driver_pci/Cargo.toml +++ b/crates/driver_pci/Cargo.toml @@ -10,4 +10,4 @@ repository = "https://github.com/rcore-os/arceos/tree/main/crates/driver_pci" documentation = "https://rcore-os.github.io/arceos/driver_pci/index.html" [dependencies] -virtio-drivers = { git = "https://github.com/syswonder/virtio-drivers.git", rev = "256ec4c"} +virtio-drivers = { git = "https://github.com/syswonder/virtio-drivers.git", rev = "62dbe5a"} diff --git a/crates/driver_virtio/Cargo.toml b/crates/driver_virtio/Cargo.toml index 3e76f764a..2415441ec 100644 --- a/crates/driver_virtio/Cargo.toml +++ b/crates/driver_virtio/Cargo.toml @@ -23,4 +23,4 @@ driver_block = { path = "../driver_block", optional = true } driver_net = { path = "../driver_net", optional = true } driver_display = { path = "../driver_display", optional = true} driver_9p = { path = "../driver_9p", optional = true} -virtio-drivers = { git = "https://github.com/syswonder/virtio-drivers.git", rev = "256ec4c" } +virtio-drivers = { git = "https://github.com/syswonder/virtio-drivers.git", rev = "62dbe5a" } diff --git a/modules/rux9p/src/drv.rs b/modules/rux9p/src/drv.rs index 43df23b54..131aa8cb1 100644 --- a/modules/rux9p/src/drv.rs +++ b/modules/rux9p/src/drv.rs @@ -400,7 +400,7 @@ impl Drv9pOps { /// write perform I/O on the file represented by fid. /// Note that in v9fs, a read(2) or write(2) system call for a chunk of the file that won't fit in a single request is broken up into multiple requests. - pub fn twrite(&mut self, fid: u32, offset: u64, data: &[u8]) -> Result { + pub fn twrite(&mut self, fid: u32, offset: u64, data: &[u8]) -> Result { const MAX_READ_LEN: u32 = _9P_MAX_PSIZE - 32; let mut writing_len = data.len() as u32; if writing_len > MAX_READ_LEN { @@ -416,7 +416,7 @@ impl Drv9pOps { } request.finish(); match self.request(&request.buffer, &mut response_buffer) { - Ok(_) => Ok(lbytes2u64(&response_buffer[7..11]) as u8), // index from 7 to 11 corresponing to total count of writed byte + Ok(_) => Ok(lbytes2u64(&response_buffer[7..11]) as usize), // index from 7 to 11 corresponing to total count of writed byte Err(ecode) => Err(ecode), } } @@ -903,6 +903,10 @@ impl FileAttr { } } + pub fn get_perm(&self) -> u32 { + self.mode + } + pub fn set_size(&mut self, size: u64) { self.vaild |= _9P_SETATTR_SIZE; self.size = size; @@ -1018,6 +1022,10 @@ impl UStatFs { self.name.clone() } + pub fn get_perm(&self) -> u32 { + self.mode + } + pub fn get_ftype(&self) -> u8 { match self.qid.ftype { 0x00 => 0o10, diff --git a/modules/rux9p/src/fs.rs b/modules/rux9p/src/fs.rs index b25c19b6e..72843870e 100644 --- a/modules/rux9p/src/fs.rs +++ b/modules/rux9p/src/fs.rs @@ -11,11 +11,10 @@ //! //! The implementation is based on [`axfs_vfs`]. use crate::drv::{self, Drv9pOps}; -use alloc::{ - collections::BTreeMap, string::String, string::ToString, sync::Arc, sync::Weak, vec::Vec, -}; +use alloc::{string::String, string::ToString, sync::Arc, sync::Weak, vec::Vec}; use axfs_vfs::{ - VfsDirEntry, VfsError, VfsNodeAttr, VfsNodeOps, VfsNodeRef, VfsNodeType, VfsOps, VfsResult, + VfsDirEntry, VfsError, VfsNodeAttr, VfsNodeOps, VfsNodePerm, VfsNodeRef, VfsNodeType, VfsOps, + VfsResult, }; use log::*; use spin::{once::Once, RwLock}; @@ -62,8 +61,7 @@ impl _9pFileSystem { let fid = match dev.write().get_fid() { Some(id) => id, None => { - warn!("9pfs: No enough fids! Check fid_MAX constrant or fid leaky."); - 0xff_ff_ff_ff + panic!("9pfs: No enough fids! Check fid_MAX constrant or fid leaky."); } }; @@ -120,23 +118,35 @@ impl CommonNode { dev: Arc>, protocol: Arc, ) -> Arc { - const OPEN_FLAG: u8 = 0x02; - if *protocol == "9P2000.L" { - match dev.write().l_topen(fid, OPEN_FLAG as u32) { - Ok(_) => {} - Err(errcode) => { - error!("9pfs open failed! error code: {}", errcode); - } - } + const O_RDWR: u8 = 0x02; + const O_RDONLY: u8 = 0x00; + const EISDIR: u8 = 21; + + let result = if *protocol == "9P2000.L" { + dev.write().l_topen(fid, O_RDWR as u32) } else if *protocol == "9P2000.u" { - match dev.write().topen(fid, OPEN_FLAG) { - Ok(_) => {} - Err(errcode) => { - error!("9pfs open failed! error code: {}", errcode); - } - } + dev.write().topen(fid, O_RDWR) } else { error!("9pfs open failed! Unsupported protocol version"); + Ok(()) + }; + + if let Err(EISDIR) = result { + if *protocol == "9P2000.L" { + handle_result!( + dev.write().l_topen(fid, O_RDONLY as u32), + "9pfs l_topen failed! error code: {}" + ); + } else if *protocol == "9P2000.u" { + handle_result!( + dev.write().topen(fid, O_RDONLY), + "9pfs topen failed! error code: {}" + ); + } else { + error!("9pfs open failed! Unsupported protocol version"); + } + } else if let Err(ecode) = result { + error!("9pfs topen failed! error code: {}", ecode); } Arc::new_cyclic(|this| Self { @@ -166,8 +176,7 @@ impl CommonNode { let fid = match self.inner.write().get_fid() { Some(id) => id, None => { - error!("No enough fids! Check fid_MAX constrant or fid leaky."); - 0xff_ff_ff_ff + panic!("9pfs: No enough fids! Check fid_MAX constrant or fid leaky."); } }; match ty { @@ -223,8 +232,7 @@ impl CommonNode { let fid = match self.inner.write().get_fid() { Some(id) => id, None => { - warn!("9pfs: No enough fids! Check fid_MAX constrant or fid leaky."); - 0xff_ff_ff_ff + panic!("9pfs: No enough fids! Check fid_MAX constrant or fid leaky."); } }; @@ -273,8 +281,7 @@ impl CommonNode { let new_fid = match self.inner.write().get_fid() { Some(id) => id, None => { - warn!("9pfs: No enough fids! Check fid_MAX constrant or fid leaky."); - 0xff_ff_ff_ff + panic!("9pfs: No enough fids! Check fid_MAX constrant or fid leaky."); } }; @@ -294,45 +301,6 @@ impl CommonNode { Err(_) => Err(VfsError::BadState), } } - - /// Update nodes from host filesystem - fn all_nodes(&self) -> Result, VfsError> { - let mut node_map: BTreeMap = BTreeMap::new(); - debug!("reading all nodes in 9p, fid={}", *self.fid); - let dirents = match self.protocol.as_str() { - "9P2000.L" => match self.inner.write().treaddir(*self.fid) { - Ok(contents) => contents, - Err(errcode) => { - error!("9pfs treaddir failed! error code: {}", errcode); - return Err(VfsError::BadState); - } - }, - "9P2000.u" => match self.inner.write().u_treaddir(*self.fid) { - Ok(contents) => contents, - Err(errcode) => { - error!("9pfs u_treaddir failed! error code: {}", errcode); - return Err(VfsError::BadState); - } - }, - _ => { - error!("Unsupport 9P protocol version: {}", *self.protocol); - return Err(VfsError::BadState); - } - }; - - for direntry in dirents { - let fname = direntry.get_name(); - if fname.eq("..") || fname.eq(".") { - continue; - } - - trace!("9pfs update node {}, type {}", fname, direntry.get_type()); - - let node: VfsNodeRef = self.try_get(fname).unwrap(); - node_map.insert(fname.into(), node); - } - Ok(node_map) - } } impl Drop for CommonNode { @@ -413,10 +381,16 @@ impl VfsNodeOps for CommonNode { debug!("get_attr {:?}", resp); match resp { Ok(stat) if stat.get_ftype() == 0o4 => { - Ok(VfsNodeAttr::new_dir(stat.get_size(), stat.get_blk_num())) + let mut attr = VfsNodeAttr::new_dir(stat.get_size(), stat.get_blk_num()); + let mode = stat.get_perm() as u16 & 0o777_u16; + attr.set_perm(VfsNodePerm::from_bits(mode).unwrap()); + Ok(attr) } Ok(stat) if stat.get_ftype() == 0o10 => { - Ok(VfsNodeAttr::new_file(stat.get_size(), stat.get_blk_num())) + let mut attr = VfsNodeAttr::new_file(stat.get_size(), stat.get_blk_num()); + let mode = stat.get_perm() as u16 & 0o777_u16; + attr.set_perm(VfsNodePerm::from_bits(mode).unwrap()); + Ok(attr) } _ => Err(VfsError::BadState), } @@ -424,10 +398,16 @@ impl VfsNodeOps for CommonNode { let resp = self.inner.write().tstat(*self.fid); match resp { Ok(stat) if stat.get_ftype() == 0o4 => { - Ok(VfsNodeAttr::new_dir(stat.get_length(), stat.get_blk_num())) + let mut attr = VfsNodeAttr::new_dir(stat.get_length(), stat.get_blk_num()); + let mode = stat.get_perm() as u16 & 0o777_u16; + attr.set_perm(VfsNodePerm::from_bits(mode).unwrap()); + Ok(attr) } Ok(stat) if stat.get_ftype() == 0o10 => { - Ok(VfsNodeAttr::new_file(stat.get_length(), stat.get_blk_num())) + let mut attr = VfsNodeAttr::new_file(stat.get_length(), stat.get_blk_num()); + let mode = stat.get_perm() as u16 & 0o777_u16; + attr.set_perm(VfsNodePerm::from_bits(mode).unwrap()); + Ok(attr) } _ => Err(VfsError::BadState), } @@ -446,39 +426,59 @@ impl VfsNodeOps for CommonNode { self.try_get(path) } - fn read_dir(&self, start_idx: usize, dirents: &mut [VfsDirEntry]) -> VfsResult { - debug!("9pfs reading dirents"); - let _9p_map = match self.all_nodes() { - Ok(contents) => contents, - Err(errcode) => { - error!("9pfs all_nodes failed! error code = {}", errcode); + fn read_dir(&self, start_idx: usize, vfs_dirents: &mut [VfsDirEntry]) -> VfsResult { + debug!("9pfs reading dirents: start_idx = {:x?}", start_idx); + let dirents = match self.protocol.as_str() { + "9P2000.L" => match self.inner.write().treaddir(*self.fid) { + Ok(contents) => contents, + Err(errcode) => { + error!("9pfs treaddir failed! error code: {}", errcode); + return Err(VfsError::BadState); + } + }, + "9P2000.u" => match self.inner.write().u_treaddir(*self.fid) { + Ok(contents) => contents, + Err(errcode) => { + error!("9pfs u_treaddir failed! error code: {}", errcode); + return Err(VfsError::BadState); + } + }, + _ => { + error!("Unsupport 9P protocol version: {}", *self.protocol); return Err(VfsError::BadState); } }; - let mut item_iter = _9p_map.iter().skip(start_idx.max(2) - 2); - for (i, ent) in dirents.iter_mut().enumerate() { + let mut item_iter = dirents + .iter() + .filter(|&e| !(e.get_name().eq(".") || e.get_name().eq(".."))) + .skip(start_idx.max(2) - 2); // read from start_idx + for (i, ent) in vfs_dirents.iter_mut().enumerate() { match i + start_idx { 0 => *ent = VfsDirEntry::new(".", VfsNodeType::Dir), 1 => *ent = VfsDirEntry::new("..", VfsNodeType::Dir), _ => { - if let Some((name, node)) = item_iter.next() { - let attr = node.get_attr(); - let file_type = match attr { - Ok(attr) => attr.file_type(), - Err(ecode) => { - error!("get [{}] attribute failed, error code:{}.", name, ecode); - continue; - } + if let Some(entry) = item_iter.next() { + let file_type = match entry.get_type() { + 0o1_u8 => VfsNodeType::Fifo, + 0o2_u8 => VfsNodeType::CharDevice, + 0o4_u8 => VfsNodeType::Dir, + 0o6_u8 => VfsNodeType::BlockDevice, + 0o10_u8 => VfsNodeType::File, + 0o12_u8 => VfsNodeType::SymLink, + 0o14_u8 => VfsNodeType::Socket, + _ => panic!("9pfs: Unexpected file type found!"), }; - *ent = VfsDirEntry::new(name, file_type); + *ent = VfsDirEntry::new(entry.get_name(), file_type); } else { + debug!("9pfs read dirents finished: start_idx = {:x?}", start_idx); return Ok(i); } } } } - Ok(dirents.len()) + debug!("9pfs read dirents finished: start_idx = {:x?}", start_idx); + Ok(vfs_dirents.len()) } fn create(&self, path: &str, ty: VfsNodeType) -> VfsResult { @@ -533,6 +533,7 @@ impl VfsNodeOps for CommonNode { /// Read data from the file at the given offset. fn read_at(&self, offset: u64, buf: &mut [u8]) -> VfsResult { + debug!("read 9pid:{} length: {}", self.fid, buf.len()); let mut dev = self.inner.write(); let mut read_len = buf.len(); let mut offset_ptr = 0; @@ -552,19 +553,19 @@ impl VfsNodeOps for CommonNode { read_len -= rlen; offset_ptr += rlen; } - debug!("9P Reading {}, length: {}", self.fid, buf.len()); Ok(buf.len()) } /// Write data to the file at the given offset. fn write_at(&self, offset: u64, buf: &[u8]) -> VfsResult { + debug!("write 9pid:{} length: {}", self.fid, buf.len()); let mut dev = self.inner.write(); let mut write_len = buf.len(); let mut offset_ptr = 0; while write_len > 0 { let target_buf = &buf[offset_ptr..]; let wlen = match dev.twrite(*self.fid, offset + offset_ptr as u64, target_buf) { - Ok(writed_length) => writed_length as usize, + Ok(writed_length) => writed_length, Err(_) => return Err(VfsError::BadState), }; if wlen == 0 { @@ -573,6 +574,7 @@ impl VfsNodeOps for CommonNode { write_len -= wlen; offset_ptr += wlen; } + Ok(buf.len()) } diff --git a/modules/ruxhal/src/platform/aarch64_common/pl011.rs b/modules/ruxhal/src/platform/aarch64_common/pl011.rs index 170abcda8..2ce048ac8 100644 --- a/modules/ruxhal/src/platform/aarch64_common/pl011.rs +++ b/modules/ruxhal/src/platform/aarch64_common/pl011.rs @@ -8,21 +8,67 @@ */ //! PL011 UART. - use arm_pl011::pl011::Pl011Uart; +use cfg_if::cfg_if; use memory_addr::PhysAddr; use spinlock::SpinNoIrq; use crate::mem::phys_to_virt; const UART_BASE: PhysAddr = PhysAddr::from(ruxconfig::UART_PADDR); +#[cfg(feature = "irq")] +const BUFFER_SIZE: usize = 128; + +#[cfg(feature = "irq")] +struct RxRingBuffer { + buffer: [u8; BUFFER_SIZE], + head: usize, + tail: usize, +} + +#[cfg(feature = "irq")] +impl RxRingBuffer { + const fn new() -> Self { + RxRingBuffer { + buffer: [0_u8; BUFFER_SIZE], + head: 0_usize, + tail: 0_usize, + } + } + + fn push(&mut self, n: u8) { + if self.tail != self.head { + self.buffer[self.tail] = n; + self.tail = (self.tail + 1) % BUFFER_SIZE; + } + } + + fn pop(&mut self) -> Option { + if self.head == self.tail { + None + } else { + let ret = self.buffer[self.head]; + self.head += (self.head + 1) % BUFFER_SIZE; + Some(ret) + } + } +} + +struct UartDrv { + inner: SpinNoIrq, + #[cfg(feature = "irq")] + buffer: SpinNoIrq, +} -static UART: SpinNoIrq = - SpinNoIrq::new(Pl011Uart::new(phys_to_virt(UART_BASE).as_mut_ptr())); +static UART: UartDrv = UartDrv { + inner: SpinNoIrq::new(Pl011Uart::new(phys_to_virt(UART_BASE).as_mut_ptr())), + #[cfg(feature = "irq")] + buffer: SpinNoIrq::new(RxRingBuffer::new()), +}; /// Writes a byte to the console. pub fn putchar(c: u8) { - let mut uart = UART.lock(); + let mut uart = UART.inner.lock(); match c { b'\n' => { uart.putchar(b'\r'); @@ -34,27 +80,37 @@ pub fn putchar(c: u8) { /// Reads a byte from the console, or returns [`None`] if no input is available. pub fn getchar() -> Option { - UART.lock().getchar() + cfg_if! { + if #[cfg(feature = "irq")] { + UART.buffer.lock().pop() + }else{ + UART.inner.lock().getchar() + } + } } /// Initialize the UART pub fn init_early() { - UART.lock().init(); + UART.inner.lock().init(); } /// Set UART IRQ Enable pub fn init() { #[cfg(feature = "irq")] - crate::irq::set_enable(crate::platform::irq::UART_IRQ_NUM, true); + { + crate::irq::register_handler(crate::platform::irq::UART_IRQ_NUM, handle); + crate::irq::set_enable(crate::platform::irq::UART_IRQ_NUM, true); + } } /// UART IRQ Handler +#[cfg(feature = "irq")] pub fn handle() { - let is_receive_interrupt = UART.lock().is_receive_interrupt(); - UART.lock().ack_interrupts(); + let is_receive_interrupt = UART.inner.lock().is_receive_interrupt(); if is_receive_interrupt { - while let Some(c) = getchar() { - putchar(c); + UART.inner.lock().ack_interrupts(); + while let Some(c) = UART.inner.lock().getchar() { + UART.buffer.lock().push(c); } } } From 810d48bbff9a8820213f4c92adab15f37c4ecc6c Mon Sep 17 00:00:00 2001 From: Sssssaltyfish <47731209+Sssssaltyfish@users.noreply.github.com> Date: Sat, 27 Jan 2024 20:14:04 +0800 Subject: [PATCH 2/8] Implemented futex and updated `WaitQueue` to support it 1. Implemented futex in modules/ruxfutex. Now supporting `WAIT(_BITSET)` and `WAKE(_BITSET)`. 2. Updated `WaitQueue` in modules/ruxtask to support more operations. 3. Added a new script `scripts/debug/rust-gdb.sh` to help debugging. 4. Added a new make rule `debug_no_attach` to make debugging in external gdb environment (like VSCode) easier. 5. Enhanced debug format for aarch64 `TrapFrame`. 6. Added a "reldebug" mode that emits debug info with full optimization. --- .gitignore | 5 + Cargo.lock | 116 +++++- Cargo.toml | 6 + Makefile | 7 +- api/ruxos_posix_api/Cargo.toml | 7 +- api/ruxos_posix_api/src/imp/pthread/futex.rs | 223 +++++------ api/ruxos_posix_api/src/imp/pthread/mod.rs | 11 +- apps/c/libc-bench/axbuild.mk | 6 +- modules/ruxfutex/Cargo.toml | 33 ++ modules/ruxfutex/src/api.rs | 212 +++++++++++ modules/ruxfutex/src/lib.rs | 49 +++ modules/ruxfutex/src/types.rs | 101 +++++ modules/ruxhal/src/arch/aarch64/context.rs | 30 +- modules/ruxruntime/Cargo.toml | 8 +- modules/ruxruntime/src/lib.rs | 6 +- modules/ruxtask/src/api.rs | 2 +- modules/ruxtask/src/task.rs | 18 +- modules/ruxtask/src/wait_queue.rs | 377 ++++++++++++++++--- scripts/debug/rust-gdb.sh | 31 ++ scripts/make/build_c.mk | 2 + scripts/make/build_musl.mk | 10 +- scripts/make/cargo.mk | 1 + ulib/ruxmusl/src/aarch64/mod.rs | 8 +- ulib/ruxmusl/src/x86_64/mod.rs | 8 +- 24 files changed, 1045 insertions(+), 232 deletions(-) create mode 100644 modules/ruxfutex/Cargo.toml create mode 100644 modules/ruxfutex/src/api.rs create mode 100644 modules/ruxfutex/src/lib.rs create mode 100644 modules/ruxfutex/src/types.rs create mode 100755 scripts/debug/rust-gdb.sh diff --git a/.gitignore b/.gitignore index fd3f0164b..6cb396144 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,11 @@ /target /.vscode .DS_Store + +# clangd +/.cache +compile_commands.json + *.asm disk.img actual.out diff --git a/Cargo.lock b/Cargo.lock index d083c4aa9..cce3811d1 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=62dbe5a#62dbe5 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..29f519fc3 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", @@ -67,5 +68,10 @@ members = [ [profile.release] lto = true +[profile.reldebug] +inherits = "release" +debug = true + + [patch.crates-io] crate_interface = { path = "crates/crate_interface" } diff --git a/Makefile b/Makefile index 7e60f6ab7..3ffc9d96e 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ # - `ARCH`: Target architecture: x86_64, riscv64, aarch64 # - `PLATFORM`: Target platform in the `platforms` directory # - `SMP`: Number of CPUs -# - `MODE`: Build mode: release, debug +# - `MODE`: Build mode: release, debug, reldebug # - `LOG:` Logging level: warn, error, info, debug, trace # - `V`: Verbose level: (empty), 1, 2 # - `ARGS`: Command-line arguments separated by comma. Only available when feature `alloc` is enabled. @@ -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 aec121a60..4d4dda594 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"] @@ -42,6 +42,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 } @@ -56,5 +57,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/apps/c/libc-bench/axbuild.mk b/apps/c/libc-bench/axbuild.mk index 1134c7a37..9e73e52e3 100644 --- a/apps/c/libc-bench/axbuild.mk +++ b/apps/c/libc-bench/axbuild.mk @@ -5,6 +5,8 @@ bench-obj := $(package-name)/libctest.o app-objs := $(bench-obj) +force_rebuild: + $(libc-bench-dir): @echo "Download libc-bench code" wget https://git.musl-libc.org/cgit/libc-bench/snapshot/$(package-name).tar.gz -P $(APP) @@ -14,10 +16,10 @@ $(libc-bench-dir): $(APP)/$(bench-obj): build_libc_bench -build_libc_bench: $(libc-bench-dir) +build_libc_bench: $(libc-bench-dir) force_rebuild cd $(libc-bench-dir) && $(MAKE) CC=$(CC) CFLAGS="$(CFLAGS)" -j clean_c:: $(MAKE) -C $(libc-bench-dir) clean -.PHONY: build_libc_bench clean_c +.PHONY: build_libc_bench clean_c force_rebuild 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..86c0a0ef6 --- /dev/null +++ b/modules/ruxfutex/src/api.rs @@ -0,0 +1,212 @@ +/* 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::{AxError, 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: expected {}, found {}", futex_val, actual_val); + if actual_val != futex_val { + // it's not actually an error but rather a notice to user, + // so no `ax_err` and no warning + return Err(AxError::WouldBlock); + } + + 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/ruxhal/src/arch/aarch64/context.rs b/modules/ruxhal/src/arch/aarch64/context.rs index 9571bc845..d129d23f1 100644 --- a/modules/ruxhal/src/arch/aarch64/context.rs +++ b/modules/ruxhal/src/arch/aarch64/context.rs @@ -7,12 +7,15 @@ * See the Mulan PSL v2 for more details. */ -use core::arch::asm; +use core::{ + arch::asm, + fmt::{Debug, LowerHex}, +}; use memory_addr::VirtAddr; /// Saved registers when a trap (exception) occurs. #[repr(C)] -#[derive(Debug, Default, Clone, Copy)] +#[derive(Default, Clone, Copy)] pub struct TrapFrame { /// General-purpose registers (R0..R30). pub r: [u64; 31], @@ -24,6 +27,29 @@ pub struct TrapFrame { pub spsr: u64, } +struct EnumerateReg<'a, T>(&'a [T]); + +impl<'a, T: Debug + LowerHex> Debug for EnumerateReg<'a, T> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let mut dbg_l = f.debug_list(); + for (i, r) in self.0.iter().enumerate() { + dbg_l.entry(&format_args!("x{}: {:#x}", i, r)); + } + dbg_l.finish() + } +} + +impl Debug for TrapFrame { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("TrapFrame") + .field("r", &EnumerateReg(&self.r)) + .field("usp", &self.usp) + .field("elr", &self.elr) + .field("spsr", &self.spsr) + .finish() + } +} + /// FP & SIMD registers. #[repr(C, align(16))] #[derive(Debug, Default)] 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..2466bc59c 100644 --- a/modules/ruxtask/src/wait_queue.rs +++ b/modules/ruxtask/src/wait_queue.rs @@ -13,7 +13,10 @@ use spinlock::SpinRaw; use crate::{AxRunQueue, AxTaskRef, CurrentTask, RUN_QUEUE}; -/// A queue to store sleeping tasks. +type ItemType = (AxTaskRef, Meta); +type QueueType = VecDeque>; + +/// A queue to store sleeping tasks, each with its metadata. /// /// # Examples /// @@ -35,11 +38,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 { @@ -55,13 +61,17 @@ impl WaitQueue { } fn cancel_events(&self, curr: CurrentTask) { + Self::cancel_events_locked(&mut self.queue.lock(), curr); + } + + fn cancel_events_locked(queue: &mut QueueType, curr: CurrentTask) { // A task can be wake up only one events (timer or `notify()`), remove // the event from another queue. if curr.in_wait_queue() { // 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)); + queue.retain(|(t, _)| !curr.ptr_eq(t)); curr.set_in_wait_queue(false); } #[cfg(feature = "irq")] @@ -73,97 +83,111 @@ 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 rq = RUN_QUEUE.lock(); + let mut wq = self.queue.lock(); + condition()?; + + rq.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 rq = RUN_QUEUE.lock(); + 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); - }); - } + rq.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 +210,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 +227,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 +274,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_c.mk b/scripts/make/build_c.mk index 7c931cb17..1d97ea6da 100644 --- a/scripts/make/build_c.mk +++ b/scripts/make/build_c.mk @@ -23,6 +23,8 @@ LDFLAGS += -nostdlib -static -no-pie --gc-sections -T$(LD_SCRIPT) ifeq ($(MODE), release) CFLAGS += -O3 +else ifeq ($(MODE), reldebug) + CFLAGS += -O3 -g endif ifeq ($(ARCH), x86_64) diff --git a/scripts/make/build_musl.mk b/scripts/make/build_musl.mk index e97747b5a..9c8af1318 100644 --- a/scripts/make/build_musl.mk +++ b/scripts/make/build_musl.mk @@ -11,11 +11,15 @@ c_lib := $(muslibc_dir)/install/lib/libc.a libgcc := CFLAGS += -nostdinc -fno-builtin -ffreestanding -Wall -CFLAGS += -I$(CURDIR)/$(inc_dir) +CFLAGS += -isystem$(CURDIR)/$(inc_dir) LDFLAGS += -nostdlib -static -no-pie --gc-sections -T$(LD_SCRIPT) ifeq ($(MODE), release) CFLAGS += -O3 +else ifeq ($(MODE), reldebug) + CFLAGS += -O3 -g +else + CFLAGS += -Og -g endif ifeq ($(ARCH), x86_64) @@ -37,7 +41,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 +49,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/scripts/make/cargo.mk b/scripts/make/cargo.mk index 1b1bfa403..308896d27 100644 --- a/scripts/make/cargo.mk +++ b/scripts/make/cargo.mk @@ -9,6 +9,7 @@ else endif build_args-release := --release +build_args-reldebug := --profile=reldebug build_args := \ --target $(TARGET) \ diff --git a/ulib/ruxmusl/src/aarch64/mod.rs b/ulib/ruxmusl/src/aarch64/mod.rs index 679a5381a..d6d03dee9 100644 --- a/ulib/ruxmusl/src/aarch64/mod.rs +++ b/ulib/ruxmusl/src/aarch64/mod.rs @@ -187,11 +187,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 9b4fc85e6..19792017a 100644 --- a/ulib/ruxmusl/src/x86_64/mod.rs +++ b/ulib/ruxmusl/src/x86_64/mod.rs @@ -366,11 +366,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")] From 3276e300a495991d177207caf36b11075ac4fda5 Mon Sep 17 00:00:00 2001 From: Sssssaltyfish <47731209+Sssssaltyfish@users.noreply.github.com> Date: Sun, 24 Mar 2024 21:42:03 +0800 Subject: [PATCH 3/8] Removed embedded opensbi binary to avoid potential license problem. User can still choose the sbi binary used by setting the `RISCV_BIOS` variable. A script `scripts/download_rvsbi.sh` is provided to download the opensbi binary from the official website for convenience. --- .gitignore | 3 +++ Makefile | 2 +- platforms/riscv/fw_dynamic.bin | Bin 270224 -> 0 bytes scripts/download_rvsbi.sh | 27 +++++++++++++++++++++++++++ 4 files changed, 31 insertions(+), 1 deletion(-) delete mode 100755 platforms/riscv/fw_dynamic.bin create mode 100755 scripts/download_rvsbi.sh diff --git a/.gitignore b/.gitignore index 6cb396144..da9c3bd6e 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,9 @@ /.cache compile_commands.json +# downloaded opensbi binary +/fw_dynamic.bin + *.asm disk.img actual.out diff --git a/Makefile b/Makefile index 3ffc9d96e..d7cd094da 100644 --- a/Makefile +++ b/Makefile @@ -56,7 +56,7 @@ NET ?= n GRAPHIC ?= n V9P ?= n BUS ?= mmio -RISCV_BIOS ?= $(shell realpath ./platforms/riscv/fw_dynamic.bin) +RISCV_BIOS ?= default DISK_IMG ?= disk.img QEMU_LOG ?= n diff --git a/platforms/riscv/fw_dynamic.bin b/platforms/riscv/fw_dynamic.bin deleted file mode 100755 index 0a2071bafe99a008d9aadcedb10be3c088d6f382..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 270224 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 A!T Date: Mon, 25 Mar 2024 19:56:23 +0800 Subject: [PATCH 4/8] add flags to tcp recv/send --- api/arceos_api/src/imp/net.rs | 2 +- api/ruxos_posix_api/src/imp/net.rs | 10 ++++----- modules/axnet/src/smoltcp_impl/tcp.rs | 28 ++++++++++++++++++++------ modules/rux9p/src/netdev.rs | 2 +- platforms/riscv/fw_dynamic.bin | Bin 270224 -> 0 bytes 5 files changed, 29 insertions(+), 13 deletions(-) delete mode 100755 platforms/riscv/fw_dynamic.bin diff --git a/api/arceos_api/src/imp/net.rs b/api/arceos_api/src/imp/net.rs index 1ccf1e59a..16920dc7a 100644 --- a/api/arceos_api/src/imp/net.rs +++ b/api/arceos_api/src/imp/net.rs @@ -62,7 +62,7 @@ pub fn ax_tcp_send(socket: &AxTcpSocketHandle, buf: &[u8]) -> AxResult { } pub fn ax_tcp_recv(socket: &AxTcpSocketHandle, buf: &mut [u8]) -> AxResult { - socket.0.recv(buf) + socket.0.recv(buf, 0) } pub fn ax_tcp_poll(socket: &AxTcpSocketHandle) -> AxResult { diff --git a/api/ruxos_posix_api/src/imp/net.rs b/api/ruxos_posix_api/src/imp/net.rs index 9b5d981b8..56cb8b6a1 100644 --- a/api/ruxos_posix_api/src/imp/net.rs +++ b/api/ruxos_posix_api/src/imp/net.rs @@ -45,10 +45,10 @@ impl Socket { } } - fn recv(&self, buf: &mut [u8]) -> LinuxResult { + fn recv(&self, buf: &mut [u8], flags: i32) -> LinuxResult { match self { Socket::Udp(udpsocket) => Ok(udpsocket.lock().recv_from(buf).map(|e| e.0)?), - Socket::Tcp(tcpsocket) => Ok(tcpsocket.lock().recv(buf)?), + Socket::Tcp(tcpsocket) => Ok(tcpsocket.lock().recv(buf, flags)?), } } @@ -102,7 +102,7 @@ impl Socket { .lock() .recv_from(buf) .map(|res| (res.0, Some(res.1)))?), - Socket::Tcp(tcpsocket) => Ok(tcpsocket.lock().recv(buf).map(|res| (res, None))?), + Socket::Tcp(tcpsocket) => Ok(tcpsocket.lock().recv(buf, 0).map(|res| (res, None))?), } } @@ -141,7 +141,7 @@ impl Socket { impl FileLike for Socket { fn read(&self, buf: &mut [u8]) -> LinuxResult { - self.recv(buf) + self.recv(buf, 0) } fn write(&self, buf: &[u8]) -> LinuxResult { @@ -422,7 +422,7 @@ pub fn sys_recv( return Err(LinuxError::EFAULT); } let buf = unsafe { core::slice::from_raw_parts_mut(buf_ptr as *mut u8, len) }; - Socket::from_fd(socket_fd)?.recv(buf) + Socket::from_fd(socket_fd)?.recv(buf, flag) }) } diff --git a/modules/axnet/src/smoltcp_impl/tcp.rs b/modules/axnet/src/smoltcp_impl/tcp.rs index a66cb2e42..8bde01549 100644 --- a/modules/axnet/src/smoltcp_impl/tcp.rs +++ b/modules/axnet/src/smoltcp_impl/tcp.rs @@ -34,6 +34,11 @@ const STATE_CONNECTING: u8 = 2; const STATE_CONNECTED: u8 = 3; const STATE_LISTENING: u8 = 4; +const MSG_OOB: i32 = 1; +const MSG_PEEK: i32 = 2; +const MSG_DONTWAIT: i32 = 4; +const MSG_CTRUNC: i32 = 8; + /// A TCP socket that provides POSIX-like APIs. /// /// - [`connect`] is for TCP clients. @@ -282,7 +287,7 @@ impl TcpSocket { } /// Receives data from the socket, stores it in the given buffer. - pub fn recv(&self, buf: &mut [u8]) -> AxResult { + pub fn recv(&self, buf: &mut [u8], flags: i32) -> AxResult { if self.is_connecting() { return Err(AxError::WouldBlock); } else if !self.is_connected() { @@ -302,10 +307,20 @@ impl TcpSocket { } else if socket.recv_queue() > 0 { // data available // TODO: use socket.recv(|buf| {...}) - let len = socket - .recv_slice(buf) - .map_err(|_| ax_err_type!(BadState, "socket recv() failed"))?; - Ok(len) + if flags & MSG_DONTWAIT != 0 { + self.set_nonblocking(true); + } + if flags & MSG_PEEK != 0 { + let len = socket + .peek_slice(buf) + .map_err(|_| ax_err_type!(BadState, "socket recv() failed"))?; + Ok(len) + } else { + let len = socket + .recv_slice(buf) + .map_err(|_| ax_err_type!(BadState, "socket recv() failed"))?; + Ok(len) + } } else { // no more data Err(AxError::WouldBlock) @@ -315,6 +330,7 @@ impl TcpSocket { } /// Transmits data in the given buffer. + /// TODO: impl send flags pub fn send(&self, buf: &[u8]) -> AxResult { if self.is_connecting() { return Err(AxError::WouldBlock); @@ -517,7 +533,7 @@ impl Drop for TcpSocket { impl axio::Read for TcpSocket { fn read(&mut self, buf: &mut [u8]) -> AxResult { - self.recv(buf) + self.recv(buf, 0) } } diff --git a/modules/rux9p/src/netdev.rs b/modules/rux9p/src/netdev.rs index 53d28d997..07c78865b 100644 --- a/modules/rux9p/src/netdev.rs +++ b/modules/rux9p/src/netdev.rs @@ -70,7 +70,7 @@ impl _9pDriverOps for Net9pDev { return Err(0); } } - match self.socket.recv(outputs) { + match self.socket.recv(outputs, 0) { Ok(length) => { debug!("net9p recv successfully,length = {}", length); Ok(length as u32) diff --git a/platforms/riscv/fw_dynamic.bin b/platforms/riscv/fw_dynamic.bin deleted file mode 100755 index 0a2071bafe99a008d9aadcedb10be3c088d6f382..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 270224 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 A!T Date: Tue, 26 Mar 2024 14:48:19 +0800 Subject: [PATCH 5/8] add random-hw feature in ruxmusl --- apps/c/cpp/features.txt | 2 ++ apps/c/cpp/main.cpp | 8 ++++++++ ulib/ruxmusl/Cargo.toml | 1 + 3 files changed, 11 insertions(+) diff --git a/apps/c/cpp/features.txt b/apps/c/cpp/features.txt index 9812e2a76..662a64968 100644 --- a/apps/c/cpp/features.txt +++ b/apps/c/cpp/features.txt @@ -2,3 +2,5 @@ alloc paging irq multitask +fs +random-hw diff --git a/apps/c/cpp/main.cpp b/apps/c/cpp/main.cpp index 0a77600ab..f970043fe 100644 --- a/apps/c/cpp/main.cpp +++ b/apps/c/cpp/main.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include extern "C" { @@ -65,5 +66,12 @@ int main() } std::cout << std::endl; + // random test + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> dis(1, 100); + for (int i = 0; i < 10; i++) { + std::cout << "Random number: " << dis(gen) << std::endl; + } return 0; } diff --git a/ulib/ruxmusl/Cargo.toml b/ulib/ruxmusl/Cargo.toml index b8242a496..ca56428b1 100644 --- a/ulib/ruxmusl/Cargo.toml +++ b/ulib/ruxmusl/Cargo.toml @@ -41,6 +41,7 @@ epoll = ["ruxos_posix_api/epoll"] poll = ["ruxos_posix_api/poll"] rtc = ["ruxfeat/rtc"] signal = ["ruxos_posix_api/signal"] +random-hw = ["ruxos_posix_api/random-hw"] musl = ["ruxos_posix_api/musl", "tls"] From 5b48a0edeaffbf95b7045f291d37269fbe6fb53b Mon Sep 17 00:00:00 2001 From: AuYang261 <459461160@qq.com> Date: Tue, 26 Mar 2024 14:48:41 +0800 Subject: [PATCH 6/8] add c++ benchmark --- apps/c/cpp/.gitignore | 1 + apps/c/cpp/axbuild.mk | 56 +++++++++++++---- apps/c/cpp/main.cpp | 77 ----------------------- apps/c/cpp/std_benchmark.patch | 109 +++++++++++++++++++++++++++++++++ modules/ruxfs/src/mounts.rs | 2 + 5 files changed, 156 insertions(+), 89 deletions(-) create mode 100644 apps/c/cpp/.gitignore delete mode 100644 apps/c/cpp/main.cpp create mode 100644 apps/c/cpp/std_benchmark.patch diff --git a/apps/c/cpp/.gitignore b/apps/c/cpp/.gitignore new file mode 100644 index 000000000..bebd92107 --- /dev/null +++ b/apps/c/cpp/.gitignore @@ -0,0 +1 @@ +std-benchmark/ diff --git a/apps/c/cpp/axbuild.mk b/apps/c/cpp/axbuild.mk index 94d073e32..37e8ea604 100644 --- a/apps/c/cpp/axbuild.mk +++ b/apps/c/cpp/axbuild.mk @@ -1,21 +1,53 @@ +CMAKE = cmake + ARCH ?= x86_64 C_COMPILER := $(shell which $(CC)) +CXX_COMPILER := $(shell which $(CC)) +AR := $(shell which $(AR)) +RANLIB := $(shell which $(RANLIB)) CROSS_COMPILE_PATH := $(shell dirname $(C_COMPILER))/.. -CXX_STD := c++20 +CXX_STD ?= 20 + +app-objs := std_benchmark.o +std_benchmark_dir := $(APP)/std-benchmark +std_benchmark_build = $(std_benchmark_dir)/build + +bench ?= all +benches_available := $(wildcard $(std_benchmark_dir)/cxx/*.bench.cpp) +benches_available := $(patsubst $(std_benchmark_dir)/cxx/%.bench.cpp,%,$(benches_available)) -main-obj := main.o -app-objs := cpp.o +$(std_benchmark_dir): + @echo "Download std-benchmark source code" + cd $(APP)/ && git clone --recursive https://github.com/hiraditya/std-benchmark + patch -p1 -N -d $(std_benchmark_dir) --no-backup-if-mismatch -r - < $(APP)/std_benchmark.patch -$(APP)/$(app-objs): build_cpp -build_cpp: $(APP)/axbuild.mk $(APP)/main.cpp - $(C_COMPILER) -o $(APP)/$(main-obj) -nostdlib -static -no-pie -c -std=$(CXX_STD) \ - $(APP)/main.cpp -I$(CROSS_COMPILE_PATH)/*-linux-musl/include/c++/* - $(LD) -o $(app-objs) $(APP)/$(main-obj) -nostdlib -static -no-pie -r -e main \ +$(APP)/$(app-objs): build_std-benchmark +build_std-benchmark: $(std_benchmark_dir) $(APP)/axbuild.mk + cd $(std_benchmark_dir) && mkdir -p build && cd build && \ + $(CMAKE) .. -DCMAKE_CXX_STANDARD=$(CXX_STD) -DCMAKE_C_COMPILER=$(C_COMPILER) -DCMAKE_CXX_COMPILER=$(CXX_COMPILER) -DCMAKE_AR=$(AR) -DCMAKE_RANLIB=$(RANLIB) \ + -DENABLE_C_BENCHMARKS=OFF -DENABLE_C_VS_CXX_BENCHMARKS=OFF -DENABLE_COMPILER_VS_PROGRAMMER=OFF -DBENCHMARK_ENABLE_TESTING=OFF && \ + $(MAKE) -j + mkdir -p $(std_benchmark_build)/libgcc && cd $(std_benchmark_build)/libgcc && \ + ln -s -f $(CROSS_COMPILE_PATH)/lib/gcc/*-linux-musl/*/libgcc.a ./ && \ + $(AR) x libgcc.a _clrsbsi2.o +ifeq ($(bench), all) + $(error "Running all benches automatically is not supported, please add 'bench=' arg. \ + Available benches: $(benches_available)") +endif +ifneq ($(filter $(bench),$(benches_available)),) + $(LD) -o $(app-objs) -nostdlib -static -no-pie -r -e main \ + $(std_benchmark_build)/cxx/lib$(bench).bench.cpp.out.a \ + $(std_benchmark_build)/benchmark/src/libbenchmark.a \ $(CROSS_COMPILE_PATH)/*-linux-musl/lib/libstdc++.a \ - $(CROSS_COMPILE_PATH)/lib/gcc/*-linux-musl/*/libgcc_eh.a + $(CROSS_COMPILE_PATH)/lib/gcc/*-linux-musl/*/libgcc_eh.a \ + $(std_benchmark_build)/libgcc/_clrsbsi2.o +else + $(error "Available benches: $(benches_available)") +endif clean_c:: - rm -rf $(app-objs) - rm -rf $(APP)/$(main-obj) + rm -rf $(std_benchmark_build)/ + +.PHONY: build_std-benchmark clean_c + -.PHONY: build_cpp clean_c diff --git a/apps/c/cpp/main.cpp b/apps/c/cpp/main.cpp deleted file mode 100644 index f970043fe..000000000 --- a/apps/c/cpp/main.cpp +++ /dev/null @@ -1,77 +0,0 @@ -#include -#include -#include -#include -#include -#include - -extern "C" { -__attribute__((weak)) void *__dso_handle; -} - -int main() -{ - - // cpp version - std::cout << "C++ version: " << __cplusplus << std::endl; - - // Create a list of integers - std::list myList = {5, 2, 8, 3, 1}; - - // Print the original list - std::cout << "Original list: "; - for (const auto &num : myList) { - std::cout << num << " "; - } - std::cout << std::endl; - - // Sort the list in ascending order - myList.sort(); - - // Print the sorted list - std::cout << "Sorted list: "; - for (const auto &num : myList) { - std::cout << num << " "; - } - std::cout << std::endl; - - // Create a vector of integers - std::vector myVector = {5, 2, 8, 3, 1}; - - // Print the original vector - std::cout << "Original vector: "; - for (const auto &num : myVector) { - std::cout << num << " "; - } - std::cout << std::endl; - - // Sort the vector in ascending order - std::sort(myVector.begin(), myVector.end()); - - // Print the sorted vector - std::cout << "Sorted vector: "; - for (const auto &num : myVector) { - std::cout << num << " "; - } - std::cout << std::endl; - - // Create a map of strings to integers - std::map myMap = { - {"apple", 5}, {"banana", 2}, {"orange", 8}, {"grape", 3}, {"kiwi", 1}}; - - // Print the map - std::cout << "Map: "; - for (const auto &pair : myMap) { - std::cout << pair.first << ":" << pair.second << " "; - } - std::cout << std::endl; - - // random test - std::random_device rd; - std::mt19937 gen(rd()); - std::uniform_int_distribution<> dis(1, 100); - for (int i = 0; i < 10; i++) { - std::cout << "Random number: " << dis(gen) << std::endl; - } - return 0; -} diff --git a/apps/c/cpp/std_benchmark.patch b/apps/c/cpp/std_benchmark.patch new file mode 100644 index 000000000..55e03db0b --- /dev/null +++ b/apps/c/cpp/std_benchmark.patch @@ -0,0 +1,109 @@ +--- /cxx/CMakeLists.txt ++++ /cxx/CMakeLists.txt +@@ -3,7 +3,7 @@ foreach(test_path ${BENCHMARK_TESTS}) + get_filename_component(test_file "${test_path}" NAME) + set(target ${test_file}.out) + #EXCLUDE_FROM_ALL +- add_executable(${target} ${test_file}) ++ add_library(${target} ${test_file}) + # shlwapi for MSVC + #target_link_libraries(${target} benchmark -pthread shlwapi) + target_link_libraries(${target} benchmark -pthread) +--- /cxx/mutators.bench.cpp ++++ /cxx/mutators.bench.cpp +@@ -26,8 +27,8 @@ void BM_write_seq(benchmark::State& state) { + template + void BM_push_back(benchmark::State& state) { + int N = state.range(0); +- V v; + while (state.KeepRunning()) { ++ V v; + for (int i = 0; i < N; ++i) + v.push_back(i); + } +@@ -37,8 +38,8 @@ void BM_push_back(benchmark::State& state) { + template + void BM_push_back_resize(benchmark::State& state) { + int N = state.range(0); +- V v(N); + while (state.KeepRunning()) { ++ V v(N); + for (int i = 0; i < N; ++i) + v.push_back(i); + } +@@ -48,9 +49,9 @@ void BM_push_back_resize(benchmark::State& state) { + template + void BM_push_back_vector_reserve(benchmark::State& state) { + int N = state.range(0); +- V v; +- v.reserve(N); + while (state.KeepRunning()) { ++ V v; ++ v.reserve(N); + for (int i = 0; i < N; ++i) + v.push_back(i); + } +@@ -60,9 +61,9 @@ void BM_push_back_vector_reserve(benchmark::State& state) { + template + void BM_insert_begin(benchmark::State& state) { + int N = state.range(0); +- V v(N, 1); +- auto val = *v.begin(); + while (state.KeepRunning()) { ++ V v(N, 1); ++ auto val = *v.begin(); + v.insert(v.begin(), val); + } + state.SetComplexityN(N); +@@ -94,14 +95,15 @@ void BM_assoc_insert_random(benchmark::State& state) { + int N = state.range(0); + using CVT = typename V::value_type; + using VT = typename remove_const::type; +- std::vector temp(N*1000); ++ // TODO: It will panic if *100 or *1000 ++ std::vector temp(N*10); + fill_random(temp); + V v; + auto it = temp.begin(); + while (state.KeepRunning()) { + v.insert(*it++); + if (it == temp.end()) // FIXME: After temp.end insert will just return. +- assert(0);//it = temp.begin(); ++ it = temp.begin(); + } + state.SetComplexityN(N); + } +--- /include/test_configs.h ++++ /include/test_configs.h +@@ -1,6 +1,8 @@ + #ifndef TEST_CONFIGS_H + #define TEST_CONFIGS_H + ++void *__dso_handle = 0; ++ + #define KB << 10 + #define MB << 20 + #define GB << 30 +@@ -12,9 +12,9 @@ + #ifdef i7_4770 + // To benchmark data residing completely in L1 cache. + #ifndef ENABLE_TRAVIS_BUILD +-#define L1 (32 KB) ++#define L1 (16) + // To benchmark data residing in L2 cache. +-#define L2 (256 KB) ++#define L2 (L1 << 7) + #else + // For the Travis CI to run the entire test. + #define L1 (16 KB) +--- /benchmark/CMakeLists.txt ++++ /benchmark/CMakeLists.txt +@@ -177,6 +177,8 @@ endif(BENCHMARK_USE_LIBCXX) + cxx_feature_check(STD_REGEX) + cxx_feature_check(GNU_POSIX_REGEX) + cxx_feature_check(POSIX_REGEX) ++add_compile_definitions(HAVE_STD_REGEX) ++set(HAVE_STD_REGEX 1) + if(NOT HAVE_STD_REGEX AND NOT HAVE_GNU_POSIX_REGEX AND NOT HAVE_POSIX_REGEX) + message(FATAL_ERROR "Failed to determine the source files for the regular expression backend") + endif() diff --git a/modules/ruxfs/src/mounts.rs b/modules/ruxfs/src/mounts.rs index a32978eb7..b6dbf224f 100644 --- a/modules/ruxfs/src/mounts.rs +++ b/modules/ruxfs/src/mounts.rs @@ -20,11 +20,13 @@ pub(crate) fn devfs() -> Arc { let zero = fs::devfs::ZeroDev; let bar = fs::devfs::ZeroDev; let random = fs::devfs::RandomDev; + let urandom = fs::devfs::RandomDev; let devfs = fs::devfs::DeviceFileSystem::new(); let foo_dir = devfs.mkdir("foo"); devfs.add("null", Arc::new(null)); devfs.add("zero", Arc::new(zero)); devfs.add("random", Arc::new(random)); + devfs.add("urandom", Arc::new(urandom)); foo_dir.add("bar", Arc::new(bar)); Arc::new(devfs) } From 71b813db329d23424b1a762f17bb65da5458f9fd Mon Sep 17 00:00:00 2001 From: wuzheng Date: Wed, 27 Mar 2024 15:43:29 +0800 Subject: [PATCH 7/8] fix bugs in pl011 --- modules/ruxhal/src/arch/aarch64/trap.rs | 4 ++++ .../src/platform/aarch64_common/pl011.rs | 23 ++++++++++++------- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/modules/ruxhal/src/arch/aarch64/trap.rs b/modules/ruxhal/src/arch/aarch64/trap.rs index b9a19ed5b..5a863e6b7 100644 --- a/modules/ruxhal/src/arch/aarch64/trap.rs +++ b/modules/ruxhal/src/arch/aarch64/trap.rs @@ -9,6 +9,8 @@ use core::arch::global_asm; +#[cfg(feature = "irq")] +use crate::arch::enable_irqs; use aarch64_cpu::registers::{ESR_EL1, FAR_EL1}; use tock_registers::interfaces::Readable; @@ -56,6 +58,8 @@ fn handle_sync_exception(tf: &mut TrapFrame) { #[cfg(feature = "musl")] Some(ESR_EL1::EC::Value::SVC64) => { debug!("Handle supervisor call {}", tf.r[8]); + #[cfg(feature = "irq")] + enable_irqs(); let result = crate::trap::handle_syscall( tf.r[8] as usize, [ diff --git a/modules/ruxhal/src/platform/aarch64_common/pl011.rs b/modules/ruxhal/src/platform/aarch64_common/pl011.rs index 2ce048ac8..577371850 100644 --- a/modules/ruxhal/src/platform/aarch64_common/pl011.rs +++ b/modules/ruxhal/src/platform/aarch64_common/pl011.rs @@ -24,6 +24,7 @@ struct RxRingBuffer { buffer: [u8; BUFFER_SIZE], head: usize, tail: usize, + empty: bool, } #[cfg(feature = "irq")] @@ -33,22 +34,27 @@ impl RxRingBuffer { buffer: [0_u8; BUFFER_SIZE], head: 0_usize, tail: 0_usize, + empty: true, } } fn push(&mut self, n: u8) { - if self.tail != self.head { + if self.tail != self.head || self.empty { self.buffer[self.tail] = n; self.tail = (self.tail + 1) % BUFFER_SIZE; + self.empty = false; } } fn pop(&mut self) -> Option { - if self.head == self.tail { + if self.empty { None } else { let ret = self.buffer[self.head]; - self.head += (self.head + 1) % BUFFER_SIZE; + self.head = (self.head + 1) % BUFFER_SIZE; + if self.head == self.tail { + self.empty = true; + } Some(ret) } } @@ -98,18 +104,19 @@ pub fn init_early() { pub fn init() { #[cfg(feature = "irq")] { - crate::irq::register_handler(crate::platform::irq::UART_IRQ_NUM, handle); + crate::irq::register_handler(crate::platform::irq::UART_IRQ_NUM, irq_handler); crate::irq::set_enable(crate::platform::irq::UART_IRQ_NUM, true); } } /// UART IRQ Handler #[cfg(feature = "irq")] -pub fn handle() { - let is_receive_interrupt = UART.inner.lock().is_receive_interrupt(); +pub fn irq_handler() { + let mut dev = UART.inner.lock(); + let is_receive_interrupt = dev.is_receive_interrupt(); if is_receive_interrupt { - UART.inner.lock().ack_interrupts(); - while let Some(c) = UART.inner.lock().getchar() { + dev.ack_interrupts(); + while let Some(c) = dev.getchar() { UART.buffer.lock().push(c); } } From 4aaffd14ed66cad0f18c0d001b6beca8c794ca27 Mon Sep 17 00:00:00 2001 From: wuzheng Date: Wed, 27 Mar 2024 21:08:27 +0800 Subject: [PATCH 8/8] Implement logic to open symlink in 9pfs --- modules/rux9p/src/fs.rs | 54 ++++++++++++++++++++++++++++------------- 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/modules/rux9p/src/fs.rs b/modules/rux9p/src/fs.rs index 72843870e..2cc87c4b4 100644 --- a/modules/rux9p/src/fs.rs +++ b/modules/rux9p/src/fs.rs @@ -121,6 +121,7 @@ impl CommonNode { const O_RDWR: u8 = 0x02; const O_RDONLY: u8 = 0x00; const EISDIR: u8 = 21; + const ELOOP: u8 = 40; let result = if *protocol == "9P2000.L" { dev.write().l_topen(fid, O_RDWR as u32) @@ -131,23 +132,36 @@ impl CommonNode { Ok(()) }; - if let Err(EISDIR) = result { - if *protocol == "9P2000.L" { - handle_result!( - dev.write().l_topen(fid, O_RDONLY as u32), - "9pfs l_topen failed! error code: {}" - ); - } else if *protocol == "9P2000.u" { - handle_result!( - dev.write().topen(fid, O_RDONLY), - "9pfs topen failed! error code: {}" - ); - } else { - error!("9pfs open failed! Unsupported protocol version"); + match result { + Err(EISDIR) if *protocol == "9P2000.L" => handle_result!( + dev.write().l_topen(fid, O_RDONLY as u32), + "9pfs l_topen failed! error code: {}" + ), + Err(EISDIR) if *protocol == "9P2000.u" => handle_result!( + dev.write().topen(fid, O_RDONLY), + "9pfs topen failed! error code: {}" + ), + Err(ELOOP) if *protocol == "9P2000.L" => { + let try_readlink = dev.write().treadlink(fid); + if let Ok(path) = try_readlink { + debug!("read link path ==> {:}", path); + let mut splited: Vec<&str> = path + .split('/') + .filter(|&x| !x.is_empty() && (x != ".")) + .collect(); + splited.insert(0, ".."); + let try_walk = dev.write().twalk(fid, fid, splited.len() as u16, &splited); + match try_walk { + Ok(_) => return Self::new(fid, parent, dev, protocol), + Err(ecode) => error!("9pfs twalk failed! error code: {}", ecode), + } + } else { + error!("9pfs treadlink failed! error code: {:?}", try_readlink); + } } - } else if let Err(ecode) = result { - error!("9pfs topen failed! error code: {}", ecode); - } + Err(ecode) => error!("9pfs topen failed! error code: {}", ecode), + _ => {} + }; Arc::new_cyclic(|this| Self { inner: dev, @@ -224,7 +238,13 @@ impl CommonNode { fn try_get(&self, path: &str) -> VfsResult { let (name, rest) = split_path(path); if name == ".." { - return self.parent().unwrap().lookup(rest.unwrap_or("")); + match self.parent() { + Some(parent) => return parent.lookup(rest.unwrap_or("")), + None => { + error!("9pfs: try_get a directory out of 9pfs boundary"); + return Err(VfsError::BadState); + } + } } else if name == "." { return self.try_get(rest.unwrap_or("")); }