mirror of
https://github.com/hexagonal-sun/moss-kernel.git
synced 2025-12-23 22:47:55 -05:00
futex impl
# Conflicts: # usertest/src/main.rs
This commit is contained in:
@@ -162,6 +162,9 @@ pub enum KernelError {
|
|||||||
#[error("Buffer is full")]
|
#[error("Buffer is full")]
|
||||||
BufferFull,
|
BufferFull,
|
||||||
|
|
||||||
|
#[error("Operation would block")]
|
||||||
|
TryAgain,
|
||||||
|
|
||||||
#[error("No such process")]
|
#[error("No such process")]
|
||||||
NoProcess,
|
NoProcess,
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ pub fn kern_err_to_syscall(err: KernelError) -> isize {
|
|||||||
KernelError::BadFd => EBADF,
|
KernelError::BadFd => EBADF,
|
||||||
KernelError::InvalidValue => EINVAL,
|
KernelError::InvalidValue => EINVAL,
|
||||||
KernelError::Fault => EFAULT,
|
KernelError::Fault => EFAULT,
|
||||||
|
KernelError::TryAgain => EAGAIN,
|
||||||
KernelError::BrokenPipe => EPIPE,
|
KernelError::BrokenPipe => EPIPE,
|
||||||
KernelError::Fs(FsError::NotFound) => ENOENT,
|
KernelError::Fs(FsError::NotFound) => ENOENT,
|
||||||
KernelError::NotATty => ENOTTY,
|
KernelError::NotATty => ENOTTY,
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ use crate::{
|
|||||||
umask::sys_umask,
|
umask::sys_umask,
|
||||||
wait::sys_wait4,
|
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,
|
sched::current_task,
|
||||||
};
|
};
|
||||||
@@ -170,6 +170,17 @@ pub async fn handle_syscall() {
|
|||||||
0x5d => sys_exit(arg1 as _),
|
0x5d => sys_exit(arg1 as _),
|
||||||
0x5e => sys_exit_group(arg1 as _),
|
0x5e => sys_exit_group(arg1 as _),
|
||||||
0x60 => sys_set_tid_address(VA::from_value(arg1 as _)).await,
|
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,
|
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,
|
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,
|
0x71 => sys_clock_gettime(arg1 as _, TUA::from_value(arg2 as _)).await,
|
||||||
|
|||||||
@@ -1,11 +1,33 @@
|
|||||||
use core::ffi::c_long;
|
use core::ffi::c_long;
|
||||||
|
use core::mem::size_of;
|
||||||
|
|
||||||
|
use crate::memory::uaccess::copy_from_user;
|
||||||
use crate::sched::current_task;
|
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::{
|
use libkernel::{
|
||||||
error::{KernelError, Result},
|
error::{KernelError, Result},
|
||||||
memory::address::{TUA, VA},
|
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<SpinLock<BTreeMap<usize, Arc<SpinLock<FutexWaitQueue>>>>> =
|
||||||
|
OnceLock::new();
|
||||||
|
|
||||||
pub async fn sys_set_tid_address(_tidptr: VA) -> Result<usize> {
|
pub async fn sys_set_tid_address(_tidptr: VA) -> Result<usize> {
|
||||||
let tid = current_task().tid;
|
let tid = current_task().tid;
|
||||||
|
|
||||||
@@ -38,3 +60,71 @@ pub async fn sys_set_robust_list(head: TUA<RobustListHead>, len: usize) -> Resul
|
|||||||
|
|
||||||
Ok(0)
|
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<u32>,
|
||||||
|
op: i32,
|
||||||
|
val: u32,
|
||||||
|
_timeout: VA,
|
||||||
|
_uaddr2: TUA<u32>,
|
||||||
|
_val3: u32,
|
||||||
|
) -> Result<usize> {
|
||||||
|
// 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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -111,6 +111,74 @@ fn test_write() {
|
|||||||
println!(" OK");
|
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::<libc::c_void>(),
|
||||||
|
std::ptr::null::<libc::c_void>(),
|
||||||
|
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::<libc::c_void>(),
|
||||||
|
std::ptr::null::<libc::c_void>(),
|
||||||
|
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() {
|
fn test_rust_file() {
|
||||||
print!("Testing rust file operations ...");
|
print!("Testing rust file operations ...");
|
||||||
use std::fs::{self, File};
|
use std::fs::{self, File};
|
||||||
@@ -176,6 +244,8 @@ fn main() {
|
|||||||
run_test(test_fork);
|
run_test(test_fork);
|
||||||
run_test(test_read);
|
run_test(test_read);
|
||||||
run_test(test_write);
|
run_test(test_write);
|
||||||
|
run_test(test_futex);
|
||||||
|
run_test(test_rust_mutex);
|
||||||
run_test(test_rust_file);
|
run_test(test_rust_file);
|
||||||
run_test(test_rust_dir);
|
run_test(test_rust_dir);
|
||||||
let end = std::time::Instant::now();
|
let end = std::time::Instant::now();
|
||||||
|
|||||||
Reference in New Issue
Block a user