Kernel: Rewrite whole scheduler
Current context saving was very hacky and dependant on compiler behaviour that was not consistent. Now we always use iret for context saving. This makes everything more clean.
This commit is contained in:
+125
-187
@@ -11,9 +11,6 @@
|
||||
namespace Kernel
|
||||
{
|
||||
|
||||
extern "C" [[noreturn]] void start_thread(uintptr_t sp, uintptr_t ip);
|
||||
extern "C" [[noreturn]] void continue_thread(uintptr_t sp, uintptr_t ip);
|
||||
|
||||
static Scheduler* s_instance = nullptr;
|
||||
static BAN::Atomic<bool> s_started { false };
|
||||
|
||||
@@ -46,10 +43,8 @@ namespace Kernel
|
||||
void Scheduler::start()
|
||||
{
|
||||
ASSERT(Processor::get_interrupt_state() == InterruptState::Disabled);
|
||||
m_lock.lock();
|
||||
s_started = true;
|
||||
advance_current_thread();
|
||||
execute_current_thread_locked();
|
||||
ASSERT(!m_active_threads.empty());
|
||||
yield();
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
@@ -71,41 +66,125 @@ namespace Kernel
|
||||
return Scheduler::get().current_thread().tid();
|
||||
}
|
||||
|
||||
void Scheduler::setup_next_thread()
|
||||
{
|
||||
ASSERT(m_lock.current_processor_has_lock());
|
||||
|
||||
if (auto* current = Processor::get_current_thread())
|
||||
{
|
||||
auto* thread = current->thread;
|
||||
|
||||
if (thread->state() == Thread::State::Terminated)
|
||||
{
|
||||
PageTable::kernel().load();
|
||||
delete thread;
|
||||
delete current;
|
||||
}
|
||||
else
|
||||
{
|
||||
// thread->state() can be NotStarted when calling exec or cleaning up process
|
||||
if (thread->state() != Thread::State::NotStarted)
|
||||
{
|
||||
thread->interrupt_stack() = Processor::get_interrupt_stack();
|
||||
thread->interrupt_stack().sp = thread->interrupt_sp();
|
||||
thread->interrupt_registers() = Processor::get_interrupt_registers();
|
||||
}
|
||||
|
||||
if (current->should_block)
|
||||
{
|
||||
current->should_block = false;
|
||||
m_blocking_threads.add_with_wake_time(current);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_active_threads.push_back(current);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SchedulerQueue::Node* node = nullptr;
|
||||
while (!m_active_threads.empty())
|
||||
{
|
||||
node = m_active_threads.pop_front();
|
||||
if (node->thread->state() != Thread::State::Terminated)
|
||||
break;
|
||||
|
||||
PageTable::kernel().load();
|
||||
delete node->thread;
|
||||
delete node;
|
||||
node = nullptr;
|
||||
}
|
||||
|
||||
Processor::set_current_thread(node);
|
||||
|
||||
auto* thread = node ? node->thread : Processor::idle_thread();
|
||||
|
||||
if (thread->has_process())
|
||||
thread->process().page_table().load();
|
||||
else
|
||||
PageTable::kernel().load();
|
||||
|
||||
if (thread->state() == Thread::State::NotStarted)
|
||||
thread->m_state = Thread::State::Executing;
|
||||
|
||||
ASSERT(thread->interrupt_stack().ip);
|
||||
ASSERT(thread->interrupt_stack().sp);
|
||||
|
||||
Processor::gdt().set_tss_stack(thread->kernel_stack_top());
|
||||
|
||||
Processor::get_interrupt_stack() = thread->interrupt_stack();
|
||||
Processor::get_interrupt_registers() = thread->interrupt_registers();
|
||||
}
|
||||
|
||||
void Scheduler::timer_reschedule()
|
||||
{
|
||||
// Broadcast IPI to all other processors for them
|
||||
// to perform reschedule
|
||||
InterruptController::get().broadcast_ipi();
|
||||
|
||||
auto state = m_lock.lock();
|
||||
m_blocking_threads.remove_with_wake_time(m_active_threads, SystemTimer::get().ms_since_boot());
|
||||
if (save_current_thread())
|
||||
return Processor::set_interrupt_state(state);
|
||||
advance_current_thread();
|
||||
execute_current_thread_locked();
|
||||
ASSERT_NOT_REACHED();
|
||||
{
|
||||
SpinLockGuard _(m_lock);
|
||||
m_blocking_threads.remove_with_wake_time(m_active_threads, SystemTimer::get().ms_since_boot());
|
||||
}
|
||||
|
||||
yield();
|
||||
}
|
||||
|
||||
void Scheduler::reschedule()
|
||||
void Scheduler::yield()
|
||||
{
|
||||
auto state = m_lock.lock();
|
||||
if (save_current_thread())
|
||||
return Processor::set_interrupt_state(state);
|
||||
advance_current_thread();
|
||||
execute_current_thread_locked();
|
||||
ASSERT_NOT_REACHED();
|
||||
auto state = Processor::get_interrupt_state();
|
||||
Processor::set_interrupt_state(InterruptState::Disabled);
|
||||
|
||||
asm volatile(
|
||||
"movq %%rsp, %[save_sp];"
|
||||
"movq %[load_sp], %%rsp;"
|
||||
"int %[ipi];"
|
||||
: [save_sp]"=m"(Thread::current().interrupt_sp())
|
||||
: [load_sp]"r"(Processor::current_stack_top()),
|
||||
[ipi]"i"(IRQ_VECTOR_BASE + IRQ_IPI)
|
||||
: "memory"
|
||||
);
|
||||
|
||||
Processor::set_interrupt_state(state);
|
||||
}
|
||||
|
||||
void Scheduler::irq_reschedule()
|
||||
{
|
||||
SpinLockGuard _(m_lock);
|
||||
setup_next_thread();
|
||||
}
|
||||
|
||||
void Scheduler::reschedule_if_idling()
|
||||
{
|
||||
auto state = m_lock.lock();
|
||||
if (m_active_threads.empty() || Processor::get_current_thread())
|
||||
return m_lock.unlock(state);
|
||||
if (save_current_thread())
|
||||
return Processor::set_interrupt_state(state);
|
||||
advance_current_thread();
|
||||
execute_current_thread_locked();
|
||||
ASSERT_NOT_REACHED();
|
||||
{
|
||||
SpinLockGuard _(m_lock);
|
||||
if (Processor::get_current_thread())
|
||||
return;
|
||||
if (m_active_threads.empty())
|
||||
return;
|
||||
}
|
||||
|
||||
yield();
|
||||
}
|
||||
|
||||
BAN::ErrorOr<void> Scheduler::add_thread(Thread* thread)
|
||||
@@ -120,190 +199,49 @@ namespace Kernel
|
||||
|
||||
void Scheduler::terminate_thread(Thread* thread)
|
||||
{
|
||||
SpinLockGuard _(m_lock);
|
||||
auto state = m_lock.lock();
|
||||
|
||||
ASSERT(thread->state() == Thread::State::Executing);
|
||||
thread->m_state = Thread::State::Terminated;
|
||||
if (thread == ¤t_thread())
|
||||
execute_current_thread_locked();
|
||||
}
|
||||
thread->interrupt_stack().sp = Processor::current_stack_top();
|
||||
|
||||
void Scheduler::advance_current_thread()
|
||||
{
|
||||
ASSERT(m_lock.current_processor_has_lock());
|
||||
m_lock.unlock(InterruptState::Disabled);
|
||||
|
||||
if (auto* current = Processor::get_current_thread())
|
||||
m_active_threads.push_back(current);
|
||||
Processor::set_current_thread(nullptr);
|
||||
// actual deletion will be done while rescheduling
|
||||
|
||||
if (!m_active_threads.empty())
|
||||
Processor::set_current_thread(m_active_threads.pop_front());
|
||||
}
|
||||
|
||||
// NOTE: this is declared always inline, so we don't corrupt the stack
|
||||
// after getting the rsp
|
||||
ALWAYS_INLINE bool Scheduler::save_current_thread()
|
||||
{
|
||||
ASSERT(m_lock.current_processor_has_lock());
|
||||
|
||||
uintptr_t sp, ip;
|
||||
push_callee_saved();
|
||||
if (!(ip = read_ip()))
|
||||
if (¤t_thread() == thread)
|
||||
{
|
||||
pop_callee_saved();
|
||||
return true;
|
||||
}
|
||||
read_rsp(sp);
|
||||
|
||||
Thread& current = current_thread();
|
||||
current.set_ip(ip);
|
||||
current.set_sp(sp);
|
||||
|
||||
load_temp_stack();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Scheduler::delete_current_process_and_thread()
|
||||
{
|
||||
m_lock.lock();
|
||||
|
||||
load_temp_stack();
|
||||
PageTable::kernel().load();
|
||||
|
||||
auto* current = Processor::get_current_thread();
|
||||
ASSERT(current);
|
||||
delete ¤t->thread->process();
|
||||
delete current->thread;
|
||||
delete current;
|
||||
Processor::set_current_thread(nullptr);
|
||||
|
||||
advance_current_thread();
|
||||
execute_current_thread_locked();
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
void Scheduler::execute_current_thread()
|
||||
{
|
||||
m_lock.lock();
|
||||
load_temp_stack();
|
||||
PageTable::kernel().load();
|
||||
execute_current_thread_stack_loaded();
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
void Scheduler::execute_current_thread_locked()
|
||||
{
|
||||
ASSERT(m_lock.current_processor_has_lock());
|
||||
load_temp_stack();
|
||||
PageTable::kernel().load();
|
||||
execute_current_thread_stack_loaded();
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
NEVER_INLINE void Scheduler::execute_current_thread_stack_loaded()
|
||||
{
|
||||
ASSERT(m_lock.current_processor_has_lock());
|
||||
|
||||
#if SCHEDULER_VERIFY_STACK
|
||||
vaddr_t rsp;
|
||||
read_rsp(rsp);
|
||||
ASSERT(Processor::current_stack_bottom() <= rsp && rsp <= Processor::current_stack_top());
|
||||
ASSERT(&PageTable::current() == &PageTable::kernel());
|
||||
#endif
|
||||
|
||||
Thread* current = ¤t_thread();
|
||||
|
||||
#if __enable_sse
|
||||
if (current != Thread::sse_thread())
|
||||
{
|
||||
#if ARCH(x86_64)
|
||||
asm volatile(
|
||||
"movq %cr0, %rax;"
|
||||
"orq $(1 << 3), %rax;"
|
||||
"movq %rax, %cr0"
|
||||
);
|
||||
#elif ARCH(i686)
|
||||
asm volatile(
|
||||
"movl %cr0, %eax;"
|
||||
"orl $(1 << 3), %eax;"
|
||||
"movl %eax, %cr0"
|
||||
);
|
||||
#else
|
||||
#error
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
while (current->state() == Thread::State::Terminated)
|
||||
{
|
||||
auto* node = Processor::get_current_thread();
|
||||
if (node->thread->has_process())
|
||||
if (node->thread->process().on_thread_exit(*node->thread))
|
||||
break;
|
||||
|
||||
delete node->thread;
|
||||
delete node;
|
||||
Processor::set_current_thread(nullptr);
|
||||
|
||||
advance_current_thread();
|
||||
current = ¤t_thread();
|
||||
yield();
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
if (current->has_process())
|
||||
{
|
||||
current->process().page_table().load();
|
||||
Processor::gdt().set_tss_stack(current->kernel_stack_top());
|
||||
}
|
||||
else
|
||||
PageTable::kernel().load();
|
||||
|
||||
switch (current->state())
|
||||
{
|
||||
case Thread::State::NotStarted:
|
||||
current->set_started();
|
||||
m_lock.unlock(InterruptState::Disabled);
|
||||
start_thread(current->sp(), current->ip());
|
||||
case Thread::State::Executing:
|
||||
m_lock.unlock(InterruptState::Disabled);
|
||||
while (current->can_add_signal_to_execute())
|
||||
current->handle_signal();
|
||||
continue_thread(current->sp(), current->ip());
|
||||
case Thread::State::Terminated:
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
ASSERT_NOT_REACHED();
|
||||
Processor::set_interrupt_state(state);
|
||||
}
|
||||
|
||||
void Scheduler::set_current_thread_sleeping_impl(Semaphore* semaphore, uint64_t wake_time)
|
||||
{
|
||||
ASSERT(m_lock.current_processor_has_lock());
|
||||
|
||||
if (save_current_thread())
|
||||
return;
|
||||
auto state = m_lock.lock();
|
||||
|
||||
auto* current = Processor::get_current_thread();
|
||||
current->semaphore = semaphore;
|
||||
current->wake_time = wake_time;
|
||||
m_blocking_threads.add_with_wake_time(current);
|
||||
Processor::set_current_thread(nullptr);
|
||||
current->should_block = true;
|
||||
|
||||
advance_current_thread();
|
||||
execute_current_thread_locked();
|
||||
ASSERT_NOT_REACHED();
|
||||
m_lock.unlock(InterruptState::Disabled);
|
||||
|
||||
yield();
|
||||
|
||||
Processor::set_interrupt_state(state);
|
||||
}
|
||||
|
||||
void Scheduler::set_current_thread_sleeping(uint64_t wake_time)
|
||||
{
|
||||
auto state = m_lock.lock();
|
||||
set_current_thread_sleeping_impl(nullptr, wake_time);
|
||||
Processor::set_interrupt_state(state);
|
||||
}
|
||||
|
||||
void Scheduler::block_current_thread(Semaphore* semaphore, uint64_t wake_time)
|
||||
{
|
||||
auto state = m_lock.lock();
|
||||
set_current_thread_sleeping_impl(semaphore, wake_time);
|
||||
Processor::set_interrupt_state(state);
|
||||
}
|
||||
|
||||
void Scheduler::unblock_threads(Semaphore* semaphore)
|
||||
|
||||
Reference in New Issue
Block a user