keos_project4/round_robin.rs
1//! # Multicore Round-Robin Scheduling.
2//!
3//! The scheduler is an essential component of process management in any
4//! operating system. It ensures that multiple threads share CPU time in a fair
5//! and orderly manner.
6//!
7//! In an operating system, a **thread** is an abstraction of a CPU core. The
8//! thread abstraction enables the operating system to run multiple tasks
9//! concurrently, even on a single CPU core. At any given time, **exactly one
10//! thread runs** on the CPU, while other threads that are not active remain in
11//! an inactive state.If there are no threads ready to run, a special **idle
12//! thread** is executed to prevent the CPU from being idle.
13//!
14//! Round-Robin scheduling is a preemptive scheduling algorithm that assigns
15//! each thread a fixed time slice (or quantum) in a circular order. Once a
16//! thread’s time slice expires, it is preempted and pushed back to the end of
17//! the ready queue, while the next thread in the queue gets to run. This
18//! guarantees that all threads receive a fair share of CPU time.
19//!
20//! ## Scheduling in KeOS
21//!
22//! KeOS already provides basic thread
23//! functionalities such as thread creation and thread switching. You can create
24//! a new thread using the [`ThreadBuilder`]. By calling
25//! [`ThreadBuilder::spawn`], you pass a function that will be executed when the
26//! thread is first run. The thread will terminate once the function completes
27//! its execution. Each thread effectively acts as a mini-program running within
28//! the kernel, isolated from the others.
29//!
30//! The **scheduler** manages the CPU resources between the threads, by
31//! determines which thread runs next or whether yielding the running thread.
32//! After determining the next thread, the kernel conducts a **context switch**
33//! to run the thread. The magic of a **context switch** happens through the
34//! [`Thread::run`] function, which saves the state of the currently running
35//! thread and restores the state of the next thread to be executed.
36//!
37//! The core of schedulers in KeOS lies in [`Scheduler`] traits. This trait
38//! defines the scheduling policy. The kernel consults with the scheduler
39//! implementation to determine next thread with [`Scheduler::next_to_run`], and
40//! determine to yield the current thread within [`Scheduler::timer_tick`].
41//!
42//! #### Per-Core Scheduler State
43//!
44//! In a multi-core system, each CPU core must manage its own scheduling queue
45//! and state independently. This is crucial for implementing an efficient
46//! multi-core scheduler, which is why the [`PerCore`] structure is used.
47//!
48//! The [`PerCore`] struct represents the per-core scheduling state. Each core
49//! has its own scheduling queue (`run_queue`) and time slice (`remain`). These
50//! allow each CPU core to manage threads independently, ensuring
51//! no thread monopolizes CPU time on any core.
52//!
53//! ```rust
54//! struct PerCore {
55//! /// Queue of threads ready to run on this CPU core.
56//! run_queue: SpinLock<VecDeque<Box<Thread>>>,
57//!
58//! /// Remaining time slice for the currently running thread.
59//! remain: AtomicIsize,
60//! }
61//! ```
62//!
63//! The [`PerCore`] struct is essential in ensuring each CPU core can
64//! independently manage its threads. Key components include:
65//!
66//! - **run_queue**: This is a queue of threads ready to run on the respective
67//! CPU core. It is protected by a [`SpinLock`] to ensure that only one core
68//! can modify the queue at a time, preventing race conditions. The
69//! [`VecDeque`] allows for efficient push and pop operations, making it an
70//! ideal choice for managing threads in the ready state.
71//!
72//! - **remain**: This field holds the remaining time slice for the currently
73//! running thread on the CPU core. The remaining time slice is typically
74//! decremented on each timer interrupt, and when it reaches zero, a context
75//! switch occurs, and the current thread is preempted to allow the next
76//! thread to execute.
77//!
78//! #### Round-Robin Scheduler
79//!
80//! The [`RoundRobin`] scheduler in KeOS implements a time-sharing policy
81//! across multiple CPU cores using a round-robin approach. Each CPU core is
82//! associated with a dedicated [`PerCore`] structure, which maintains its own
83//! ready queue and scheduling state. This per-core design enables efficient
84//! parallel scheduling, minimizes contention, and ensures that all cores
85//! participate in thread execution without interference.
86//!
87//! In a round-robin policy, each runnable thread is assigned a fixed time slice
88//! (quantum) during which it can execute before being preempted. You will use
89//! the default quantum as 5 milliseconds. When a thread exhausts its time
90//! slice, it is reschdules with [`Scheduler::reschedule`]. This ensures fair
91//! CPU allocation among all threads and prevents starvation.
92//!
93//! KeOS employs a periodic timer interrupt that fires every 1 millisecond on
94//! each core. These timer interrupts invoke [`Scheduler::timer_tick`], which
95//! updates the scheduling state, decrements the current thread's time slice,
96//! and triggers a context switch if the quantum has expired.
97//!
98//! A challenge in per-core scheduling arises when a core's local run queue is
99//! empty: the CPU becomes idle, even though other cores may have work queued.
100//! To address this, the scheduler implements **work stealing**, a mechanism
101//! that allows idle cores to "steal" runnable threads from the queues of other
102//! cores. This dynamic load-balancing strategy ensures that CPU resources are
103//! used efficiently and that no core remains idle while runnable threads exist
104//! elsewhere in the system.
105//!
106//! Overall, the round-robin scheduler in KeOS offers a simple yet effective
107//! baseline for multicore scheduling, balancing responsiveness, fairness, and
108//! throughput across all available cores.
109//!
110//! ## Implementation Requirements
111//! You need to implement the followings:
112//! - [`RoundRobin::next_to_run`]
113//! - [`RoundRobin::push_to_queue`]
114//! - [`RoundRobin::timer_tick`]
115//!
116//! This ends project 4.
117//!
118//! [`Scheduler::reschedule`]:../../keos/thread/scheduler/trait.Scheduler.html#method.reschedule
119//! [`RoundRobin`]: RoundRobin
120//! [`Thread::run`]: keos::thread::Thread::run
121//! [`Box`]: https://doc.rust-lang.org/alloc/boxed/struct.Box.html
122//! [`VecDeque`]: https://doc.rust-lang.org/alloc/collections/vec_deque/struct.VecDeque.html
123//! [`STACK_SIZE`]: keos::thread::STACK_SIZE
124//! [`ThreadBuilder`]: keos::thread::ThreadBuilder
125//! [`ThreadBuilder::spawn`]: keos::thread::ThreadBuilder::spawn
126//! [`Scheduler`]: keos::thread::scheduler::Scheduler
127//! [`Scheduler::next_to_run`]: keos::thread::scheduler::Scheduler::next_to_run
128
129use alloc::{boxed::Box, collections::VecDeque};
130use keos::{
131 MAX_CPU,
132 intrinsics::cpuid,
133 sync::SpinLock,
134 sync::atomic::AtomicIsize,
135 thread::{Thread, scheduler::Scheduler},
136};
137
138/// Per-core scheduler state.
139///
140/// The [`PerCore`] struct represents the per-core scheduling state in a
141/// multi-core system. Each CPU core maintains its own scheduling queue to
142/// manage runnable threads independently. This structure is used within the
143/// [`RoundRobin`] scheduler to ensure that each core schedules and executes
144/// threads in a fair and efficient manner.
145pub struct PerCore {
146 /// Queue of threads ready to run on this CPU core.
147 /// - Protected by a [`SpinLock`] to prevent concurrent access issues.
148 /// - Threads are stored in a [`VecDeque`] to allow efficient push/pop
149 /// operations.
150 pub run_queue: SpinLock<VecDeque<Box<Thread>>>,
151
152 /// Remaining time slice for the currently running thread.
153 /// - Uses [`AtomicIsize`] for safe atomic updates across multiple CPU
154 /// cores.
155 /// - Typically decremented on each timer interrupt, triggering a context
156 /// switch when it reaches zero.
157 /// - You can use [``] for load, store to this variable.
158 pub remain: AtomicIsize,
159}
160
161/// A round robin scheduler.
162///
163/// The [RoundRobin] struct represents a round-robin scheduler, which is a type
164/// of preemptive scheduling used in operating systems to manage the execution
165/// of processes. In a round-robin scheduler, each process is assigned a fixed
166/// time slice (quantum) in which it can run. Once the time slice expires,
167/// the process is suspended, and the next process in the queue is scheduled to
168/// run. This continues in a circular manner, ensuring fair distribution of CPU
169/// time among processes.
170pub struct RoundRobin {
171 percores: [PerCore; MAX_CPU],
172}
173unsafe impl Send for RoundRobin {}
174unsafe impl Sync for RoundRobin {}
175
176impl Default for RoundRobin {
177 fn default() -> Self {
178 Self::new()
179 }
180}
181
182impl RoundRobin {
183 /// Create a new [`RoundRobin`] scheduler.
184 pub fn new() -> Self {
185 Self {
186 percores: [0; MAX_CPU].map(|_| PerCore {
187 run_queue: SpinLock::new(VecDeque::new()),
188 remain: AtomicIsize::new(0),
189 }),
190 }
191 }
192}
193
194impl Scheduler for RoundRobin {
195 fn next_to_run(&self) -> Option<Box<Thread>> {
196 todo!()
197 }
198 fn push_to_queue(&self, thread: Box<Thread>) {
199 let coreid = cpuid();
200 todo!()
201 }
202 fn timer_tick(&self) {
203 // Hint: you can yield the current thread by calling
204 // [`keos::thread::scheduler::Scheduler::reschedule`]
205 let coreid = cpuid();
206 todo!()
207 }
208}