forked from Bananymous/banan-os
Kernel: Rewrite the whole scheduler and re-architecture SMP handling
Change Semaphore -> ThreadBlocker
This was not a semaphore, I just named it one because I didn't know
what semaphore was. I have meant to change this sooner, but it was in
no way urgent :D
Implement SMP events. Processors can now be sent SMP events through
IPIs. SMP events can be sent either to a single processor or broadcasted
to every processor.
PageTable::{map_page,map_range,unmap_page,unmap_range}() now send SMP
event to invalidate TLB caches for the changed pages.
Scheduler no longer uses a global run queue. Each processor has its own
scheduler that keeps track of the load on the processor. Once every
second schedulers do load balancing. Schedulers have no access to other
processors' schedulers, they just see approximate loads. If scheduler
decides that it has too much load, it will send a thread to another
processor through a SMP event.
Schedulers are currently run using the timer interrupt on BSB. This
should be not the case, and each processor should use its LAPIC timer
for interrupts. There is no reason to broadcast SMP event to all
processors when BSB gets timer interrupt.
Old scheduler only achieved 20% idle load on qemu. That was probably a
very inefficient implementation. This new scheduler seems to average
around 1% idle load. This is much closer to what I would expect. On my
own laptop idle load seems to be only around 0.5% on each processor.
This commit is contained in:
@@ -1,55 +1,149 @@
|
||||
#pragma once
|
||||
|
||||
#include <kernel/SchedulerQueue.h>
|
||||
#include <kernel/Semaphore.h>
|
||||
#include <kernel/Thread.h>
|
||||
#include <BAN/Array.h>
|
||||
#include <BAN/ForwardList.h>
|
||||
#include <BAN/NoCopyMove.h>
|
||||
#include <kernel/InterruptStack.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
namespace Kernel
|
||||
{
|
||||
|
||||
class Scheduler
|
||||
class Thread;
|
||||
class ThreadBlocker;
|
||||
|
||||
class SchedulerQueue
|
||||
{
|
||||
public:
|
||||
static BAN::ErrorOr<void> initialize();
|
||||
static Scheduler& get();
|
||||
struct Node
|
||||
{
|
||||
Node(Thread* thread)
|
||||
: thread(thread)
|
||||
{}
|
||||
|
||||
[[noreturn]] void start();
|
||||
Node* next { nullptr };
|
||||
Node* prev { nullptr };
|
||||
|
||||
void yield();
|
||||
Thread* thread;
|
||||
ThreadBlocker* blocker { nullptr };
|
||||
uint64_t wake_time_ns { static_cast<uint64_t>(-1) };
|
||||
|
||||
void timer_reschedule();
|
||||
void irq_reschedule();
|
||||
void reschedule_if_idling();
|
||||
uint64_t last_start_ns { 0 };
|
||||
uint64_t time_used_ns { 0 };
|
||||
};
|
||||
|
||||
void set_current_thread_sleeping(uint64_t wake_time);
|
||||
public:
|
||||
void add_thread_to_back(Node*);
|
||||
void add_thread_with_wake_time(Node*);
|
||||
template<typename F>
|
||||
Node* remove_with_condition(F callback);
|
||||
void remove_node(Node*);
|
||||
Node* front();
|
||||
Node* pop_front();
|
||||
|
||||
void block_current_thread(Semaphore*, uint64_t wake_time);
|
||||
void unblock_threads(Semaphore*);
|
||||
// Makes sleeping or blocked thread with tid active.
|
||||
bool empty() const { return m_head == nullptr; }
|
||||
|
||||
private:
|
||||
Node* m_head { nullptr };
|
||||
Node* m_tail { nullptr };
|
||||
};
|
||||
|
||||
class Scheduler
|
||||
{
|
||||
BAN_NON_COPYABLE(Scheduler);
|
||||
BAN_NON_MOVABLE(Scheduler);
|
||||
|
||||
public:
|
||||
struct NewThreadRequest
|
||||
{
|
||||
SchedulerQueue::Node* node;
|
||||
bool blocked;
|
||||
};
|
||||
|
||||
struct UnblockRequest
|
||||
{
|
||||
enum class Type
|
||||
{
|
||||
ThreadBlocker,
|
||||
ThreadID,
|
||||
};
|
||||
Type type;
|
||||
union
|
||||
{
|
||||
ThreadBlocker* blocker;
|
||||
pid_t tid;
|
||||
};
|
||||
};
|
||||
|
||||
public:
|
||||
static BAN::ErrorOr<Scheduler*> create();
|
||||
BAN::ErrorOr<void> initialize();
|
||||
|
||||
void reschedule(InterruptStack*, InterruptRegisters*);
|
||||
void reschedule_if_idle();
|
||||
|
||||
void timer_interrupt();
|
||||
|
||||
BAN::ErrorOr<void> add_thread(Thread*);
|
||||
|
||||
void block_current_thread(ThreadBlocker* thread_blocker, uint64_t wake_time_ns);
|
||||
void unblock_threads(ThreadBlocker*);
|
||||
void unblock_thread(pid_t tid);
|
||||
|
||||
Thread& current_thread();
|
||||
static pid_t current_tid();
|
||||
Thread& idle_thread();
|
||||
|
||||
// This is no return if called on current thread
|
||||
void terminate_thread(Thread*);
|
||||
pid_t current_tid() const;
|
||||
bool is_idle() const;
|
||||
|
||||
private:
|
||||
Scheduler() = default;
|
||||
|
||||
void set_current_thread_sleeping_impl(Semaphore* semaphore, uint64_t wake_time);
|
||||
void add_current_to_most_loaded(SchedulerQueue* target_queue);
|
||||
void update_most_loaded_node_queue(SchedulerQueue::Node*, SchedulerQueue* target_queue);
|
||||
void remove_node_from_most_loaded(SchedulerQueue::Node*);
|
||||
|
||||
void setup_next_thread();
|
||||
bool do_unblock(ThreadBlocker*);
|
||||
bool do_unblock(pid_t);
|
||||
void do_load_balancing();
|
||||
|
||||
BAN::ErrorOr<void> add_thread(Thread*);
|
||||
class ProcessorID find_least_loaded_processor() const;
|
||||
|
||||
void preempt();
|
||||
|
||||
void handle_unblock_request(const UnblockRequest&);
|
||||
void handle_new_thread_request(const NewThreadRequest&);
|
||||
|
||||
private:
|
||||
SpinLock m_lock;
|
||||
SchedulerQueue m_run_queue;
|
||||
SchedulerQueue m_block_queue;
|
||||
SchedulerQueue::Node* m_current { nullptr };
|
||||
bool m_current_will_block { false };
|
||||
|
||||
SchedulerQueue m_active_threads;
|
||||
SchedulerQueue m_blocking_threads;
|
||||
uint32_t m_thread_count { 0 };
|
||||
|
||||
friend class Process;
|
||||
InterruptStack* m_interrupt_stack { nullptr };
|
||||
InterruptRegisters* m_interrupt_registers { nullptr };
|
||||
|
||||
uint64_t m_last_reschedule_ns { 0 };
|
||||
uint64_t m_last_load_balance_ns { 0 };
|
||||
|
||||
struct ThreadInfo
|
||||
{
|
||||
SchedulerQueue* queue { nullptr };
|
||||
SchedulerQueue::Node* node { nullptr };
|
||||
};
|
||||
BAN::Array<ThreadInfo, 10> m_most_loaded_threads;
|
||||
|
||||
uint64_t m_idle_start_ns { 0 };
|
||||
uint64_t m_idle_ns { 0 };
|
||||
|
||||
bool m_should_calculate_max_load_threads { true };
|
||||
|
||||
Thread* m_idle_thread { nullptr };
|
||||
|
||||
friend class Processor;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user