From 5b5e620d8aad7f591741b02e1bccc6517f39499e Mon Sep 17 00:00:00 2001 From: Bananymous Date: Thu, 2 Feb 2023 23:24:12 +0200 Subject: [PATCH] Kernel: Improve multithreading support We can now use arbitary BAN::function as the Thread. I also implemented multithreading for i386 since it was not done on the initial multithreading commit. --- kernel/arch/i386/Thread.S | 38 ++++++++++++++ kernel/arch/i386/make.config | 1 + kernel/arch/x86_64/Thread.S | 32 ++++++++++++ kernel/arch/x86_64/make.config | 1 + kernel/include/kernel/Scheduler.h | 24 ++++++--- kernel/include/kernel/Thread.h | 30 ++++++++--- kernel/kernel/Scheduler.cpp | 82 +++++++++---------------------- kernel/kernel/Thread.cpp | 38 ++++++++------ 8 files changed, 158 insertions(+), 88 deletions(-) create mode 100644 kernel/arch/i386/Thread.S create mode 100644 kernel/arch/x86_64/Thread.S diff --git a/kernel/arch/i386/Thread.S b/kernel/arch/i386/Thread.S new file mode 100644 index 0000000000..ab55387465 --- /dev/null +++ b/kernel/arch/i386/Thread.S @@ -0,0 +1,38 @@ +# uint32_t read_rip() +.global read_rip +read_rip: + popl %eax + jmp *%eax + +exit_thread_trampoline: + addl $16, %esp + popl %eax + pushl $0x696969 + pushl %eax + ret + +# void start_thread(uint32_t arg0, uint32_t arg1, uint32_t arg2, uint32_t arg3, uint32_t rsp, uint32_t rbp, uint32_t rip) +.global start_thread +start_thread: + movl %esp, %eax + movl 28(%eax), %ecx + movl 24(%eax), %ebp + movl 20(%eax), %esp + + pushl 16(%eax) + pushl 12(%eax) + pushl 8(%eax) + pushl 4(%eax) + pushl $exit_thread_trampoline + + sti + jmp *%ecx + +# void continue_thread(uint32_t rsp, uint32_t rbp, uint32_t rip) +.global continue_thread +continue_thread: + movl 12(%esp), %ecx + movl 8(%esp), %ebp + movl 4(%esp), %esp + movl $0, %eax + jmp *%ecx \ No newline at end of file diff --git a/kernel/arch/i386/make.config b/kernel/arch/i386/make.config index ab57708aa2..fa74f25bc4 100644 --- a/kernel/arch/i386/make.config +++ b/kernel/arch/i386/make.config @@ -8,4 +8,5 @@ $(ARCHDIR)/boot.o \ $(ARCHDIR)/IDT.o \ $(ARCHDIR)/MMU.o \ $(ARCHDIR)/SpinLock.o \ +$(ARCHDIR)/Thread.o \ \ No newline at end of file diff --git a/kernel/arch/x86_64/Thread.S b/kernel/arch/x86_64/Thread.S new file mode 100644 index 0000000000..ef616922f7 --- /dev/null +++ b/kernel/arch/x86_64/Thread.S @@ -0,0 +1,32 @@ +# uint64_t read_rip() +.global read_rip +read_rip: + popq %rax + jmp *%rax + +.global get_thread_at_exit +get_thread_at_exit: + movq 8(%rdi), %rax + ret + +exit_thread_trampoline: + movq 8(%rsp), %rdi + ret + +# void start_thread(uint64_t arg0, uint64_t arg1, uint64_t arg2, uint64_t arg3, uint64_t rsp, uint64_t rbp, uint64_t rip) +.global start_thread +start_thread: + movq 8(%rsp), %rcx + movq %r8, %rsp + movq %r9, %rbp + pushq $exit_thread_trampoline + sti + jmp *%rcx + +# void continue_thread(uint64_t rsp, uint64_t rbp, uint64_t rip) +.global continue_thread +continue_thread: + movq %rdi, %rsp + movq %rsi, %rbp + movq $0, %rax + jmp *%rdx \ No newline at end of file diff --git a/kernel/arch/x86_64/make.config b/kernel/arch/x86_64/make.config index cd4839a95d..e79262b03c 100644 --- a/kernel/arch/x86_64/make.config +++ b/kernel/arch/x86_64/make.config @@ -9,4 +9,5 @@ $(ARCHDIR)/IDT.o \ $(ARCHDIR)/interrupts.o \ $(ARCHDIR)/MMU.o \ $(ARCHDIR)/SpinLock.o \ +$(ARCHDIR)/Thread.o \ \ No newline at end of file diff --git a/kernel/include/kernel/Scheduler.h b/kernel/include/kernel/Scheduler.h index b66e94c7f6..ecd0fffdba 100644 --- a/kernel/include/kernel/Scheduler.h +++ b/kernel/include/kernel/Scheduler.h @@ -13,16 +13,26 @@ namespace Kernel BAN_NON_MOVABLE(Scheduler); public: - static void Initialize(); - static Scheduler& Get(); + static void initialize(); + static Scheduler& get(); - const Thread& CurrentThread() const; + const Thread& current_thread() const; - void AddThread(void(*)()); - void Switch(); - void Start(); + template + void add_thread(const BAN::Function& func, Args... args) + { + uintptr_t flags; + asm volatile("pushf; pop %0" : "=r"(flags)); + asm volatile("cli"); + MUST(m_threads.emplace_back(func, BAN::forward(args)...)); + if (flags & (1 << 9)) + asm volatile("sti"); + } - static constexpr size_t ms_between_switch = 4; + void switch_thread(); + void start(); + + static constexpr size_t ms_between_switch = 1; private: Scheduler() {} diff --git a/kernel/include/kernel/Thread.h b/kernel/include/kernel/Thread.h index 0fd57e517a..e3f5e1098a 100644 --- a/kernel/include/kernel/Thread.h +++ b/kernel/include/kernel/Thread.h @@ -1,6 +1,7 @@ #pragma once -#include +#include +#include namespace Kernel { @@ -20,28 +21,45 @@ namespace Kernel }; public: - Thread(void(*)()); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpmf-conversions" + template + Thread(const BAN::Function& func, Args... args) + : Thread((uintptr_t)(void*)&BAN::Function::operator(), (uintptr_t)&func, ((uintptr_t)args)...) + { + static_assert(((BAN::is_integral_v || BAN::is_pointer_v) && ...)); + } +#pragma GCC diagnostic pop + ~Thread(); uint32_t id() const { return m_id; } - void set_rip(uintptr_t rip) { m_rip = rip; } void set_rsp(uintptr_t rsp) { m_rsp = rsp; } + void set_rbp(uintptr_t rbp) { m_rbp = rbp; } + void set_rip(uintptr_t rip) { m_rip = rip; } void set_state(State state) { m_state = state; } - uintptr_t rip() const { return m_rip; } uintptr_t rsp() const { return m_rsp; } + uintptr_t rbp() const { return m_rbp; } + uintptr_t rip() const { return m_rip; } State state() const { return m_state; } + const uintptr_t* args() const { return m_args; } + private: - static void on_exit(); + Thread(uintptr_t rip, uintptr_t func, uintptr_t arg1 = 0, uintptr_t arg2 = 0, uintptr_t arg3 = 0); + void on_exit(); private: void* m_stack_base = nullptr; State m_state = State::NotStarted; + uintptr_t m_args[4] = {}; uintptr_t m_rip = 0; + uintptr_t m_rbp = 0; uintptr_t m_rsp = 0; const uint32_t m_id = 0; + + alignas(max_align_t) uint8_t m_function[BAN::Function::size()] { 0 }; }; - } \ No newline at end of file diff --git a/kernel/kernel/Scheduler.cpp b/kernel/kernel/Scheduler.cpp index 65e075e20f..67777ca390 100644 --- a/kernel/kernel/Scheduler.cpp +++ b/kernel/kernel/Scheduler.cpp @@ -7,18 +7,9 @@ namespace Kernel static Scheduler* s_instance = nullptr; + extern "C" void start_thread(uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3, uintptr_t rsp, uintptr_t rbp, uintptr_t rip); + extern "C" void continue_thread(uintptr_t rsp, uintptr_t rbp, uintptr_t rip); extern "C" uintptr_t read_rip(); - asm( - ".global read_rip;" - "read_rip:" -#if ARCH(x86_64) - "popq %rax;" - "jmp *%rax" -#else - "popl %eax;" - "jmp *%eax" -#endif - ); void Scheduler::initialize() { @@ -37,13 +28,14 @@ namespace Kernel return *m_current_iterator; } - //void Scheduler::AddThread(const BAN::Function& function) - //{ - // MUST(m_threads.EmplaceBack(function)); - //} - void Scheduler::switch_thread() { + uintptr_t rsp, rbp, rip; + if (!(rip = read_rip())) + return; + read_rsp(rsp); + read_rbp(rbp); + static uint8_t cnt = 0; if (cnt++ % ms_between_switch) return; @@ -63,6 +55,8 @@ namespace Kernel Thread& current = *m_current_iterator; Thread& next = *next_iterator; + ASSERT(next.state() == Thread::State::Paused || next.state() == Thread::State::NotStarted); + if (current.state() == Thread::State::Done) { // NOTE: this does not invalidate the next/next_iterator @@ -71,21 +65,11 @@ namespace Kernel m_current_iterator = decltype(m_threads)::iterator(); } - uintptr_t rip = read_rip(); - if (rip == 0) - return; - - uintptr_t rsp; -#if ARCH(x86_64) - asm volatile("movq %%rsp, %0" : "=r"(rsp)); -#else - asm volatile("movl %%esp, %0" : "=r"(rsp)); -#endif - if (m_current_iterator) { - current.set_rip(rip); current.set_rsp(rsp); + current.set_rbp(rbp); + current.set_rip(rip); current.set_state(Thread::State::Paused); } @@ -93,33 +77,16 @@ namespace Kernel if (next.state() == Thread::State::NotStarted) { - InterruptController::Get().EOI(PIT_IRQ); + InterruptController::get().eoi(PIT_IRQ); next.set_state(Thread::State::Running); - asm volatile( -#if ARCH(x86_64) - "movq %0, %%rsp;" -#else - "movl %0, %%esp;" -#endif - "sti;" - "jmp *%1;" - :: "r"(next.rsp()), "r"(next.rip()) - ); + const uintptr_t* args = next.args(); + start_thread(args[0], args[1], args[2], args[3], next.rsp(), next.rbp(), next.rip()); } else if (next.state() == Thread::State::Paused) { next.set_state(Thread::State::Running); - asm volatile( -#if ARCH(x86_64) - "movq %0, %%rsp;" - "movq $0, %%rax;" -#else - "movl %0, %%esp;" - "movl $0, %%eax;" -#endif - "jmp *%1;" - :: "r"(next.rsp()), "r"(next.rip()) - ); + BOCHS_BREAK(); + continue_thread(next.rsp(), next.rbp(), next.rip()); } ASSERT(false); @@ -134,16 +101,11 @@ namespace Kernel Thread& current = *m_current_iterator; ASSERT(current.state() == Thread::State::NotStarted); current.set_state(Thread::State::Running); - asm volatile( -#if ARCH(x86_64) - "movq %0, %%rsp;" -#else - "movl %0, %%esp;" -#endif - "sti;" - "jmp *%1;" - :: "r"(current.rsp()), "r"(current.rip()) - ); + + const uintptr_t* args = current.args(); + start_thread(args[0], args[1], args[2], args[3], current.rsp(), current.rbp(), current.rip()); + + ASSERT(false); } } \ No newline at end of file diff --git a/kernel/kernel/Thread.cpp b/kernel/kernel/Thread.cpp index 5d99d6244f..8ed19fb6ff 100644 --- a/kernel/kernel/Thread.cpp +++ b/kernel/kernel/Thread.cpp @@ -14,23 +14,37 @@ namespace Kernel static constexpr size_t thread_stack_size = PAGE_SIZE; - template + template static void write_to_stack(uintptr_t& rsp, const T& value) { - rsp -= sizeof(T); - *(T*)rsp = value; + rsp -= size; + memcpy((void*)rsp, (void*)&value, size); } - Thread::Thread(void(*function)()) + Thread::Thread(uintptr_t rip, uintptr_t func, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3) : m_id(s_next_id++) { m_stack_base = kmalloc(thread_stack_size, PAGE_SIZE); ASSERT(m_stack_base); + + m_rbp = (uintptr_t)m_stack_base + thread_stack_size; + m_rsp = m_rbp; + m_rip = rip; + m_args[1] = arg1; + m_args[2] = arg2; + m_args[3] = arg3; - m_rip = (uintptr_t)function; - m_rsp = (uintptr_t)m_stack_base + thread_stack_size; - write_to_stack(m_rsp, this); - write_to_stack(m_rsp, &Thread::on_exit); + // NOTE: in System V ABI arg0 is the pointer to 'this' + // we copy the function object to Thread object + // so we can ensure the lifetime of it. We store + // it as raw bytes so that Thread can be non-templated. + // This requires BAN::Function to be trivially copyable + // but for now it should be. + memcpy(m_function, (void*)func, sizeof(m_function)); + m_args[0] = (uintptr_t)m_function; + + write_to_stack(m_rsp, this); + write_to_stack(m_rsp, &Thread::on_exit); } Thread::~Thread() @@ -40,13 +54,7 @@ namespace Kernel void Thread::on_exit() { - Thread* thread = nullptr; -#if ARCH(x86_64) - asm volatile("movq (%%rsp), %0" : "=r"(thread)); -#else - asm volatile("movl (%%esp), %0" : "=r"(thread)); -#endif - thread->m_state = State::Done; + m_state = State::Done; for (;;) asm volatile("hlt"); }