2023-07-18 18:03:45 +03:00
|
|
|
//! Per-CPU queue implementation
|
|
|
|
|
2023-08-01 18:05:10 +03:00
|
|
|
// use aarch64_cpu::registers::CNTPCT_EL0;
|
2023-07-18 18:03:45 +03:00
|
|
|
use alloc::{collections::VecDeque, rc::Rc, vec::Vec};
|
2023-08-01 18:05:10 +03:00
|
|
|
use cfg_if::cfg_if;
|
2023-07-18 18:03:45 +03:00
|
|
|
|
|
|
|
use crate::{
|
2023-08-01 18:05:10 +03:00
|
|
|
// arch::aarch64::{context::TaskContext, cpu::Cpu},
|
2023-08-02 19:53:54 +03:00
|
|
|
arch::{Architecture, ArchitectureImpl},
|
2023-07-18 18:03:45 +03:00
|
|
|
sync::{IrqSafeSpinlock, IrqSafeSpinlockGuard},
|
|
|
|
util::OneTimeInit,
|
|
|
|
};
|
|
|
|
|
|
|
|
use super::{
|
2023-08-02 20:53:14 +03:00
|
|
|
context::TaskContextImpl,
|
2023-07-25 17:48:26 +03:00
|
|
|
process::{CurrentProcess, Process, ProcessState},
|
2023-08-01 18:05:10 +03:00
|
|
|
Cpu, ProcessId, TaskContext,
|
2023-07-18 18:03:45 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
/// Per-CPU statistics
|
|
|
|
#[derive(Default)]
|
|
|
|
pub struct CpuQueueStats {
|
|
|
|
/// Ticks spent idling
|
|
|
|
pub idle_time: u64,
|
|
|
|
/// Ticks spent running CPU tasks
|
|
|
|
pub cpu_time: u64,
|
|
|
|
|
|
|
|
/// Time since last measurement
|
|
|
|
measure_time: u64,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Per-CPU queue's inner data, normally resides under a lock
|
|
|
|
pub struct CpuQueueInner {
|
|
|
|
/// Current process, None if idling
|
|
|
|
pub current: Option<Rc<Process>>,
|
|
|
|
/// LIFO queue for processes waiting for execution
|
|
|
|
pub queue: VecDeque<Rc<Process>>,
|
|
|
|
|
|
|
|
/// CPU time usage statistics
|
|
|
|
pub stats: CpuQueueStats,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Per-CPU queue
|
|
|
|
pub struct CpuQueue {
|
|
|
|
inner: IrqSafeSpinlock<CpuQueueInner>,
|
|
|
|
idle: TaskContext,
|
|
|
|
}
|
|
|
|
|
|
|
|
static QUEUES: OneTimeInit<Vec<CpuQueue>> = OneTimeInit::new();
|
|
|
|
|
|
|
|
#[naked]
|
|
|
|
extern "C" fn __idle(_x: usize) -> ! {
|
|
|
|
unsafe {
|
2023-08-01 18:05:10 +03:00
|
|
|
cfg_if! {
|
|
|
|
if #[cfg(target_arch = "aarch64")] {
|
|
|
|
core::arch::asm!("1: nop; b 1b", options(noreturn));
|
|
|
|
} else if #[cfg(target_arch = "x86_64")] {
|
|
|
|
core::arch::asm!(r#"
|
|
|
|
1:
|
|
|
|
nop
|
|
|
|
jmp 1b
|
|
|
|
"#, options(noreturn, att_syntax));
|
|
|
|
} else {
|
|
|
|
core::arch::asm!("", options(noreturn));
|
|
|
|
}
|
|
|
|
}
|
2023-07-18 18:03:45 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl CpuQueueStats {
|
|
|
|
/// Reset the stats to zero values
|
|
|
|
pub fn reset(&mut self) {
|
|
|
|
self.cpu_time = 0;
|
|
|
|
self.idle_time = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl CpuQueueInner {
|
|
|
|
/// Picks a next task for execution, skipping (dropping) those that were suspended. May return
|
|
|
|
/// None if the queue is empty or no valid task was found, in which case the scheduler should
|
|
|
|
/// go idle.
|
|
|
|
pub fn next_ready_task(&mut self) -> Option<Rc<Process>> {
|
|
|
|
while !self.queue.is_empty() {
|
|
|
|
let task = self.queue.pop_front().unwrap();
|
|
|
|
|
|
|
|
match task.state() {
|
|
|
|
ProcessState::Ready => {
|
|
|
|
return Some(task);
|
|
|
|
}
|
|
|
|
// Drop suspended tasks from the queue
|
2023-08-07 09:53:47 +03:00
|
|
|
ProcessState::Suspended | ProcessState::Terminated => (),
|
|
|
|
ProcessState::Running => {
|
|
|
|
// TODO fix this finally
|
|
|
|
}
|
2023-08-05 16:32:12 +03:00
|
|
|
// e => panic!(
|
|
|
|
// "Unexpected process state in CpuQueue: {:?} ({} {:?}, cpu_id={})",
|
|
|
|
// e,
|
|
|
|
// task.id(),
|
|
|
|
// task.name(),
|
|
|
|
// task.cpu_id()
|
|
|
|
// ),
|
2023-07-18 18:03:45 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns an iterator over all the processes in the queue plus the currently running process,
|
|
|
|
/// if there is one.
|
|
|
|
pub fn iter(&self) -> impl Iterator<Item = &Rc<Process>> {
|
|
|
|
Iterator::chain(self.queue.iter(), self.current.iter())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl CpuQueue {
|
|
|
|
/// Constructs an empty queue with its own idle task
|
|
|
|
pub fn new() -> Self {
|
2023-08-05 16:32:12 +03:00
|
|
|
let idle = TaskContext::kernel(__idle, Cpu::local_id() as usize)
|
|
|
|
.expect("Could not construct an idle task");
|
2023-07-18 18:03:45 +03:00
|
|
|
|
|
|
|
Self {
|
|
|
|
inner: {
|
|
|
|
IrqSafeSpinlock::new(CpuQueueInner {
|
|
|
|
current: None,
|
|
|
|
queue: VecDeque::new(),
|
|
|
|
stats: CpuQueueStats::default(),
|
|
|
|
})
|
|
|
|
},
|
|
|
|
idle,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Starts queue execution on current CPU.
|
|
|
|
///
|
|
|
|
/// # Safety
|
|
|
|
///
|
|
|
|
/// Only meant to be called from [crate::task::enter()] function.
|
|
|
|
pub unsafe fn enter(&self) -> ! {
|
2023-08-02 19:53:54 +03:00
|
|
|
assert!(ArchitectureImpl::interrupt_mask());
|
2023-07-18 18:03:45 +03:00
|
|
|
// Start from idle thread to avoid having a Rc stuck here without getting dropped
|
2023-08-01 18:05:10 +03:00
|
|
|
// let t = CNTPCT_EL0.get();
|
|
|
|
// self.lock().stats.measure_time = t;
|
2023-07-18 19:22:30 +03:00
|
|
|
self.idle.enter()
|
2023-07-18 18:03:45 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Yields CPU execution to the next task in queue (or idle task if there aren't any).
|
|
|
|
///
|
|
|
|
/// # Safety
|
|
|
|
///
|
|
|
|
/// The function is only meant to be called from kernel threads (e.g. if they want to yield
|
|
|
|
/// CPU execution to wait for something) or interrupt handlers.
|
|
|
|
pub unsafe fn yield_cpu(&self) {
|
2023-08-02 20:43:21 +03:00
|
|
|
// Make sure the scheduling process doesn't get interrupted
|
|
|
|
ArchitectureImpl::set_interrupt_mask(true);
|
|
|
|
|
2023-07-18 18:03:45 +03:00
|
|
|
let mut inner = self.inner.lock();
|
|
|
|
|
2023-08-01 18:05:10 +03:00
|
|
|
// let t = CNTPCT_EL0.get();
|
|
|
|
// let delta = t - inner.stats.measure_time;
|
|
|
|
// inner.stats.measure_time = t;
|
2023-07-18 18:03:45 +03:00
|
|
|
|
|
|
|
let current = inner.current.clone();
|
|
|
|
|
|
|
|
if let Some(current) = current.as_ref() {
|
|
|
|
if current.state() == ProcessState::Running {
|
|
|
|
current.set_state(ProcessState::Ready);
|
|
|
|
}
|
|
|
|
inner.queue.push_back(current.clone());
|
|
|
|
|
2023-08-01 18:05:10 +03:00
|
|
|
// inner.stats.cpu_time += delta;
|
2023-07-18 18:03:45 +03:00
|
|
|
} else {
|
2023-08-01 18:05:10 +03:00
|
|
|
// inner.stats.idle_time += delta;
|
2023-07-18 18:03:45 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
let next = inner.next_ready_task();
|
|
|
|
|
|
|
|
inner.current = next.clone();
|
|
|
|
|
|
|
|
// Can drop the lock, we hold current and next Rc's
|
|
|
|
drop(inner);
|
|
|
|
|
|
|
|
let (from, _from_rc) = if let Some(current) = current.as_ref() {
|
2023-07-25 16:47:00 +03:00
|
|
|
(current.current_context(), Rc::strong_count(current))
|
2023-07-18 18:03:45 +03:00
|
|
|
} else {
|
|
|
|
(&self.idle, 0)
|
|
|
|
};
|
|
|
|
|
|
|
|
let (to, _to_rc) = if let Some(next) = next.as_ref() {
|
|
|
|
next.set_running(Cpu::local_id());
|
2023-07-25 16:47:00 +03:00
|
|
|
(next.current_context(), Rc::strong_count(next))
|
2023-07-18 18:03:45 +03:00
|
|
|
} else {
|
|
|
|
(&self.idle, 0)
|
|
|
|
};
|
|
|
|
|
2023-07-18 19:22:30 +03:00
|
|
|
// log_print_raw!(crate::debug::LogLevel::Info, "{}: ", Cpu::local_id());
|
2023-07-18 18:03:45 +03:00
|
|
|
// if let Some(from) = current.as_ref() {
|
2023-08-02 19:53:54 +03:00
|
|
|
// log_print_raw!(crate::debug::LogLevel::Info, "{}", from.id(),);
|
2023-07-18 18:03:45 +03:00
|
|
|
// } else {
|
|
|
|
// log_print_raw!(crate::debug::LogLevel::Info, "{{idle}}");
|
|
|
|
// }
|
|
|
|
|
|
|
|
// log_print_raw!(crate::debug::LogLevel::Info, " -> ");
|
|
|
|
|
|
|
|
// if let Some(to) = next.as_ref() {
|
2023-08-02 19:53:54 +03:00
|
|
|
// log_print_raw!(crate::debug::LogLevel::Info, "{}", to.id(),);
|
2023-07-18 18:03:45 +03:00
|
|
|
// } else {
|
|
|
|
// log_print_raw!(crate::debug::LogLevel::Info, "{{idle}}");
|
|
|
|
// }
|
|
|
|
|
|
|
|
// log_print_raw!(crate::debug::LogLevel::Info, "\n");
|
|
|
|
|
|
|
|
to.switch(from)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Pushes the process to the back of the execution queue.
|
|
|
|
///
|
|
|
|
/// # Safety
|
|
|
|
///
|
|
|
|
/// Only meant to be called from Process impl. The function does not set any process accounting
|
|
|
|
/// information, which may lead to invalid states.
|
|
|
|
pub unsafe fn enqueue(&self, p: Rc<Process>) {
|
2023-08-05 16:32:12 +03:00
|
|
|
assert_eq!(p.state(), ProcessState::Ready);
|
2023-07-18 18:03:45 +03:00
|
|
|
self.inner.lock().queue.push_back(p);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Removes process with given PID from the exeuction queue.
|
|
|
|
pub fn dequeue(&self, _pid: ProcessId) {
|
|
|
|
todo!();
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the queue length at this moment.
|
|
|
|
///
|
|
|
|
/// # Note
|
|
|
|
///
|
|
|
|
/// This value may immediately change.
|
|
|
|
pub fn len(&self) -> usize {
|
|
|
|
self.inner.lock().queue.len()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns `true` if the queue is empty at the moment.
|
|
|
|
///
|
|
|
|
/// # Note
|
|
|
|
///
|
|
|
|
/// This may immediately change.
|
|
|
|
pub fn is_empty(&self) -> bool {
|
|
|
|
self.inner.lock().queue.is_empty()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns a safe reference to the inner data structure.
|
|
|
|
pub fn lock(&self) -> IrqSafeSpinlockGuard<CpuQueueInner> {
|
|
|
|
self.inner.lock()
|
|
|
|
}
|
|
|
|
|
2023-08-05 16:32:12 +03:00
|
|
|
/// Returns an unsafe reference to the queue.
|
|
|
|
///
|
|
|
|
/// # Safety
|
|
|
|
///
|
|
|
|
/// Only meant to be called to dump the queue contents when panicking.
|
|
|
|
#[allow(clippy::mut_from_ref)]
|
|
|
|
pub unsafe fn grab(&self) -> &mut CpuQueueInner {
|
|
|
|
self.inner.grab()
|
|
|
|
}
|
|
|
|
|
2023-07-18 18:03:45 +03:00
|
|
|
/// Returns the process currently being executed.
|
|
|
|
///
|
|
|
|
/// # Note
|
|
|
|
///
|
|
|
|
/// This function should be safe in all kernel thread/interrupt contexts:
|
|
|
|
///
|
|
|
|
/// * (in kthread) the code calling this will still remain on the same thread.
|
|
|
|
/// * (in irq) the code cannot be interrupted and other CPUs shouldn't change this queue, so it
|
|
|
|
/// will remain valid until the end of the interrupt or until [CpuQueue::yield_cpu]
|
|
|
|
/// is called.
|
2023-07-25 17:48:26 +03:00
|
|
|
pub fn current_process(&self) -> Option<CurrentProcess> {
|
|
|
|
self.inner
|
|
|
|
.lock()
|
|
|
|
.current
|
|
|
|
.clone()
|
|
|
|
.map(|p| unsafe { CurrentProcess::new(p) })
|
2023-07-18 18:03:45 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns a queue for given CPU index
|
|
|
|
pub fn for_cpu(id: usize) -> &'static CpuQueue {
|
|
|
|
&QUEUES.get()[id]
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns an iterator over all queues of the system
|
|
|
|
#[inline]
|
|
|
|
pub fn all() -> impl Iterator<Item = &'static CpuQueue> {
|
|
|
|
QUEUES.get().iter()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Picks a queue with the least amount of tasks in it
|
|
|
|
pub fn least_loaded() -> Option<(usize, &'static CpuQueue)> {
|
|
|
|
let queues = QUEUES.get();
|
|
|
|
|
|
|
|
queues.iter().enumerate().min_by_key(|(_, q)| q.len())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Initializes the global queue list
|
|
|
|
pub fn init_queues(queues: Vec<CpuQueue>) {
|
|
|
|
QUEUES.init(queues);
|
|
|
|
}
|