kernel/process/
scheduler.rs

1use core::{
2    cell::RefCell,
3    mem,
4    sync::atomic::{AtomicBool, Ordering},
5};
6
7use alloc::{
8    boxed::Box,
9    collections::{BTreeMap, BinaryHeap},
10    vec::Vec,
11};
12use tracing::{info, trace};
13
14use crate::{
15    cpu::{self, idt::InterruptAllSavedState, interrupts},
16    devices::clock::{self, ClockTime},
17    memory_management::virtual_memory_mapper,
18    process::{syscalls, FxSave},
19    sync::spin::mutex::Mutex,
20};
21
22use super::{Process, ProcessContext};
23
24static SCHEDULER: Mutex<Scheduler> = Mutex::new(Scheduler::new());
25static SHUTDOWN: AtomicBool = AtomicBool::new(false);
26
27// an arbitrary value to reset the priority counters
28// we don't want to get to 0, as it will result in underflow on subtract
29const MIN_PRIORITY_VALUE: u64 = 100;
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum ProcessState {
33    Running,
34    Scheduled,
35    WaitingForPid(u64),
36    WaitingForTime(ClockTime),
37}
38
39/// A wrapper around [`Process`] that has extra details the scheduler cares about
40struct SchedulerProcess {
41    // using box here so that moving this around won't be as expensive
42    process: RefCell<Box<Process>>,
43    state: ProcessState,
44    priority_counter: u64,
45}
46
47impl PartialEq for SchedulerProcess {
48    fn eq(&self, other: &Self) -> bool {
49        self.priority_counter == other.priority_counter
50    }
51}
52
53impl PartialOrd for SchedulerProcess {
54    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
55        Some(self.cmp(other))
56    }
57}
58
59impl Eq for SchedulerProcess {}
60impl Ord for SchedulerProcess {
61    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
62        self.priority_counter.cmp(&other.priority_counter)
63    }
64}
65
66struct Scheduler {
67    interrupt_initialized: bool,
68    scheduled_processes: BinaryHeap<SchedulerProcess>,
69    running_waiting_procs: BTreeMap<u64, SchedulerProcess>,
70    exited_processes: Vec<Process>,
71    max_priority: u64,
72}
73
74impl Scheduler {
75    const fn new() -> Self {
76        Self {
77            interrupt_initialized: false,
78            scheduled_processes: BinaryHeap::new(),
79            running_waiting_procs: BTreeMap::new(),
80            exited_processes: Vec::new(),
81            max_priority: u64::MAX,
82        }
83    }
84
85    pub fn push_process(&mut self, process: Process) {
86        // data will be rewritten
87        self.reschedule_process(SchedulerProcess {
88            process: RefCell::new(Box::new(process)),
89            state: ProcessState::Scheduled,
90            priority_counter: self.max_priority,
91        })
92    }
93
94    fn init_interrupt(&mut self) {
95        if self.interrupt_initialized {
96            return;
97        }
98        self.interrupt_initialized = true;
99
100        interrupts::create_scheduler_interrupt(scheduler_interrupt_handler);
101        interrupts::create_syscall_interrupt(syscall_interrupt_handler);
102    }
103
104    fn reschedule_process(&mut self, mut process: SchedulerProcess) {
105        if SHUTDOWN.load(Ordering::Acquire) {
106            let mut inner_proc = process.process.into_inner();
107            info!(
108                "Process {} is not rescheduled as the scheduler is shutting down",
109                inner_proc.id
110            );
111            inner_proc.exit(0xFF);
112            self.exited_processes.push(*inner_proc);
113            return;
114        }
115        process.priority_counter = self.max_priority;
116        process.state = ProcessState::Scheduled;
117        self.scheduled_processes.push(process);
118    }
119
120    fn reset_scheduled_processes_counters(&mut self) {
121        let max_priority = u64::MAX;
122        self.scheduled_processes = self
123            .scheduled_processes
124            .drain()
125            .map(|mut p| {
126                p.priority_counter = max_priority;
127                p
128            })
129            .collect::<BinaryHeap<_>>();
130    }
131
132    fn try_wake_waiting_processes(&mut self) {
133        let time_now = clock::clocks().time_since_startup();
134
135        // First, check waiting processes
136        let extracted = self
137            .running_waiting_procs
138            .extract_if(.., |_, process: &mut SchedulerProcess| {
139                let mut remove = false;
140                let mut inner_proc = process.process.borrow_mut();
141                match process.state {
142                    ProcessState::WaitingForPid(_) | ProcessState::Running => {
143                        self.exited_processes.retain_mut(|exited_proc| {
144                            let found_parent = exited_proc.parent_id == inner_proc.id;
145
146                            // add to parent
147                            if found_parent {
148                                inner_proc.add_child_exit(exited_proc.id, exited_proc.exit_code);
149                            }
150
151                            // wake explicit waiters
152                            if let ProcessState::WaitingForPid(pid) = process.state {
153                                if pid == exited_proc.id {
154                                    remove = true;
155                                    // put the exit code in rax
156                                    // this should return to user mode directly
157                                    assert_eq!(
158                                        inner_proc.context.cs & 0x3,
159                                        3,
160                                        "must be from user only"
161                                    );
162                                    inner_proc.context.rax = exited_proc.exit_code as u64;
163                                }
164                            }
165
166                            // retain if we didn't find the parent
167                            !found_parent
168                        });
169                    }
170                    ProcessState::WaitingForTime(t) => {
171                        if t <= time_now {
172                            remove = true;
173                        }
174                    }
175                    _ => unreachable!("We can't have Scheduled state here"),
176                }
177                remove
178            })
179            .collect::<Vec<_>>();
180
181        for (_, process) in extracted {
182            self.reschedule_process(process);
183        }
184
185        // here are processes with parent either in `scheduled_processes` or already gone
186        //let mut scheduled_list = self.scheduled_processes.drain().collect::<Vec<_>>();
187        for exited_proc in self.exited_processes.drain(..) {
188            for process in self.scheduled_processes.iter() {
189                let mut inner_proc = process.process.borrow_mut();
190                if inner_proc.id == exited_proc.parent_id {
191                    inner_proc.add_child_exit(exited_proc.id, exited_proc.exit_code);
192                }
193            }
194        }
195
196        // we can clear here, since we don't use the vm of the process anymore
197        self.exited_processes.clear();
198    }
199
200    /// Exits all non-running (waiting and scheduled) processes.
201    /// The [`schedule`] function will return when all processes are done.
202    fn exit_idle_processes(&mut self) {
203        // TODO: implement graceful shutdown and wait for processes to exit
204        for process in self.scheduled_processes.drain() {
205            let mut inner_proc = process.process.into_inner();
206            info!("Force stopping process {}", inner_proc.id);
207            inner_proc.exit(0);
208        }
209        // shutdown the waiting processes
210        self.running_waiting_procs
211            .retain(|_, process| match process.state {
212                ProcessState::Running => true,
213                ProcessState::Scheduled
214                | ProcessState::WaitingForPid(_)
215                | ProcessState::WaitingForTime(_) => {
216                    let mut inner_proc = process.process.borrow_mut();
217                    info!("Force stopping process {}", inner_proc.id);
218                    inner_proc.exit(0);
219                    false
220                }
221            });
222    }
223}
224
225pub fn push_process(process: Process) {
226    SCHEDULER.lock().push_process(process);
227}
228
229/// What this function does is that it tells the scheduler to stop scheduling any more processes.
230/// And start the shutdown process.
231pub fn stop_scheduler() {
232    SHUTDOWN.store(true, Ordering::Relaxed);
233}
234
235pub fn schedule() {
236    SCHEDULER.lock().init_interrupt();
237
238    loop {
239        let current_cpu = cpu::cpu();
240        assert!(current_cpu.context.is_none());
241
242        let mut scheduler = SCHEDULER.lock();
243        let shutdown = SHUTDOWN.load(Ordering::Acquire);
244        if shutdown {
245            scheduler.exit_idle_processes();
246        }
247
248        current_cpu.push_cli();
249
250        scheduler.try_wake_waiting_processes();
251
252        // check if we need to reset the priority counters
253        if scheduler
254            .scheduled_processes
255            .peek()
256            .map(|p| p.priority_counter < MIN_PRIORITY_VALUE)
257            .unwrap_or(false)
258        {
259            scheduler.reset_scheduled_processes_counters();
260        }
261
262        let top = scheduler.scheduled_processes.pop();
263
264        if let Some(mut top) = top {
265            assert_eq!(top.state, ProcessState::Scheduled);
266            top.state = ProcessState::Running;
267            let pid;
268            if !shutdown {
269                {
270                    let mut inner_proc = top.process.borrow_mut();
271                    pid = inner_proc.id;
272
273                    // the higher the value, the lower the priority
274                    let decrement = 6 - inner_proc.priority as u64;
275                    top.priority_counter -= decrement;
276
277                    scheduler.max_priority = top.priority_counter;
278                    // SAFETY: we are the scheduler and running in kernel space, so it's safe to switch to this vm
279                    // as it has clones of our kernel mappings
280                    unsafe { inner_proc.switch_to_this_vm() };
281                    current_cpu.process_id = inner_proc.id;
282                    current_cpu.context = Some(inner_proc.context);
283                    current_cpu.scheduling = true;
284                }
285                scheduler.running_waiting_procs.insert(pid, top);
286            }
287        }
288
289        current_cpu.pop_cli();
290
291        if shutdown
292            && scheduler.scheduled_processes.is_empty()
293            && scheduler.running_waiting_procs.is_empty()
294            && current_cpu.context.is_none()
295        {
296            break;
297        }
298
299        drop(scheduler);
300
301        if current_cpu.context.is_some() {
302            // call scheduler_interrupt_handler
303            // we are using interrupts to switch context since it allows us to save the registers of exit, which is
304            // very convenient
305            // The `sys_exit` syscall changes the context from user to kernel,
306            // and because of how we implemented syscalls, the result will be in `rax`, so we tell
307            // the compiler to ignore `rax` as it may be clobbered after this call
308            unsafe { core::arch::asm!("int 0xff", out("rax") _) }
309            // SAFETY: we are not running in any process context, so it's safe to go back to the kernel
310            unsafe { virtual_memory_mapper::switch_to_kernel() };
311        } else {
312            // no process to run, just wait for interrupts
313            unsafe { cpu::halt() };
314        }
315    }
316}
317
318fn with_current_process_and_state<F, U>(f: F) -> U
319where
320    F: FnOnce(&mut SchedulerProcess) -> U,
321{
322    let current_cpu = cpu::cpu();
323    let mut scheduler = SCHEDULER.lock();
324    let process = scheduler
325        .running_waiting_procs
326        .get_mut(&current_cpu.process_id)
327        .expect("current process not found");
328    assert_eq!(process.state, ProcessState::Running);
329    f(process)
330}
331
332/// # Safety
333/// Must ensure that this is called and handled inside pop_cli and push_cli block, as an interrupt in the middle
334/// causes the `current_process` to be unavailable later on
335unsafe fn take_current_process() -> SchedulerProcess {
336    let current_cpu = cpu::cpu();
337    let process = SCHEDULER
338        .lock()
339        .running_waiting_procs
340        .remove(&current_cpu.process_id)
341        .expect("current process not found");
342    assert_eq!(process.state, ProcessState::Running);
343    process
344}
345
346pub fn with_current_process<F, U>(f: F) -> U
347where
348    F: FnOnce(&mut Process) -> U,
349{
350    with_current_process_and_state(|p| f(&mut p.process.borrow_mut()))
351}
352
353pub fn with_process<F, U>(pid: u64, f: F) -> U
354where
355    F: FnOnce(&mut Process) -> U,
356{
357    let scheduler = SCHEDULER.lock();
358    let process = scheduler
359        .running_waiting_procs
360        .get(&pid)
361        .unwrap_or_else(|| {
362            scheduler
363                .scheduled_processes
364                .iter()
365                .find(|p| p.process.borrow().id == pid)
366                .expect("process not found")
367        });
368    let r = f(&mut process.process.borrow_mut());
369    r
370}
371
372/// Exit the current process, and move the `all_state` to the scheduler.
373/// The caller of this function (i.e. interrupt) will use the `all_state` to go back to the scheduler.
374/// This function will remove the context from the CPU, and thus the value in `all_state` will be dropped.
375pub fn exit_current_process(exit_code: i32, all_state: &mut InterruptAllSavedState) {
376    let current_cpu = cpu::cpu();
377    assert!(current_cpu.context.is_some());
378    current_cpu.push_cli();
379
380    // SAFETY: called within push_cli and pop_cli
381    let process = unsafe { take_current_process() };
382
383    let mut inner_proc = process.process.into_inner();
384
385    trace!("Process {} exited with code {}", inner_proc.id, exit_code);
386
387    swap_context(current_cpu.context.as_mut().unwrap(), all_state);
388    // Even though this context won't run again
389    // This may be useful if a process wants to read that context later on.
390    // The virtual memory will be cleared once we drop the process
391    // thus, we can't drop the process here
392    inner_proc.context = current_cpu.context.take().unwrap();
393    inner_proc.exit(exit_code);
394
395    SCHEDULER.lock().exited_processes.push(*inner_proc);
396
397    current_cpu.pop_cli();
398    // go back to the kernel after the scheduler interrupt
399}
400
401pub fn sleep_current_process(time: ClockTime, all_state: &mut InterruptAllSavedState) {
402    let current_cpu = cpu::cpu();
403    assert!(current_cpu.context.is_some());
404
405    let deadline = clock::clocks().time_since_startup() + time;
406
407    with_current_process_and_state(|p| {
408        current_cpu.push_cli();
409        let mut inner_proc = p.process.borrow_mut();
410        p.state = ProcessState::WaitingForTime(deadline);
411        trace!(
412            "Process {} is waiting for time {:?}",
413            inner_proc.id,
414            deadline
415        );
416        swap_context(current_cpu.context.as_mut().unwrap(), all_state);
417
418        inner_proc.context = current_cpu.context.take().unwrap();
419    });
420
421    current_cpu.pop_cli();
422    // go back to the kernel after the scheduler interrupt
423}
424
425pub fn yield_current_if_any(all_state: &mut InterruptAllSavedState) {
426    let current_cpu = cpu::cpu();
427    // do not yield if we don't have context, or we are in the middle of scheduling
428    if current_cpu.context.is_none() || current_cpu.scheduling {
429        return;
430    }
431    current_cpu.push_cli();
432    // SAFETY: called within push_cli and pop_cli
433    let process = unsafe { take_current_process() };
434    swap_context(current_cpu.context.as_mut().unwrap(), all_state);
435    process.process.borrow_mut().context = current_cpu.context.take().unwrap();
436
437    SCHEDULER.lock().reschedule_process(process);
438    current_cpu.pop_cli();
439    // go back to the kernel after the scheduler interrupt
440}
441
442pub fn is_process_running(pid: u64) -> bool {
443    let scheduler = SCHEDULER.lock();
444    scheduler
445        .running_waiting_procs
446        .keys()
447        .cloned()
448        .chain(
449            scheduler
450                .scheduled_processes
451                .iter()
452                .map(|p| p.process.borrow().id),
453        )
454        .any(|id| id == pid)
455}
456
457pub fn wait_for_pid(all_state: &mut InterruptAllSavedState, pid: u64) -> bool {
458    let current_cpu = cpu::cpu();
459    assert!(current_cpu.context.is_some());
460
461    // we can't wait for a process that doesn't exist now, unless we are a parent of a process that has exited
462    // see [`exit_current_process`]
463    let process_found = is_process_running(pid);
464    if !process_found {
465        return false;
466    }
467
468    with_current_process_and_state(|p| {
469        current_cpu.push_cli();
470        let mut inner_proc = p.process.borrow_mut();
471        p.state = ProcessState::WaitingForPid(pid);
472        trace!("Process {} is waiting for process {}", inner_proc.id, pid);
473
474        swap_context(current_cpu.context.as_mut().unwrap(), all_state);
475        inner_proc.context = current_cpu.context.take().unwrap();
476    });
477
478    current_cpu.pop_cli();
479    // go back to the kernel after the scheduler interrupt
480    true
481}
482
483pub fn swap_context(context: &mut ProcessContext, all_state: &mut InterruptAllSavedState) {
484    let mut fxsave = FxSave::default();
485    unsafe { core::arch::x86_64::_fxsave64(&mut fxsave as *mut FxSave as _) };
486    unsafe { core::arch::x86_64::_fxrstor64(context.fxsave.0.as_ptr() as _) };
487    context.fxsave = fxsave;
488
489    mem::swap(&mut all_state.frame.rflags, &mut context.rflags);
490    mem::swap(&mut all_state.frame.rip, &mut context.rip);
491    all_state.frame.cs = mem::replace(&mut context.cs, all_state.frame.cs as _) as _;
492    mem::swap(&mut all_state.frame.rsp, &mut context.rsp);
493    all_state.frame.ss = mem::replace(&mut context.ss, all_state.frame.ss as _) as _;
494
495    mem::swap(&mut all_state.rest.ds, &mut context.ds);
496    mem::swap(&mut all_state.rest.es, &mut context.es);
497    mem::swap(&mut all_state.rest.fs, &mut context.fs);
498    mem::swap(&mut all_state.rest.gs, &mut context.gs);
499    mem::swap(&mut all_state.rest.dr0, &mut context.dr0);
500    mem::swap(&mut all_state.rest.dr1, &mut context.dr1);
501    mem::swap(&mut all_state.rest.dr2, &mut context.dr2);
502    mem::swap(&mut all_state.rest.dr3, &mut context.dr3);
503    mem::swap(&mut all_state.rest.dr6, &mut context.dr6);
504    mem::swap(&mut all_state.rest.dr7, &mut context.dr7);
505    mem::swap(&mut all_state.rest.rax, &mut context.rax);
506    mem::swap(&mut all_state.rest.rbx, &mut context.rbx);
507    mem::swap(&mut all_state.rest.rcx, &mut context.rcx);
508    mem::swap(&mut all_state.rest.rdx, &mut context.rdx);
509    mem::swap(&mut all_state.rest.rsi, &mut context.rsi);
510    mem::swap(&mut all_state.rest.rdi, &mut context.rdi);
511    mem::swap(&mut all_state.rest.rbp, &mut context.rbp);
512    mem::swap(&mut all_state.rest.r8, &mut context.r8);
513    mem::swap(&mut all_state.rest.r9, &mut context.r9);
514    mem::swap(&mut all_state.rest.r10, &mut context.r10);
515    mem::swap(&mut all_state.rest.r11, &mut context.r11);
516    mem::swap(&mut all_state.rest.r12, &mut context.r12);
517    mem::swap(&mut all_state.rest.r13, &mut context.r13);
518    mem::swap(&mut all_state.rest.r14, &mut context.r14);
519    mem::swap(&mut all_state.rest.r15, &mut context.r15);
520}
521
522extern "C" fn scheduler_interrupt_handler(all_state: &mut InterruptAllSavedState) {
523    assert_eq!(all_state.frame.cs & 0x3, 0, "must be from kernel only");
524    let current_cpu = cpu::cpu();
525    assert!(current_cpu.context.is_some());
526    assert!(current_cpu.scheduling);
527    assert!(current_cpu.interrupts_disabled());
528
529    // we can yield at this point after we go to the process
530    current_cpu.scheduling = false;
531
532    swap_context(current_cpu.context.as_mut().unwrap(), all_state);
533}
534
535extern "C" fn syscall_interrupt_handler(all_state: &mut InterruptAllSavedState) {
536    assert_eq!(all_state.frame.cs & 0x3, 3, "must be from user only");
537    let current_cpu = cpu::cpu();
538    assert!(current_cpu.context.is_some());
539
540    syscalls::handle_syscall(all_state);
541}