Skip to content

Commit

Permalink
add futex syscalls
Browse files Browse the repository at this point in the history
  • Loading branch information
joboet committed Sep 4, 2022
1 parent 275aae1 commit 2de4047
Show file tree
Hide file tree
Showing 10 changed files with 278 additions and 3 deletions.
25 changes: 25 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,10 @@ dhcpv4 = [
]

[dependencies]
ahash = { version = "0.8", default-features = false }
bitflags = "1.3"
crossbeam-utils = { version = "0.8", default-features = false }
hashbrown = { version = "0.12", default-features = false }
hermit-entry = { version = "0.9", features = ["kernel"] }
include-transformed = { version = "0.2", optional = true }
log = { version = "0.4", default-features = false }
Expand Down
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#![allow(incomplete_features)]
#![feature(abi_x86_interrupt)]
#![feature(allocator_api)]
#![feature(atomic_mut_ptr)]
#![feature(asm_const)]
#![feature(asm_sym)]
#![feature(const_btree_new)]
Expand All @@ -23,6 +24,8 @@
#![feature(alloc_error_handler)]
#![feature(vec_into_raw_parts)]
#![feature(drain_filter)]
#![feature(strict_provenance)]
#![feature(is_some_with)]
#![no_std]
#![cfg_attr(target_os = "none", feature(custom_test_frameworks))]
#![cfg_attr(target_os = "none", cfg_attr(test, test_runner(crate::test_runner)))]
Expand Down
15 changes: 13 additions & 2 deletions src/scheduler/task.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ impl PartialEq for TaskHandle {
impl Eq for TaskHandle {}

/// Realize a priority queue for task handles
#[derive(Default)]
pub struct TaskHandlePriorityQueue {
queues: [Option<VecDeque<TaskHandle>>; NO_PRIORITIES],
prio_bitmap: u64,
Expand All @@ -158,6 +159,11 @@ impl TaskHandlePriorityQueue {
}
}

/// Checks if the queue is empty.
pub fn is_empty(&self) -> bool {
self.prio_bitmap == 0
}

/// Add a task handle by its priority to the queue
pub fn push(&mut self, task: TaskHandle) {
let i = task.priority.into() as usize;
Expand Down Expand Up @@ -196,16 +202,19 @@ impl TaskHandlePriorityQueue {
None
}

/// Remove a specific task handle from the priority queue.
pub fn remove(&mut self, task: TaskHandle) {
/// Remove a specific task handle from the priority queue. Returns `true` if
/// the handle was in the queue.
pub fn remove(&mut self, task: TaskHandle) -> bool {
let queue_index = task.priority.into() as usize;
//assert!(queue_index < NO_PRIORITIES, "Priority {} is too high", queue_index);

let mut success = false;
if let Some(queue) = &mut self.queues[queue_index] {
let mut i = 0;
while i != queue.len() {
if queue[i].id == task.id {
queue.remove(i);
success = true;
} else {
i += 1;
}
Expand All @@ -215,6 +224,8 @@ impl TaskHandlePriorityQueue {
self.prio_bitmap &= !(1 << queue_index as u64);
}
}

success
}
}

Expand Down
107 changes: 107 additions & 0 deletions src/synch/futex.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
use ahash::RandomState;
use core::sync::atomic::{AtomicU32, Ordering::SeqCst};
use hashbrown::{hash_map::Entry, HashMap};

use crate::{
arch::kernel::{percore::core_scheduler, processor::get_timer_ticks},
errno::{EAGAIN, EINVAL, ETIMEDOUT},
scheduler::task::TaskHandlePriorityQueue,
};

use super::spinlock::SpinlockIrqSave;

// TODO: Replace with a concurrent hashmap.
static PARKING_LOT: SpinlockIrqSave<HashMap<usize, TaskHandlePriorityQueue, RandomState>> =
SpinlockIrqSave::new(HashMap::with_hasher(RandomState::with_seeds(0, 0, 0, 0)));

bitflags! {
pub struct Flags: u32 {
/// Use a relative timeout
const RELATIVE = 0b01;
}
}

/// If the value at address matches the expected value, park the current thread until it is either
/// woken up with `futex_wake` (returns 0) or the specified timeout elapses (returns -ETIMEDOUT).
///
/// The timeout is given in microseconds. If [`Flags::RELATIVE`] is given, it is interpreted as
/// relative to the current time. Otherwise it is understood to be an absolute time
/// (see `get_timer_ticks`).
pub fn futex_wait(address: &AtomicU32, expected: u32, timeout: Option<u64>, flags: Flags) -> i32 {
let mut parking_lot = PARKING_LOT.lock();
// Check the futex value after locking the parking lot so that all changes are observed.
if address.load(SeqCst) != expected {
return -EAGAIN;
}

let wakeup_time = if flags.contains(Flags::RELATIVE) {
timeout.and_then(|t| get_timer_ticks().checked_add(t))
} else {
timeout
};

let scheduler = core_scheduler();
scheduler.block_current_task(wakeup_time);
let handle = scheduler.get_current_task_handle();
parking_lot
.entry(address.as_mut_ptr().addr())
.or_default()
.push(handle);
drop(parking_lot);

loop {
scheduler.reschedule();

// Try to remove ourselves from the waiting queue.
let mut parking_lot = PARKING_LOT.lock();
let mut wakeup = true;
if let Entry::Occupied(mut queue) = parking_lot.entry(address.as_mut_ptr().addr()) {
// If we are not in the waking queue, this must have been a wakeup.
wakeup = !queue.get_mut().remove(handle);
if queue.get().is_empty() {
queue.remove();
}
};

if wakeup {
return 0;
} else if wakeup_time.is_some_and(|&t| t <= get_timer_ticks()) {
// If the current time is past the wakeup time, the operation timed out.
return -ETIMEDOUT;
}

// A spurious wakeup occurred, sleep again.
scheduler.block_current_task(wakeup_time);
}
}

/// Wake `count` threads waiting on the futex at address. Returns the number of threads
/// woken up (saturates to `i32::MAX`). If `count` is `i32::MAX`, wake up all matching
/// waiting threads. If `count` is negative, returns -EINVAL.
pub fn futex_wake(address: &AtomicU32, count: i32) -> i32 {
if count < 0 {
return -EINVAL;
}

let mut parking_lot = PARKING_LOT.lock();
let mut queue = match parking_lot.entry(address.as_mut_ptr().addr()) {
Entry::Occupied(entry) => entry,
Entry::Vacant(_) => return 0,
};

let scheduler = core_scheduler();
let mut woken = 0;
while woken != count || count == i32::MAX {
match queue.get_mut().pop() {
Some(handle) => scheduler.custom_wakeup(handle),
None => break,
}
woken = woken.saturating_add(1);
}

if queue.get().is_empty() {
queue.remove();
}

woken
}
1 change: 1 addition & 0 deletions src/synch/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Synchronization primitives

pub mod futex;
pub mod recmutex;
pub mod semaphore;
pub mod spinlock;
67 changes: 67 additions & 0 deletions src/syscalls/futex.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
use core::sync::atomic::AtomicU32;

use crate::{
errno::EINVAL,
synch::futex::{self as synch, Flags},
timespec, timespec_to_microseconds,
};

/// Like `synch::futex_wait`, but does extra sanity checks and takes a `timespec`.
///
/// Returns -EINVAL if
/// * `address` is null
/// * `timeout` is negative
/// * `flags` contains unknown flags
extern "C" fn __sys_futex_wait(
address: *mut u32,
expected: u32,
timeout: *const timespec,
flags: u32,
) -> i32 {
if address.is_null() {
return -EINVAL;
}

let address = unsafe { &*(address as *const AtomicU32) };
let timeout = if timeout.is_null() {
None
} else {
match timespec_to_microseconds(unsafe { timeout.read() }) {
t @ Some(_) => t,
None => return -EINVAL,
}
};
let flags = match Flags::from_bits(flags) {
Some(flags) => flags,
None => return -EINVAL,
};

synch::futex_wait(address, expected, timeout, flags)
}

#[no_mangle]
pub extern "C" fn sys_futex_wait(
address: *mut u32,
expected: u32,
timeout: *const timespec,
flags: u32,
) -> i32 {
kernel_function!(__sys_futex_wait(address, expected, timeout, flags))
}

/// Like `synch::futex_wake`, but does extra sanity checks.
///
/// Returns -EINVAL if `address` is null.
extern "C" fn __sys_futex_wake(address: *mut u32, count: i32) -> i32 {
if address.is_null() {
return -EINVAL;
}

let address = unsafe { &*(address as *const AtomicU32) };
synch::futex_wake(address, count)
}

#[no_mangle]
pub extern "C" fn sys_futex_wake(address: *mut u32, count: i32) -> i32 {
kernel_function!(__sys_futex_wake(address, count))
}
2 changes: 2 additions & 0 deletions src/syscalls/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use crate::syscalls::interfaces::SyscallInterface;
use crate::{__sys_free, __sys_malloc, __sys_realloc};

pub use self::condvar::*;
pub use self::futex::*;
pub use self::processor::*;
pub use self::random::*;
pub use self::recmutex::*;
Expand All @@ -19,6 +20,7 @@ pub use self::timer::*;

mod condvar;
pub(crate) mod fs;
mod futex;
mod interfaces;
#[cfg(feature = "newlib")]
mod lwip;
Expand Down
7 changes: 7 additions & 0 deletions src/syscalls/timer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@ fn microseconds_to_timeval(microseconds: u64, result: &mut timeval) {
result.tv_usec = (microseconds % 1_000_000) as i64;
}

pub(crate) fn timespec_to_microseconds(time: timespec) -> Option<u64> {
u64::try_from(time.tv_sec)
.ok()
.and_then(|secs| secs.checked_mul(1_000_000))
.and_then(|millions| millions.checked_add(u64::try_from(time.tv_nsec).ok()? / 1000))
}

extern "C" fn __sys_clock_getres(clock_id: u64, res: *mut timespec) -> i32 {
assert!(
!res.is_null(),
Expand Down
Loading

0 comments on commit 2de4047

Please sign in to comment.