From 7db47d0e382fbf41b5642cb0ddbe00cf88f14bf6 Mon Sep 17 00:00:00 2001 From: Microindole Date: Sun, 10 May 2026 12:04:10 +0800 Subject: [PATCH] refactor(kernel): centralize selftests and split scheduler code --- Makefile | 2 +- arch/x86/Makefile | 2 + arch/x86/mm/page_table.c | 40 +-- arch/x86/mm/page_table.h | 8 + docs/agents/code-style.md | 2 + docs/agents/tasks/03-memory.md | 6 +- docs/agents/tasks/04-time-scheduler.md | 8 +- kernel/Makefile | 5 + kernel/sched/core.c | 171 +++++++++++ kernel/sched/idle.c | 22 ++ kernel/sched/sched.h | 26 ++ kernel/sched/thread.c | 379 ++----------------------- kernel/sched/wait.c | 64 +++++ kernel/selftest/page_table.c | 43 +++ kernel/selftest/sched.c | 108 +++++++ scripts/check.sh | 1 + scripts/checks/structure.sh | 39 +++ 17 files changed, 534 insertions(+), 392 deletions(-) create mode 100644 arch/x86/mm/page_table.h create mode 100644 kernel/sched/core.c create mode 100644 kernel/sched/idle.c create mode 100644 kernel/sched/sched.h create mode 100644 kernel/sched/wait.c create mode 100644 kernel/selftest/page_table.c create mode 100644 kernel/selftest/sched.c create mode 100755 scripts/checks/structure.sh diff --git a/Makefile b/Makefile index 94a63d8..8ee0fbd 100644 --- a/Makefile +++ b/Makefile @@ -31,7 +31,7 @@ include kernel/Makefile all: $(BOOT_EFI) $(KERNEL_ELF) dirs: - mkdir -p $(BUILD_DIR)/arch/boot $(BUILD_DIR)/arch/kernel $(BUILD_DIR)/arch/mm $(BUILD_DIR)/kernel $(BUILD_DIR)/kernel/locking $(BUILD_DIR)/mm $(EFI_DIR) + mkdir -p $(BUILD_DIR)/arch/boot $(BUILD_DIR)/arch/kernel $(BUILD_DIR)/arch/mm $(BUILD_DIR)/kernel $(BUILD_DIR)/kernel/locking $(BUILD_DIR)/kernel/sched $(BUILD_DIR)/kernel/selftest $(BUILD_DIR)/kernel/time $(BUILD_DIR)/mm $(EFI_DIR) $(BUILD_DIR)/arch/boot/%.obj: $(ARCH_DIR)/boot/%.c $(ARCH_DIR)/include/efi.h $(ARCH_DIR)/boot/debug_log.h include/tianole/boot_info.h include/tianole/elf.h | dirs $(CC) $(CFLAGS) -c $< -o $@ diff --git a/arch/x86/Makefile b/arch/x86/Makefile index 98168c8..bc2eb62 100644 --- a/arch/x86/Makefile +++ b/arch/x86/Makefile @@ -34,7 +34,9 @@ KERNEL_CFLAGS := \ -Wall \ -Wextra \ -Werror \ + -I. \ -Iinclude \ + -Ikernel \ -I$(ARCH_DIR)/include KERNEL_ASFLAGS := \ diff --git a/arch/x86/mm/page_table.c b/arch/x86/mm/page_table.c index f265862..ad584f6 100644 --- a/arch/x86/mm/page_table.c +++ b/arch/x86/mm/page_table.c @@ -4,11 +4,12 @@ #include #include +#include "page_table.h" + #define ENTRY_COUNT 512 #define PAGE_MASK 0x000ffffffffff000ull #define PAGE_SIZE_FLAG (1ull << 7) #define MAX_RESERVED_TABLE_PAGES 4096 -#define TEST_VIRTUAL_PAGE 0xffffff0000000000ull static phys_addr_t reserved_table_pages[MAX_RESERVED_TABLE_PAGES]; static uint64_t reserved_table_page_count; @@ -141,7 +142,7 @@ static uint64_t make_table_entry(phys_addr_t table) return table | PAGE_PRESENT | PAGE_WRITABLE; } -static void page_tables_init(void) +void page_tables_init(void) { uint64_t index; uint64_t *firmware_pml4; @@ -268,38 +269,3 @@ int virt_to_phys(virt_addr_t virt, phys_addr_t *phys) *phys = (*entry & PAGE_MASK) | (virt & (PAGE_SIZE - 1)); return 0; } - -void page_table_selftest(void) -{ - phys_addr_t page = alloc_page(); - phys_addr_t resolved; - volatile uint64_t *mapped = - (volatile uint64_t *)(uintptr_t)TEST_VIRTUAL_PAGE; - - if (page == 0) { - panic("page table selftest allocation failed"); - } - - page_tables_init(); - - if (map_page(TEST_VIRTUAL_PAGE, page, PAGE_WRITABLE) != 0) { - panic("page table selftest map failed"); - } - - if (virt_to_phys(TEST_VIRTUAL_PAGE, &resolved) != 0 || - resolved != page) { - panic("page table selftest resolve failed"); - } - - *mapped = 0x54494f4c45504d4dull; - if (*mapped != 0x54494f4c45504d4dull) { - panic("page table selftest access failed"); - } - - if (unmap_page(TEST_VIRTUAL_PAGE) != 0) { - panic("page table selftest unmap failed"); - } - - free_page(page); - early_log_puts("page table selftest ok\n"); -} diff --git a/arch/x86/mm/page_table.h b/arch/x86/mm/page_table.h new file mode 100644 index 0000000..aed94be --- /dev/null +++ b/arch/x86/mm/page_table.h @@ -0,0 +1,8 @@ +#ifndef ARCH_X86_MM_PAGE_TABLE_H +#define ARCH_X86_MM_PAGE_TABLE_H + +#include + +void page_tables_init(void); + +#endif diff --git a/docs/agents/code-style.md b/docs/agents/code-style.md index 807af78..1c20e98 100644 --- a/docs/agents/code-style.md +++ b/docs/agents/code-style.md @@ -67,6 +67,8 @@ - 可独立执行的检查放在 `scripts/checks/`。 - 多个检查共享的函数放在 `scripts/lib/`。 - 不把所有检查逻辑持续堆进 `scripts/check.sh`。 +- 项目结构规则放在 `scripts/checks/structure.sh`,优先用宿主 Linux/LLVM 工具实现底层扫描,把 Tianole 自己的目录和 include 约束固化在脚本里。 +- 启动阶段内核自测集中放在 `kernel/selftest/`,不要散落在具体实现目录里。 ## 验证 diff --git a/docs/agents/tasks/03-memory.md b/docs/agents/tasks/03-memory.md index e5dda59..631ee44 100644 --- a/docs/agents/tasks/03-memory.md +++ b/docs/agents/tasks/03-memory.md @@ -12,7 +12,10 @@ ## 建议边界 - `mm/`:架构无关内存管理。 -- `arch/x86/mm/`:页表格式、地址空间切换、TLB 操作。 +- `arch/x86/mm/page_table.c`:页表格式、地址空间切换、TLB 操作和 map/unmap/query 主路径。 +- `arch/x86/mm/fault.c`:page fault 诊断。 +- `arch/x86/mm/page_table.h`:x86 页表子目录私有接口。 +- `kernel/selftest/page_table.c`:页表 map/unmap/query 启动自测。 - `include/tianole/`:通用内存接口。 ## 实现内容 @@ -55,6 +58,7 @@ - 已切换到内核自有 PML4,不再直接修改固件页表。 - 已提供最小 `map_page()`、`unmap_page()` 和 `virt_to_phys()` 接口。 - 已加入页表 map/unmap/query selftest。 +- 已把 x86 页表 selftest 从 `page_table.c` 移到 `kernel/selftest/page_table.c`,避免页表主路径和启动验证逻辑混在同一目录边界。 - 已拆出 x86 page fault 诊断路径,能输出 fault address、错误码、访问类型和权限来源。 - 已建立最小内核堆,提供 `kmalloc()` 和 `kfree()`,底层通过页表按需映射物理页。 - 已加入内核堆分配、写入、释放、复用 selftest。 diff --git a/docs/agents/tasks/04-time-scheduler.md b/docs/agents/tasks/04-time-scheduler.md index 880b8b7..ffc0273 100644 --- a/docs/agents/tasks/04-time-scheduler.md +++ b/docs/agents/tasks/04-time-scheduler.md @@ -12,7 +12,12 @@ ## 建议边界 -- `kernel/sched/`:线程、调度器、等待队列。 +- `kernel/sched/core.c`:run queue、调度选择、tick 和 IRQ exit 调度边界。 +- `kernel/sched/thread.c`:线程对象创建、初始栈、退出和 DEAD 线程回收。 +- `kernel/sched/wait.c`:等待队列。 +- `kernel/sched/idle.c`:idle thread。 +- `kernel/sched/sched.h`:调度子系统私有接口,不向通用内核层公开内部状态。 +- `kernel/selftest/sched.c`:当前阶段的调度自测和启动演示线程。 - `kernel/time/`:通用时间与 timer 抽象。 - `arch/x86/`:具体 timer、上下文切换。 @@ -66,6 +71,7 @@ - 已把 `kernel_thread_create()` 中的线程 id 分配和 run queue 入队纳入 interrupt-safe lock 保护。 - 已建立 `sched_irq_exit()`,timer IRQ 只设置 `need_resched`,trap 的 IRQ 返回边界统一消费调度请求。 - 已建立最小 DEAD 线程回收路径,调度前会释放非当前 DEAD 线程的内核栈和线程对象。 +- 已把调度代码按职责拆分为 `core.c`、`thread.c`、`wait.c`、`idle.c` 和私有 `sched.h`,并把当前阶段自测/演示线程移到 `kernel/selftest/sched.c`。 - `scripts/check.sh` 已验证 `timer initialized`、`timer tick=1/2/3`、`scheduler initialized`、`kernel thread selftest ok`、timer 驱动线程轮转、`sched_sleep()` 和 wait queue wakeup。 后续扩展: diff --git a/kernel/Makefile b/kernel/Makefile index fb1f029..10dcb24 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -3,7 +3,12 @@ KERNEL_SRCS := \ kernel/boot_report.c \ kernel/early_log.c \ kernel/locking/spinlock.c \ + kernel/sched/core.c \ + kernel/sched/idle.c \ kernel/sched/thread.c \ + kernel/sched/wait.c \ + kernel/selftest/page_table.c \ + kernel/selftest/sched.c \ kernel/time/timer.c KERNEL_OBJS := \ diff --git a/kernel/sched/core.c b/kernel/sched/core.c new file mode 100644 index 0000000..1ee6abb --- /dev/null +++ b/kernel/sched/core.c @@ -0,0 +1,171 @@ +#include + +#include + +#include +#include +#include + +#include "sched.h" + +struct thread *run_queue_head; +struct thread *run_queue_tail; +struct thread *current_thread; +uintptr_t boot_stack_pointer; +uint64_t next_thread_id = 1; +int scheduler_ready; +int schedule_locked; +int need_resched; +struct thread *idle_thread; +struct spinlock scheduler_lock = SPINLOCK_INITIALIZER; + +void enqueue_thread(struct thread *thread) +{ + thread->next = 0; + + if (run_queue_tail != 0) { + run_queue_tail->next = thread; + } else { + run_queue_head = thread; + } + + run_queue_tail = thread; +} + +static struct thread *next_runnable_thread(void) +{ + struct thread *start; + struct thread *thread; + + if (current_thread == 0 || current_thread->next == 0) { + start = run_queue_head; + } else { + start = current_thread->next; + } + + thread = start; + while (thread != 0) { + if (thread->state == THREAD_READY && thread != idle_thread) { + return thread; + } + thread = thread->next; + } + + for (thread = run_queue_head; thread != start; thread = thread->next) { + if (thread->state == THREAD_READY && thread != idle_thread) { + return thread; + } + } + + if (idle_thread != 0 && idle_thread->state == THREAD_READY) { + return idle_thread; + } + + return 0; +} + +static void wake_sleeping_threads(uint64_t tick) +{ + struct thread *thread; + + for (thread = run_queue_head; thread != 0; thread = thread->next) { + if (thread->state == THREAD_SLEEPING && + thread->wake_tick <= tick) { + thread->wake_tick = 0; + thread->state = THREAD_READY; + } + } +} + +void sched_yield(void) +{ + struct thread *prev; + struct thread *next; + + if (schedule_locked != 0) { + return; + } + + sched_reap_dead_threads(); + + prev = current_thread; + next = next_runnable_thread(); + + if (next == 0 || next == prev) { + return; + } + + schedule_locked = 1; + + if (prev != 0 && prev->state == THREAD_RUNNING) { + prev->state = THREAD_READY; + } + + next->state = THREAD_RUNNING; + current_thread = next; + schedule_locked = 0; + + if (prev == 0) { + arch_context_switch(&boot_stack_pointer, next->stack_pointer); + return; + } + + arch_context_switch(&prev->stack_pointer, next->stack_pointer); +} + +void sched_tick(uint64_t tick) +{ + wake_sleeping_threads(tick); + + if (current_thread != 0 && current_thread->state == THREAD_RUNNING) { + need_resched = 1; + } +} + +void sched_irq_exit(void) +{ + if (need_resched == 0 || current_thread == 0 || schedule_locked != 0) { + return; + } + + need_resched = 0; + sched_yield(); +} + +void sched_sleep(uint64_t ticks) +{ + uint64_t now; + + if (current_thread == 0 || ticks == 0) { + return; + } + + now = timer_ticks(); + current_thread->wake_tick = now + ticks; + current_thread->state = THREAD_SLEEPING; + sched_yield(); +} + +void sched_init(void) +{ + if (scheduler_ready != 0) { + return; + } + + run_queue_head = 0; + run_queue_tail = 0; + idle_thread = 0; + scheduler_ready = 1; + + early_log_puts("scheduler initialized\n"); + sched_selftest(); +} + +void sched_start(void) +{ + if (sched_idle_create() != 0) { + panic("idle thread creation failed"); + } + + sched_demo_start(); +} diff --git a/kernel/sched/idle.c b/kernel/sched/idle.c new file mode 100644 index 0000000..c4f42e0 --- /dev/null +++ b/kernel/sched/idle.c @@ -0,0 +1,22 @@ +#include + +#include "sched.h" + +static void idle_thread_entry(void *arg) +{ + (void)arg; + + for (;;) { + __asm__ volatile("hlt"); + } +} + +int sched_idle_create(void) +{ + idle_thread = kernel_thread_create("idle", idle_thread_entry, 0); + if (idle_thread == 0) { + return -1; + } + + return 0; +} diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h new file mode 100644 index 0000000..df7b1b0 --- /dev/null +++ b/kernel/sched/sched.h @@ -0,0 +1,26 @@ +#ifndef KERNEL_SCHED_SCHED_H +#define KERNEL_SCHED_SCHED_H + +#include + +#include +#include + +extern struct thread *run_queue_head; +extern struct thread *run_queue_tail; +extern struct thread *current_thread; +extern uintptr_t boot_stack_pointer; +extern uint64_t next_thread_id; +extern int scheduler_ready; +extern int schedule_locked; +extern int need_resched; +extern struct thread *idle_thread; +extern struct spinlock scheduler_lock; + +void enqueue_thread(struct thread *thread); +void sched_reap_dead_threads(void); +void sched_selftest(void); +int sched_idle_create(void); +void sched_demo_start(void) __attribute__((noreturn)); + +#endif diff --git a/kernel/sched/thread.c b/kernel/sched/thread.c index 3782374..6549b1d 100644 --- a/kernel/sched/thread.c +++ b/kernel/sched/thread.c @@ -1,28 +1,16 @@ #include #include -#include - #include #include #include #include -#include + +#include "sched.h" #define KERNEL_STACK_SIZE (PAGE_SIZE * 4u) #define STACK_ALIGNMENT 16u -static struct thread *run_queue_head; -static struct thread *run_queue_tail; -static struct thread *current_thread; -static uintptr_t boot_stack_pointer; -static uint64_t next_thread_id = 1; -static int scheduler_ready; -static int schedule_locked; -static int need_resched; -static struct thread *idle_thread; -static struct spinlock scheduler_lock = SPINLOCK_INITIALIZER; - static void thread_trampoline(void) __attribute__((noreturn)); static uintptr_t align_down_uintptr(uintptr_t value, uintptr_t alignment) @@ -49,53 +37,6 @@ static void copy_thread_name(char *dest, size_t dest_size, const char *src) dest[index] = '\0'; } -static void enqueue_thread(struct thread *thread) -{ - thread->next = 0; - - if (run_queue_tail != 0) { - run_queue_tail->next = thread; - } else { - run_queue_head = thread; - } - - run_queue_tail = thread; -} - -static void release_thread(struct thread *thread) -{ - kfree(thread->stack_base); - kfree(thread); -} - -static void reap_dead_threads(void) -{ - struct thread *prev = 0; - struct thread *thread = run_queue_head; - - while (thread != 0) { - struct thread *next = thread->next; - - if (thread->state == THREAD_DEAD && thread != current_thread) { - if (prev != 0) { - prev->next = next; - } else { - run_queue_head = next; - } - - if (run_queue_tail == thread) { - run_queue_tail = prev; - } - - release_thread(thread); - } else { - prev = thread; - } - - thread = next; - } -} - static uintptr_t prepare_initial_stack(uintptr_t stack_top) { uintptr_t *stack = (uintptr_t *)stack_top; @@ -155,178 +96,37 @@ struct thread *kernel_thread_create( return thread; } -static struct thread *next_runnable_thread(void) +static void release_thread(struct thread *thread) { - struct thread *start; - struct thread *thread; + kfree(thread->stack_base); + kfree(thread); +} - if (current_thread == 0 || current_thread->next == 0) { - start = run_queue_head; - } else { - start = current_thread->next; - } +void sched_reap_dead_threads(void) +{ + struct thread *prev = 0; + struct thread *thread = run_queue_head; - thread = start; while (thread != 0) { - if (thread->state == THREAD_READY && thread != idle_thread) { - return thread; + struct thread *next = thread->next; + + if (thread->state == THREAD_DEAD && thread != current_thread) { + if (prev != 0) { + prev->next = next; + } else { + run_queue_head = next; + } + + if (run_queue_tail == thread) { + run_queue_tail = prev; + } + + release_thread(thread); + } else { + prev = thread; } - thread = thread->next; - } - for (thread = run_queue_head; thread != start; thread = thread->next) { - if (thread->state == THREAD_READY && thread != idle_thread) { - return thread; - } - } - - if (idle_thread != 0 && idle_thread->state == THREAD_READY) { - return idle_thread; - } - - return 0; -} - -static void wake_sleeping_threads(uint64_t tick) -{ - struct thread *thread; - - for (thread = run_queue_head; thread != 0; thread = thread->next) { - if (thread->state == THREAD_SLEEPING && - thread->wake_tick <= tick) { - thread->wake_tick = 0; - thread->state = THREAD_READY; - } - } -} - -void sched_yield(void) -{ - struct thread *prev; - struct thread *next; - - if (schedule_locked != 0) { - return; - } - - reap_dead_threads(); - - prev = current_thread; - next = next_runnable_thread(); - - if (next == 0 || next == prev) { - return; - } - - schedule_locked = 1; - - if (prev != 0 && prev->state == THREAD_RUNNING) { - prev->state = THREAD_READY; - } - - next->state = THREAD_RUNNING; - current_thread = next; - schedule_locked = 0; - - if (prev == 0) { - arch_context_switch(&boot_stack_pointer, next->stack_pointer); - return; - } - - arch_context_switch(&prev->stack_pointer, next->stack_pointer); -} - -void sched_tick(uint64_t tick) -{ - wake_sleeping_threads(tick); - - if (current_thread != 0 && current_thread->state == THREAD_RUNNING) { - need_resched = 1; - } -} - -void sched_irq_exit(void) -{ - if (need_resched == 0 || current_thread == 0 || schedule_locked != 0) { - return; - } - - need_resched = 0; - sched_yield(); -} - -void sched_sleep(uint64_t ticks) -{ - uint64_t now; - - if (current_thread == 0 || ticks == 0) { - return; - } - - now = timer_ticks(); - current_thread->wake_tick = now + ticks; - current_thread->state = THREAD_SLEEPING; - sched_yield(); -} - -void wait_queue_init(struct wait_queue *queue) -{ - if (queue == 0) { - return; - } - - queue->head = 0; - queue->tail = 0; -} - -static void wait_queue_enqueue(struct wait_queue *queue, struct thread *thread) -{ - thread->wait_next = 0; - - if (queue->tail != 0) { - queue->tail->wait_next = thread; - } else { - queue->head = thread; - } - - queue->tail = thread; -} - -void wait_queue_sleep(struct wait_queue *queue) -{ - if (queue == 0 || current_thread == 0) { - return; - } - - wait_queue_enqueue(queue, current_thread); - current_thread->state = THREAD_WAITING; - sched_yield(); -} - -void wait_queue_wake_one(struct wait_queue *queue) -{ - struct thread *thread; - - if (queue == 0 || queue->head == 0) { - return; - } - - thread = queue->head; - queue->head = thread->wait_next; - if (queue->head == 0) { - queue->tail = 0; - } - - thread->wait_next = 0; - if (thread->state == THREAD_WAITING) { - thread->state = THREAD_READY; - } -} - -void wait_queue_wake_all(struct wait_queue *queue) -{ - while (queue != 0 && queue->head != 0) { - wait_queue_wake_one(queue); + thread = next; } } @@ -345,128 +145,3 @@ static void thread_trampoline(void) sched_yield(); } } - -static void thread_selftest_entry(void *arg) -{ - (void)arg; -} - -static void scheduler_selftest(void) -{ - struct spinlock test_lock; - struct thread *first = - kernel_thread_create("worker-a", thread_selftest_entry, 0); - struct thread *second = - kernel_thread_create("worker-b", thread_selftest_entry, 0); - uint64_t flags; - - test_lock.locked = 0; - - if (first == 0 || second == 0 || first == second) { - panic("kernel thread selftest allocation failed"); - } - - if (first->id == second->id || first->state != THREAD_READY || - second->state != THREAD_READY) { - panic("kernel thread selftest state failed"); - } - - if ((first->stack_top & (STACK_ALIGNMENT - 1)) != 0 || - (second->stack_top & (STACK_ALIGNMENT - 1)) != 0) { - panic("kernel thread selftest stack alignment failed"); - } - - if (run_queue_head != first || first->next != second || - run_queue_tail != second) { - panic("kernel thread selftest run queue failed"); - } - - spin_lock_irqsave(&test_lock, &flags); - spin_unlock_irqrestore(&test_lock, flags); - - early_log_puts("kernel thread selftest ok\n"); -} - -static void scheduler_demo_entry(void *arg) -{ - uint64_t id = (uint64_t)(uintptr_t)arg; - uint64_t step; - - for (step = 1; step <= 3; step++) { - early_log_puts("preempt thread "); - early_log_u64_decimal(id); - early_log_puts(" step="); - early_log_u64_decimal(step); - early_log_puts("\n"); - sched_sleep(2); - } -} - -static struct wait_queue demo_wait_queue; - -static void wait_queue_demo_waiter(void *arg) -{ - (void)arg; - - early_log_puts("waiter sleeping\n"); - wait_queue_sleep(&demo_wait_queue); - early_log_puts("waiter woke\n"); -} - -static void wait_queue_demo_waker(void *arg) -{ - (void)arg; - - early_log_puts("waker sleeping\n"); - sched_sleep(4); - early_log_puts("waker wake_one\n"); - wait_queue_wake_one(&demo_wait_queue); -} - -static void idle_thread_entry(void *arg) -{ - (void)arg; - - for (;;) { - __asm__ volatile("hlt"); - } -} - -void sched_init(void) -{ - if (scheduler_ready != 0) { - return; - } - - run_queue_head = 0; - run_queue_tail = 0; - idle_thread = 0; - scheduler_ready = 1; - - early_log_puts("scheduler initialized\n"); - scheduler_selftest(); -} - -void sched_start(void) -{ - struct thread *first = kernel_thread_create( - "round-robin-a", scheduler_demo_entry, (void *)(uintptr_t)1); - struct thread *second = kernel_thread_create( - "round-robin-b", scheduler_demo_entry, (void *)(uintptr_t)2); - struct thread *waiter = - kernel_thread_create("waiter", wait_queue_demo_waiter, 0); - struct thread *waker = - kernel_thread_create("waker", wait_queue_demo_waker, 0); - idle_thread = kernel_thread_create("idle", idle_thread_entry, 0); - - if (first == 0 || second == 0 || waiter == 0 || waker == 0 || - idle_thread == 0) { - panic("scheduler demo thread creation failed"); - } - - wait_queue_init(&demo_wait_queue); - early_log_puts("scheduler starting\n"); - sched_yield(); - - panic("scheduler returned to boot context"); -} diff --git a/kernel/sched/wait.c b/kernel/sched/wait.c new file mode 100644 index 0000000..979e92f --- /dev/null +++ b/kernel/sched/wait.c @@ -0,0 +1,64 @@ +#include + +#include "sched.h" + +void wait_queue_init(struct wait_queue *queue) +{ + if (queue == 0) { + return; + } + + queue->head = 0; + queue->tail = 0; +} + +static void wait_queue_enqueue(struct wait_queue *queue, struct thread *thread) +{ + thread->wait_next = 0; + + if (queue->tail != 0) { + queue->tail->wait_next = thread; + } else { + queue->head = thread; + } + + queue->tail = thread; +} + +void wait_queue_sleep(struct wait_queue *queue) +{ + if (queue == 0 || current_thread == 0) { + return; + } + + wait_queue_enqueue(queue, current_thread); + current_thread->state = THREAD_WAITING; + sched_yield(); +} + +void wait_queue_wake_one(struct wait_queue *queue) +{ + struct thread *thread; + + if (queue == 0 || queue->head == 0) { + return; + } + + thread = queue->head; + queue->head = thread->wait_next; + if (queue->head == 0) { + queue->tail = 0; + } + + thread->wait_next = 0; + if (thread->state == THREAD_WAITING) { + thread->state = THREAD_READY; + } +} + +void wait_queue_wake_all(struct wait_queue *queue) +{ + while (queue != 0 && queue->head != 0) { + wait_queue_wake_one(queue); + } +} diff --git a/kernel/selftest/page_table.c b/kernel/selftest/page_table.c new file mode 100644 index 0000000..efdb23d --- /dev/null +++ b/kernel/selftest/page_table.c @@ -0,0 +1,43 @@ +#include + +#include +#include + +#include "arch/x86/mm/page_table.h" + +#define TEST_VIRTUAL_PAGE 0xffffff0000000000ull + +void page_table_selftest(void) +{ + phys_addr_t page = alloc_page(); + phys_addr_t resolved; + volatile uint64_t *mapped = + (volatile uint64_t *)(uintptr_t)TEST_VIRTUAL_PAGE; + + if (page == 0) { + panic("page table selftest allocation failed"); + } + + page_tables_init(); + + if (map_page(TEST_VIRTUAL_PAGE, page, PAGE_WRITABLE) != 0) { + panic("page table selftest map failed"); + } + + if (virt_to_phys(TEST_VIRTUAL_PAGE, &resolved) != 0 || + resolved != page) { + panic("page table selftest resolve failed"); + } + + *mapped = 0x54494f4c45504d4dull; + if (*mapped != 0x54494f4c45504d4dull) { + panic("page table selftest access failed"); + } + + if (unmap_page(TEST_VIRTUAL_PAGE) != 0) { + panic("page table selftest unmap failed"); + } + + free_page(page); + early_log_puts("page table selftest ok\n"); +} diff --git a/kernel/selftest/sched.c b/kernel/selftest/sched.c new file mode 100644 index 0000000..a983a79 --- /dev/null +++ b/kernel/selftest/sched.c @@ -0,0 +1,108 @@ +#include + +#include +#include +#include + +#include "sched/sched.h" + +#define STACK_ALIGNMENT 16u + +static void thread_selftest_entry(void *arg) +{ + (void)arg; +} + +void sched_selftest(void) +{ + struct spinlock test_lock; + struct thread *first = + kernel_thread_create("worker-a", thread_selftest_entry, 0); + struct thread *second = + kernel_thread_create("worker-b", thread_selftest_entry, 0); + uint64_t flags; + + test_lock.locked = 0; + + if (first == 0 || second == 0 || first == second) { + panic("kernel thread selftest allocation failed"); + } + + if (first->id == second->id || first->state != THREAD_READY || + second->state != THREAD_READY) { + panic("kernel thread selftest state failed"); + } + + if ((first->stack_top & (STACK_ALIGNMENT - 1)) != 0 || + (second->stack_top & (STACK_ALIGNMENT - 1)) != 0) { + panic("kernel thread selftest stack alignment failed"); + } + + if (run_queue_head != first || first->next != second || + run_queue_tail != second) { + panic("kernel thread selftest run queue failed"); + } + + spin_lock_irqsave(&test_lock, &flags); + spin_unlock_irqrestore(&test_lock, flags); + + early_log_puts("kernel thread selftest ok\n"); +} + +static void scheduler_demo_entry(void *arg) +{ + uint64_t id = (uint64_t)(uintptr_t)arg; + uint64_t step; + + for (step = 1; step <= 3; step++) { + early_log_puts("preempt thread "); + early_log_u64_decimal(id); + early_log_puts(" step="); + early_log_u64_decimal(step); + early_log_puts("\n"); + sched_sleep(2); + } +} + +static struct wait_queue demo_wait_queue; + +static void wait_queue_demo_waiter(void *arg) +{ + (void)arg; + + early_log_puts("waiter sleeping\n"); + wait_queue_sleep(&demo_wait_queue); + early_log_puts("waiter woke\n"); +} + +static void wait_queue_demo_waker(void *arg) +{ + (void)arg; + + early_log_puts("waker sleeping\n"); + sched_sleep(4); + early_log_puts("waker wake_one\n"); + wait_queue_wake_one(&demo_wait_queue); +} + +void sched_demo_start(void) +{ + struct thread *first = kernel_thread_create( + "round-robin-a", scheduler_demo_entry, (void *)(uintptr_t)1); + struct thread *second = kernel_thread_create( + "round-robin-b", scheduler_demo_entry, (void *)(uintptr_t)2); + struct thread *waiter = + kernel_thread_create("waiter", wait_queue_demo_waiter, 0); + struct thread *waker = + kernel_thread_create("waker", wait_queue_demo_waker, 0); + + if (first == 0 || second == 0 || waiter == 0 || waker == 0) { + panic("scheduler demo thread creation failed"); + } + + wait_queue_init(&demo_wait_queue); + early_log_puts("scheduler starting\n"); + sched_yield(); + + panic("scheduler returned to boot context"); +} diff --git a/scripts/check.sh b/scripts/check.sh index b78580b..1807fa2 100755 --- a/scripts/check.sh +++ b/scripts/check.sh @@ -4,6 +4,7 @@ set -euo pipefail cd "$(dirname "$0")/.." ./scripts/checks/format.sh +./scripts/checks/structure.sh ./scripts/checks/boot.sh ./scripts/checks/trap.sh ./scripts/checks/page-fault.sh diff --git a/scripts/checks/structure.sh b/scripts/checks/structure.sh new file mode 100755 index 0000000..806eb99 --- /dev/null +++ b/scripts/checks/structure.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")/../.." + +fail() +{ + echo "structure check failed: $*" >&2 + exit 1 +} + +check_no_relative_parent_includes() +{ + local matches + + matches=$(find arch include kernel mm -name '*.c' -o -name '*.h' | + xargs grep -nE '^[[:space:]]*#include[[:space:]]+[<"]\.\./' || true) + + if [ -n "$matches" ]; then + echo "$matches" >&2 + fail "do not use ../ includes; promote shared headers or add a private include root" + fi +} + +check_selftests_are_centralized() +{ + local matches + + matches=$(find arch kernel mm -name '*selftest*.c' | + grep -v '^kernel/selftest/' || true) + + if [ -n "$matches" ]; then + echo "$matches" >&2 + fail "kernel selftests belong under kernel/selftest/" + fi +} + +check_no_relative_parent_includes +check_selftests_are_centralized