From 49662aa994895d330055a43576660cbd9975a072 Mon Sep 17 00:00:00 2001 From: Bananymous Date: Tue, 7 Jul 2026 04:48:32 +0300 Subject: [PATCH] Kernel: Don't use peridic interrupts for scheduling Scheduler will now set a timer interrupt deadline for when it should get interrupted next. This fixes a big issue I've had for a long time where sleeping on an idle core would have to wait for the next periodic interrupt to wake up. This could cause 1 ms sleeps to be close to 10 ms. This currently only supports lapic timers and will still fallback to periodic interrupts when running with PIC. I should add deadline support to PIT/HPET but why would you run with APIC disabled ;) --- kernel/arch/i686/Yield.S | 2 +- kernel/arch/x86_64/Yield.S | 2 +- kernel/include/kernel/APIC.h | 1 + kernel/include/kernel/Scheduler.h | 6 ++- kernel/kernel/APIC.cpp | 23 ++++++++- kernel/kernel/IDT.cpp | 2 +- kernel/kernel/Scheduler.cpp | 84 ++++++++++++++++++------------- kernel/kernel/Timer/HPET.cpp | 2 +- kernel/kernel/Timer/PIT.cpp | 2 +- 9 files changed, 81 insertions(+), 43 deletions(-) diff --git a/kernel/arch/i686/Yield.S b/kernel/arch/i686/Yield.S index 3a577637..43af418d 100644 --- a/kernel/arch/i686/Yield.S +++ b/kernel/arch/i686/Yield.S @@ -12,7 +12,7 @@ asm_yield_trampoline: pushl %ebp pushl %esp - call scheduler_on_yield + call scheduler_on_yield_trampoline addl $4, %esp popl %ebp diff --git a/kernel/arch/x86_64/Yield.S b/kernel/arch/x86_64/Yield.S index 11c57ff1..e24a8d90 100644 --- a/kernel/arch/x86_64/Yield.S +++ b/kernel/arch/x86_64/Yield.S @@ -15,7 +15,7 @@ asm_yield_trampoline: pushq %r15 movq %rsp, %rdi - call scheduler_on_yield + call scheduler_on_yield_trampoline popq %r15 popq %r14 diff --git a/kernel/include/kernel/APIC.h b/kernel/include/kernel/APIC.h index 194e1689..4a0a687d 100644 --- a/kernel/include/kernel/APIC.h +++ b/kernel/include/kernel/APIC.h @@ -26,6 +26,7 @@ namespace Kernel BAN::ErrorOr reserve_gsi(uint32_t gsi); void initialize_timer(); + void set_timer_dealine(uint64_t ns); private: uint32_t read_from_local_apic(ptrdiff_t); diff --git a/kernel/include/kernel/Scheduler.h b/kernel/include/kernel/Scheduler.h index affd62f3..9c8f35e7 100644 --- a/kernel/include/kernel/Scheduler.h +++ b/kernel/include/kernel/Scheduler.h @@ -23,7 +23,7 @@ namespace Kernel public: void add_thread_to_back(Node*); - void add_thread_with_wake_time(Node*); + bool add_thread_with_wake_time(Node*); // return true if node was inserted as the first element template Node* remove_with_condition(F callback); void remove_node(Node*); @@ -60,7 +60,8 @@ namespace Kernel void reschedule(YieldRegisters*); void reschedule_if_idle(); - void timer_interrupt(); + void on_timer_interrupt(); + void on_yield(YieldRegisters*); static BAN::ErrorOr bind_thread_to_processor(Thread*, ProcessorID); // if thread is already bound, this will never fail @@ -82,6 +83,7 @@ namespace Kernel void update_most_loaded_node_queue(SchedulerQueue::Node*, SchedulerQueue* target_queue); void remove_node_from_most_loaded(SchedulerQueue::Node*); + void update_wake_up_deadline(); void wake_up_sleeping_threads(); void do_load_balancing(); diff --git a/kernel/kernel/APIC.cpp b/kernel/kernel/APIC.cpp index ee89d9a5..5a23848c 100644 --- a/kernel/kernel/APIC.cpp +++ b/kernel/kernel/APIC.cpp @@ -437,8 +437,27 @@ namespace Kernel dprintln("CPU {}: lapic timer frequency: {} Hz", Kernel::Processor::current_id(), m_lapic_timer_frequency_hz); - write_to_local_apic(LAPIC_TIMER_LVT, TimerModePeriodic | IRQ_TIMER); - write_to_local_apic(LAPIC_TIMER_INITIAL_REG, m_lapic_timer_frequency_hz / 2 / 100); + write_to_local_apic(LAPIC_TIMER_LVT, TimerModeOneShot | IRQ_TIMER); + write_to_local_apic(LAPIC_TIMER_INITIAL_REG, 0); + } + + void APIC::set_timer_dealine(uint64_t target_ns) + { + // TODO: don't hardcode divide by 2 + const uint64_t effective_freq = m_lapic_timer_frequency_hz / 2; + + const uint64_t delta_ns = [&]() -> uint64_t { + const uint64_t current_ns = SystemTimer::get().ns_since_boot(); + if (current_ns >= target_ns) + return 1; + const uint64_t delta_ns = target_ns - current_ns; + if (BAN::Math::will_multiplication_overflow(delta_ns, effective_freq)) + return BAN::numeric_limits::max() / effective_freq; + return delta_ns; + }(); + + const uint64_t ticks = BAN::Math::div_round_up(delta_ns * effective_freq, 1'000'000'000); + write_to_local_apic(LAPIC_TIMER_INITIAL_REG, BAN::Math::min(ticks, BAN::numeric_limits::max())); } uint32_t APIC::read_from_local_apic(ptrdiff_t offset) diff --git a/kernel/kernel/IDT.cpp b/kernel/kernel/IDT.cpp index 8cbefe28..3799186a 100644 --- a/kernel/kernel/IDT.cpp +++ b/kernel/kernel/IDT.cpp @@ -435,7 +435,7 @@ namespace Kernel if (Processor::current_is_bsp()) Process::update_alarm_queue(); - Processor::scheduler().timer_interrupt(); + Processor::scheduler().on_timer_interrupt(); } extern "C" void cpp_irq_handler(uint32_t irq) diff --git a/kernel/kernel/Scheduler.cpp b/kernel/kernel/Scheduler.cpp index 590bc97d..82d4deea 100644 --- a/kernel/kernel/Scheduler.cpp +++ b/kernel/kernel/Scheduler.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -45,12 +46,15 @@ namespace Kernel m_tail = node; } - void SchedulerQueue::add_thread_with_wake_time(Node* node) + bool SchedulerQueue::add_thread_with_wake_time(Node* node) { ASSERT(Processor::get_interrupt_state() == InterruptState::Disabled); if (m_tail == nullptr || node->wake_time_ns >= m_tail->wake_time_ns) - return add_thread_to_back(node); + { + add_thread_to_back(node); + return node == m_head; + } Node* next = m_head; Node* prev = nullptr; @@ -64,6 +68,8 @@ namespace Kernel node->prev = prev; (next ? next->prev : m_tail) = node; (prev ? prev->next : m_head) = node; + + return node == m_head; } template @@ -122,15 +128,9 @@ namespace Kernel m_idle_thread = TRY(Thread::create_kernel([](void*) { asm volatile("1: hlt; jmp 1b"); }, nullptr)); ASSERT(m_idle_thread); - size_t processor_index = 0; - for (; processor_index < Processor::count(); processor_index++) - if (Processor::id_from_index(processor_index) == Processor::current_id()) - break; - ASSERT(processor_index < Processor::count()); - // each CPU does load balance at different times. This calulates the offset to other CPUs - m_last_load_balance_ns = s_load_balance_interval_ns * processor_index / Processor::count(); - m_idle_ns = -m_last_load_balance_ns; + m_last_load_balance_ns = s_load_balance_interval_ns * Processor::current_index() / Processor::count(); + m_idle_ns = m_last_load_balance_ns; s_schedulers_initialized++; while (s_schedulers_initialized < Processor::count()) @@ -139,8 +139,6 @@ namespace Kernel if (Processor::count() > 1) Processor::set_smp_enabled(); - m_next_reschedule_ns = SystemTimer::get().ns_since_boot(); - return {}; } @@ -232,17 +230,14 @@ namespace Kernel m_thread_count--; break; case Thread::State::Executing: - { - const uint64_t current_ns = SystemTimer::get().ns_since_boot(); m_current->thread->yield_registers() = *yield_registers; - m_current->time_used_ns += current_ns - m_current->last_start_ns; + m_current->time_used_ns += SystemTimer::get().ns_since_boot() - m_current->last_start_ns; add_current_to_most_loaded(m_current->blocked ? &m_block_queue : &m_run_queue); if (!m_current->blocked) m_run_queue.add_thread_to_back(m_current); - else - m_block_queue.add_thread_with_wake_time(m_current); + else if (m_block_queue.add_thread_with_wake_time(m_current)) + update_wake_up_deadline(); break; - } case Thread::State::NotStarted: ASSERT(!m_current->blocked); m_current->time_used_ns = 0; @@ -321,26 +316,50 @@ namespace Kernel } } + void Scheduler::update_wake_up_deadline() + { + ASSERT(Processor::get_interrupt_state() == InterruptState::Disabled); + + auto& interrupt_controller = InterruptController::get(); + + // TODO: support timer deadlines on non-apic timers and abstract it :) + if (!interrupt_controller.is_using_apic()) + return; + + wake_up_sleeping_threads(); + + uint64_t deadline_ns = m_next_reschedule_ns; + if (!m_block_queue.empty()) + deadline_ns = BAN::Math::min(deadline_ns, m_block_queue.front()->wake_time_ns); + if (Processor::is_smp_enabled()) + deadline_ns = BAN::Math::min(deadline_ns, m_last_load_balance_ns + s_load_balance_interval_ns); + + static_cast(interrupt_controller).set_timer_dealine(deadline_ns); + } + void Scheduler::reschedule_if_idle() { ASSERT(Processor::get_interrupt_state() == InterruptState::Disabled); - if (m_current != nullptr) - return; - - if (m_run_queue.empty()) - wake_up_sleeping_threads(); - - if (!m_run_queue.empty()) + if (m_current == nullptr && !m_run_queue.empty()) Processor::yield(); } - extern "C" void scheduler_on_yield(YieldRegisters* yield_registers) + extern "C" void scheduler_on_yield_trampoline(YieldRegisters* yield_registers) { - Processor::scheduler().reschedule(yield_registers); + Processor::scheduler().on_yield(yield_registers); } - void Scheduler::timer_interrupt() + void Scheduler::on_yield(YieldRegisters* yield_registers) + { + reschedule(yield_registers); + m_next_reschedule_ns = !is_idle() + ? SystemTimer::get().ns_since_boot() + s_reschedule_interval_ns + : BAN::numeric_limits::max(); + update_wake_up_deadline(); + } + + void Scheduler::on_timer_interrupt() { ASSERT(Processor::get_interrupt_state() == InterruptState::Disabled); @@ -349,11 +368,8 @@ namespace Kernel wake_up_sleeping_threads(); - if (SystemTimer::get().ns_since_boot() >= m_next_reschedule_ns) - { - m_next_reschedule_ns += s_reschedule_interval_ns; + if (is_idle() || SystemTimer::get().ns_since_boot() >= m_next_reschedule_ns) Processor::yield(); - } } void Scheduler::unblock_thread(SchedulerQueue::Node* node) @@ -394,8 +410,8 @@ namespace Kernel if (!node->blocked) m_run_queue.add_thread_to_back(node); - else - m_block_queue.add_thread_with_wake_time(node); + else if (m_block_queue.add_thread_with_wake_time(node)) + update_wake_up_deadline(); if (auto* thread = node->thread; thread->is_userspace() && thread->has_process()) thread->update_processor_index_address(); diff --git a/kernel/kernel/Timer/HPET.cpp b/kernel/kernel/Timer/HPET.cpp index fab39499..b3e67783 100644 --- a/kernel/kernel/Timer/HPET.cpp +++ b/kernel/kernel/Timer/HPET.cpp @@ -306,7 +306,7 @@ namespace Kernel SystemTimer::get().update_tsc(); if (should_invoke_scheduler()) - Processor::scheduler().timer_interrupt(); + Processor::scheduler().on_timer_interrupt(); } uint64_t HPET::ms_since_boot() const diff --git a/kernel/kernel/Timer/PIT.cpp b/kernel/kernel/Timer/PIT.cpp index 7447bca6..06f58677 100644 --- a/kernel/kernel/Timer/PIT.cpp +++ b/kernel/kernel/Timer/PIT.cpp @@ -61,7 +61,7 @@ namespace Kernel SystemTimer::get().update_tsc(); if (should_invoke_scheduler()) - Processor::scheduler().timer_interrupt(); + Processor::scheduler().on_timer_interrupt(); } uint64_t PIT::read_counter_ns() const