Skip to content

Commit 9166b00

Browse files
committed
task: support multiple waiters in WaitQueue
WaitQueue previously supported only a single waiter; a second waiter would overwrite the first and strand it forever. Store waiters in a queue so every waiter is woken. Fixes #1104 Signed-off-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
1 parent cd7ea68 commit 9166b00

3 files changed

Lines changed: 131 additions & 19 deletions

File tree

kernel/src/task/schedule.rs

Lines changed: 96 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ extern crate alloc;
3737
use super::tasks::TASK_ACTIVE_OFFSET;
3838
use super::tasks::TASK_CUR_CPU_OFFSET;
3939
use super::tasks::TaskExitStatus;
40+
use super::tasks::TaskWaitListAdapter;
4041
use super::{
4142
INITIAL_TASK_ID, KernelThreadStartInfo, Task, TaskListAdapter, TaskPointer, TaskRunListAdapter,
4243
};
@@ -281,7 +282,7 @@ impl TaskList {
281282
/// # Safety
282283
/// The caller must ensure that `task` is already a member of this task
283284
/// list.
284-
unsafe fn terminate(&mut self, task: TaskPointer) -> Option<TaskPointer> {
285+
unsafe fn terminate(&mut self, task: TaskPointer) -> LinkedList<TaskWaitListAdapter> {
285286
// Set the task state as terminated. If the task being terminated is the
286287
// current task then the task context will still need to be in scope until
287288
// the next schedule() has completed. Schedule will keep a reference to this
@@ -291,7 +292,7 @@ impl TaskList {
291292
let mut cursor = unsafe { self.list().cursor_mut_from_ptr(task.as_ref()) };
292293
cursor.remove();
293294

294-
// Inform the caller about any task that may need to be woken.
295+
// Inform the caller about any tasks that may need to be woken.
295296
wakeup
296297
}
297298
}
@@ -432,11 +433,11 @@ fn current_task_terminated() {
432433
// SAFETY: the scheduler guarantees that `current_task` always points to a
433434
// valid task, and every task has its pointer pushed into the global task
434435
// list during its creation.
435-
let wakeup = unsafe { TASKLIST.lock().terminate(task_node.clone()) };
436+
let mut wakeup = unsafe { TASKLIST.lock().terminate(task_node.clone()) };
436437

437-
// If another thread must be woken as a result of the termination, then
438-
// schedule it now.
439-
if let Some(wake_task) = wakeup {
438+
// Wake all tasks that were waiting for this task to terminate, scheduling
439+
// each on the current runqueue.
440+
while let Some(wake_task) = wakeup.pop_front() {
440441
rq.prepare_run_task(wake_task);
441442
}
442443
}
@@ -902,11 +903,13 @@ const CONTEXT_SWITCH_RESTORE_TOKEN: VirtAddr = SVSM_CONTEXT_SWITCH_SHADOW_STACK.
902903
mod test {
903904
extern crate alloc;
904905
use super::KernelThreadStartInfo;
906+
use super::schedule;
905907
use super::set_affinity;
906908
use super::start_kernel_task;
907909
use super::wait_for_termination;
908910
use crate::cpu::percpu::{PERCPU_AREAS, this_cpu};
909911
use alloc::string::String;
912+
use core::sync::atomic::AtomicBool;
910913
use core::sync::atomic::AtomicU32;
911914
use core::sync::atomic::Ordering;
912915

@@ -960,6 +963,93 @@ mod test {
960963
wait_for_termination(task);
961964
}
962965

966+
static MULTI_WAITER_COUNTER: AtomicU32 = AtomicU32::new(0);
967+
static MULTI_WAITER_WAITING_COUNTER: AtomicU32 = AtomicU32::new(0);
968+
static MULTI_WAITER_TARGET_RELEASED: AtomicBool = AtomicBool::new(false);
969+
const MULTI_WAITER_TASK_INCREMENT: u32 = 10;
970+
971+
fn multi_waiter_target(_: usize) {
972+
let current_cpu = this_cpu().get_cpu_index();
973+
let target_cpu = PERCPU_AREAS.len() - 1;
974+
set_affinity(if current_cpu == target_cpu {
975+
0
976+
} else {
977+
target_cpu
978+
});
979+
980+
while !MULTI_WAITER_TARGET_RELEASED.load(Ordering::Acquire) {
981+
core::hint::spin_loop();
982+
}
983+
984+
MULTI_WAITER_COUNTER.fetch_add(1, Ordering::Relaxed);
985+
}
986+
987+
fn waiting_task(target_id: usize) {
988+
// Look up the target task by id and wait for it to terminate.
989+
let task = super::TASKLIST
990+
.lock()
991+
.get_task(target_id as u32)
992+
.expect("Failed to find multi-waiter target task");
993+
super::preemption_checks();
994+
let guard = task
995+
.wait_for_exit()
996+
.expect("Multi-waiter target terminated before waiter blocked");
997+
MULTI_WAITER_WAITING_COUNTER.fetch_add(1, Ordering::Release);
998+
super::select_new_task(false, Some(guard));
999+
MULTI_WAITER_COUNTER.fetch_add(MULTI_WAITER_TASK_INCREMENT, Ordering::Relaxed);
1000+
}
1001+
1002+
#[test]
1003+
#[cfg_attr(not(test_in_svsm), ignore = "Can only be run inside guest")]
1004+
fn test_wait_for_termination_multiple_waiters() {
1005+
MULTI_WAITER_COUNTER.store(0, Ordering::Relaxed);
1006+
MULTI_WAITER_WAITING_COUNTER.store(0, Ordering::Relaxed);
1007+
MULTI_WAITER_TARGET_RELEASED.store(false, Ordering::Relaxed);
1008+
1009+
if PERCPU_AREAS.len() < 2 {
1010+
return;
1011+
}
1012+
1013+
// Start the task that will be waited on.
1014+
let target = start_kernel_task(
1015+
KernelThreadStartInfo::new(multi_waiter_target, 0),
1016+
String::from("multi-waiter target"),
1017+
)
1018+
.expect("Failed to start target task");
1019+
let target_id = target.get_task_id() as usize;
1020+
1021+
// Start two waiter tasks that each wait on the same target task.
1022+
let waiter1 = start_kernel_task(
1023+
KernelThreadStartInfo::new(waiting_task, target_id),
1024+
String::from("waiter 1"),
1025+
)
1026+
.expect("Failed to start waiter 1");
1027+
1028+
let waiter2 = start_kernel_task(
1029+
KernelThreadStartInfo::new(waiting_task, target_id),
1030+
String::from("waiter 2"),
1031+
)
1032+
.expect("Failed to start waiter 2");
1033+
1034+
while MULTI_WAITER_WAITING_COUNTER.load(Ordering::Acquire) < 2 {
1035+
schedule();
1036+
}
1037+
1038+
// Wait for the target task from this task too, ensuring it has
1039+
// terminated before we check results.
1040+
MULTI_WAITER_TARGET_RELEASED.store(true, Ordering::Release);
1041+
wait_for_termination(target);
1042+
wait_for_termination(waiter1);
1043+
wait_for_termination(waiter2);
1044+
1045+
// Both waiter tasks must have run: multi_waiter_target adds 1,
1046+
// each waiting_task adds MULTI_WAITER_TASK_INCREMENT.
1047+
assert_eq!(
1048+
MULTI_WAITER_COUNTER.load(Ordering::Relaxed),
1049+
1 + (MULTI_WAITER_TASK_INCREMENT * 2)
1050+
);
1051+
}
1052+
9631053
#[test]
9641054
#[cfg_attr(not(test_in_svsm), ignore = "Can only be run inside guest")]
9651055
fn test_set_affinity() {

kernel/src/task/tasks.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ use crate::mm::{
4747
use crate::syscall::{Obj, ObjError, ObjHandle};
4848
use crate::types::{SVSM_USER_CS, SVSM_USER_DS};
4949
use crate::utils::{MemoryRegion, is_aligned};
50-
use intrusive_collections::{LinkedListAtomicLink, intrusive_adapter};
50+
use intrusive_collections::{LinkedList, LinkedListAtomicLink, intrusive_adapter};
5151

5252
use super::WaitQueue;
5353
use super::schedule::complete_task_switch;
@@ -349,6 +349,9 @@ pub struct Task {
349349
/// Link to scheduler run queue
350350
runlist_link: LinkedListAtomicLink,
351351

352+
/// Link to wait queue
353+
waitlist_link: LinkedListAtomicLink,
354+
352355
/// Objects shared among threads within the same process
353356
objs: Arc<RWLock<BTreeMap<ObjHandle, Arc<dyn Obj>>>>,
354357

@@ -378,6 +381,7 @@ pub type TaskPointer = Arc<Task>;
378381

379382
intrusive_adapter!(pub TaskRunListAdapter = TaskPointer: Task { runlist_link: LinkedListAtomicLink });
380383
intrusive_adapter!(pub TaskListAdapter = TaskPointer: Task { list_link: LinkedListAtomicLink });
384+
intrusive_adapter!(pub TaskWaitListAdapter = TaskPointer: Task { waitlist_link: LinkedListAtomicLink });
381385

382386
impl PartialEq for Task {
383387
fn eq(&self, other: &Self) -> bool {
@@ -529,6 +533,7 @@ impl Task {
529533
rootdir: args.rootdir,
530534
list_link: LinkedListAtomicLink::default(),
531535
runlist_link: LinkedListAtomicLink::default(),
536+
waitlist_link: LinkedListAtomicLink::default(),
532537
objs: objtree,
533538
wait_queue: SpinLockIrqSafe::new(WaitQueue::new()),
534539
exit_status: AtomicU32::new(TaskExitStatus::default().into()),
@@ -626,11 +631,11 @@ impl Task {
626631
self.sched_state.set_state(TaskState::RUNNING);
627632
}
628633

629-
pub fn set_task_terminated(&self) -> Option<TaskPointer> {
634+
pub fn set_task_terminated(&self) -> LinkedList<TaskWaitListAdapter> {
630635
self.sched_state
631636
.panic_on_idle("Trying to terminate idle task")
632637
.set_state(TaskState::TERMINATED);
633-
self.wait_queue.lock().wakeup()
638+
self.wait_queue.lock().wakeup(true)
634639
}
635640

636641
pub fn set_task_blocked(&self) {

kernel/src/task/waiting.rs

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,43 @@
44
//
55
// Author: Joerg Roedel <jroedel@suse.de>
66

7-
use super::tasks::TaskPointer;
7+
use intrusive_collections::LinkedList;
88

9-
#[derive(Debug, Default)]
9+
use super::tasks::{TaskPointer, TaskWaitListAdapter};
10+
11+
/// A queue of tasks waiting for a single event. Multiple tasks may wait
12+
/// simultaneously.
13+
#[derive(Debug)]
1014
pub struct WaitQueue {
11-
waiter: Option<TaskPointer>,
15+
waiters: LinkedList<TaskWaitListAdapter>,
1216
}
1317

1418
impl WaitQueue {
15-
pub const fn new() -> Self {
16-
Self { waiter: None }
19+
pub fn new() -> Self {
20+
Self {
21+
waiters: LinkedList::new(TaskWaitListAdapter::new()),
22+
}
1723
}
1824

25+
/// Register `current_task` as a waiter on this queue. The task is
26+
/// immediately marked as blocked. Multiple callers may wait concurrently.
1927
pub fn wait_for_event(&mut self, current_task: TaskPointer) {
20-
assert!(self.waiter.is_none());
21-
2228
current_task.set_task_blocked();
23-
self.waiter = Some(current_task);
29+
self.waiters.push_back(current_task);
2430
}
2531

26-
pub fn wakeup(&mut self) -> Option<TaskPointer> {
27-
self.waiter.take()
32+
/// Wake waiting tasks. If `wake_all` is false, only the head waiter is
33+
/// removed. Returns the removed waiters so the caller can schedule each
34+
/// one.
35+
pub fn wakeup(&mut self, wake_all: bool) -> LinkedList<TaskWaitListAdapter> {
36+
if wake_all {
37+
self.waiters.take()
38+
} else {
39+
let mut waiters = LinkedList::new(TaskWaitListAdapter::new());
40+
if let Some(task) = self.waiters.pop_front() {
41+
waiters.push_back(task);
42+
}
43+
waiters
44+
}
2845
}
2946
}

0 commit comments

Comments
 (0)