kernel/scheduler.rs
1// Licensed under the Apache License, Version 2.0 or the MIT License.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// Copyright Tock Contributors 2022.
4
5//! Interface for Tock kernel schedulers.
6
7pub mod cooperative;
8pub mod mlfq;
9pub mod priority;
10pub mod round_robin;
11
12use crate::deferred_call::DeferredCall;
13use crate::platform::chip::Chip;
14use crate::process::ProcessId;
15use crate::process::StoppedExecutingReason;
16
17use core::num::NonZeroU32;
18
19/// Trait which any scheduler must implement.
20pub trait Scheduler<C: Chip> {
21 /// Decide which process to run next.
22 ///
23 /// The scheduler must decide whether to run a process, and if so, which
24 /// one. If the scheduler chooses not to run a process, it can request that
25 /// the chip enter sleep mode.
26 ///
27 /// If the scheduler selects a process to run it must provide its `ProcessId`
28 /// and an optional timeslice length in microseconds to provide to that
29 /// process. If the timeslice is `None`, the process will be run
30 /// cooperatively (i.e. without preemption). Otherwise the process will run
31 /// with a timeslice set to the specified length.
32 fn next(&self) -> SchedulingDecision;
33
34 /// Inform the scheduler of why the last process stopped executing, and how
35 /// long it executed for. Notably, `execution_time_us` will be `None`
36 /// if the the scheduler requested this process be run cooperatively.
37 fn result(&self, result: StoppedExecutingReason, execution_time_us: Option<u32>);
38
39 /// Tell the scheduler to execute kernel work such as interrupt bottom
40 /// halves and dynamic deferred calls. Most schedulers will use this default
41 /// implementation, but schedulers which at times wish to defer interrupt
42 /// handling will reimplement it.
43 ///
44 /// Providing this interface allows schedulers to fully manage how the main
45 /// kernel loop executes. For example, a more advanced scheduler that
46 /// attempts to help processes meet their deadlines may need to defer bottom
47 /// half interrupt handling or to selectively service certain interrupts.
48 /// Or, a power aware scheduler may want to selectively choose what work to
49 /// complete at any time to meet power requirements.
50 ///
51 /// Custom implementations of this function must be very careful, however,
52 /// as this function is called in the core kernel loop.
53 unsafe fn execute_kernel_work(&self, chip: &C) {
54 chip.service_pending_interrupts();
55 while DeferredCall::has_tasks() && !chip.has_pending_interrupts() {
56 DeferredCall::service_next_pending();
57 }
58 }
59
60 /// Ask the scheduler whether to take a break from executing userspace
61 /// processes to handle kernel tasks. Most schedulers will use this default
62 /// implementation, which always prioritizes kernel work, but schedulers
63 /// that wish to defer interrupt handling may reimplement it.
64 unsafe fn do_kernel_work_now(&self, chip: &C) -> bool {
65 chip.has_pending_interrupts() || DeferredCall::has_tasks()
66 }
67
68 /// Ask the scheduler whether to continue trying to execute a process.
69 ///
70 /// Once a process is scheduled the kernel will try to execute it until it
71 /// has no more work to do or exhausts its timeslice. The kernel will call
72 /// this function before every loop to check with the scheduler if it wants
73 /// to continue trying to execute this process.
74 ///
75 /// Most schedulers will use this default implementation, which causes the
76 /// `do_process()` loop to return if there are interrupts or deferred calls
77 /// that need to be serviced. However, schedulers which wish to defer
78 /// interrupt handling may change this, or priority schedulers which wish to
79 /// check if the execution of the current process has caused a higher
80 /// priority process to become ready (such as in the case of IPC). If this
81 /// returns `false`, then `do_process` will exit with a `KernelPreemption`.
82 ///
83 /// `id` is the identifier of the currently active process.
84 unsafe fn continue_process(&self, _id: ProcessId, chip: &C) -> bool {
85 !(chip.has_pending_interrupts() || DeferredCall::has_tasks())
86 }
87}
88
89/// Enum representing the actions the scheduler can request in each call to
90/// `scheduler.next()`.
91#[derive(Copy, Clone)]
92pub enum SchedulingDecision {
93 /// Tell the kernel to run the specified process with the passed timeslice.
94 /// If `None` is passed as a timeslice, the process will be run
95 /// cooperatively.
96 RunProcess((ProcessId, Option<NonZeroU32>)),
97
98 /// Tell the kernel to go to sleep. Notably, if the scheduler asks the
99 /// kernel to sleep when kernel tasks are ready, the kernel will not sleep,
100 /// and will instead restart the main loop and call `next()` again.
101 TrySleep,
102}