Kernel: Implement somewhat functioning multithread support

This still uses only a single cpu, but we can now have 'parallelization'

This seems to work fine in qemu and bochs, but my own computer did not
like this when I last tried.

I have absolutely no idea how multithreading should actually be
implmemented and I just thought and implemented the most simple one I
could think of. This might not be in any way correct :D
This commit is contained in:
Bananymous 2023-02-01 01:53:35 +02:00
parent 7d8aafa0b5
commit 6a9d60a8fb
10 changed files with 320 additions and 18 deletions

View File

@ -43,10 +43,12 @@ kernel/kmalloc.o \
kernel/PIC.o \
kernel/PIT.o \
kernel/RTC.o \
kernel/Scheduler.o \
kernel/Serial.o \
kernel/Shell.o \
kernel/SpinLock.o \
kernel/SSP.o \
kernel/Thread.o \
kernel/TTY.o \
kernel/VesaTerminalDriver.o \
icxxabi.o \

View File

@ -2,6 +2,8 @@
#include <stdint.h>
#define PIT_IRQ 0
namespace PIT
{

View File

@ -0,0 +1,34 @@
#pragma once
#include <BAN/LinkedList.h>
#include <kernel/Thread.h>
namespace Kernel
{
class Scheduler
{
BAN_NON_COPYABLE(Scheduler);
BAN_NON_MOVABLE(Scheduler);
public:
static void Initialize();
static Scheduler& Get();
const Thread& CurrentThread() const;
void AddThread(void(*)());
void Switch();
void Start();
static constexpr size_t ms_between_switch = 4;
private:
Scheduler() {}
private:
BAN::LinkedList<Thread> m_threads;
BAN::LinkedList<Thread>::iterator m_current_iterator;
};
}

View File

@ -1,6 +1,7 @@
#pragma once
#include <kernel/TerminalDriver.h>
#include <kernel/SpinLock.h>
class TTY
{
@ -51,4 +52,5 @@ private:
Cell* m_buffer { nullptr };
AnsiState m_ansi_state;
TerminalDriver* m_terminal_driver { nullptr };
Kernel::SpinLock m_lock;
};

View File

@ -0,0 +1,47 @@
#pragma once
#include <BAN/Memory.h>
namespace Kernel
{
class Thread
{
BAN_NON_COPYABLE(Thread);
BAN_NON_MOVABLE(Thread);
public:
enum class State
{
NotStarted,
Running,
Paused,
Done,
};
public:
Thread(void(*)());
~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_state(State state) { m_state = state; }
uintptr_t rip() const { return m_rip; }
uintptr_t rsp() const { return m_rsp; }
State state() const { return m_state; }
private:
static void on_exit();
private:
void* m_stack_base = nullptr;
State m_state = State::NotStarted;
uintptr_t m_rip = 0;
uintptr_t m_rsp = 0;
const uint32_t m_id = 0;
};
}

View File

@ -2,8 +2,7 @@
#include <kernel/InterruptController.h>
#include <kernel/IO.h>
#include <kernel/kprint.h>
#define IRQ_TIMER 0
#include <kernel/Scheduler.h>
#define TIMER0_CTL 0x40
#define TIMER1_CTL 0x41
@ -26,9 +25,10 @@ namespace PIT
{
static uint64_t s_system_time = 0;
void clock_handle()
void irq_handler()
{
s_system_time++;
Kernel::Scheduler::Get().Switch();
}
uint64_t ms_since_boot()
@ -45,9 +45,9 @@ namespace PIT
IO::outb(TIMER0_CTL, (timer_reload >> 0) & 0xff);
IO::outb(TIMER0_CTL, (timer_reload >> 8) & 0xff);
IDT::register_irq_handler(IRQ_TIMER, clock_handle);
IDT::register_irq_handler(PIT_IRQ, irq_handler);
InterruptController::Get().EnableIrq(IRQ_TIMER);
InterruptController::Get().EnableIrq(PIT_IRQ);
}
}

150
kernel/kernel/Scheduler.cpp Normal file
View File

@ -0,0 +1,150 @@
#include <kernel/Arch.h>
#include <kernel/InterruptController.h>
#include <kernel/Scheduler.h>
namespace Kernel
{
static Scheduler* s_instance = nullptr;
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()
{
ASSERT(!s_instance);
s_instance = new Scheduler();
}
Scheduler& Scheduler::Get()
{
ASSERT(s_instance);
return *s_instance;
}
const Thread& Scheduler::CurrentThread() const
{
return *m_current_iterator;
}
void Scheduler::AddThread(void(*thread)())
{
MUST(m_threads.EmplaceBack(thread));
if (!m_current_iterator)
m_current_iterator = m_threads.begin();
}
void Scheduler::Switch()
{
static uint8_t cnt = 0;
if (cnt++ % ms_between_switch)
return;
ASSERT(InterruptController::Get().IsInService(PIT_IRQ));
ASSERT(m_threads.Size() > 0);
if (m_threads.Size() == 1)
return;
ASSERT(m_current_iterator);
auto next_iterator = m_current_iterator;
if (++next_iterator == m_threads.end())
next_iterator = m_threads.begin();
Thread& current = *m_current_iterator;
Thread& next = *next_iterator;
if (current.state() == Thread::State::Done)
{
// NOTE: this does not invalidate the next/next_iterator
// since we are working with linked list
m_threads.Remove(m_current_iterator);
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_state(Thread::State::Paused);
}
m_current_iterator = next_iterator;
if (next.state() == Thread::State::NotStarted)
{
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())
);
}
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())
);
}
ASSERT(false);
}
void Scheduler::Start()
{
ASSERT(m_threads.Size() > 0);
ASSERT(m_current_iterator);
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())
);
}
}

View File

@ -1,5 +1,7 @@
#include <BAN/Errors.h>
#include <BAN/ScopeGuard.h>
#include <kernel/Debug.h>
#include <kernel/LockGuard.h>
#include <kernel/TTY.h>
#include <string.h>
@ -261,6 +263,8 @@ void TTY::PutCharAt(uint16_t ch, uint32_t x, uint32_t y)
void TTY::PutChar(char ch)
{
Kernel::LockGuard guard(m_lock);
uint16_t cp = handle_unicode(ch);
if (cp == 0xFFFF)
return;

53
kernel/kernel/Thread.cpp Normal file
View File

@ -0,0 +1,53 @@
#include <BAN/Errors.h>
#include <kernel/Arch.h>
#include <kernel/InterruptController.h>
#include <kernel/kmalloc.h>
#include <kernel/Scheduler.h>
#include <kernel/Thread.h>
#define PAGE_SIZE 4096
namespace Kernel
{
static uint32_t s_next_id = 1;
static constexpr size_t thread_stack_size = PAGE_SIZE;
template<typename T>
static void write_to_stack(uintptr_t& rsp, const T& value)
{
rsp -= sizeof(T);
*(T*)rsp = value;
}
Thread::Thread(void(*function)())
: m_id(s_next_id++)
{
m_stack_base = kmalloc(thread_stack_size, PAGE_SIZE);
ASSERT(m_stack_base);
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);
}
Thread::~Thread()
{
kfree(m_stack_base);
}
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;
for (;;) asm volatile("hlt");
}
}

View File

@ -10,6 +10,7 @@
#include <kernel/PIT.h>
#include <kernel/Serial.h>
#include <kernel/Shell.h>
#include <kernel/Scheduler.h>
#include <kernel/TTY.h>
#include <kernel/VesaTerminalDriver.h>
@ -55,8 +56,12 @@ ParsedCommandLine ParseCommandLine()
return result;
}
static TTY* tty1 = nullptr;
extern "C" void kernel_main()
{
using namespace Kernel;
DISABLE_INTERRUPTS();
auto cmdline = ParseCommandLine();
@ -82,7 +87,7 @@ extern "C" void kernel_main()
TerminalDriver* terminal_driver = VesaTerminalDriver::Create();
ASSERT(terminal_driver);
dprintln("VESA initialized");
TTY* tty1 = new TTY(terminal_driver);
tty1 = new TTY(terminal_driver);
InterruptController::Initialize(cmdline.force_pic);
dprintln("Interrupt controller initialized");
@ -93,15 +98,18 @@ extern "C" void kernel_main()
return;
dprintln("8042 initialized");
ENABLE_INTERRUPTS();
kprintln("Hello from the kernel!");
Kernel::Shell shell(tty1);
shell.Run();
for (;;)
Scheduler::Initialize();
Scheduler& scheduler = Scheduler::Get();
scheduler.AddThread([](){ Shell(tty1).Run(); });
scheduler.AddThread(
[]()
{
asm("hlt");
uint64_t start = PIT::ms_since_boot();
while (PIT::ms_since_boot() < start + 3000)
continue;
kprintln("\nHello!");
}
);
scheduler.Start();
ASSERT(false);
}