Skip to content

Commit 0d1a512

Browse files
committed
Implement an actual scheduler.
As disk operations are still blocking, it doesn't quite work as well as one would want, but it's a start.
1 parent bf05565 commit 0d1a512

6 files changed

Lines changed: 161 additions & 62 deletions

File tree

pixie-uefi/src/main.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -132,11 +132,7 @@ async fn run() -> Result<()> {
132132
log::warn!("Started waiting for another command...");
133133
}
134134
last_was_wait = true;
135-
const WAIT_10MSECS: u64 = 50;
136-
for _ in 0..WAIT_10MSECS {
137-
Executor::deep_sleep_us(10_000);
138-
Executor::sched_yield().await;
139-
}
135+
Executor::sleep_us(100_000).await;
140136
} else {
141137
last_was_wait = false;
142138
log::info!("Command: {command:?}");

pixie-uefi/src/os/executor.rs

Lines changed: 114 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use alloc::boxed::Box;
2+
use alloc::collections::binary_heap::BinaryHeap;
23
use alloc::collections::VecDeque;
34
use alloc::sync::Arc;
45
use alloc::task::Wake;
@@ -8,10 +9,9 @@ use core::future::{poll_fn, Future};
89
use core::pin::Pin;
910
use core::sync::atomic::{AtomicBool, AtomicU64, Ordering};
1011
use core::task::{Context, Poll, Waker};
11-
use futures::channel::oneshot;
1212

13+
use futures::channel::oneshot;
1314
use spin::Mutex;
14-
use uefi::boot::{EventType, TimerTrigger, Tpl};
1515
use uefi::proto::console::text::Color;
1616

1717
use crate::os::send_wrapper::SendWrapper;
@@ -53,10 +53,31 @@ impl Wake for Task {
5353
}
5454
}
5555

56-
static EXECUTOR: Mutex<Executor> = Mutex::new(Executor {
57-
ready_tasks: VecDeque::new(),
58-
tasks: vec![],
59-
});
56+
struct TimedWait {
57+
wake_at: i64,
58+
waker: Waker,
59+
}
60+
61+
impl PartialEq for TimedWait {
62+
fn eq(&self, other: &Self) -> bool {
63+
self.wake_at == other.wake_at
64+
}
65+
}
66+
67+
impl Eq for TimedWait {}
68+
69+
impl PartialOrd for TimedWait {
70+
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
71+
Some(self.cmp(other))
72+
}
73+
}
74+
75+
impl Ord for TimedWait {
76+
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
77+
// Reversed order: min-heap.
78+
other.wake_at.cmp(&self.wake_at)
79+
}
80+
}
6081

6182
pub struct JoinHandle<T>(oneshot::Receiver<T>);
6283

@@ -66,8 +87,16 @@ impl<T> JoinHandle<T> {
6687
}
6788
}
6889

90+
static EXECUTOR: Mutex<Executor> = Mutex::new(Executor {
91+
wake_on_interrupt: vec![],
92+
timed_wait: BinaryHeap::new(),
93+
ready_tasks: VecDeque::new(),
94+
tasks: vec![],
95+
});
96+
6997
pub struct Executor {
70-
// TODO(veluca): scheduling.
98+
wake_on_interrupt: Vec<Waker>,
99+
timed_wait: BinaryHeap<TimedWait>,
71100
ready_tasks: VecDeque<Arc<Task>>,
72101
tasks: Vec<Arc<Task>>,
73102
}
@@ -167,24 +196,70 @@ impl Executor {
167196

168197
pub fn run() -> ! {
169198
Self::spawn("[show_tasks]", Self::draw_tasks());
199+
200+
// Maximum amount of microseconds between wakeups of interrupt-based wakers.
201+
const INTERRUPT_MICROS: i64 = 500;
202+
203+
let mut last_interrupt_wakeup = Timer::micros();
204+
205+
let mut do_wake = |force_interrupt_wake| {
206+
// Wake timed-waiting tasks.
207+
loop {
208+
let waker = {
209+
let mut ex = EXECUTOR.lock();
210+
let Some(w) = ex.timed_wait.peek() else {
211+
break;
212+
};
213+
if w.wake_at > Timer::micros() {
214+
break;
215+
}
216+
let w = ex.timed_wait.pop().unwrap();
217+
w.waker
218+
};
219+
waker.wake();
220+
}
221+
// Since we don't notice interrupts that happened while we are not hlt-ing,
222+
// make sure that we wake up all the interrupt-based waiting tasks every at
223+
// most INTERRUPT_MICROS micros to make it unlikely to miss interrupts.
224+
if last_interrupt_wakeup + INTERRUPT_MICROS <= Timer::micros() || force_interrupt_wake {
225+
last_interrupt_wakeup = Timer::micros();
226+
let to_wake = core::mem::take(&mut EXECUTOR.lock().wake_on_interrupt);
227+
for w in to_wake {
228+
w.wake();
229+
}
230+
}
231+
};
232+
170233
loop {
171-
let task = EXECUTOR
172-
.lock()
173-
.ready_tasks
174-
.pop_front()
175-
.expect("Executor should never run out of ready tasks");
176-
177-
task.in_queue.store(false, Ordering::Relaxed);
178-
let waker = Waker::from(task.clone());
179-
let mut context = Context::from_waker(&waker);
180-
let mut fut = task.future.try_lock().unwrap();
181-
let begin = Timer::micros();
182-
let done = fut.0.as_mut().poll(&mut context);
183-
let end = Timer::micros();
184-
task.micros
185-
.fetch_add((end - begin) as u64, Ordering::Relaxed);
186-
if done.is_ready() {
187-
task.done.swap(true, Ordering::Relaxed);
234+
let task = EXECUTOR.lock().ready_tasks.pop_front();
235+
if let Some(task) = task {
236+
{
237+
task.in_queue.store(false, Ordering::Relaxed);
238+
let waker = Waker::from(task.clone());
239+
let mut context = Context::from_waker(&waker);
240+
let mut fut = task.future.try_lock().unwrap();
241+
let begin = Timer::micros();
242+
let done = fut.0.as_mut().poll(&mut context);
243+
let end = Timer::micros();
244+
task.micros
245+
.fetch_add((end - begin) as u64, Ordering::Relaxed);
246+
if done.is_ready() {
247+
task.done.swap(true, Ordering::Relaxed);
248+
}
249+
}
250+
do_wake(false);
251+
} else {
252+
do_wake(false);
253+
if !EXECUTOR.lock().ready_tasks.is_empty() {
254+
continue;
255+
}
256+
// If we still don't have anything ready, sleep until the next interrupt.
257+
// SAFETY: hlt is available on all reasonable x86 processors and has no safety
258+
// requirements.
259+
unsafe {
260+
core::arch::asm!("hlt");
261+
}
262+
do_wake(true);
188263
}
189264
}
190265
}
@@ -204,27 +279,31 @@ impl Executor {
204279
})
205280
}
206281

282+
// Note: there are no guarantees on whether the amount of time we will sleep for
283+
// will be exceeded.
207284
pub fn sleep_us(us: u64) -> impl Future<Output = ()> {
208-
let tgt = Timer::micros() as u64 + us;
285+
let tgt = Timer::micros() + us as i64;
209286
poll_fn(move |cx| {
210-
let now = Timer::micros() as u64;
287+
let now = Timer::micros();
211288
if now >= tgt {
212289
Poll::Ready(())
213290
} else {
214-
// TODO(veluca): actually suspend the task.
215-
cx.waker().wake_by_ref();
291+
Self::wake_at_micros(tgt, cx.waker().clone());
216292
Poll::Pending
217293
}
218294
})
219295
}
220296

221-
/// **WARNING**: this function halts all tasks
222-
pub fn deep_sleep_us(us: u64) {
223-
// SAFETY: we are not using a callback
224-
let e =
225-
unsafe { uefi::boot::create_event(EventType::TIMER, Tpl::NOTIFY, None, None).unwrap() };
226-
uefi::boot::set_timer(&e, TimerTrigger::Relative(10 * us)).unwrap();
227-
uefi::boot::wait_for_event(&mut [e]).unwrap();
297+
// Wakes a task as soon as *any* interrupt is received.
298+
pub(super) fn wake_on_interrupt(waker: Waker) {
299+
EXECUTOR.lock().wake_on_interrupt.push(waker);
300+
}
301+
302+
pub(super) fn wake_at_micros(micros: i64, waker: Waker) {
303+
EXECUTOR.lock().timed_wait.push(TimedWait {
304+
wake_at: micros,
305+
waker,
306+
});
228307
}
229308

230309
/// Spawn a new task.

pixie-uefi/src/os/input.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use uefi::boot::ScopedProtocol;
77
use uefi::proto::console::text::{Input, Key};
88

99
use crate::os::error::Result;
10+
use crate::os::executor::Executor;
1011
use crate::os::send_wrapper::SendWrapper;
1112

1213
static INPUT: Lazy<Mutex<SendWrapper<ScopedProtocol<Input>>>> = Lazy::new(|| {
@@ -22,7 +23,7 @@ pub fn read_key() -> impl Future<Output = Result<Key>> {
2223
Err(e) => Poll::Ready(Err(e.into())),
2324
Ok(Some(key)) => Poll::Ready(Ok(key)),
2425
Ok(None) => {
25-
cx.waker().wake_by_ref();
26+
Executor::wake_on_interrupt(cx.waker().clone());
2627
Poll::Pending
2728
}
2829
}

pixie-uefi/src/os/net/interface.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ use uefi::proto::network::snp::{ReceiveFlags, SimpleNetwork};
55
use uefi::Status;
66

77
use super::ETH_PACKET_SIZE;
8+
use crate::os::send_wrapper::SendWrapper;
89

9-
type Snp = ScopedProtocol<SimpleNetwork>;
10+
type Snp = SendWrapper<ScopedProtocol<SimpleNetwork>>;
1011

1112
pub struct SnpDevice {
1213
snp: Snp,
@@ -15,9 +16,6 @@ pub struct SnpDevice {
1516
rx_buf: [u8; ETH_PACKET_SIZE + 4],
1617
}
1718

18-
// SAFETY: we never create threads anyway.
19-
unsafe impl Send for SnpDevice {}
20-
2119
impl SnpDevice {
2220
pub fn new(snp: Snp) -> SnpDevice {
2321
// Shut down the SNP protocol if needed.

pixie-uefi/src/os/net/mod.rs

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
use alloc::string::{String, ToString};
2+
use alloc::vec::Vec;
23
use core::fmt::Write;
34
use core::future::{poll_fn, Future};
45
use core::net::Ipv4Addr;
56
use core::sync::atomic::{AtomicU64, Ordering};
6-
use core::task::Poll;
7+
use core::task::{Poll, Waker};
78

8-
use smoltcp::iface::{Config, Interface, PollResult, SocketHandle, SocketSet};
9+
use smoltcp::iface::{
10+
Config, Interface, PollIngressSingleResult, PollResult, SocketHandle, SocketSet,
11+
};
912
use smoltcp::socket::dhcpv4::{Event, Socket as Dhcpv4Socket};
1013
use smoltcp::wire::{DhcpOption, HardwareAddress, IpCidr};
1114
use spin::Mutex;
@@ -23,6 +26,7 @@ use crate::os::executor::Executor;
2326
use crate::os::net::interface::SnpDevice;
2427
pub use crate::os::net::tcp::TcpStream;
2528
pub use crate::os::net::udp::UdpSocket;
29+
use crate::os::send_wrapper::SendWrapper;
2630
use crate::os::timer::rdtsc;
2731
use crate::os::ui;
2832

@@ -44,6 +48,8 @@ struct NetworkData {
4448

4549
static NETWORK_DATA: Mutex<Option<NetworkData>> = Mutex::new(None);
4650

51+
static WAITING_FOR_IP: Mutex<Vec<Waker>> = Mutex::new(vec![]);
52+
4753
fn with_net<T, F: FnOnce(&mut NetworkData) -> T>(f: F) -> T {
4854
let mut mg = NETWORK_DATA.try_lock().expect("Network is locked");
4955
f(mg.as_mut().expect("Network is not initialized"))
@@ -97,7 +103,7 @@ pub(super) fn init() {
97103
&snp.mode().current_address.0[..6],
98104
));
99105

100-
let mut device = SnpDevice::new(snp);
106+
let mut device = SnpDevice::new(SendWrapper(snp));
101107

102108
let mut interface_config = Config::new(hw_addr);
103109
interface_config.random_seed = rdtsc() as u64;
@@ -121,9 +127,16 @@ pub(super) fn init() {
121127
Executor::spawn(
122128
"[net_poll]",
123129
poll_fn(move |cx| {
124-
poll();
125-
// TODO(veluca): figure out whether we can suspend the task.
126-
cx.waker().wake_by_ref();
130+
let wait = poll();
131+
if wait == 0 {
132+
cx.waker().wake_by_ref();
133+
} else {
134+
// Halve the waiting time, to try to ensure that we don't exceed the suggested
135+
// waiting time.
136+
let deadline = Timer::micros() + wait as i64 / 2;
137+
Executor::wake_on_interrupt(cx.waker().clone());
138+
Executor::wake_at_micros(deadline, cx.waker().clone());
139+
}
127140
Poll::<()>::Pending
128141
}),
129142
);
@@ -152,7 +165,7 @@ pub fn wait_for_ip() -> impl Future<Output = ()> {
152165
if ip().is_some() {
153166
Poll::Ready(())
154167
} else {
155-
cx.waker().wake_by_ref();
168+
WAITING_FOR_IP.lock().push(cx.waker().clone());
156169
Poll::Pending
157170
}
158171
})
@@ -167,25 +180,27 @@ fn get_ephemeral_port() -> u16 {
167180
((ans % (60999 - 49152)) + 49152) as u16
168181
}
169182

170-
fn poll() {
183+
// Returns # of microseconds to wait until we should call poll() again (possibly 0).
184+
fn poll() -> u64 {
171185
let now = Timer::instant();
172186

173187
let mut data = NETWORK_DATA.lock();
174188

175-
let Some(NetworkData {
189+
let NetworkData {
176190
interface,
177191
device,
178192
socket_set,
179193
dhcp_socket_handle,
180-
}) = data.as_mut()
181-
else {
182-
return;
183-
};
194+
} = data.as_mut().unwrap();
184195

185-
let status = interface.poll(now, device, socket_set);
196+
let status_out = interface.poll_egress(now, device, socket_set);
197+
let status_in = interface.poll_ingress_single(now, device, socket_set);
186198

187-
if status == PollResult::None {
188-
return;
199+
if status_in == PollIngressSingleResult::None && status_out == PollResult::None {
200+
return interface
201+
.poll_delay(now, socket_set)
202+
.map(|x| x.micros())
203+
.unwrap_or(0);
189204
}
190205

191206
let dhcp_status = socket_set
@@ -203,11 +218,16 @@ fn poll() {
203218
.add_default_ipv4_route(router)
204219
.unwrap();
205220
}
221+
let to_wake = core::mem::take(&mut *WAITING_FOR_IP.lock());
222+
for w in to_wake {
223+
w.wake();
224+
}
206225
} else {
207226
interface.update_ip_addrs(|a| {
208227
a.clear();
209228
});
210229
interface.routes_mut().remove_default_ipv4_route();
211230
}
212231
}
232+
0
213233
}

pixie-uefi/src/os/net/tcp.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,12 @@ impl TcpStream {
6565
let state = with_net(|n| n.socket_set.get_mut::<TcpSocket>(self.handle).state());
6666
let res = f(state);
6767
if matches!(res, Poll::Pending) {
68-
cx.waker().wake_by_ref();
68+
with_net(|n| {
69+
let socket = n.socket_set.get_mut::<TcpSocket>(self.handle);
70+
// Every meaningful state change causes either send or recv to become possible (possibly erroring).
71+
socket.register_send_waker(cx.waker());
72+
socket.register_recv_waker(cx.waker());
73+
});
6974
}
7075
res
7176
})

0 commit comments

Comments
 (0)