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}