diff --git a/kernel/include/thread/Scheduler.h b/kernel/include/thread/Scheduler.h new file mode 100644 index 00000000..f2e99c36 --- /dev/null +++ b/kernel/include/thread/Scheduler.h @@ -0,0 +1,15 @@ +#pragma once +#include "thread/Task.h" + +namespace Scheduler +{ + void init(); + void yield(); + void sleep(unsigned long ms); + void add_kernel_task(void (*task)(void)); + + Task* current_task(); + + void task_yield(Context* context); + void task_tick(Context* context); +} \ No newline at end of file diff --git a/kernel/include/thread/Task.h b/kernel/include/thread/Task.h new file mode 100644 index 00000000..285df701 --- /dev/null +++ b/kernel/include/thread/Task.h @@ -0,0 +1,16 @@ +#pragma once +#include "interrupts/Context.h" + +struct Task +{ + uint64_t id; + Context regs; + + int64_t task_sleep = 0; + int64_t task_time = 0; + + Task* next_task = nullptr; +}; + +void set_context_from_task(Task& task, Context* ctx); +void get_context_to_task(Task& task, Context* ctx); \ No newline at end of file diff --git a/kernel/src/interrupts/Entry.cpp b/kernel/src/interrupts/Entry.cpp index aefdbff5..be8b9cbb 100644 --- a/kernel/src/interrupts/Entry.cpp +++ b/kernel/src/interrupts/Entry.cpp @@ -7,6 +7,7 @@ #include "log/Log.h" #include "panic/hang.h" #include "std/stdio.h" +#include "thread/Scheduler.h" #include "trace/StackTracer.h" extern "C" void common_handler(Context* context) @@ -47,6 +48,7 @@ extern "C" void common_handler(Context* context) tracer.trace(); hang(); } + if (context->number == 48) { Scheduler::task_yield(context); } if (context->number == 256) { kwarnln("Unused interrupt"); } return; } \ No newline at end of file diff --git a/kernel/src/interrupts/IRQ.cpp b/kernel/src/interrupts/IRQ.cpp index 366b1b8e..d40ab001 100644 --- a/kernel/src/interrupts/IRQ.cpp +++ b/kernel/src/interrupts/IRQ.cpp @@ -7,12 +7,16 @@ #include "rand/Init.h" #include "scheduling/PIT.h" #include "std/stdio.h" +#include "thread/Scheduler.h" void IRQ::interrupt_handler(Context* context) { switch (context->irq_number) { - case 0: PIT::tick(); break; + case 0: + PIT::tick(); + Scheduler::task_tick(context); + break; case 1: { [[maybe_unused]] volatile unsigned char scancode = IO::inb(0x60); kdbgln("Keyboard key pressed/released, seconds since boot: %ld.%ld", PIT::ms_since_boot / 1000, diff --git a/kernel/src/main.cpp b/kernel/src/main.cpp index 92b7af10..48647cd7 100644 --- a/kernel/src/main.cpp +++ b/kernel/src/main.cpp @@ -11,6 +11,7 @@ #include "interrupts/Install.h" #include "interrupts/Interrupts.h" #include "io/PIC.h" +#include "io/Serial.h" #include "log/Log.h" #include "memory/KernelMemoryManager.h" #include "memory/Memory.h" @@ -25,6 +26,7 @@ #include "std/stdio.h" #include "std/stdlib.h" #include "std/string.h" +#include "thread/Scheduler.h" extern "C" void _start() { @@ -63,9 +65,34 @@ extern "C" void _start() kinfoln("Prepared PIT"); - Interrupts::enable(); + Scheduler::init(); - kinfoln("Interrupts enabled"); + kinfoln("Prepared scheduler"); + + Scheduler::add_kernel_task([]() { + while (1) + { + sleep(1000); + for (int i = 0; i < 5; i++) { Serial::println("woo!"); } + } + }); + + Scheduler::add_kernel_task([]() { + sleep(500); + while (1) + { + sleep(2000); + for (int i = 0; i < 6; i++) { Serial::println("boo!"); } + } + }); + + Scheduler::add_kernel_task([]() { + sleep(20000); + kinfoln("Here i am, the hard-to-get thread!"); + while (1) Scheduler::yield(); + }); + + kinfoln("Prepared scheduler tasks"); ACPI::SDTHeader* rootSDT = ACPI::GetRSDTOrXSDT(); bool isXSDT = ACPI::IsXSDT(); @@ -73,8 +100,10 @@ extern "C" void _start() framebuffer0.clear(Color::Cyan); - sleep(2500); - reboot(); + Interrupts::enable(); - while (1) halt(); + kinfoln("Interrupts enabled"); + + sleep(20000); + reboot(); } \ No newline at end of file diff --git a/kernel/src/std/stdlib.cpp b/kernel/src/std/stdlib.cpp index 50eb9e62..af014abb 100644 --- a/kernel/src/std/stdlib.cpp +++ b/kernel/src/std/stdlib.cpp @@ -1,5 +1,5 @@ #include "panic/hang.h" -#include "scheduling/PIT.h" +#include "thread/Scheduler.h" #include static void strrev(char* arr, int start, int end) @@ -145,14 +145,7 @@ char* ultoa(uint64_t number, char* arr, int base) return arr; } -#pragma GCC push_options -#pragma GCC optimize("O0") - void sleep(uint64_t ms) { - volatile uint64_t start = PIT::ms_since_boot; - volatile uint64_t end = (uint64_t)(start + ms); - while (PIT::ms_since_boot < end) { asm volatile("hlt"); } -} - -#pragma GCC pop_options \ No newline at end of file + Scheduler::sleep(ms); +} \ No newline at end of file diff --git a/kernel/src/thread/Scheduler.cpp b/kernel/src/thread/Scheduler.cpp new file mode 100644 index 00000000..34c1518a --- /dev/null +++ b/kernel/src/thread/Scheduler.cpp @@ -0,0 +1,145 @@ +#define MODULE "sched" + +#include "thread/Scheduler.h" +#include "interrupts/Interrupts.h" +#include "log/Log.h" +#include "memory/KernelMemoryManager.h" +#include "panic/hang.h" +#include "scheduling/PIT.h" +#include "std/string.h" +#include "thread/Task.h" + +static Task tasks[32]; +static uint64_t task_num = 0; + +static Task idle_task; + +static uint64_t free_tid = 0; + +static Task* sched_current_task; +static Task* base_task; +static Task* end_task; + +static void idle_task_function() +{ + Interrupts::enable(); + while (1) halt(); +} + +static uint64_t frequency; + +void Scheduler::init() +{ + memset(&idle_task, 0, sizeof(Task)); + idle_task.id = free_tid++; + idle_task.regs.rip = (uint64_t)&idle_task_function; + idle_task.regs.rsp = (uint64_t)KernelMemoryManager::get_page(); + idle_task.regs.cs = 0x08; + idle_task.regs.ds = 0x10; + asm volatile("pushfq; movq (%%rsp), %%rax; movq %%rax, %0; popfq;" : "=m"(idle_task.regs.rflags)::"%rax"); + idle_task.regs.rflags |= 0x200; + idle_task.task_sleep = 1000; + + base_task = &tasks[task_num++]; + memset(base_task, 0, sizeof(Task)); + end_task = base_task; + sched_current_task = base_task; + sched_current_task->id = free_tid++; + sched_current_task->task_time = 30; // gets 30 ms of cpu time before next switch + sched_current_task->next_task = nullptr; + // the other registers will be saved next task switch + + frequency = 1000 / PIT::frequency(); +} + +void Scheduler::add_kernel_task(void (*task)(void)) +{ + if (task_num == 32) return; // FIXME: allow for dynamically allocated linked list instead of a fixed array of Tasks + Task* new_task = &tasks[task_num++]; + memset(new_task, 0, sizeof(Task)); + new_task->id = free_tid++; + new_task->regs.rip = (uint64_t)task; + new_task->regs.rsp = (uint64_t)KernelMemoryManager::get_pages(2); // 8 KB is enough for everyone, right? + new_task->regs.cs = 0x08; + new_task->regs.ds = 0x10; + asm volatile("pushfq; movq (%%rsp), %%rax; movq %%rax, %0; popfq;" : "=m"(new_task->regs.rflags)::"%rax"); + new_task->regs.rflags |= 0x200; + new_task->task_sleep = 0; + new_task->task_time = 0; + end_task->next_task = new_task; + end_task = new_task; + kinfoln("Adding task: starts at %lx, tid %ld, stack at %lx, total tasks: %ld", new_task->regs.rip, new_task->id, + new_task->regs.rsp, task_num); +} + +static void sched_decrement_sleep_times() +{ + Task* task = base_task; + while (task) + { + if (task->task_sleep > 0) + { + task->task_sleep -= frequency; + if (task->task_sleep < 0) task->task_sleep = 0; + } + task = task->next_task; + } +} + +void Scheduler::task_tick(Context* context) +{ + sched_decrement_sleep_times(); + sched_current_task->task_time -= frequency; + if (sched_current_task->task_time < 0) + { + sched_current_task->task_time = 0; + task_yield(context); + } +} + +void Scheduler::task_yield(Context* context) +{ + get_context_to_task(*sched_current_task, context); + bool was_idle = false; + if (sched_current_task->id == 0) // idle task + { + sched_current_task = base_task; + was_idle = true; + } + Task* original_task = sched_current_task; + do { + sched_current_task = sched_current_task->next_task; + if (!sched_current_task) { sched_current_task = base_task; } + if (sched_current_task->task_sleep == 0) + { + sched_current_task->task_time = 30; + set_context_from_task(*sched_current_task, context); + return; + } + } while (sched_current_task != original_task); + if (original_task->task_sleep > 0) + { + sched_current_task = &idle_task; + if (!was_idle) { set_context_from_task(*sched_current_task, context); } + return; + } + kinfoln("Resuming current task %ld", original_task->id); + original_task->task_time = 30; // grant 30 more ms, there is no other task available + return; +} + +void Scheduler::yield() +{ + asm volatile("int $48"); +} + +void Scheduler::sleep(unsigned long ms) +{ + current_task()->task_sleep = ms; + yield(); +} + +Task* Scheduler::current_task() +{ + return sched_current_task; +} \ No newline at end of file diff --git a/kernel/src/thread/Task.cpp b/kernel/src/thread/Task.cpp new file mode 100644 index 00000000..202b7de9 --- /dev/null +++ b/kernel/src/thread/Task.cpp @@ -0,0 +1,12 @@ +#include "thread/Task.h" +#include "std/string.h" + +void set_context_from_task(Task& task, Context* ctx) +{ + memcpy(ctx, &task.regs, sizeof(Context)); +} + +void get_context_to_task(Task& task, Context* ctx) +{ + memcpy(&task.regs, ctx, sizeof(Context)); +} \ No newline at end of file