diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt index e20cca1c..5e8e03c6 100644 --- a/kernel/CMakeLists.txt +++ b/kernel/CMakeLists.txt @@ -12,6 +12,8 @@ set(SOURCES src/boot/Init.cpp src/arch/Serial.cpp src/arch/Timer.cpp + src/thread/Thread.cpp + src/thread/Scheduler.cpp ) if("${ARCH}" MATCHES "x86_64") @@ -22,6 +24,7 @@ if("${ARCH}" MATCHES "x86_64") src/arch/x86_64/MMU.cpp src/arch/x86_64/CPU.cpp src/arch/x86_64/Timer.cpp + src/arch/x86_64/Thread.cpp ) endif() diff --git a/kernel/src/arch/CPU.h b/kernel/src/arch/CPU.h index abf826e3..35159630 100644 --- a/kernel/src/arch/CPU.h +++ b/kernel/src/arch/CPU.h @@ -13,11 +13,11 @@ namespace CPU [[noreturn]] void efficient_halt(); + [[noreturn]] void idle_loop(); + void switch_kernel_stack(u64 top); void enable_interrupts(); void disable_interrupts(); void wait_for_interrupt(); -} - -extern "C" void kernel_yield(); \ No newline at end of file +} \ No newline at end of file diff --git a/kernel/src/arch/Serial.cpp b/kernel/src/arch/Serial.cpp index 1f0d59ea..7e6f5f98 100644 --- a/kernel/src/arch/Serial.cpp +++ b/kernel/src/arch/Serial.cpp @@ -1,6 +1,6 @@ #include "arch/Serial.h" -#include #include +#include #include namespace Serial diff --git a/kernel/src/arch/Timer.cpp b/kernel/src/arch/Timer.cpp index 35599f2c..4ed1b780 100644 --- a/kernel/src/arch/Timer.cpp +++ b/kernel/src/arch/Timer.cpp @@ -58,6 +58,11 @@ namespace Timer timer_ticks++; } + usize raw_ticks() + { + return timer_ticks; + } + usize ticks() { return ticks_ms() / 1000; @@ -126,4 +131,10 @@ namespace Timer boot_timestamp = bootloader_time_to_unix(bootboot.datetime); arch_init(); } +} + +bool should_invoke_scheduler() +{ + // FIXME: Modulo is SLOW. We're calling this every tick. + return (timer_ticks % ARCH_TIMER_FREQ) == 0; } \ No newline at end of file diff --git a/kernel/src/arch/Timer.h b/kernel/src/arch/Timer.h index c540631b..2cd462d1 100644 --- a/kernel/src/arch/Timer.h +++ b/kernel/src/arch/Timer.h @@ -34,4 +34,6 @@ namespace Timer void arch_init(); void init(); -} \ No newline at end of file +} + +bool should_invoke_scheduler(); \ No newline at end of file diff --git a/kernel/src/arch/x86_64/CPU.asm b/kernel/src/arch/x86_64/CPU.asm index 59f4d68a..f3e2d2fc 100644 --- a/kernel/src/arch/x86_64/CPU.asm +++ b/kernel/src/arch/x86_64/CPU.asm @@ -48,12 +48,7 @@ load_tr: ltr ax ret -global switch_task -switch_task: - cli -.loop: - hlt - jmp .loop +extern switch_task global kernel_yield kernel_yield: diff --git a/kernel/src/arch/x86_64/CPU.cpp b/kernel/src/arch/x86_64/CPU.cpp index e3ef18a2..6a9f8600 100644 --- a/kernel/src/arch/x86_64/CPU.cpp +++ b/kernel/src/arch/x86_64/CPU.cpp @@ -1,14 +1,15 @@ #include "arch/CPU.h" -#include "arch/x86_64/CPU.h" #include "Log.h" #include "arch/Timer.h" +#include "arch/x86_64/CPU.h" #include "arch/x86_64/IO.h" +#include "thread/Scheduler.h" #include +#include +#include #include #include #include -#include -#include extern "C" void enable_sse(); extern "C" void enable_write_protect(); @@ -336,6 +337,7 @@ extern "C" void arch_interrupt_entry(Registers* regs) else if (regs->isr == 32) { Timer::tick(); + if (should_invoke_scheduler()) Scheduler::invoke(regs); pic_eoi(regs); } else @@ -430,8 +432,22 @@ namespace CPU goto loop; // Safeguard: if we ever wake up, start our low-power rest again } + [[noreturn]] void idle_loop() + { + asm volatile("sti"); + loop: + asm volatile("hlt"); + goto loop; + } + void switch_kernel_stack(u64 top) { task_state_segment.rsp[0] = top; } +} + +// called by kernel_yield +extern "C" void switch_task(Registers* regs) +{ + Scheduler::switch_task(regs); } \ No newline at end of file diff --git a/kernel/src/arch/x86_64/CPU.h b/kernel/src/arch/x86_64/CPU.h index 9e81fd41..a06de7b9 100644 --- a/kernel/src/arch/x86_64/CPU.h +++ b/kernel/src/arch/x86_64/CPU.h @@ -1,3 +1,4 @@ +#pragma once #include struct Registers // Saved CPU registers for x86-64 diff --git a/kernel/src/arch/x86_64/MMU.cpp b/kernel/src/arch/x86_64/MMU.cpp index 7d76da52..25edd6ff 100644 --- a/kernel/src/arch/x86_64/MMU.cpp +++ b/kernel/src/arch/x86_64/MMU.cpp @@ -1,8 +1,8 @@ #include "arch/MMU.h" #include "memory/MemoryManager.h" +#include #include #include -#include #pragma GCC push_options #pragma GCC diagnostic ignored "-Wconversion" diff --git a/kernel/src/arch/x86_64/Thread.cpp b/kernel/src/arch/x86_64/Thread.cpp new file mode 100644 index 00000000..4e1ffe86 --- /dev/null +++ b/kernel/src/arch/x86_64/Thread.cpp @@ -0,0 +1,50 @@ +#include "thread/Thread.h" +#include + +bool is_in_kernel(Registers* regs) +{ + return regs->cs == 8; +} + +void Thread::set_ip(u64 ip) +{ + regs.rip = ip; +} + +u64 Thread::ip() +{ + return regs.rip; +} + +void Thread::set_sp(u64 sp) +{ + regs.rsp = sp; +} + +u64 Thread::sp() +{ + return regs.rsp; +} + +void Thread::init_regs_kernel() +{ + memset(®s, 0, sizeof(Registers)); + regs.cs = 0x08; + regs.ss = 0x10; + regs.rflags = 1 << 9; // IF (Interrupt enable flag) +} + +void Thread::set_arguments(u64 arg1, u64 arg2, u64 arg3, u64 arg4) +{ + regs.rdi = arg1; + regs.rsi = arg2; + regs.rdx = arg3; + regs.rcx = arg4; +} + +void switch_context(Thread* old_thread, Thread* new_thread, Registers* regs) +{ + if (!old_thread->is_idle()) memcpy(&old_thread->regs, regs, sizeof(Registers)); + + memcpy(regs, &new_thread->regs, sizeof(Registers)); +} \ No newline at end of file diff --git a/kernel/src/boot/Init.cpp b/kernel/src/boot/Init.cpp index 870d385e..64605a5b 100644 --- a/kernel/src/boot/Init.cpp +++ b/kernel/src/boot/Init.cpp @@ -4,8 +4,8 @@ #include "boot/bootboot.h" #include "memory/MemoryManager.h" #include "video/Framebuffer.h" -#include #include +#include extern const BOOTBOOT bootboot; diff --git a/kernel/src/main.cpp b/kernel/src/main.cpp index 6bc1c5b6..80c7accc 100644 --- a/kernel/src/main.cpp +++ b/kernel/src/main.cpp @@ -1,11 +1,27 @@ #include "Log.h" #include "arch/CPU.h" +#include "arch/Serial.h" #include "arch/Timer.h" #include "boot/Init.h" #include "config.h" #include "memory/MemoryManager.h" -#include +#include "thread/Scheduler.h" #include +#include + +void async_thread() +{ + while (true) + { + Thread* current = Scheduler::current(); + kinfoln("Ticks: %lu, %lu user, %lu kernel, %lu idle", current->ticks, current->ticks_in_user, + current->ticks_in_kernel, Scheduler::idle()->ticks); + + CPU::wait_for_interrupt(); + + kernel_sleep(1000); + } +} Result init() { @@ -17,10 +33,6 @@ Result init() Timer::init(); - CPU::platform_finish_init(); - - CPU::enable_interrupts(); - char buffer[64]; to_dynamic_unit(MemoryManager::total(), buffer, sizeof(buffer)); kinfoln("Total memory: %s", buffer); @@ -31,6 +43,14 @@ Result init() to_dynamic_unit(MemoryManager::reserved(), buffer, sizeof(buffer)); kinfoln("Reserved memory: %s", buffer); + Scheduler::init(); + + TRY(Scheduler::new_kernel_thread(async_thread)); + + CPU::platform_finish_init(); + + CPU::enable_interrupts(); + return {}; } @@ -40,5 +60,5 @@ extern "C" [[noreturn]] void _start() Init::early_init(); auto rc = init(); if (rc.has_error()) kerrorln("Runtime error: %s", rc.error_string()); - CPU::efficient_halt(); + CPU::idle_loop(); } \ No newline at end of file diff --git a/kernel/src/memory/Heap.cpp b/kernel/src/memory/Heap.cpp index ad16eebe..e7dba4f2 100644 --- a/kernel/src/memory/Heap.cpp +++ b/kernel/src/memory/Heap.cpp @@ -178,7 +178,7 @@ Result kmalloc(usize size) heap.append(block); } - HeapBlock* block = heap.first().value(); + HeapBlock* block = heap.expect_first(); while (block) { // Trying to find a free block... @@ -330,7 +330,7 @@ void dump_heap_usage() } usize alloc_total = 0; usize alloc_used = 0; - HeapBlock* block = heap.first().value(); + HeapBlock* block = heap.expect_first(); while (block) { if (is_block_free(block)) diff --git a/kernel/src/memory/KernelVM.cpp b/kernel/src/memory/KernelVM.cpp index 0a4a445b..dbb4eb85 100644 --- a/kernel/src/memory/KernelVM.cpp +++ b/kernel/src/memory/KernelVM.cpp @@ -21,6 +21,7 @@ namespace KernelVM void init() { g_kernelvm_bitmap.initialize(bitmap_memory, sizeof(bitmap_memory)); + g_kernelvm_bitmap.clear(false); } Result alloc_one_page() @@ -36,7 +37,7 @@ namespace KernelVM return err(ENOMEM); } - Result alloc_several_pages(usize count) + bool find_several_pages_impl(usize count, u64& start_index) { u64 first_free_index = 0; u64 free_contiguous_pages = 0; @@ -56,12 +57,24 @@ namespace KernelVM // Found enough contiguous free pages!! if (free_contiguous_pages == count) { - g_used_vm += ARCH_PAGE_SIZE * count; - g_kernelvm_bitmap.clear_region(first_free_index, count, true); - return KERNEL_VM_RANGE_START + (first_free_index * ARCH_PAGE_SIZE); + start_index = first_free_index; + return true; } } + return false; + } + + Result alloc_several_pages(const usize count) + { + u64 start_index; + if (find_several_pages_impl(count, start_index)) + { + g_kernelvm_bitmap.clear_region(start_index, count, true); + g_used_vm += ARCH_PAGE_SIZE * count; + return KERNEL_VM_RANGE_START + (start_index * ARCH_PAGE_SIZE); + } + return err(ENOMEM); } diff --git a/kernel/src/thread/Scheduler.cpp b/kernel/src/thread/Scheduler.cpp new file mode 100644 index 00000000..c6b7fe90 --- /dev/null +++ b/kernel/src/thread/Scheduler.cpp @@ -0,0 +1,170 @@ +#include "thread/Scheduler.h" +#include "Log.h" +#include "arch/CPU.h" +#include "arch/MMU.h" +#include "memory/KernelVM.h" +#include "memory/MemoryManager.h" +#include + +static Thread g_idle; +static Thread* g_current = nullptr; + +static const usize TICKS_PER_TIMESLICE = 20; + +namespace Scheduler +{ + void init() + { + g_idle.id = 0; + g_idle.init_regs_kernel(); + g_idle.set_ip((u64)CPU::idle_loop); + g_idle.state = ThreadState::Idle; + + g_idle.ticks_left = 1; + + // Map some stack for the idle task + u64 idle_stack_vm = KernelVM::alloc_one_page().release_value(); + MemoryManager::alloc_at(idle_stack_vm, 1, MMU::NoExecute | MMU::ReadWrite).release_value(); + + Stack idle_stack{idle_stack_vm, ARCH_PAGE_SIZE}; + g_idle.set_sp(idle_stack.top()); + + kinfoln("CREATED IDLE THREAD: id %lu with ip %lx and sp %lx", g_idle.id, g_idle.ip(), g_idle.sp()); + + g_current = &g_idle; + } + + Thread* current() + { + return g_current; + } + + Thread* idle() + { + return &g_idle; + } + + Result kernel_thread_alloc_stack_and_append_impl(Thread* thread) + { + // FIXME: We will leak the thread if VM allocation or alloc_at fail. + u64 thread_stack_vm = TRY(KernelVM::alloc_several_pages(4)); + TRY(MemoryManager::alloc_at(thread_stack_vm, 4, MMU::NoExecute | MMU::ReadWrite)); + + Stack thread_stack{thread_stack_vm, ARCH_PAGE_SIZE * 4}; + thread->set_sp(thread_stack.top()); + + g_threads.append(thread); + + kinfoln("CREATED THREAD: id %lu with ip %lx and sp %lx", thread->id, thread->ip(), thread->sp()); + + return {}; + } + + Result new_kernel_thread(u64 address) + { + Thread* thread = TRY(new_thread()); + thread->init_regs_kernel(); + thread->set_ip(address); + + return kernel_thread_alloc_stack_and_append_impl(thread); + } + + Result new_kernel_thread(void (*func)(void)) + { + Thread* thread = TRY(new_thread()); + thread->init_regs_kernel(); + thread->set_ip((u64)func); + + return kernel_thread_alloc_stack_and_append_impl(thread); + } + + Result new_kernel_thread(void (*func)(void*), void* arg) + { + Thread* thread = TRY(new_thread()); + thread->init_regs_kernel(); + thread->set_ip((u64)func); + thread->set_arguments((u64)arg, 0, 0, 0); + + return kernel_thread_alloc_stack_and_append_impl(thread); + } + + Thread* pick_task() + { + Thread* old = g_current; + if (old->is_idle()) + { + auto maybe_last = g_threads.last(); + if (maybe_last.has_error()) // No threads!! + return &g_idle; + g_current = old = maybe_last.value(); + } + + bool has_found_thread = false; + + do { + auto maybe_next = g_threads.next(g_current); + if (maybe_next.has_error()) g_current = g_threads.expect_first(); + else + g_current = maybe_next.value(); + + if (g_current->state == ThreadState::Runnable) + { + has_found_thread = true; + break; + } + } while (g_current != old); + + if (!has_found_thread) g_current = &g_idle; + + return g_current; + } + + void generic_switch_context(Thread* old_thread, Thread* new_thread, Registers* regs) + { + if (old_thread != new_thread) switch_context(old_thread, new_thread, regs); + + if (new_thread->is_idle()) + { + new_thread->ticks_left = 1; // The idle task only runs for 1 tick so we can check for new runnable tasks + // as fast as possible. + } + else + new_thread->ticks_left = TICKS_PER_TIMESLICE; + } + + void switch_task(Registers* regs) + { + Thread* old_thread = g_current; + Thread* new_thread = pick_task(); + generic_switch_context(old_thread, new_thread, regs); + } + + void invoke(Registers* regs) + { + CPU::disable_interrupts(); + + g_current->ticks++; + + if (is_in_kernel(regs)) g_current->ticks_in_kernel++; + else + g_current->ticks_in_user++; + + g_current->ticks_left--; + + g_threads.for_each([](Thread* thread) { + if (thread->state == ThreadState::Sleeping) + { + if (--thread->sleep_ticks_left == 0) thread->state = ThreadState::Runnable; + } + }); + + if (!g_current->ticks_left) switch_task(regs); + } +} + +void kernel_sleep(u64 ms) +{ + g_current->sleep_ticks_left = ms; + g_current->state = ThreadState::Sleeping; + kernel_yield(); +} \ No newline at end of file diff --git a/kernel/src/thread/Scheduler.h b/kernel/src/thread/Scheduler.h new file mode 100644 index 00000000..57bb85ad --- /dev/null +++ b/kernel/src/thread/Scheduler.h @@ -0,0 +1,23 @@ +#pragma once +#include "thread/Thread.h" + +namespace Scheduler +{ + void init(); + + Thread* current(); + Thread* idle(); + + Result new_kernel_thread(u64 address); + Result new_kernel_thread(void (*func)(void)); + Result new_kernel_thread(void (*func)(void*), void* arg); + + Thread* pick_task(); + + void switch_task(Registers* regs); + + void invoke(Registers* regs); +} + +extern "C" void kernel_yield(); +void kernel_sleep(u64 ms); \ No newline at end of file diff --git a/kernel/src/thread/Thread.cpp b/kernel/src/thread/Thread.cpp new file mode 100644 index 00000000..5f834d6e --- /dev/null +++ b/kernel/src/thread/Thread.cpp @@ -0,0 +1,15 @@ +#include "thread/Thread.h" +#include + +static u64 g_next_id = 1; + +DoublyLinkedList g_threads; + +Result new_thread() +{ + Thread* thread = TRY(make()); + + thread->id = g_next_id++; + + return thread; +} \ No newline at end of file diff --git a/kernel/src/thread/Thread.h b/kernel/src/thread/Thread.h new file mode 100644 index 00000000..53e41a21 --- /dev/null +++ b/kernel/src/thread/Thread.h @@ -0,0 +1,59 @@ +#pragma once + +#include "arch/CPU.h" +#include +#include + +#ifdef ARCH_X86_64 +#include "arch/x86_64/CPU.h" +#else +#error "Unknown architecture." +#endif + +enum class ThreadState +{ + Idle, + Runnable, + Sleeping +}; + +struct Thread : public DoublyLinkedListNode +{ + Registers regs; + + u64 id; + + u64 ticks = 0; + u64 ticks_in_user = 0; + u64 ticks_in_kernel = 0; + + u64 ticks_left; + u64 sleep_ticks_left; + + ThreadState state = ThreadState::Runnable; + + bool is_idle() + { + return state == ThreadState::Idle; + } + + void init_regs_kernel(); + void init_regs_user(); + + void set_arguments(u64 arg1, u64 arg2, u64 arg3, u64 arg4); + + void set_ip(u64 ip); + u64 ip(); + + void set_sp(u64 sp); + u64 sp(); +}; + +void switch_context(Thread* old_thread, Thread* new_thread, Registers* regs); + +bool is_in_kernel(Registers* regs); + +Result new_thread(); +Result create_idle_thread(); + +extern DoublyLinkedList g_threads; \ No newline at end of file diff --git a/kernel/src/video/TextConsole.cpp b/kernel/src/video/TextConsole.cpp index 80565daf..dc5f96a0 100644 --- a/kernel/src/video/TextConsole.cpp +++ b/kernel/src/video/TextConsole.cpp @@ -2,8 +2,8 @@ #include "boot/bootboot.h" #include "video/Framebuffer.h" #include -#include #include +#include #include extern const BOOTBOOT bootboot; diff --git a/luna/CMakeLists.txt b/luna/CMakeLists.txt index b8e43f9e..a8af5718 100644 --- a/luna/CMakeLists.txt +++ b/luna/CMakeLists.txt @@ -7,6 +7,7 @@ set(FREESTANDING_SOURCES src/Units.cpp src/SystemError.cpp src/Bitmap.cpp + src/Stack.cpp ) set(SOURCES @@ -41,4 +42,10 @@ target_compile_options(luna-freestanding PRIVATE -mno-red-zone) target_compile_options(luna-freestanding PRIVATE -mno-80387 -mno-mmx -mno-sse -mno-sse2) target_compile_definitions(luna-freestanding PUBLIC ARCH_X86_64) target_compile_definitions(luna PUBLIC ARCH_X86_64) +endif() + +if(LUNA_DEBUG_SYMBOLS) + message(STATUS "Building Luna with debug symbols") + target_compile_options(luna PRIVATE -ggdb) + target_compile_options(luna-freestanding PRIVATE -ggdb) endif() \ No newline at end of file diff --git a/luna/include/luna/LinkedList.h b/luna/include/luna/LinkedList.h index 5e343b3d..698e5a9a 100644 --- a/luna/include/luna/LinkedList.h +++ b/luna/include/luna/LinkedList.h @@ -106,11 +106,21 @@ template class DoublyLinkedList return nonnull_or_error((T*)m_start_node); } + T* expect_first() + { + return first().value(); + } + Result last() { return nonnull_or_error((T*)m_end_node); } + T* expect_last() + { + return last().value(); + } + Result next(T* item) { return nonnull_or_error((T*)extract_node(item)->get_next()); diff --git a/luna/include/luna/Stack.h b/luna/include/luna/Stack.h new file mode 100644 index 00000000..7870402c --- /dev/null +++ b/luna/include/luna/Stack.h @@ -0,0 +1,23 @@ +#pragma once +#include + +struct Stack +{ + Stack(u64 base, usize bytes); + + u64 bottom() + { + return m_base; + } + + u64 top(); + + usize bytes() + { + return m_bytes; + } + + private: + u64 m_base; + usize m_bytes; +}; \ No newline at end of file diff --git a/luna/src/Bitmap.cpp b/luna/src/Bitmap.cpp index 1928208e..7fb5302b 100644 --- a/luna/src/Bitmap.cpp +++ b/luna/src/Bitmap.cpp @@ -62,11 +62,14 @@ void Bitmap::clear_region(usize start, usize bits, bool value) expect(initialized(), "Bitmap was never initialized"); expect((start + bits) <= size(), "Bitmap clear out of range"); + if (!bits) return; + // Set individual bits while not on a byte boundary. - while ((start % 8) && bits--) + while ((start % 8) && bits) { set(start, value); start++; + bits--; } // Clear out the rest in bytes. diff --git a/luna/src/Format.cpp b/luna/src/Format.cpp index ccdde3ab..a74b0a38 100644 --- a/luna/src/Format.cpp +++ b/luna/src/Format.cpp @@ -2,8 +2,8 @@ #include #include #include -#include #include +#include extern "C" usize strlen(const char*); diff --git a/luna/src/Stack.cpp b/luna/src/Stack.cpp new file mode 100644 index 00000000..0c53e6f3 --- /dev/null +++ b/luna/src/Stack.cpp @@ -0,0 +1,10 @@ +#include + +Stack::Stack(u64 base, usize bytes) : m_base(base), m_bytes(bytes) +{ +} + +u64 Stack::top() +{ + return (m_base + m_bytes) - sizeof(void*); +} \ No newline at end of file diff --git a/luna/src/Units.cpp b/luna/src/Units.cpp index d0362a75..0b64dd9e 100644 --- a/luna/src/Units.cpp +++ b/luna/src/Units.cpp @@ -1,6 +1,6 @@ #include -#include #include +#include Result to_dynamic_unit(usize value, char* buffer, usize max) { diff --git a/tools/build-debug.sh b/tools/build-debug.sh index 85d83455..0fcb695e 100755 --- a/tools/build-debug.sh +++ b/tools/build-debug.sh @@ -9,7 +9,7 @@ tools/setup.sh tools/full-clean.sh -cmake -S . -B $BUILD_DIR -DMOON_DEBUG_SYMBOLS=ON -G "$CMAKE_GEN" +cmake -S . -B $BUILD_DIR -DMOON_DEBUG_SYMBOLS=ON -DLUNA_DEBUG_SYMBOLS=ON -G "$CMAKE_GEN" cmake --build $BUILD_DIR cmake --install $BUILD_DIR