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 ;)
This commit is contained in:
@@ -12,7 +12,7 @@ asm_yield_trampoline:
|
|||||||
pushl %ebp
|
pushl %ebp
|
||||||
|
|
||||||
pushl %esp
|
pushl %esp
|
||||||
call scheduler_on_yield
|
call scheduler_on_yield_trampoline
|
||||||
addl $4, %esp
|
addl $4, %esp
|
||||||
|
|
||||||
popl %ebp
|
popl %ebp
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ asm_yield_trampoline:
|
|||||||
pushq %r15
|
pushq %r15
|
||||||
|
|
||||||
movq %rsp, %rdi
|
movq %rsp, %rdi
|
||||||
call scheduler_on_yield
|
call scheduler_on_yield_trampoline
|
||||||
|
|
||||||
popq %r15
|
popq %r15
|
||||||
popq %r14
|
popq %r14
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ namespace Kernel
|
|||||||
BAN::ErrorOr<uint8_t> reserve_gsi(uint32_t gsi);
|
BAN::ErrorOr<uint8_t> reserve_gsi(uint32_t gsi);
|
||||||
|
|
||||||
void initialize_timer();
|
void initialize_timer();
|
||||||
|
void set_timer_dealine(uint64_t ns);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint32_t read_from_local_apic(ptrdiff_t);
|
uint32_t read_from_local_apic(ptrdiff_t);
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ namespace Kernel
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
void add_thread_to_back(Node*);
|
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<typename F>
|
template<typename F>
|
||||||
Node* remove_with_condition(F callback);
|
Node* remove_with_condition(F callback);
|
||||||
void remove_node(Node*);
|
void remove_node(Node*);
|
||||||
@@ -60,7 +60,8 @@ namespace Kernel
|
|||||||
void reschedule(YieldRegisters*);
|
void reschedule(YieldRegisters*);
|
||||||
void reschedule_if_idle();
|
void reschedule_if_idle();
|
||||||
|
|
||||||
void timer_interrupt();
|
void on_timer_interrupt();
|
||||||
|
void on_yield(YieldRegisters*);
|
||||||
|
|
||||||
static BAN::ErrorOr<void> bind_thread_to_processor(Thread*, ProcessorID);
|
static BAN::ErrorOr<void> bind_thread_to_processor(Thread*, ProcessorID);
|
||||||
// if thread is already bound, this will never fail
|
// 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 update_most_loaded_node_queue(SchedulerQueue::Node*, SchedulerQueue* target_queue);
|
||||||
void remove_node_from_most_loaded(SchedulerQueue::Node*);
|
void remove_node_from_most_loaded(SchedulerQueue::Node*);
|
||||||
|
|
||||||
|
void update_wake_up_deadline();
|
||||||
void wake_up_sleeping_threads();
|
void wake_up_sleeping_threads();
|
||||||
|
|
||||||
void do_load_balancing();
|
void do_load_balancing();
|
||||||
|
|||||||
@@ -437,8 +437,27 @@ namespace Kernel
|
|||||||
|
|
||||||
dprintln("CPU {}: lapic timer frequency: {} Hz", Kernel::Processor::current_id(), m_lapic_timer_frequency_hz);
|
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_LVT, TimerModeOneShot | IRQ_TIMER);
|
||||||
write_to_local_apic(LAPIC_TIMER_INITIAL_REG, m_lapic_timer_frequency_hz / 2 / 100);
|
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<uint64_t>::max() / effective_freq;
|
||||||
|
return delta_ns;
|
||||||
|
}();
|
||||||
|
|
||||||
|
const uint64_t ticks = BAN::Math::div_round_up<uint64_t>(delta_ns * effective_freq, 1'000'000'000);
|
||||||
|
write_to_local_apic(LAPIC_TIMER_INITIAL_REG, BAN::Math::min<uint64_t>(ticks, BAN::numeric_limits<uint32_t>::max()));
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t APIC::read_from_local_apic(ptrdiff_t offset)
|
uint32_t APIC::read_from_local_apic(ptrdiff_t offset)
|
||||||
|
|||||||
@@ -435,7 +435,7 @@ namespace Kernel
|
|||||||
if (Processor::current_is_bsp())
|
if (Processor::current_is_bsp())
|
||||||
Process::update_alarm_queue();
|
Process::update_alarm_queue();
|
||||||
|
|
||||||
Processor::scheduler().timer_interrupt();
|
Processor::scheduler().on_timer_interrupt();
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void cpp_irq_handler(uint32_t irq)
|
extern "C" void cpp_irq_handler(uint32_t irq)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include <BAN/Optional.h>
|
#include <BAN/Optional.h>
|
||||||
#include <BAN/Sort.h>
|
#include <BAN/Sort.h>
|
||||||
|
#include <kernel/APIC.h>
|
||||||
#include <kernel/InterruptController.h>
|
#include <kernel/InterruptController.h>
|
||||||
#include <kernel/Lock/Mutex.h>
|
#include <kernel/Lock/Mutex.h>
|
||||||
#include <kernel/Process.h>
|
#include <kernel/Process.h>
|
||||||
@@ -45,12 +46,15 @@ namespace Kernel
|
|||||||
m_tail = node;
|
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);
|
ASSERT(Processor::get_interrupt_state() == InterruptState::Disabled);
|
||||||
|
|
||||||
if (m_tail == nullptr || node->wake_time_ns >= m_tail->wake_time_ns)
|
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* next = m_head;
|
||||||
Node* prev = nullptr;
|
Node* prev = nullptr;
|
||||||
@@ -64,6 +68,8 @@ namespace Kernel
|
|||||||
node->prev = prev;
|
node->prev = prev;
|
||||||
(next ? next->prev : m_tail) = node;
|
(next ? next->prev : m_tail) = node;
|
||||||
(prev ? prev->next : m_head) = node;
|
(prev ? prev->next : m_head) = node;
|
||||||
|
|
||||||
|
return node == m_head;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename F>
|
template<typename F>
|
||||||
@@ -122,15 +128,9 @@ namespace Kernel
|
|||||||
m_idle_thread = TRY(Thread::create_kernel([](void*) { asm volatile("1: hlt; jmp 1b"); }, nullptr));
|
m_idle_thread = TRY(Thread::create_kernel([](void*) { asm volatile("1: hlt; jmp 1b"); }, nullptr));
|
||||||
ASSERT(m_idle_thread);
|
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
|
// 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_last_load_balance_ns = s_load_balance_interval_ns * Processor::current_index() / Processor::count();
|
||||||
m_idle_ns = -m_last_load_balance_ns;
|
m_idle_ns = m_last_load_balance_ns;
|
||||||
|
|
||||||
s_schedulers_initialized++;
|
s_schedulers_initialized++;
|
||||||
while (s_schedulers_initialized < Processor::count())
|
while (s_schedulers_initialized < Processor::count())
|
||||||
@@ -139,8 +139,6 @@ namespace Kernel
|
|||||||
if (Processor::count() > 1)
|
if (Processor::count() > 1)
|
||||||
Processor::set_smp_enabled();
|
Processor::set_smp_enabled();
|
||||||
|
|
||||||
m_next_reschedule_ns = SystemTimer::get().ns_since_boot();
|
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,17 +230,14 @@ namespace Kernel
|
|||||||
m_thread_count--;
|
m_thread_count--;
|
||||||
break;
|
break;
|
||||||
case Thread::State::Executing:
|
case Thread::State::Executing:
|
||||||
{
|
|
||||||
const uint64_t current_ns = SystemTimer::get().ns_since_boot();
|
|
||||||
m_current->thread->yield_registers() = *yield_registers;
|
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);
|
add_current_to_most_loaded(m_current->blocked ? &m_block_queue : &m_run_queue);
|
||||||
if (!m_current->blocked)
|
if (!m_current->blocked)
|
||||||
m_run_queue.add_thread_to_back(m_current);
|
m_run_queue.add_thread_to_back(m_current);
|
||||||
else
|
else if (m_block_queue.add_thread_with_wake_time(m_current))
|
||||||
m_block_queue.add_thread_with_wake_time(m_current);
|
update_wake_up_deadline();
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
case Thread::State::NotStarted:
|
case Thread::State::NotStarted:
|
||||||
ASSERT(!m_current->blocked);
|
ASSERT(!m_current->blocked);
|
||||||
m_current->time_used_ns = 0;
|
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<APIC&>(interrupt_controller).set_timer_dealine(deadline_ns);
|
||||||
|
}
|
||||||
|
|
||||||
void Scheduler::reschedule_if_idle()
|
void Scheduler::reschedule_if_idle()
|
||||||
{
|
{
|
||||||
ASSERT(Processor::get_interrupt_state() == InterruptState::Disabled);
|
ASSERT(Processor::get_interrupt_state() == InterruptState::Disabled);
|
||||||
|
|
||||||
if (m_current != nullptr)
|
if (m_current == nullptr && !m_run_queue.empty())
|
||||||
return;
|
|
||||||
|
|
||||||
if (m_run_queue.empty())
|
|
||||||
wake_up_sleeping_threads();
|
|
||||||
|
|
||||||
if (!m_run_queue.empty())
|
|
||||||
Processor::yield();
|
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<uint64_t>::max();
|
||||||
|
update_wake_up_deadline();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Scheduler::on_timer_interrupt()
|
||||||
{
|
{
|
||||||
ASSERT(Processor::get_interrupt_state() == InterruptState::Disabled);
|
ASSERT(Processor::get_interrupt_state() == InterruptState::Disabled);
|
||||||
|
|
||||||
@@ -349,11 +368,8 @@ namespace Kernel
|
|||||||
|
|
||||||
wake_up_sleeping_threads();
|
wake_up_sleeping_threads();
|
||||||
|
|
||||||
if (SystemTimer::get().ns_since_boot() >= m_next_reschedule_ns)
|
if (is_idle() || SystemTimer::get().ns_since_boot() >= m_next_reschedule_ns)
|
||||||
{
|
|
||||||
m_next_reschedule_ns += s_reschedule_interval_ns;
|
|
||||||
Processor::yield();
|
Processor::yield();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Scheduler::unblock_thread(SchedulerQueue::Node* node)
|
void Scheduler::unblock_thread(SchedulerQueue::Node* node)
|
||||||
@@ -394,8 +410,8 @@ namespace Kernel
|
|||||||
|
|
||||||
if (!node->blocked)
|
if (!node->blocked)
|
||||||
m_run_queue.add_thread_to_back(node);
|
m_run_queue.add_thread_to_back(node);
|
||||||
else
|
else if (m_block_queue.add_thread_with_wake_time(node))
|
||||||
m_block_queue.add_thread_with_wake_time(node);
|
update_wake_up_deadline();
|
||||||
|
|
||||||
if (auto* thread = node->thread; thread->is_userspace() && thread->has_process())
|
if (auto* thread = node->thread; thread->is_userspace() && thread->has_process())
|
||||||
thread->update_processor_index_address();
|
thread->update_processor_index_address();
|
||||||
|
|||||||
@@ -306,7 +306,7 @@ namespace Kernel
|
|||||||
SystemTimer::get().update_tsc();
|
SystemTimer::get().update_tsc();
|
||||||
|
|
||||||
if (should_invoke_scheduler())
|
if (should_invoke_scheduler())
|
||||||
Processor::scheduler().timer_interrupt();
|
Processor::scheduler().on_timer_interrupt();
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t HPET::ms_since_boot() const
|
uint64_t HPET::ms_since_boot() const
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ namespace Kernel
|
|||||||
SystemTimer::get().update_tsc();
|
SystemTimer::get().update_tsc();
|
||||||
|
|
||||||
if (should_invoke_scheduler())
|
if (should_invoke_scheduler())
|
||||||
Processor::scheduler().timer_interrupt();
|
Processor::scheduler().on_timer_interrupt();
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t PIT::read_counter_ns() const
|
uint64_t PIT::read_counter_ns() const
|
||||||
|
|||||||
Reference in New Issue
Block a user