Skip to content

Commit 1cc900a

Browse files
futex2 (#313)
* add util functions for processing waker sets * data structures for futex queues * write one core for both futex impl; futex 1 uses futex_wait_multi with 1 waiter * wire up the futex2 syscalls * add tests for futex2 * add doc for futex2 changes * change readme supported syscall number * tests: additional tests after looking at linux src * fixes from code review * fix a bug with the calculation of time * remove docs * code formatting * remove unused function * update outdated comment about not returning EINVAL when you requeue a futex on itself * addressing PR comments
1 parent d7b9908 commit 1cc900a

13 files changed

Lines changed: 1746 additions & 150 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ model within the kernel context:
4848
* Full task management including both UP and SMP scheduling via EEVDF and task
4949
migration via IPIs.
5050
* Capable of running dynamically linked ELF binaries from Arch Linux.
51-
* Currently implements [105 Linux syscalls](./etc/syscalls_linux_aarch64.md)
51+
* Currently implements [109 Linux syscalls](./etc/syscalls_linux_aarch64.md)
5252
* `fork()`, `execve()`, `clone()`, and full process lifecycle management.
5353
* Job control support (process groups, waitpid, background tasks).
5454
* Signal delivery, masking, and propagation (SIGTERM, SIGSTOP, SIGCONT, SIGCHLD,
@@ -172,7 +172,7 @@ moss is under active development. Current focus areas include:
172172

173173
* Networking Stack: TCP/IP implementation.
174174
* A fully read/write capable filesystem driver.
175-
* Expanding coverage beyond the current 105 calls.
175+
* Expanding coverage beyond the current 109 calls.
176176
* systemd bringup.
177177

178178
## Non-Goals (for now)

etc/syscalls_linux_aarch64.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -304,14 +304,14 @@
304304
| 0x1be (446) | landlock_restrict_self | (const int ruleset_fd, const __u32 flags) | __arm64_sys_landlock_restrict_self | false |
305305
| 0x1bf (447) | memfd_secret | (unsigned int flags) | __arm64_sys_memfd_secret | false |
306306
| 0x1c0 (448) | process_mrelease | (int pidfd, unsigned int flags) | __arm64_sys_process_mrelease | false |
307-
| 0x1c1 (449) | futex_waitv | (struct futex_waitv *waiters, unsigned int nr_futexes, unsigned int flags, struct __kernel_timespec *timeout, clockid_t clockid) | __arm64_sys_futex_waitv | false |
307+
| 0x1c1 (449) | futex_waitv | (struct futex_waitv *waiters, unsigned int nr_futexes, unsigned int flags, struct __kernel_timespec *timeout, clockid_t clockid) | __arm64_sys_futex_waitv | true |
308308
| 0x1c2 (450) | set_mempolicy_home_node | (unsigned long start, unsigned long len, unsigned long home_node, unsigned long flags) | __arm64_sys_set_mempolicy_home_node | false |
309309
| 0x1c3 (451) | cachestat | (unsigned int fd, struct cachestat_range *cstat_range, struct cachestat *cstat, unsigned int flags) | __arm64_sys_cachestat | false |
310310
| 0x1c4 (452) | fchmodat2 | (int dfd, const char *filename, umode_t mode, unsigned int flags) | __arm64_sys_fchmodat2 | false |
311311
| 0x1c5 (453) | map_shadow_stack | (unsigned long addr, unsigned long size, unsigned int flags) | __arm64_sys_map_shadow_stack | false |
312-
| 0x1c6 (454) | futex_wake | (void *uaddr, unsigned long mask, int nr, unsigned int flags) | __arm64_sys_futex_wake | false |
313-
| 0x1c7 (455) | futex_wait | (void *uaddr, unsigned long val, unsigned long mask, unsigned int flags, struct __kernel_timespec *timeout, clockid_t clockid) | __arm64_sys_futex_wait | false |
314-
| 0x1c8 (456) | futex_requeue | (struct futex_waitv *waiters, unsigned int flags, int nr_wake, int nr_requeue) | __arm64_sys_futex_requeue | false |
312+
| 0x1c6 (454) | futex_wake | (void *uaddr, unsigned long mask, int nr, unsigned int flags) | __arm64_sys_futex_wake | true |
313+
| 0x1c7 (455) | futex_wait | (void *uaddr, unsigned long val, unsigned long mask, unsigned int flags, struct __kernel_timespec *timeout, clockid_t clockid) | __arm64_sys_futex_wait | true |
314+
| 0x1c8 (456) | futex_requeue | (struct futex_waitv *waiters, unsigned int flags, int nr_wake, int nr_requeue) | __arm64_sys_futex_requeue | true |
315315
| 0x1c9 (457) | statmount | (const struct mnt_id_req *req, struct statmount *buf, size_t bufsize, unsigned int flags) | __arm64_sys_statmount | false |
316316
| 0x1ca (458) | listmount | (const struct mnt_id_req *req, u64 *mnt_ids, size_t nr_mnt_ids, unsigned int flags) | __arm64_sys_listmount | false |
317317
| 0x1cb (459) | lsm_get_self_attr | (unsigned int attr, struct lsm_ctx *ctx, u32 *size, u32 flags) | __arm64_sys_lsm_get_self_attr | false |

libkernel/src/sync/waker_set.rs

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,45 @@ impl<T> WakerSet<T> {
8989
}
9090
}
9191

92+
/// Removes and returns the first (lowest-token, i.e. FIFO) entry whose
93+
/// data matches `predicate`, without waking it.
94+
pub fn take_if(&mut self, predicate: impl Fn(&T) -> bool) -> Option<(Waker, T)> {
95+
let key = self
96+
.waiters
97+
.iter()
98+
.find(|(_, (_, data))| predicate(data))
99+
.map(|(key, _)| *key)?;
100+
101+
self.waiters.remove(&key)
102+
}
103+
104+
/// Removes and returns the first (lowest-token, i.e. FIFO) entry, without
105+
/// waking it.
106+
pub fn take_first(&mut self) -> Option<(Waker, T)> {
107+
self.waiters.pop_first().map(|(_, entry)| entry)
108+
}
109+
110+
/// Returns `true` if no wakers are registered.
111+
pub fn is_empty(&self) -> bool {
112+
self.waiters.is_empty()
113+
}
114+
115+
/// Number of registered wakers.
116+
pub fn len(&self) -> usize {
117+
self.waiters.len()
118+
}
119+
92120
/// Registers a waker together with associated data, returning its token.
93121
pub fn register_with_data(&mut self, waker: &Waker, data: T) -> u64 {
122+
self.insert(waker.clone(), data)
123+
}
124+
125+
/// Inserts an already-owned waker with associated data, returning its
126+
/// token.
127+
pub fn insert(&mut self, waker: Waker, data: T) -> u64 {
94128
let id = self.allocate_id();
95129

96-
self.waiters.insert(id, (waker.clone(), data));
130+
self.waiters.insert(id, (waker, data));
97131

98132
id
99133
}
@@ -193,6 +227,54 @@ where
193227
}
194228
}
195229

230+
#[cfg(test)]
231+
mod waker_set_tests {
232+
use super::*;
233+
234+
fn set_with_data(data: &[u32]) -> WakerSet<u32> {
235+
let mut set = WakerSet::new();
236+
for &d in data {
237+
set.insert(Waker::noop().clone(), d);
238+
}
239+
set
240+
}
241+
242+
#[test]
243+
fn take_if_removes_first_match_in_fifo_order() {
244+
let mut set = set_with_data(&[0b01, 0b10, 0b11]);
245+
246+
let (_, data) = set.take_if(|d| d & 0b10 != 0).unwrap();
247+
assert_eq!(data, 0b10);
248+
249+
let (_, data) = set.take_if(|d| d & 0b10 != 0).unwrap();
250+
assert_eq!(data, 0b11);
251+
252+
assert!(set.take_if(|d| d & 0b10 != 0).is_none());
253+
assert!(!set.is_empty());
254+
}
255+
256+
#[test]
257+
fn take_first_is_fifo() {
258+
let mut set = set_with_data(&[1, 2, 3]);
259+
260+
assert_eq!(set.take_first().unwrap().1, 1);
261+
assert_eq!(set.take_first().unwrap().1, 2);
262+
assert_eq!(set.take_first().unwrap().1, 3);
263+
assert!(set.take_first().is_none());
264+
assert!(set.is_empty());
265+
}
266+
267+
#[test]
268+
fn insert_then_remove_by_token() {
269+
let mut set = WakerSet::new();
270+
let token = set.insert(Waker::noop().clone(), 7u32);
271+
272+
assert!(set.contains_token(token));
273+
set.remove(token);
274+
assert!(set.is_empty());
275+
}
276+
}
277+
196278
#[cfg(test)]
197279
mod wait_until_tests {
198280
use super::*;

src/arch/arm64/exceptions/syscall.rs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,11 @@ use crate::{
103103
umask::sys_umask,
104104
wait::{sys_wait4, sys_waitid},
105105
},
106-
threading::{futex::sys_futex, sys_set_robust_list, sys_set_tid_address},
106+
threading::{
107+
futex::futex2::{sys_futex_requeue, sys_futex_wait, sys_futex_waitv, sys_futex_wake},
108+
futex::sys_futex,
109+
sys_set_robust_list, sys_set_tid_address,
110+
},
107111
},
108112
sched::{
109113
self,
@@ -824,6 +828,40 @@ pub async fn handle_syscall(mut ctx: ProcessCtx) {
824828
.await
825829
}
826830
0x1b8 => Ok(0), // process_madvise is a no-op
831+
0x1c1 => {
832+
sys_futex_waitv(
833+
&ctx,
834+
TUA::from_value(arg1 as _),
835+
arg2 as _,
836+
arg3 as _,
837+
TUA::from_value(arg4 as _),
838+
arg5 as _,
839+
)
840+
.await
841+
}
842+
0x1c6 => sys_futex_wake(&ctx, arg1, arg2, arg3 as _, arg4 as _),
843+
0x1c7 => {
844+
sys_futex_wait(
845+
&ctx,
846+
arg1,
847+
arg2,
848+
arg3,
849+
arg4 as _,
850+
TUA::from_value(arg5 as _),
851+
arg6 as _,
852+
)
853+
.await
854+
}
855+
0x1c8 => {
856+
sys_futex_requeue(
857+
&ctx,
858+
TUA::from_value(arg1 as _),
859+
arg2 as _,
860+
arg3 as _,
861+
arg4 as _,
862+
)
863+
.await
864+
}
827865
_ => panic!(
828866
"Unhandled syscall 0x{nr:x}, PC: 0x{:x}",
829867
ctx.task().ctx.user().elr_el1

src/clock/mod.rs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,92 @@ pub mod syscalls;
33
pub mod timer;
44
pub mod timespec;
55

6+
use core::time::Duration;
7+
8+
use futures::FutureExt;
9+
10+
use crate::drivers::timer::{sleep, uptime};
11+
use realtime::{clock_set_generation, clock_was_set_since, date};
12+
13+
/// An absolute deadline expressed against a particular clock.
14+
///
15+
/// Keeping the clock alongside the deadline (rather than pre-flattening to a
16+
/// relative duration) lets [`Deadline::sleep`] re-evaluate against the live
17+
/// clock, so a `CLOCK_REALTIME` deadline still fires at the right wall-clock
18+
/// instant even if the clock is stepped (e.g. by `clock_settime`) while a
19+
/// wait is in progress.
20+
#[derive(Clone, Copy)]
21+
pub enum Deadline {
22+
/// Absolute instant on the monotonic clock (`CLOCK_MONOTONIC`).
23+
Monotonic(Duration),
24+
/// Absolute instant on the realtime clock (`CLOCK_REALTIME`).
25+
Realtime(Duration),
26+
}
27+
28+
impl Deadline {
29+
/// The clock's current reading.
30+
fn clock_now(self) -> Duration {
31+
match self {
32+
Deadline::Monotonic(_) => uptime(),
33+
Deadline::Realtime(_) => date(),
34+
}
35+
}
36+
37+
/// The absolute deadline value.
38+
fn target(self) -> Duration {
39+
match self {
40+
Deadline::Monotonic(d) | Deadline::Realtime(d) => d,
41+
}
42+
}
43+
44+
/// Sleeps until this deadline.
45+
///
46+
/// The monotonic clock advances uniformly, so a single relative sleep is
47+
/// exact. The realtime clock can be stepped by `clock_settime`, so a
48+
/// realtime wait races the timer against a clock-was-set notification: on
49+
/// either it re-evaluates the deadline against the live clock and re-arms
50+
/// if the target has not yet been reached. This retargets an in-progress
51+
/// wait in both directions across a step.
52+
pub async fn sleep(self) {
53+
loop {
54+
// Sample the clock-set generation *before* reading the clock. A
55+
// realtime step that lands after this point bumps the generation,
56+
// so `clock_was_set_since` below fires immediately and we re-loop;
57+
// sampling after `clock_now()` would leave a window where a step
58+
// makes `remaining` stale yet goes unnoticed.
59+
let generation = clock_set_generation();
60+
61+
let now = self.clock_now();
62+
let target = self.target();
63+
64+
if now >= target {
65+
return;
66+
}
67+
68+
let remaining = target - now;
69+
70+
match self {
71+
// The monotonic clock never steps, so one relative sleep is
72+
// exact.
73+
Deadline::Monotonic(_) => {
74+
sleep(remaining).await;
75+
return;
76+
}
77+
// A realtime step (in either direction) wakes the notifier;
78+
// loop to re-evaluate against the new wall time.
79+
Deadline::Realtime(_) => {
80+
let mut timer = core::pin::pin!(sleep(remaining).fuse());
81+
let mut was_set = core::pin::pin!(clock_was_set_since(generation).fuse());
82+
futures::select_biased! {
83+
_ = timer => {}
84+
_ = was_set => {}
85+
}
86+
}
87+
}
88+
}
89+
}
90+
}
91+
692
pub enum ClockId {
793
Realtime = 0,
894
Monotonic = 1,

src/clock/realtime.rs

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
use crate::{
22
drivers::timer::{Instant, now, uptime},
3-
sync::SpinLock,
3+
sync::{OnceLock, SpinLock},
44
};
5+
use core::future::poll_fn;
6+
use core::task::Poll;
57
use core::time::Duration;
8+
use libkernel::sync::waker_set::WakerSet;
69

710
// Return a duration from the epoch.
811
pub fn date() -> Duration {
@@ -23,11 +26,79 @@ pub fn set_date(duration: Duration) {
2326
let mut epoch_info = EPOCH_DURATION.lock_save_irq();
2427
*epoch_info = Some((duration, now));
2528
}
29+
30+
// The realtime clock was stepped; wake anyone sleeping against an absolute
31+
// realtime deadline so they can re-evaluate (and re-arm) against the new
32+
// wall time.
33+
let mut waiters = clock_set_waiters().lock_save_irq();
34+
*CLOCK_SET_GEN.lock_save_irq() += 1;
35+
waiters.wake_all();
2636
}
2737

2838
// Represents a known duration since the epoch at the associated instant.
2939
static EPOCH_DURATION: SpinLock<Option<(Duration, Instant)>> = SpinLock::new(None);
3040

41+
/// Tasks waiting to be notified when the realtime clock is stepped.
42+
static CLOCK_SET_WAITERS: OnceLock<SpinLock<WakerSet>> = OnceLock::new();
43+
44+
fn clock_set_waiters() -> &'static SpinLock<WakerSet> {
45+
CLOCK_SET_WAITERS.get_or_init(|| SpinLock::new(WakerSet::new()))
46+
}
47+
48+
/// Bumped on every realtime clock step, so a waiter can detect a step that
49+
/// happened between checking the clock and parking (closing the lost-wakeup
50+
/// race).
51+
static CLOCK_SET_GEN: SpinLock<u64> = SpinLock::new(0);
52+
53+
/// The current clock-set generation. Sample this before reading the clock, and
54+
/// pass it to [`clock_was_set_since`] to wait for the next step.
55+
pub fn clock_set_generation() -> u64 {
56+
*CLOCK_SET_GEN.lock_save_irq()
57+
}
58+
59+
/// Removes its waker from [`clock_set_waiters`] when dropped, so a caller that
60+
/// abandons the wait (e.g. a wrapping `select!` that exits via a timer) does
61+
/// not leave a stale registration lingering until the next clock step.
62+
struct ClockSetRegistration {
63+
token: Option<u64>,
64+
}
65+
66+
impl Drop for ClockSetRegistration {
67+
fn drop(&mut self) {
68+
if let Some(token) = self.token {
69+
clock_set_waiters().lock_save_irq().remove(token);
70+
}
71+
}
72+
}
73+
74+
/// Resolves once the realtime clock is stepped after `generation` was sampled.
75+
/// If a step already happened since `generation`, returns immediately.
76+
pub async fn clock_was_set_since(generation: u64) {
77+
let mut registration = ClockSetRegistration { token: None };
78+
79+
poll_fn(|cx| {
80+
// Register before re-checking the generation so a step that races our
81+
// poll cannot be missed.
82+
let mut waiters = clock_set_waiters().lock_save_irq();
83+
84+
if *CLOCK_SET_GEN.lock_save_irq() != generation {
85+
return Poll::Ready(());
86+
}
87+
88+
if registration.token.is_none() {
89+
registration.token = Some(waiters.register(cx.waker()));
90+
}
91+
92+
Poll::Pending
93+
})
94+
.await;
95+
96+
// Reaching here means the generation changed and our waker was already
97+
// consumed by `wake_all`; forget the token so drop does not try to remove
98+
// an id that may have been reused.
99+
registration.token = None;
100+
}
101+
31102
#[cfg(test)]
32103
mod tests {
33104
use super::*;

0 commit comments

Comments
 (0)