Skip to main content

strat9_kernel/process/scheduler/
timer_ops.rs

1use super::*;
2use core::sync::atomic::{AtomicBool, AtomicU64};
3
4// One-shot bootstrap nudge: ensure each CPU requests at least one preemption
5// after entering Ring 3. This breaks "first task runs forever" scenarios when
6// class accounting has not yet accumulated enough runtime to trigger resched.
7static FIRST_TICK_FORCE_RESCHED: [AtomicBool; crate::arch::x86_64::percpu::MAX_CPUS] =
8    [const { AtomicBool::new(false) }; crate::arch::x86_64::percpu::MAX_CPUS];
9
10// Per-CPU local tick counter (incremented every timer tick, independent of
11// the BSP-only global TICK_COUNT). Used for periodic force-resched below.
12static CPU_LOCAL_TICKS: [AtomicU64; crate::arch::x86_64::percpu::MAX_CPUS] =
13    [const { AtomicU64::new(0) }; crate::arch::x86_64::percpu::MAX_CPUS];
14
15// Force a reschedule at least every N ticks per CPU, regardless of GLOBAL_SCHED_STATE
16// lock availability. This guarantees kernel tasks (shell, idle) get CPU time
17// even if timer_tick consistently loses the scheduler lock race with the other CPU.
18const PERIODIC_RESCHED_TICKS: u64 = 5;
19
20/// Timer interrupt handler - called from interrupt context.
21///
22/// Increments the global tick counter unconditionally on BSP so wall-clock
23/// time never drifts even when the scheduler lock is contended. Secondary
24/// bookkeeping (interval timers, wake deadlines, per-task accounting) is
25/// deferred when the lock is unavailable.
26///
27/// Lock discipline: `tick_all_timers` and `check_wake_deadlines` each acquire
28/// the scheduler lock themselves via `try_lock`. The per-task block below uses
29/// its own `try_lock`. These are separate acquisitions by design - the inner
30/// functions must not be called while the outer lock is held (that would deadlock).
31pub fn timer_tick() {
32    let _perf = super::perf_counters::PerfScope::new(
33        &super::perf_counters::IRQ_TIMER_TSC,
34        &super::perf_counters::IRQ_TIMER_COUNT,
35    );
36
37    // Feed TSC low bits into the entropy pool (every tick).
38    crate::entropy::add_entropy(2, crate::arch::x86_64::rdtsc());
39    let cpu_idx = crate::arch::x86_64::percpu::current_cpu_index();
40
41    if cpu_is_valid(cpu_idx) {
42        CPU_TOTAL_TICKS[cpu_idx].fetch_add(1, Ordering::Relaxed);
43    }
44
45    // BSP wall-clock: ALWAYS advance, regardless of any lock state.
46    if cpu_idx == 0 {
47        TICK_COUNT.fetch_add(1, Ordering::Relaxed);
48    }
49
50    // Per-CPU local tick counter (all CPUs, not just BSP).
51    let local_tick = if cpu_is_valid(cpu_idx) {
52        CPU_LOCAL_TICKS[cpu_idx].fetch_add(1, Ordering::Relaxed) + 1
53    } else {
54        0
55    };
56
57    // Lock-free bootstrap nudge: first local timer tick requests one resched
58    // without touching GLOBAL_SCHED_STATE. This avoids pathological boot windows where
59    // another CPU holds GLOBAL_SCHED_STATE and this CPU would otherwise defer the first
60    // `need_resched` update indefinitely.
61    if cpu_is_valid(cpu_idx) && !FIRST_TICK_FORCE_RESCHED[cpu_idx].swap(true, Ordering::AcqRel) {
62        request_force_resched_hint(cpu_idx);
63    }
64
65    // Periodic force-resched: guarantee every CPU reschedules at least every
66    // PERIODIC_RESCHED_TICKS ticks. This prevents starvation of kernel tasks
67    // when timer_tick fails to acquire the scheduler lock repeatedly, or when
68    // FairClassRq::update_current returns false (e.g., single-task queue).
69    if cpu_is_valid(cpu_idx) && local_tick > 0 && local_tick % PERIODIC_RESCHED_TICKS == 0 {
70        request_force_resched_hint(cpu_idx);
71    }
72
73    // BSP-only secondary bookkeeping: interval timers and sleep wakeups.
74    // NS_PER_TICK = 1_000_000_000 / TIMER_HZ (10_000_000 ns at 100 Hz).
75    // Both helpers acquire the scheduler lock internally via try_lock and
76    // skip silently when contended - no probe needed.
77    if cpu_idx == 0 {
78        let tick = TICK_COUNT.load(Ordering::Relaxed);
79        let current_time_ns = tick * NS_PER_TICK;
80        crate::process::timer::tick_all_timers(current_time_ns);
81        check_wake_deadlines(current_time_ns);
82
83        if tick % 200 == 0 {
84            crate::hardware::thermal::poll();
85        }
86    }
87
88    // Per-task accounting on this CPU : uses LOCAL lock only (no global GLOBAL_SCHED_STATE).
89    // This ensures timer ticks are never dropped due to another CPU holding GLOBAL_SCHED_STATE.
90    if cpu_is_valid(cpu_idx) {
91        if let Some(mut guard) = LOCAL_SCHEDULERS[cpu_idx].try_lock_no_irqsave() {
92            if let Some(ref mut cpu) = *guard {
93                let should_resched = if let Some(ref current_task) = cpu.current_task {
94                    let class = cpu.class_table.class_for_task(current_task);
95                    match class {
96                        crate::process::sched::SchedClassId::RealTime => {
97                            CPU_RT_RUNTIME_TICKS[cpu_idx].fetch_add(1, Ordering::Relaxed);
98                        }
99                        crate::process::sched::SchedClassId::Fair => {
100                            CPU_FAIR_RUNTIME_TICKS[cpu_idx].fetch_add(1, Ordering::Relaxed);
101                        }
102                        crate::process::sched::SchedClassId::Idle => {
103                            CPU_IDLE_TICKS[cpu_idx].fetch_add(1, Ordering::Relaxed);
104                        }
105                    }
106                    current_task.ticks.fetch_add(1, Ordering::Relaxed);
107                    cpu.current_runtime.update();
108                    cpu.class_rqs.update_current(
109                        &cpu.current_runtime,
110                        current_task,
111                        false,
112                        &cpu.class_table,
113                    )
114                } else {
115                    false
116                };
117                if should_resched {
118                    cpu.need_resched = true;
119                }
120            }
121        } else {
122            note_try_lock_fail_on_cpu(cpu_idx);
123        }
124    }
125}
126
127/// Check wake deadlines for all tasks and wake up those whose sleep has expired.
128///
129/// Called from timer_tick() with interrupts disabled.
130/// Uses try_lock() to avoid deadlock if called while scheduler lock is held.
131///
132/// # Lock discipline
133///
134/// The `BLOCKED_TASKS` lock is held **only** during the scan + re-enqueue phase.
135/// The lock is explicitly dropped before sending IPIs (which may acquire
136/// per-CPU data) and before any `Arc<Task>` drop (which reaches
137/// `KernelStack::drop → free_frames → buddy_alloc.lock()`).
138///
139/// To guarantee this, every `Arc<Task>` removed from `blocked_tasks` is
140/// moved into the `deferred_drops` array. Those Arcs are dropped after the
141/// guard goes out of scope, ensuring `free_frames` is never called while the
142/// scheduler lock is held.
143fn check_wake_deadlines(current_time_ns: u64) {
144    let mut ipi_targets = [false; crate::arch::x86_64::percpu::MAX_CPUS];
145    let my_cpu = current_cpu_index();
146
147    // Stack-allocated storage for tasks whose Arc must be dropped outside the
148    // scheduler lock. Sized to the same batch limit used for the scan so that
149    // we never need a heap allocation here.
150    const BATCH: usize = 128;
151    let mut deferred_drops: [Option<Arc<Task>>; BATCH] = [const { None }; BATCH];
152    let mut drop_count = 0usize;
153
154    {
155        // --- begin critical section (BLOCKED_TASKS lock held) ---
156        let mut blocked = match super::BLOCKED_TASKS.try_lock_no_irqsave() {
157            Some(guard) => guard,
158            None => return,
159        };
160
161        let mut to_wake = [TaskId::from_u64(0); BATCH];
162        let mut count = 0usize;
163        for (id, task) in blocked.iter() {
164            let deadline = task.wake_deadline_ns.load(Ordering::Relaxed);
165            if deadline != 0 && current_time_ns >= deadline {
166                if count < BATCH {
167                    to_wake[count] = *id;
168                    count += 1;
169                } else {
170                    break;
171                }
172            }
173        }
174
175        for id in to_wake.iter().copied().take(count) {
176            if let Some(blocked_task) = blocked.remove(&id) {
177                blocked_task.wake_deadline_ns.store(0, Ordering::Relaxed);
178                blocked_task.set_state(TaskState::Ready);
179                let home = blocked_task.home_cpu.load(Ordering::Relaxed);
180                let cpu = if home != usize::MAX { home } else { 0 };
181                let class = {
182                    use crate::process::sched::SchedClassId;
183                    match blocked_task.sched_policy() {
184                        crate::process::sched::SchedPolicy::RealTimeRR { .. }
185                        | crate::process::sched::SchedPolicy::RealTimeFifo { .. } => {
186                            SchedClassId::RealTime
187                        }
188                        crate::process::sched::SchedPolicy::Fair(_) => SchedClassId::Fair,
189                        crate::process::sched::SchedPolicy::Idle => SchedClassId::Idle,
190                    }
191                };
192                if let Some(ref mut local_cpu) = *LOCAL_SCHEDULERS[cpu].lock() {
193                    // `enqueue` moves the Arc into the run-queue, so no
194                    // drop occurs here; the Arc is alive in class_rqs.
195                    local_cpu.class_rqs.enqueue(class, blocked_task);
196                    local_cpu.need_resched = true;
197                    if cpu != my_cpu && cpu_is_valid(cpu) {
198                        ipi_targets[cpu] = true;
199                    }
200                } else {
201                    // No valid CPU slot: stash for drop outside the lock.
202                    // This is the only path where an Arc<Task> can be the
203                    // last reference and trigger KernelStack::drop.
204                    if drop_count < BATCH {
205                        deferred_drops[drop_count] = Some(blocked_task);
206                        drop_count += 1;
207                    }
208                    // If deferred_drops is full the task Arc is dropped here,
209                    // still under the lock : but that case means we already
210                    // have 128 orphaned tasks with no valid CPU, which is a
211                    // bug elsewhere; emit a trace and accept the latency hit.
212                }
213            }
214        }
215        // `blocked` guard drops here : BLOCKED_TASKS lock released BEFORE any
216        // Arc<Task> drop and BEFORE send_resched_ipi_to_cpu.
217        // --- end critical section ---
218    }
219    unsafe { core::arch::asm!("mov al, '2'; out 0xe9, al", out("al") _) };
220
221    // Drop orphaned task Arcs outside the scheduler lock so that
222    // KernelStack::drop → free_frames → buddy_alloc.lock() does not race
223    // with any other GLOBAL_SCHED_STATE lock acquisition on this or another CPU.
224    for slot in deferred_drops[..drop_count].iter_mut() {
225        drop(slot.take());
226    }
227
228    for (cpu, send) in ipi_targets.iter().copied().enumerate() {
229        if send {
230            send_resched_ipi_to_cpu(cpu);
231        }
232    }
233}
234
235/// Get the current tick count
236pub fn ticks() -> u64 {
237    TICK_COUNT.load(Ordering::Relaxed)
238}
239
240/// Get a list of all tasks in the system (for timer checking).
241/// Returns None if scheduler is not initialized or currently locked.
242pub fn get_all_tasks() -> Option<alloc::vec::Vec<Arc<Task>>> {
243    use alloc::vec::Vec;
244    let scheduler = match GLOBAL_SCHED_STATE.try_lock() {
245        Some(guard) => guard,
246        None => {
247            note_try_lock_fail();
248            return None;
249        }
250    };
251    if let Some(ref sched) = *scheduler {
252        let mut tasks = Vec::with_capacity(sched.all_tasks.len());
253        for (_, task) in sched.all_tasks.iter() {
254            tasks.push(task.clone());
255        }
256        Some(tasks)
257    } else {
258        None
259    }
260}