mirror of
https://github.com/asterinas/asterinas.git
synced 2025-06-13 23:36:48 +00:00
Support futex wait timeout
This commit is contained in:
parent
12325733b0
commit
70505ff4f8
@ -9,7 +9,7 @@ use ostd::{
|
|||||||
};
|
};
|
||||||
use spin::Once;
|
use spin::Once;
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::{prelude::*, time::wait::TimerBuilder};
|
||||||
|
|
||||||
type FutexBitSet = u32;
|
type FutexBitSet = u32;
|
||||||
type FutexBucketRef = Arc<Mutex<FutexBucket>>;
|
type FutexBucketRef = Arc<Mutex<FutexBucket>>;
|
||||||
@ -19,21 +19,38 @@ const FUTEX_FLAGS_MASK: u32 = 0xFFFF_FFF0;
|
|||||||
const FUTEX_BITSET_MATCH_ANY: FutexBitSet = 0xFFFF_FFFF;
|
const FUTEX_BITSET_MATCH_ANY: FutexBitSet = 0xFFFF_FFFF;
|
||||||
|
|
||||||
/// do futex wait
|
/// do futex wait
|
||||||
pub fn futex_wait(futex_addr: u64, futex_val: i32, timeout: &Option<FutexTimeout>) -> Result<()> {
|
pub fn futex_wait(
|
||||||
futex_wait_bitset(futex_addr as _, futex_val, timeout, FUTEX_BITSET_MATCH_ANY)
|
futex_addr: u64,
|
||||||
|
futex_val: i32,
|
||||||
|
timer_builder: Option<TimerBuilder>,
|
||||||
|
ctx: &Context,
|
||||||
|
) -> Result<()> {
|
||||||
|
futex_wait_bitset(
|
||||||
|
futex_addr as _,
|
||||||
|
futex_val,
|
||||||
|
timer_builder,
|
||||||
|
FUTEX_BITSET_MATCH_ANY,
|
||||||
|
ctx,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// do futex wait bitset
|
/// Does futex wait bitset
|
||||||
pub fn futex_wait_bitset(
|
pub fn futex_wait_bitset(
|
||||||
futex_addr: Vaddr,
|
futex_addr: Vaddr,
|
||||||
futex_val: i32,
|
futex_val: i32,
|
||||||
timeout: &Option<FutexTimeout>,
|
timer_builder: Option<TimerBuilder>,
|
||||||
bitset: FutexBitSet,
|
bitset: FutexBitSet,
|
||||||
|
ctx: &Context,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
debug!(
|
debug!(
|
||||||
"futex_wait_bitset addr: {:#x}, val: {}, timeout: {:?}, bitset: {:#x}",
|
"futex_wait_bitset addr: {:#x}, val: {}, bitset: {:#x}",
|
||||||
futex_addr, futex_val, timeout, bitset
|
futex_addr, futex_val, bitset
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if bitset == 0 {
|
||||||
|
return_errno_with_message!(Errno::EINVAL, "at least one bit should be set");
|
||||||
|
}
|
||||||
|
|
||||||
let futex_key = FutexKey::new(futex_addr, bitset);
|
let futex_key = FutexKey::new(futex_addr, bitset);
|
||||||
let (futex_item, waiter) = FutexItem::create(futex_key);
|
let (futex_item, waiter) = FutexItem::create(futex_key);
|
||||||
|
|
||||||
@ -41,7 +58,7 @@ pub fn futex_wait_bitset(
|
|||||||
// lock futex bucket ref here to avoid data race
|
// lock futex bucket ref here to avoid data race
|
||||||
let mut futex_bucket = futex_bucket_ref.lock();
|
let mut futex_bucket = futex_bucket_ref.lock();
|
||||||
|
|
||||||
if !futex_key.load_val().is_ok_and(|val| val == futex_val) {
|
if !futex_key.load_val(ctx).is_ok_and(|val| val == futex_val) {
|
||||||
return_errno_with_message!(
|
return_errno_with_message!(
|
||||||
Errno::EAGAIN,
|
Errno::EAGAIN,
|
||||||
"futex value does not match or load_val failed"
|
"futex value does not match or load_val failed"
|
||||||
@ -53,18 +70,15 @@ pub fn futex_wait_bitset(
|
|||||||
// drop lock
|
// drop lock
|
||||||
drop(futex_bucket);
|
drop(futex_bucket);
|
||||||
|
|
||||||
// TODO: wait on the futex item with a timeout.
|
waiter.pause_timer_timeout(timer_builder.as_ref())
|
||||||
waiter.wait();
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// do futex wake
|
/// Does futex wake
|
||||||
pub fn futex_wake(futex_addr: Vaddr, max_count: usize) -> Result<usize> {
|
pub fn futex_wake(futex_addr: Vaddr, max_count: usize) -> Result<usize> {
|
||||||
futex_wake_bitset(futex_addr, max_count, FUTEX_BITSET_MATCH_ANY)
|
futex_wake_bitset(futex_addr, max_count, FUTEX_BITSET_MATCH_ANY)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Do futex wake with bitset
|
/// Does futex wake with bitset
|
||||||
pub fn futex_wake_bitset(
|
pub fn futex_wake_bitset(
|
||||||
futex_addr: Vaddr,
|
futex_addr: Vaddr,
|
||||||
max_count: usize,
|
max_count: usize,
|
||||||
@ -75,6 +89,10 @@ pub fn futex_wake_bitset(
|
|||||||
futex_addr, max_count, bitset
|
futex_addr, max_count, bitset
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if bitset == 0 {
|
||||||
|
return_errno_with_message!(Errno::EINVAL, "at least one bit should be set");
|
||||||
|
}
|
||||||
|
|
||||||
let futex_key = FutexKey::new(futex_addr, bitset);
|
let futex_key = FutexKey::new(futex_addr, bitset);
|
||||||
let (_, futex_bucket_ref) = get_futex_bucket(futex_key);
|
let (_, futex_bucket_ref) = get_futex_bucket(futex_key);
|
||||||
let mut futex_bucket = futex_bucket_ref.lock();
|
let mut futex_bucket = futex_bucket_ref.lock();
|
||||||
@ -84,7 +102,7 @@ pub fn futex_wake_bitset(
|
|||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Do futex requeue
|
/// Does futex requeue
|
||||||
pub fn futex_requeue(
|
pub fn futex_requeue(
|
||||||
futex_addr: Vaddr,
|
futex_addr: Vaddr,
|
||||||
max_nwakes: usize,
|
max_nwakes: usize,
|
||||||
@ -153,15 +171,6 @@ pub fn init() {
|
|||||||
FUTEX_BUCKETS.call_once(|| FutexBucketVec::new(get_bucket_count()));
|
FUTEX_BUCKETS.call_once(|| FutexBucketVec::new(get_bucket_count()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct FutexTimeout {}
|
|
||||||
|
|
||||||
impl FutexTimeout {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct FutexBucketVec {
|
struct FutexBucketVec {
|
||||||
vec: Vec<FutexBucketRef>,
|
vec: Vec<FutexBucketRef>,
|
||||||
}
|
}
|
||||||
@ -330,10 +339,10 @@ impl FutexKey {
|
|||||||
Self { addr, bitset }
|
Self { addr, bitset }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_val(&self) -> Result<i32> {
|
pub fn load_val(&self, ctx: &Context) -> Result<i32> {
|
||||||
// FIXME: how to implement a atomic load?
|
// FIXME: how to implement a atomic load?
|
||||||
warn!("implement an atomic load");
|
warn!("implement an atomic load");
|
||||||
CurrentUserSpace::get().read_val(self.addr)
|
ctx.get_user_space().read_val(self.addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn addr(&self) -> Vaddr {
|
pub fn addr(&self) -> Vaddr {
|
||||||
|
@ -6,7 +6,10 @@ use ostd::sync::{WaitQueue, Waiter};
|
|||||||
|
|
||||||
use super::sig_mask::SigMask;
|
use super::sig_mask::SigMask;
|
||||||
use crate::{
|
use crate::{
|
||||||
prelude::*, process::posix_thread::PosixThreadExt, thread::Thread, time::wait::WaitTimeout,
|
prelude::*,
|
||||||
|
process::posix_thread::PosixThreadExt,
|
||||||
|
thread::Thread,
|
||||||
|
time::wait::{TimerBuilder, WaitTimeout},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// `Pause` is an extension trait to make [`Waiter`] and [`WaitQueue`] signal aware.
|
/// `Pause` is an extension trait to make [`Waiter`] and [`WaitQueue`] signal aware.
|
||||||
@ -33,7 +36,7 @@ pub trait Pause: WaitTimeout {
|
|||||||
where
|
where
|
||||||
F: FnMut() -> Option<R>,
|
F: FnMut() -> Option<R>,
|
||||||
{
|
{
|
||||||
self.pause_until_or_timeout_opt(cond, None)
|
self.pause_until_or_timer_timeout_opt(cond, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pauses the execution of the current thread until the `cond` is met ( i.e., `cond()`
|
/// Pauses the execution of the current thread until the `cond` is met ( i.e., `cond()`
|
||||||
@ -56,7 +59,18 @@ pub trait Pause: WaitTimeout {
|
|||||||
return cond()
|
return cond()
|
||||||
.ok_or_else(|| Error::with_message(Errno::ETIME, "the time limit is reached"));
|
.ok_or_else(|| Error::with_message(Errno::ETIME, "the time limit is reached"));
|
||||||
}
|
}
|
||||||
self.pause_until_or_timeout_opt(cond, Some(timeout))
|
let timer_builder = TimerBuilder::new(timeout);
|
||||||
|
self.pause_until_or_timer_timeout_opt(cond, Some(&timer_builder))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Similar to [`Pause::pause_until_or_timeout`].
|
||||||
|
///
|
||||||
|
/// The only difference is that the timeout is against the user-specified clock.
|
||||||
|
fn pause_until_or_timer_timeout<F, R>(&self, cond: F, timer_builder: &TimerBuilder) -> Result<R>
|
||||||
|
where
|
||||||
|
F: FnMut() -> Option<R>,
|
||||||
|
{
|
||||||
|
self.pause_until_or_timer_timeout_opt(cond, Some(timer_builder))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pauses the execution of the current thread until the `cond` is met ( i.e., `cond()`
|
/// Pauses the execution of the current thread until the `cond` is met ( i.e., `cond()`
|
||||||
@ -71,14 +85,31 @@ pub trait Pause: WaitTimeout {
|
|||||||
///
|
///
|
||||||
/// [`ETIME`]: crate::error::Errno::ETIME
|
/// [`ETIME`]: crate::error::Errno::ETIME
|
||||||
/// [`EINTR`]: crate::error::Errno::EINTR
|
/// [`EINTR`]: crate::error::Errno::EINTR
|
||||||
#[doc(hidden)]
|
fn pause_until_or_timer_timeout_opt<F, R>(
|
||||||
fn pause_until_or_timeout_opt<F, R>(&self, cond: F, timeout: Option<&Duration>) -> Result<R>
|
&self,
|
||||||
|
cond: F,
|
||||||
|
timer_builder: Option<&TimerBuilder>,
|
||||||
|
) -> Result<R>
|
||||||
where
|
where
|
||||||
F: FnMut() -> Option<R>;
|
F: FnMut() -> Option<R>;
|
||||||
|
|
||||||
|
/// Pauses the execution of the current thread until being woken up,
|
||||||
|
/// or some signals are received by the current thread or process.
|
||||||
|
/// If the input `timeout` is set, the pausing will finish when the `timeout` is expired.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// If `timeout` is expired before woken up or some signals are received,
|
||||||
|
/// this method will return [`Errno::ETIME`].
|
||||||
|
fn pause_timer_timeout(&self, timer_builder: Option<&TimerBuilder>) -> Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Pause for Waiter {
|
impl Pause for Waiter {
|
||||||
fn pause_until_or_timeout_opt<F, R>(&self, mut cond: F, timeout: Option<&Duration>) -> Result<R>
|
fn pause_until_or_timer_timeout_opt<F, R>(
|
||||||
|
&self,
|
||||||
|
mut cond: F,
|
||||||
|
timer_builder: Option<&TimerBuilder>,
|
||||||
|
) -> Result<R>
|
||||||
where
|
where
|
||||||
F: FnMut() -> Option<R>,
|
F: FnMut() -> Option<R>,
|
||||||
{
|
{
|
||||||
@ -88,9 +119,12 @@ impl Pause for Waiter {
|
|||||||
|
|
||||||
let current_thread = self.task().data().downcast_ref::<Arc<Thread>>();
|
let current_thread = self.task().data().downcast_ref::<Arc<Thread>>();
|
||||||
|
|
||||||
let Some(posix_thread) = current_thread.and_then(|thread| thread.as_posix_thread()) else {
|
let Some(posix_thread) = current_thread
|
||||||
if let Some(timeout) = timeout {
|
.as_ref()
|
||||||
return self.wait_until_or_timeout(cond, timeout);
|
.and_then(|thread| thread.as_posix_thread())
|
||||||
|
else {
|
||||||
|
if let Some(timer_builder) = timer_builder {
|
||||||
|
return self.wait_until_or_timer_timeout(cond, timer_builder);
|
||||||
} else {
|
} else {
|
||||||
return self.wait_until_or_cancelled(cond, || Ok(()));
|
return self.wait_until_or_cancelled(cond, || Ok(()));
|
||||||
}
|
}
|
||||||
@ -107,18 +141,54 @@ impl Pause for Waiter {
|
|||||||
};
|
};
|
||||||
|
|
||||||
posix_thread.set_signalled_waker(self.waker());
|
posix_thread.set_signalled_waker(self.waker());
|
||||||
let res = if let Some(timeout) = timeout {
|
let res = if let Some(timer_builder) = timer_builder {
|
||||||
self.wait_until_or_timeout_cancelled(cond, cancel_cond, timeout)
|
self.wait_until_or_timer_timeout_cancelled(cond, cancel_cond, timer_builder)
|
||||||
} else {
|
} else {
|
||||||
self.wait_until_or_cancelled(cond, cancel_cond)
|
self.wait_until_or_cancelled(cond, cancel_cond)
|
||||||
};
|
};
|
||||||
posix_thread.clear_signalled_waker();
|
posix_thread.clear_signalled_waker();
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn pause_timer_timeout(&self, timer_builder: Option<&TimerBuilder>) -> Result<()> {
|
||||||
|
let timer = timer_builder.map(|timer_builder| {
|
||||||
|
let waker = self.waker();
|
||||||
|
timer_builder.fire(move || {
|
||||||
|
waker.wake_up();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
let current_thread = self.task().data().downcast_ref::<Arc<Thread>>();
|
||||||
|
|
||||||
|
if let Some(posix_thread) = current_thread
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|thread| thread.as_posix_thread())
|
||||||
|
{
|
||||||
|
posix_thread.set_signalled_waker(self.waker());
|
||||||
|
self.wait();
|
||||||
|
posix_thread.clear_signalled_waker();
|
||||||
|
} else {
|
||||||
|
self.wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(timer) = timer {
|
||||||
|
if timer.remain().is_zero() {
|
||||||
|
return_errno_with_message!(Errno::ETIME, "the timeout is reached");
|
||||||
|
}
|
||||||
|
// If the timeout is not expired, cancel the timer manually.
|
||||||
|
timer.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Pause for WaitQueue {
|
impl Pause for WaitQueue {
|
||||||
fn pause_until_or_timeout_opt<F, R>(&self, mut cond: F, timeout: Option<&Duration>) -> Result<R>
|
fn pause_until_or_timer_timeout_opt<F, R>(
|
||||||
|
&self,
|
||||||
|
mut cond: F,
|
||||||
|
timer_builder: Option<&TimerBuilder>,
|
||||||
|
) -> Result<R>
|
||||||
where
|
where
|
||||||
F: FnMut() -> Option<R>,
|
F: FnMut() -> Option<R>,
|
||||||
{
|
{
|
||||||
@ -131,7 +201,11 @@ impl Pause for WaitQueue {
|
|||||||
self.enqueue(waiter.waker());
|
self.enqueue(waiter.waker());
|
||||||
cond()
|
cond()
|
||||||
};
|
};
|
||||||
waiter.pause_until_or_timeout_opt(cond, timeout)
|
waiter.pause_until_or_timer_timeout_opt(cond, timer_builder)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pause_timer_timeout(&self, _timer_builder: Option<&TimerBuilder>) -> Result<()> {
|
||||||
|
panic!("`pause_timer_timeout` can only be used on `Waiter`");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,19 +1,27 @@
|
|||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
use core::time::Duration;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
process::posix_thread::futex::{
|
process::posix_thread::futex::{
|
||||||
futex_op_and_flags_from_u32, futex_requeue, futex_wait, futex_wait_bitset, futex_wake,
|
futex_op_and_flags_from_u32, futex_requeue, futex_wait, futex_wait_bitset, futex_wake,
|
||||||
futex_wake_bitset, FutexOp, FutexTimeout,
|
futex_wake_bitset, FutexFlags, FutexOp,
|
||||||
},
|
},
|
||||||
syscall::SyscallReturn,
|
syscall::SyscallReturn,
|
||||||
|
time::{
|
||||||
|
clocks::{MonotonicClock, RealTimeClock},
|
||||||
|
timer::Timeout,
|
||||||
|
timespec_t,
|
||||||
|
wait::TimerBuilder,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn sys_futex(
|
pub fn sys_futex(
|
||||||
futex_addr: Vaddr,
|
futex_addr: Vaddr,
|
||||||
futex_op: i32,
|
futex_op: i32,
|
||||||
futex_val: u32,
|
futex_val: u64,
|
||||||
utime_addr: u64,
|
utime_addr: Vaddr,
|
||||||
futex_new_addr: u64,
|
futex_new_addr: u64,
|
||||||
bitset: u64,
|
bitset: u64,
|
||||||
ctx: &Context,
|
ctx: &Context,
|
||||||
@ -21,8 +29,8 @@ pub fn sys_futex(
|
|||||||
// FIXME: we current ignore futex flags
|
// FIXME: we current ignore futex flags
|
||||||
let (futex_op, futex_flags) = futex_op_and_flags_from_u32(futex_op as _)?;
|
let (futex_op, futex_flags) = futex_op_and_flags_from_u32(futex_op as _)?;
|
||||||
debug!(
|
debug!(
|
||||||
"futex_op = {:?}, futex_flags = {:?}, futex_addr = 0x{:x}",
|
"futex_op = {:?}, futex_flags = {:?}, futex_addr = 0x{:x}, futex_val = 0x{:x}",
|
||||||
futex_op, futex_flags, futex_addr
|
futex_op, futex_flags, futex_addr, futex_val
|
||||||
);
|
);
|
||||||
|
|
||||||
let get_futex_val = |val: i32| -> Result<usize> {
|
let get_futex_val = |val: i32| -> Result<usize> {
|
||||||
@ -32,22 +40,65 @@ pub fn sys_futex(
|
|||||||
Ok(val as usize)
|
Ok(val as usize)
|
||||||
};
|
};
|
||||||
|
|
||||||
let get_futex_timeout = |timeout_addr| -> Result<Option<FutexTimeout>> {
|
let get_futex_timer_builder = |timeout_addr: Vaddr| -> Result<Option<TimerBuilder<'static>>> {
|
||||||
if timeout_addr == 0 {
|
if timeout_addr == 0 {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
// TODO: parse a timeout
|
|
||||||
todo!()
|
let timeout = {
|
||||||
|
let time_spec: timespec_t = CurrentUserSpace::get().read_val(timeout_addr)?;
|
||||||
|
Duration::try_from(time_spec)?
|
||||||
|
};
|
||||||
|
|
||||||
|
let is_real_time = futex_flags.contains(FutexFlags::FUTEX_CLOCK_REALTIME);
|
||||||
|
if is_real_time && futex_op == FutexOp::FUTEX_WAIT {
|
||||||
|
// Ref: <https://github.com/torvalds/linux/commit/4fbf5d6837bf81fd7a27d771358f4ee6c4f243f8>
|
||||||
|
return_errno_with_message!(Errno::ENOSYS, "FUTEX_WAIT cannot use CLOCK_REALTIME");
|
||||||
|
}
|
||||||
|
|
||||||
|
let timeout = {
|
||||||
|
// From man(2) futex:
|
||||||
|
// for FUTEX_WAIT, 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,
|
||||||
|
// employ FUTEX_WAIT_BITSET with val3 specified as FUTEX_BITSET_MATCH_ANY.
|
||||||
|
if futex_op == FutexOp::FUTEX_WAIT {
|
||||||
|
Timeout::After(timeout)
|
||||||
|
} else {
|
||||||
|
Timeout::When(timeout)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let timer_manager = if is_real_time {
|
||||||
|
debug!("futex timeout = {:?}, clock = CLOCK_REALTIME", timeout);
|
||||||
|
RealTimeClock::timer_manager()
|
||||||
|
} else {
|
||||||
|
debug!("futex timeout = {:?}, clock = CLOCK_MONOTONIC", timeout);
|
||||||
|
MonotonicClock::timer_manager()
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Some(TimerBuilder::new_with_timer_manager(
|
||||||
|
timeout,
|
||||||
|
timer_manager,
|
||||||
|
)))
|
||||||
};
|
};
|
||||||
|
|
||||||
let res = match futex_op {
|
let res = match futex_op {
|
||||||
FutexOp::FUTEX_WAIT => {
|
FutexOp::FUTEX_WAIT => {
|
||||||
let timeout = get_futex_timeout(utime_addr)?;
|
let timer_builder = get_futex_timer_builder(utime_addr)?;
|
||||||
futex_wait(futex_addr as _, futex_val as _, &timeout).map(|_| 0)
|
futex_wait(futex_addr as _, futex_val as _, timer_builder, ctx).map(|_| 0)
|
||||||
}
|
}
|
||||||
FutexOp::FUTEX_WAIT_BITSET => {
|
FutexOp::FUTEX_WAIT_BITSET => {
|
||||||
let timeout = get_futex_timeout(utime_addr)?;
|
let timer_builder = get_futex_timer_builder(utime_addr)?;
|
||||||
futex_wait_bitset(futex_addr as _, futex_val as _, &timeout, bitset as _).map(|_| 0)
|
futex_wait_bitset(
|
||||||
|
futex_addr as _,
|
||||||
|
futex_val as _,
|
||||||
|
timer_builder,
|
||||||
|
bitset as _,
|
||||||
|
ctx,
|
||||||
|
)
|
||||||
|
.map(|_| 0)
|
||||||
}
|
}
|
||||||
FutexOp::FUTEX_WAKE => {
|
FutexOp::FUTEX_WAKE => {
|
||||||
let max_count = get_futex_val(futex_val as i32)?;
|
let max_count = get_futex_val(futex_val as i32)?;
|
||||||
@ -68,8 +119,19 @@ pub fn sys_futex(
|
|||||||
)
|
)
|
||||||
.map(|nwakes| nwakes as _)
|
.map(|nwakes| nwakes as _)
|
||||||
}
|
}
|
||||||
_ => panic!("Unsupported futex operations"),
|
_ => {
|
||||||
}?;
|
warn!("futex op = {:?}", futex_op);
|
||||||
|
return_errno_with_message!(Errno::EINVAL, "unsupported futex op");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.map_err(|e| {
|
||||||
|
// From Linux manual, Futex returns `ETIMEDOUT` instead of `ETIME`
|
||||||
|
if e.error() == Errno::ETIME {
|
||||||
|
Error::with_message(Errno::ETIMEDOUT, "futex wait timeout")
|
||||||
|
} else {
|
||||||
|
e
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
debug!("futex returns, tid= {} ", ctx.posix_thread.tid());
|
debug!("futex returns, tid= {} ", ctx.posix_thread.tid());
|
||||||
Ok(SyscallReturn::Return(res as _))
|
Ok(SyscallReturn::Return(res as _))
|
||||||
|
@ -7,7 +7,14 @@ use ostd::sync::Waiter;
|
|||||||
use super::{clock_gettime::read_clock, ClockId, SyscallReturn};
|
use super::{clock_gettime::read_clock, ClockId, SyscallReturn};
|
||||||
use crate::{
|
use crate::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
time::{clockid_t, timespec_t, TIMER_ABSTIME},
|
time::{
|
||||||
|
clockid_t,
|
||||||
|
clocks::{BootTimeClock, MonotonicClock, RealTimeClock},
|
||||||
|
timer::Timeout,
|
||||||
|
timespec_t,
|
||||||
|
wait::TimerBuilder,
|
||||||
|
TIMER_ABSTIME,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn sys_nanosleep(
|
pub fn sys_nanosleep(
|
||||||
@ -70,7 +77,7 @@ fn do_clock_nanosleep(
|
|||||||
);
|
);
|
||||||
|
|
||||||
let start_time = read_clock(clockid, ctx)?;
|
let start_time = read_clock(clockid, ctx)?;
|
||||||
let timeout = if is_abs_time {
|
let duration = if is_abs_time {
|
||||||
if request_time < start_time {
|
if request_time < start_time {
|
||||||
return Ok(SyscallReturn::Return(0));
|
return Ok(SyscallReturn::Return(0));
|
||||||
}
|
}
|
||||||
@ -84,18 +91,39 @@ fn do_clock_nanosleep(
|
|||||||
// current process. i.e., the signals that should be ignored will not interrupt sleeping thread.
|
// current process. i.e., the signals that should be ignored will not interrupt sleeping thread.
|
||||||
let waiter = Waiter::new_pair().0;
|
let waiter = Waiter::new_pair().0;
|
||||||
|
|
||||||
let res = waiter.pause_until_or_timeout(|| None, &timeout);
|
let timer_manager = {
|
||||||
|
let clock_id = ClockId::try_from(clockid)?;
|
||||||
|
match clock_id {
|
||||||
|
ClockId::CLOCK_BOOTTIME => BootTimeClock::timer_manager(),
|
||||||
|
ClockId::CLOCK_MONOTONIC => MonotonicClock::timer_manager(),
|
||||||
|
ClockId::CLOCK_REALTIME => RealTimeClock::timer_manager(),
|
||||||
|
// FIXME: We should better not expose this prof timer manager.
|
||||||
|
ClockId::CLOCK_PROCESS_CPUTIME_ID => {
|
||||||
|
ctx.process.timer_manager().prof_timer().timer_manager()
|
||||||
|
}
|
||||||
|
// FIXME: From the manual,
|
||||||
|
// the CPU clock IDs returned by clock_getcpuclockid(3)
|
||||||
|
// and pthread_getcpuclockid(3) can also be passed in clockid.
|
||||||
|
// But it's not covered here.
|
||||||
|
_ => return_errno_with_message!(Errno::EINVAL, "unknown clockid for clock_nanosleep"),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let timer_builder =
|
||||||
|
TimerBuilder::new_with_timer_manager(Timeout::After(duration), timer_manager);
|
||||||
|
|
||||||
|
let res = waiter.pause_until_or_timer_timeout(|| None, &timer_builder);
|
||||||
match res {
|
match res {
|
||||||
Err(e) if e.error() == Errno::ETIME => Ok(SyscallReturn::Return(0)),
|
Err(e) if e.error() == Errno::ETIME => Ok(SyscallReturn::Return(0)),
|
||||||
Err(e) if e.error() == Errno::EINTR => {
|
Err(e) if e.error() == Errno::EINTR => {
|
||||||
let end_time = read_clock(clockid, ctx)?;
|
let end_time = read_clock(clockid, ctx)?;
|
||||||
|
|
||||||
if end_time >= start_time + timeout {
|
if end_time >= start_time + duration {
|
||||||
return Ok(SyscallReturn::Return(0));
|
return Ok(SyscallReturn::Return(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
if remain_timespec_addr != 0 && !is_abs_time {
|
if remain_timespec_addr != 0 && !is_abs_time {
|
||||||
let remaining_duration = (start_time + timeout) - end_time;
|
let remaining_duration = (start_time + duration) - end_time;
|
||||||
let remaining_timespec = timespec_t::from(remaining_duration);
|
let remaining_timespec = timespec_t::from(remaining_duration);
|
||||||
ctx.get_user_space()
|
ctx.get_user_space()
|
||||||
.write_val(remain_timespec_addr, &remaining_timespec)?;
|
.write_val(remain_timespec_addr, &remaining_timespec)?;
|
||||||
|
@ -16,6 +16,7 @@ use ostd::sync::SpinLock;
|
|||||||
use super::Clock;
|
use super::Clock;
|
||||||
|
|
||||||
/// A timeout, represented in one of the two ways.
|
/// A timeout, represented in one of the two ways.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub enum Timeout {
|
pub enum Timeout {
|
||||||
/// The timeout is reached _after_ the `Duration` time is elapsed.
|
/// The timeout is reached _after_ the `Duration` time is elapsed.
|
||||||
After(Duration),
|
After(Duration),
|
||||||
@ -159,6 +160,17 @@ impl TimerManager {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns whether a given `timeout` is expired.
|
||||||
|
pub fn is_expired_timeout(&self, timeout: &Timeout) -> bool {
|
||||||
|
match timeout {
|
||||||
|
Timeout::After(duration) => *duration == Duration::ZERO,
|
||||||
|
Timeout::When(duration) => {
|
||||||
|
let now = self.clock.read_time();
|
||||||
|
now >= *duration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn insert(&self, timer_callback: Arc<TimerCallback>) {
|
fn insert(&self, timer_callback: Arc<TimerCallback>) {
|
||||||
self.timer_callbacks
|
self.timer_callbacks
|
||||||
.disable_irq()
|
.disable_irq()
|
||||||
|
@ -4,7 +4,7 @@ use core::time::Duration;
|
|||||||
|
|
||||||
use ostd::sync::{WaitQueue, Waiter};
|
use ostd::sync::{WaitQueue, Waiter};
|
||||||
|
|
||||||
use super::{clocks::JIFFIES_TIMER_MANAGER, timer::Timeout};
|
use super::{clocks::JIFFIES_TIMER_MANAGER, timer::Timeout, Timer, TimerManager};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// A trait that provide the timeout related function for [`Waiter`] and [`WaitQueue`]`.
|
/// A trait that provide the timeout related function for [`Waiter`] and [`WaitQueue`]`.
|
||||||
@ -16,7 +16,19 @@ pub trait WaitTimeout {
|
|||||||
where
|
where
|
||||||
F: FnMut() -> Option<R>,
|
F: FnMut() -> Option<R>,
|
||||||
{
|
{
|
||||||
self.wait_until_or_timeout_cancelled(cond, || Ok(()), timeout)
|
let timer_builder = TimerBuilder::new(timeout);
|
||||||
|
self.wait_until_or_timer_timeout(cond, &timer_builder)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Similar to [`WaitTimeout::wait_until_or_timeout`].
|
||||||
|
///
|
||||||
|
/// The difference is that the timeout of this method is against the specified clock,
|
||||||
|
/// which is defined in `timer_builder`.
|
||||||
|
fn wait_until_or_timer_timeout<F, R>(&self, cond: F, timer_builder: &TimerBuilder) -> Result<R>
|
||||||
|
where
|
||||||
|
F: FnMut() -> Option<R>,
|
||||||
|
{
|
||||||
|
self.wait_until_or_timer_timeout_cancelled(cond, || Ok(()), timer_builder)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Waits until some condition returns `Some(_)`, or be cancelled due to
|
/// Waits until some condition returns `Some(_)`, or be cancelled due to
|
||||||
@ -24,29 +36,75 @@ pub trait WaitTimeout {
|
|||||||
/// does not becomes `Some(_)` before the timeout is reached or `cancel_cond`
|
/// does not becomes `Some(_)` before the timeout is reached or `cancel_cond`
|
||||||
/// returns `Err`, this function will return corresponding `Err`.
|
/// returns `Err`, this function will return corresponding `Err`.
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
fn wait_until_or_timeout_cancelled<F, R, FCancel>(
|
fn wait_until_or_timer_timeout_cancelled<F, R, FCancel>(
|
||||||
&self,
|
&self,
|
||||||
cond: F,
|
cond: F,
|
||||||
cancel_cond: FCancel,
|
cancel_cond: FCancel,
|
||||||
timeout: &Duration,
|
timer_builder: &TimerBuilder,
|
||||||
) -> Result<R>
|
) -> Result<R>
|
||||||
where
|
where
|
||||||
F: FnMut() -> Option<R>,
|
F: FnMut() -> Option<R>,
|
||||||
FCancel: Fn() -> Result<()>;
|
FCancel: Fn() -> Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A helper structure to build timers from a specified timeout and timer manager.
|
||||||
|
pub struct TimerBuilder<'a> {
|
||||||
|
timeout: Timeout,
|
||||||
|
timer_manager: &'a Arc<TimerManager>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TimerBuilder<'a> {
|
||||||
|
/// Creates a new `TimerBuilder` against the default JIFFIES clock.
|
||||||
|
pub fn new(timeout: &Duration) -> Self {
|
||||||
|
let timeout = Timeout::After(*timeout);
|
||||||
|
let jiffies_timer_manager = JIFFIES_TIMER_MANAGER.get().unwrap();
|
||||||
|
Self::new_with_timer_manager(timeout, jiffies_timer_manager)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new `TimerBuilder` with given timer manager.
|
||||||
|
pub const fn new_with_timer_manager(
|
||||||
|
timeout: Timeout,
|
||||||
|
timer_manager: &'a Arc<TimerManager>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
timeout,
|
||||||
|
timer_manager,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the timeout
|
||||||
|
pub const fn timeout(&self) -> &Timeout {
|
||||||
|
&self.timeout
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_expired(&self) -> bool {
|
||||||
|
self.timer_manager.is_expired_timeout(&self.timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds and sets a timer,
|
||||||
|
/// which will trigger `callback` when `self.timeout()` is reached.
|
||||||
|
pub fn fire<F>(&self, callback: F) -> Arc<Timer>
|
||||||
|
where
|
||||||
|
F: Fn() + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
let timer = self.timer_manager.create_timer(callback);
|
||||||
|
timer.set_timeout(self.timeout.clone());
|
||||||
|
timer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl WaitTimeout for Waiter {
|
impl WaitTimeout for Waiter {
|
||||||
fn wait_until_or_timeout_cancelled<F, R, FCancel>(
|
fn wait_until_or_timer_timeout_cancelled<F, R, FCancel>(
|
||||||
&self,
|
&self,
|
||||||
mut cond: F,
|
mut cond: F,
|
||||||
cancel_cond: FCancel,
|
cancel_cond: FCancel,
|
||||||
timeout: &Duration,
|
timer_builder: &TimerBuilder,
|
||||||
) -> Result<R>
|
) -> Result<R>
|
||||||
where
|
where
|
||||||
F: FnMut() -> Option<R>,
|
F: FnMut() -> Option<R>,
|
||||||
FCancel: Fn() -> Result<()>,
|
FCancel: Fn() -> Result<()>,
|
||||||
{
|
{
|
||||||
if *timeout == Duration::ZERO {
|
if timer_builder.is_expired() {
|
||||||
return cond()
|
return cond()
|
||||||
.ok_or_else(|| Error::with_message(Errno::ETIME, "the time limit is reached"));
|
.ok_or_else(|| Error::with_message(Errno::ETIME, "the time limit is reached"));
|
||||||
}
|
}
|
||||||
@ -56,15 +114,14 @@ impl WaitTimeout for Waiter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let waker = self.waker();
|
let waker = self.waker();
|
||||||
let jiffies_timer = JIFFIES_TIMER_MANAGER.get().unwrap().create_timer(move || {
|
let timer = timer_builder.fire(move || {
|
||||||
waker.wake_up();
|
waker.wake_up();
|
||||||
});
|
});
|
||||||
jiffies_timer.set_timeout(Timeout::After(*timeout));
|
|
||||||
|
|
||||||
let timeout_cond = {
|
let timeout_cond = {
|
||||||
let jiffies_timer = jiffies_timer.clone();
|
let timer = timer.clone();
|
||||||
move || {
|
move || {
|
||||||
if jiffies_timer.remain() != Duration::ZERO {
|
if timer.remain() != Duration::ZERO {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(Error::with_message(
|
Err(Error::with_message(
|
||||||
@ -88,7 +145,7 @@ impl WaitTimeout for Waiter {
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.is_err_and(|e: &Error| e.error() == Errno::ETIME)
|
.is_err_and(|e: &Error| e.error() == Errno::ETIME)
|
||||||
{
|
{
|
||||||
jiffies_timer.cancel();
|
timer.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
res
|
res
|
||||||
@ -96,17 +153,17 @@ impl WaitTimeout for Waiter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl WaitTimeout for WaitQueue {
|
impl WaitTimeout for WaitQueue {
|
||||||
fn wait_until_or_timeout_cancelled<F, R, FCancel>(
|
fn wait_until_or_timer_timeout_cancelled<F, R, FCancel>(
|
||||||
&self,
|
&self,
|
||||||
mut cond: F,
|
mut cond: F,
|
||||||
cancel_cond: FCancel,
|
cancel_cond: FCancel,
|
||||||
timeout: &Duration,
|
timer_builder: &TimerBuilder,
|
||||||
) -> Result<R>
|
) -> Result<R>
|
||||||
where
|
where
|
||||||
F: FnMut() -> Option<R>,
|
F: FnMut() -> Option<R>,
|
||||||
FCancel: Fn() -> Result<()>,
|
FCancel: Fn() -> Result<()>,
|
||||||
{
|
{
|
||||||
if *timeout == Duration::ZERO {
|
if timer_builder.is_expired() {
|
||||||
return cond()
|
return cond()
|
||||||
.ok_or_else(|| Error::with_message(Errno::ETIME, "the time limit is reached"));
|
.ok_or_else(|| Error::with_message(Errno::ETIME, "the time limit is reached"));
|
||||||
}
|
}
|
||||||
@ -120,6 +177,6 @@ impl WaitTimeout for WaitQueue {
|
|||||||
self.enqueue(waiter.waker());
|
self.enqueue(waiter.waker());
|
||||||
cond()
|
cond()
|
||||||
};
|
};
|
||||||
waiter.wait_until_or_timeout_cancelled(cond, cancel_cond, timeout)
|
waiter.wait_until_or_timer_timeout_cancelled(cond, cancel_cond, timer_builder)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ TESTS ?= \
|
|||||||
fcntl_test \
|
fcntl_test \
|
||||||
flock_test \
|
flock_test \
|
||||||
fsync_test \
|
fsync_test \
|
||||||
|
futex_test \
|
||||||
getdents_test \
|
getdents_test \
|
||||||
link_test \
|
link_test \
|
||||||
lseek_test \
|
lseek_test \
|
||||||
|
15
test/syscall_test/blocklists/futex_test
Normal file
15
test/syscall_test/blocklists/futex_test
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
PrivateFutexTest.*
|
||||||
|
RobustFutexTest.*
|
||||||
|
SharedFutexTest.WakeInterprocessFile_NoRandomSave
|
||||||
|
SharedPrivate/PrivateAndSharedFutexTest.Wake0_NoRandomSave/*
|
||||||
|
SharedPrivate/PrivateAndSharedFutexTest.WakeOpCondSuccess_NoRandomSave/*
|
||||||
|
SharedPrivate/PrivateAndSharedFutexTest.WakeOpCondFailure_NoRandomSave/*
|
||||||
|
SharedPrivate/PrivateAndSharedFutexTest.PIBasic/*
|
||||||
|
SharedPrivate/PrivateAndSharedFutexTest.PIConcurrency_NoRandomSave/*
|
||||||
|
SharedPrivate/PrivateAndSharedFutexTest.PIWaiters/*
|
||||||
|
SharedPrivate/PrivateAndSharedFutexTest.PITryLock/*
|
||||||
|
SharedPrivate/PrivateAndSharedFutexTest.PITryLockConcurrency_NoRandomSave/*
|
||||||
|
|
||||||
|
SharedPrivate/PrivateAndSharedFutexTest.WakeSome_NoRandomSave/1
|
||||||
|
SharedPrivate/PrivateAndSharedFutexTest.NoWakeInterprocessPrivateAnon_NoRandomSave/*
|
||||||
|
SharedPrivate/PrivateAndSharedFutexTest.WakeWrongKind_NoRandomSave/*
|
Loading…
x
Reference in New Issue
Block a user