feat: SMP stage 1 (4 harts, per-hart guarded stacks, home-core affinity, IPIs, TLB shootdown, one temporary kernel lock), 4-core storm
This commit is contained in:
@@ -208,6 +208,20 @@ impl Kernel<MockPlatform> {
|
||||
if self.scheduler.is_queued(t) != matches!(state, ThreadState::Ready) {
|
||||
return Err(format!("{t:?} in {state:?} has wrong run-queue membership"));
|
||||
}
|
||||
// ---- cores: a thread runs and waits only on its home core ----
|
||||
let home = self.home(t);
|
||||
match state {
|
||||
ThreadState::Running(c) if c != home => {
|
||||
return Err(format!("{t:?} runs on {c:?}, its home is {home:?}"));
|
||||
}
|
||||
ThreadState::Running(c) if self.scheduler.current(c) != Some(t) => {
|
||||
return Err(format!("{t:?} Running on {c:?} but not its current thread"));
|
||||
}
|
||||
ThreadState::Ready if self.scheduler.queued_on(t) != Some(home) => {
|
||||
return Err(format!("{t:?} queued away from its home {home:?}"));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
if let Some(r) = ipc.reply_to
|
||||
&& self.threads.generation(r.tid) == Some(r.generation)
|
||||
&& self.threads.get(r.tid).ok().map(|x| *x.state())
|
||||
@@ -216,6 +230,22 @@ impl Kernel<MockPlatform> {
|
||||
return Err(format!("{t:?} holds a reply for a thread not waiting"));
|
||||
}
|
||||
}
|
||||
for c in 0..self.scheduler.num_cores() {
|
||||
let core = types::id::CoreId::from_raw(c);
|
||||
let current = self.scheduler.current(core);
|
||||
if let Some(t) = current
|
||||
&& self.threads.get(t).ok().map(|x| *x.state()) != Some(ThreadState::Running(core))
|
||||
{
|
||||
return Err(format!("{core:?}'s current {t:?} is not running there"));
|
||||
}
|
||||
// No lost wakeups: an idle core with work waiting has been told.
|
||||
if current.is_none()
|
||||
&& self.scheduler.highest_waiting(core).is_some()
|
||||
&& !self.platform.ipi_pending(core)
|
||||
{
|
||||
return Err(format!("{core:?} idles with ready threads and no IPI"));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -282,6 +312,10 @@ fn new_round(k: &mut Kernel<MockPlatform>, init: ProcessId, init_thread: ThreadI
|
||||
.create_pool_region(hub, types::bounded::PageCount::new(2).unwrap())
|
||||
.ok();
|
||||
let mut members = std::vec![init];
|
||||
// CreateProcess below runs as init, so on init's core.
|
||||
let init_core = k.home(init_thread);
|
||||
k.platform_mut().set_core(init_core);
|
||||
resume(k, init_core);
|
||||
for _ in 0..2 {
|
||||
k.switch_to_thread_for_test(init_thread);
|
||||
if let crate::SyscallOutcome::Complete(crate::SyscallResult::Slot(cap)) =
|
||||
@@ -292,7 +326,7 @@ fn new_round(k: &mut Kernel<MockPlatform>, init: ProcessId, init_thread: ThreadI
|
||||
{
|
||||
let pid = k.process_of_cap_for_test(cap);
|
||||
for _ in 0..2 {
|
||||
k.spawn_thread_for_test(pid, Priority::DEFAULT, TimeSlice::DEFAULT);
|
||||
k.spawn_thread_internal(pid);
|
||||
}
|
||||
members.push(pid);
|
||||
} else if std::env::var("STORM_DEBUG").is_ok() {
|
||||
@@ -326,11 +360,40 @@ fn region_slots(k: &Kernel<MockPlatform>, pid: Option<ProcessId>) -> std::vec::V
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// What the adapter does whenever a core is left without a thread: run the
|
||||
/// next one in its queue, if any.
|
||||
fn resume(k: &mut Kernel<MockPlatform>, core: types::id::CoreId) {
|
||||
if k.current_thread().is_none()
|
||||
&& let Some(next) = k.pick_next_thread()
|
||||
{
|
||||
k.set_thread_running(next, core).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_kernel_survives_a_syscall_storm() {
|
||||
storm(1);
|
||||
}
|
||||
|
||||
/// The same storm with threads homed on four cores. Each step runs on one
|
||||
/// core, as the thread that core is running; the kernel is still entered
|
||||
/// one call at a time (stage 1 takes a single lock), but threads on other
|
||||
/// cores are live meanwhile and IPIs arrive late.
|
||||
#[test]
|
||||
fn the_kernel_survives_a_syscall_storm_on_four_cores() {
|
||||
storm(4);
|
||||
}
|
||||
|
||||
fn storm(cores: u8) {
|
||||
use types::id::CoreId;
|
||||
let mut total = [0usize; 36 + 1];
|
||||
for seed in [17u64, 0xABCD_EF01, 4242, 0x1357_9BDF, 8] {
|
||||
let (mut k, init) = kernel_with_process();
|
||||
let mut k = Kernel::new(
|
||||
MockPlatform::new(cores),
|
||||
types::address::PhysicalAddress::from_raw(0x8400_0000),
|
||||
1024,
|
||||
);
|
||||
let (init, _) = k.boot(Priority::DEFAULT, TimeSlice::DEFAULT).unwrap();
|
||||
let init_thread = k.current_thread().unwrap();
|
||||
k.spawn_thread_for_test(init, Priority::DEFAULT, TimeSlice::DEFAULT);
|
||||
let mut rng = Rng(seed);
|
||||
@@ -360,13 +423,35 @@ fn the_kernel_survives_a_syscall_storm() {
|
||||
// init may be blocked on objects it made itself.
|
||||
k.destroy_endpoints_owned_by(init);
|
||||
k.destroy_notifications_owned_by(init);
|
||||
let here = k.platform_mut().core_id();
|
||||
resume(&mut k, here);
|
||||
k.check_invariants()
|
||||
.unwrap_or_else(|e| panic!("seed {seed:#x} step {step} after teardown: {e}"));
|
||||
new_round(&mut k, init, init_thread);
|
||||
rounds += 1;
|
||||
continue;
|
||||
}
|
||||
let tid = runnable[rng.below(runnable.len())];
|
||||
// Pick a core and act as the thread it runs, after it takes any
|
||||
// IPI that has reached it. Now and then switch it to another
|
||||
// ready thread of its own, as a tick or a yield would.
|
||||
let core = CoreId::from_raw(rng.below(cores as usize) as u8);
|
||||
k.platform_mut().set_core(core);
|
||||
if k.platform_mut().take_ipi(core) {
|
||||
let _ = k.reschedule(core);
|
||||
}
|
||||
resume(&mut k, core);
|
||||
let homed: std::vec::Vec<ThreadId> = runnable
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|t| k.home(*t) == core)
|
||||
.collect();
|
||||
if homed.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let tid = match k.current_thread() {
|
||||
Some(t) if rng.below(3) != 0 => t,
|
||||
_ => homed[rng.below(homed.len())],
|
||||
};
|
||||
let is_init = k.thread_process(tid) == Some(init);
|
||||
let nr = rng.below(36 + 1);
|
||||
// init exiting ends the test. Halt stays in: nobody here holds
|
||||
@@ -399,6 +484,7 @@ fn the_kernel_survives_a_syscall_storm() {
|
||||
k.switch_to_thread_for_test(tid);
|
||||
k.take_pending_result(tid);
|
||||
let out = k.handle_syscall(syscall);
|
||||
resume(&mut k, core);
|
||||
executed += 1;
|
||||
if !matches!(
|
||||
out,
|
||||
|
||||
Reference in New Issue
Block a user