diff --git a/libkernel/src/error.rs b/libkernel/src/error.rs index c97a7a2..d050a55 100644 --- a/libkernel/src/error.rs +++ b/libkernel/src/error.rs @@ -162,6 +162,9 @@ pub enum KernelError { #[error("Buffer is full")] BufferFull, + #[error("Operation would block")] + TryAgain, + #[error("No such process")] NoProcess, diff --git a/libkernel/src/error/syscall_error.rs b/libkernel/src/error/syscall_error.rs index 4822e45..ab41c83 100644 --- a/libkernel/src/error/syscall_error.rs +++ b/libkernel/src/error/syscall_error.rs @@ -44,6 +44,7 @@ pub fn kern_err_to_syscall(err: KernelError) -> isize { KernelError::BadFd => EBADF, KernelError::InvalidValue => EINVAL, KernelError::Fault => EFAULT, + KernelError::TryAgain => EAGAIN, KernelError::BrokenPipe => EPIPE, KernelError::Fs(FsError::NotFound) => ENOENT, KernelError::NotATty => ENOTTY, diff --git a/src/arch/arm64/exceptions/syscall.rs b/src/arch/arm64/exceptions/syscall.rs index 3a8956e..b7c4966 100644 --- a/src/arch/arm64/exceptions/syscall.rs +++ b/src/arch/arm64/exceptions/syscall.rs @@ -61,7 +61,7 @@ use crate::{ umask::sys_umask, wait::sys_wait4, }, - threading::{sys_set_robust_list, sys_set_tid_address}, + threading::{sys_futex, sys_set_robust_list, sys_set_tid_address}, }, sched::current_task, }; @@ -170,6 +170,17 @@ pub async fn handle_syscall() { 0x5d => sys_exit(arg1 as _), 0x5e => sys_exit_group(arg1 as _), 0x60 => sys_set_tid_address(VA::from_value(arg1 as _)).await, + 0x62 => { + sys_futex( + TUA::from_value(arg1 as _), + arg2 as _, + arg3 as _, + VA::from_value(arg4 as _), + TUA::from_value(arg5 as _), + arg6 as _, + ) + .await + } 0x63 => sys_set_robust_list(TUA::from_value(arg1 as _), arg2 as _).await, 0x65 => sys_nanosleep(TUA::from_value(arg1 as _), TUA::from_value(arg2 as _)).await, 0x71 => sys_clock_gettime(arg1 as _, TUA::from_value(arg2 as _)).await, diff --git a/src/process/threading.rs b/src/process/threading.rs index b6897ec..301a6e2 100644 --- a/src/process/threading.rs +++ b/src/process/threading.rs @@ -1,11 +1,33 @@ use core::ffi::c_long; +use core::mem::size_of; +use crate::memory::uaccess::copy_from_user; use crate::sched::current_task; +use crate::sync::{OnceLock, SpinLock}; +use alloc::{collections::BTreeMap, sync::Arc}; +use libkernel::sync::waker_set::{WakerSet, wait_until}; use libkernel::{ error::{KernelError, Result}, memory::address::{TUA, VA}, }; +/// A per-futex wait queue holding wakers for blocked tasks. +struct FutexWaitQueue { + wakers: WakerSet, +} + +impl FutexWaitQueue { + fn new() -> Self { + Self { + wakers: WakerSet::new(), + } + } +} + +/// Global futex table mapping a user address to its wait queue. +static FUTEX_TABLE: OnceLock>>>> = + OnceLock::new(); + pub async fn sys_set_tid_address(_tidptr: VA) -> Result { let tid = current_task().tid; @@ -38,3 +60,71 @@ pub async fn sys_set_robust_list(head: TUA, len: usize) -> Resul Ok(0) } + +const FUTEX_WAIT: i32 = 0; +const FUTEX_WAKE: i32 = 1; +const FUTEX_PRIVATE_FLAG: i32 = 128; + +pub async fn sys_futex( + uaddr: TUA, + op: i32, + val: u32, + _timeout: VA, + _uaddr2: TUA, + _val3: u32, +) -> Result { + // Strip PRIVATE flag if present + let cmd = op & !FUTEX_PRIVATE_FLAG; + + match cmd { + FUTEX_WAIT => { + // Fail fast if the value has already changed + let current: u32 = copy_from_user(uaddr).await?; + if current != val { + return Err(KernelError::TryAgain); + } + + // Obtain (or create) the wait-queue for this futex word + let table = FUTEX_TABLE.get_or_init(|| SpinLock::new(BTreeMap::new())); + let waitq_arc = { + let mut guard = table.lock_save_irq(); + guard + .entry(uaddr.value()) + .or_insert_with(|| Arc::new(SpinLock::new(FutexWaitQueue::new()))) + .clone() + }; + + // Park the current task until the word’s value differs or it is woken + wait_until( + waitq_arc.clone(), + |state| &mut state.wakers, + |_| { + let cur = unsafe { core::ptr::read_volatile(uaddr.value() as *const u32) }; + if cur != val { Some(()) } else { None } + }, + ) + .await; + + Ok(0) + } + + FUTEX_WAKE => { + let nr_wake = val as usize; + let mut woke = 0; + + if let Some(table) = FUTEX_TABLE.get() { + if let Some(waitq_arc) = table.lock_save_irq().get(&uaddr.value()).cloned() { + let mut waitq = waitq_arc.lock_save_irq(); + for _ in 0..nr_wake { + waitq.wakers.wake_one(); + woke += 1; + } + } + } + + Ok(woke) + } + + _ => Err(KernelError::NotSupported), + } +} diff --git a/usertest/src/main.rs b/usertest/src/main.rs index 1a1ccd4..6018c7f 100644 --- a/usertest/src/main.rs +++ b/usertest/src/main.rs @@ -111,6 +111,74 @@ fn test_write() { println!(" OK"); } +fn test_futex() { + print!("Testing futex syscall ..."); + let mut futex_word: libc::c_uint = 0; + let addr = &mut futex_word as *mut libc::c_uint; + unsafe { + // FUTEX_WAKE should succeed (no waiters, returns 0) + let ret = libc::syscall( + libc::SYS_futex, + addr, + libc::FUTEX_WAKE, + 1, + std::ptr::null::(), + std::ptr::null::(), + 0, + ); + if ret < 0 { + panic!("futex wake failed"); + } + + // FUTEX_WAIT with an *unexpected* value (1) should fail immediately and + // return -1 with errno = EAGAIN. We just check the return value here + // to avoid blocking the test. + let ret2 = libc::syscall( + libc::SYS_futex, + addr, + libc::FUTEX_WAIT, + 1u32, // expected value differs from actual (0) + std::ptr::null::(), + std::ptr::null::(), + 0, + ); + if ret2 != -1 { + panic!("futex wait did not error out as expected"); + } + } + println!(" OK"); +} + +fn test_rust_mutex() { + use std::sync::{Arc, Mutex}; + use std::thread; + + print!("Testing Rust Mutex ..."); + let mutex = Arc::new(Mutex::new(0)); + let mut handles = vec![]; + + for _ in 0..10 { + let mtx_clone = Arc::clone(&mutex); + let handle = thread::spawn(move || { + for _ in 0..1000 { + let mut num = mtx_clone.lock().unwrap(); + *num += 1; + } + }); + handles.push(handle); + } + + for handle in handles { + handle.join().unwrap(); + } + + let final_count = *mutex.lock().unwrap(); + if final_count != 10_000 { + panic!("Mutex test failed, expected 10000 but got {}", final_count); + } + println!(" OK"); +} + fn test_rust_file() { print!("Testing rust file operations ..."); use std::fs::{self, File}; @@ -176,6 +244,8 @@ fn main() { run_test(test_fork); run_test(test_read); run_test(test_write); + run_test(test_futex); + run_test(test_rust_mutex); run_test(test_rust_file); run_test(test_rust_dir); let end = std::time::Instant::now();