From 76a9363e3c9d4a858ff4d959ecc5b321b1c71f82 Mon Sep 17 00:00:00 2001 From: Microindole Date: Sat, 9 May 2026 16:59:46 +0800 Subject: [PATCH] feat(sched): add kernel thread foundation --- docs/agents/tasks/04-time-scheduler.md | 8 +- docs/agents/tasks/README.md | 4 +- docs/roadmap.md | 3 +- include/tianole/sched.h | 32 ++++++ kernel/Makefile | 1 + kernel/main.c | 2 + kernel/sched/thread.c | 136 +++++++++++++++++++++++++ scripts/checks/boot.sh | 4 + 8 files changed, 184 insertions(+), 6 deletions(-) create mode 100644 include/tianole/sched.h create mode 100644 kernel/sched/thread.c diff --git a/docs/agents/tasks/04-time-scheduler.md b/docs/agents/tasks/04-time-scheduler.md index 26743a4..604976f 100644 --- a/docs/agents/tasks/04-time-scheduler.md +++ b/docs/agents/tasks/04-time-scheduler.md @@ -51,16 +51,18 @@ - 已建立通用 `timer_tick()` 入口和 `timer_ticks()` 计数接口。 - 已在 trap dispatch 中区分 CPU exception 和外部 IRQ。 - 已对 timer IRQ0 发送 EOI,避免中断只触发一次。 -- `scripts/check.sh` 已验证 `timer initialized` 和 `timer tick=1/2/3`。 +- 已建立最小 kernel thread 对象,包含 id、状态、入口、参数、内核栈和 run queue 链接。 +- 已建立动态 run queue,不固定写死线程数量。 +- 已能通过 `kernel_thread_create()` 动态分配线程对象和内核栈。 +- `scripts/check.sh` 已验证 `timer initialized`、`timer tick=1/2/3`、`scheduler initialized` 和 `kernel thread selftest ok`。 后续扩展: - 把 IRQ 分发扩展为可注册 handler 的表,而不是只处理 timer。 -- 建立 kernel thread 对象和内核栈。 - 建立上下文切换入口。 - 建立 run queue 和最小 round-robin scheduler。 - 建立 `yield()`、`sleep()`、timer wakeup 和 wait queue。 下一阶段: -- 继续在 `04-time-scheduler.md` 内推进最小 kernel thread。 +- 继续在 `04-time-scheduler.md` 内推进上下文切换入口和最小 round-robin scheduler。 diff --git a/docs/agents/tasks/README.md b/docs/agents/tasks/README.md index fdaa4f0..1fe3beb 100644 --- a/docs/agents/tasks/README.md +++ b/docs/agents/tasks/README.md @@ -28,7 +28,7 @@ | `01-early-debug.md` | 基础完成 | 已有 early log 前端、QEMU debug port、COM1 串口和最小 `panic()`。 | | `02-cpu-interrupts.md` | 基础完成 | 已有 GDT/TSS/IDT、exception vector 0-31、`trap_frame` 和 invalid opcode 回归测试。 | | `03-memory.md` | 基础完成 | 已有最小物理页 allocator、页表 map/unmap/query、page fault 诊断和内核堆。 | -| `04-time-scheduler.md` | 进行中 | 已有 PIT timer interrupt 和通用 tick 计数;kernel thread、调度和等待队列未完成。 | +| `04-time-scheduler.md` | 进行中 | 已有 PIT timer interrupt、通用 tick 计数、kernel thread 对象、内核栈和 run queue 基础;上下文切换、调度和等待队列未完成。 | | `05-input-events.md` | 未开始 | 需要输入事件模型和键盘接入。 | | `06-storage-vfs.md` | 未开始 | 需要块层、缓存、VFS 和基础文件系统。 | | `07-user-mode.md` | 未开始 | 需要 syscall、进程、用户地址空间和 ELF 加载。 | @@ -36,7 +36,7 @@ | `09-driver-expansion.md` | 未开始 | 需要设备模型、PCI/ACPI、存储、网络等驱动扩展。 | | `10-real-machine.md` | 未开始 | 需要 U 盘真机启动验证和硬件差异记录。 | -当前最合适的下一步仍在 `04-time-scheduler.md` 内:引入最小 kernel thread 和内核栈,再做调度循环。 +当前最合适的下一步仍在 `04-time-scheduler.md` 内:引入上下文切换入口,再做最小 round-robin 调度循环。 ## Linux 级能力缺口路由 diff --git a/docs/roadmap.md b/docs/roadmap.md index 09a4033..6ea48de 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -24,6 +24,7 @@ - page fault 基础:已能输出 fault address、错误码、访问类型和权限来源。 - 内核堆基础:已提供 `kmalloc/kfree`,并接入启动 selftest。 - timer 基础:已接入 x86 PIC/PIT,timer IRQ 能进入通用 tick 入口。 +- kernel thread 基础:已建立线程对象、内核栈和 run queue。 - 构建系统已拆成根 Makefile、`arch/x86/Makefile` 和目录 Makefile。 - `scripts/check.sh` 已验证启动日志、串口日志和 invalid opcode 异常路径,GitHub Actions 已接入。 - `.clang-format` 已用于强制当前 C 代码风格。 @@ -43,7 +44,7 @@ 目标: -- 建立最小 kernel thread 和调度循环。 +- 建立上下文切换入口和最小 round-robin 调度循环。 ## 任务路由 diff --git a/include/tianole/sched.h b/include/tianole/sched.h new file mode 100644 index 0000000..c483901 --- /dev/null +++ b/include/tianole/sched.h @@ -0,0 +1,32 @@ +#ifndef TIANOLE_SCHED_H +#define TIANOLE_SCHED_H + +#include +#include + +typedef void (*kernel_thread_entry_t)(void *arg); + +enum thread_state { + THREAD_READY, + THREAD_RUNNING, + THREAD_SLEEPING, + THREAD_DEAD, +}; + +struct thread { + uint64_t id; + enum thread_state state; + kernel_thread_entry_t entry; + void *arg; + void *stack_base; + uintptr_t stack_top; + size_t stack_size; + struct thread *next; + char name[32]; +}; + +void sched_init(void); +struct thread *kernel_thread_create( + const char *name, kernel_thread_entry_t entry, void *arg); + +#endif diff --git a/kernel/Makefile b/kernel/Makefile index 8ee9d58..663ec71 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -2,6 +2,7 @@ KERNEL_SRCS := \ kernel/main.c \ kernel/boot_report.c \ kernel/early_log.c \ + kernel/sched/thread.c \ kernel/time/timer.c KERNEL_OBJS := \ diff --git a/kernel/main.c b/kernel/main.c index 105022a..ab384e6 100644 --- a/kernel/main.c +++ b/kernel/main.c @@ -4,6 +4,7 @@ #include #include #include +#include void kernel_main(const boot_info_t *boot_info) { @@ -12,6 +13,7 @@ void kernel_main(const boot_info_t *boot_info) arch_traps_init(); kernel_report_boot_state(boot_info); mm_init(boot_info); + sched_init(); arch_timer_init(); #if KERNEL_TEST_TRAP diff --git a/kernel/sched/thread.c b/kernel/sched/thread.c new file mode 100644 index 0000000..e8907ac --- /dev/null +++ b/kernel/sched/thread.c @@ -0,0 +1,136 @@ +#include +#include + +#include +#include +#include + +#define KERNEL_STACK_SIZE (PAGE_SIZE * 4u) +#define STACK_ALIGNMENT 16u + +static struct thread *run_queue_head; +static struct thread *run_queue_tail; +static uint64_t next_thread_id = 1; +static int scheduler_ready; + +static uintptr_t align_down_uintptr(uintptr_t value, uintptr_t alignment) +{ + return value & ~(alignment - 1); +} + +static void copy_thread_name(char *dest, size_t dest_size, const char *src) +{ + size_t index; + + if (dest_size == 0) { + return; + } + + if (src == 0) { + src = "thread"; + } + + for (index = 0; index + 1 < dest_size && src[index] != '\0'; index++) { + dest[index] = src[index]; + } + + 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; +} + +struct thread *kernel_thread_create( + const char *name, kernel_thread_entry_t entry, void *arg) +{ + struct thread *thread; + uintptr_t stack_top; + + if (entry == 0) { + return 0; + } + + thread = kmalloc(sizeof(*thread)); + if (thread == 0) { + return 0; + } + + thread->stack_base = kmalloc(KERNEL_STACK_SIZE); + if (thread->stack_base == 0) { + kfree(thread); + return 0; + } + + stack_top = (uintptr_t)thread->stack_base + KERNEL_STACK_SIZE; + + thread->id = next_thread_id++; + thread->state = THREAD_READY; + thread->entry = entry; + thread->arg = arg; + thread->stack_top = align_down_uintptr(stack_top, STACK_ALIGNMENT); + thread->stack_size = KERNEL_STACK_SIZE; + thread->next = 0; + copy_thread_name(thread->name, sizeof(thread->name), name); + + enqueue_thread(thread); + + return thread; +} + +static void thread_selftest_entry(void *arg) +{ + (void)arg; +} + +static void scheduler_selftest(void) +{ + struct thread *first = + kernel_thread_create("worker-a", thread_selftest_entry, 0); + struct thread *second = + kernel_thread_create("worker-b", thread_selftest_entry, 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"); + } + + early_log_puts("kernel thread selftest ok\n"); +} + +void sched_init(void) +{ + if (scheduler_ready != 0) { + return; + } + + run_queue_head = 0; + run_queue_tail = 0; + scheduler_ready = 1; + + early_log_puts("scheduler initialized\n"); + scheduler_selftest(); +} diff --git a/scripts/checks/boot.sh b/scripts/checks/boot.sh index 45dbde6..7d3a0ae 100755 --- a/scripts/checks/boot.sh +++ b/scripts/checks/boot.sh @@ -23,6 +23,8 @@ check_lines build/debug.log \ "page table selftest ok" \ "kernel heap initialized" \ "kernel heap selftest ok" \ + "scheduler initialized" \ + "kernel thread selftest ok" \ "timer initialized" \ "timer tick=1" \ "timer tick=2" \ @@ -41,6 +43,8 @@ check_lines build/serial.log \ "page table selftest ok" \ "kernel heap initialized" \ "kernel heap selftest ok" \ + "scheduler initialized" \ + "kernel thread selftest ok" \ "timer initialized" \ "timer tick=1" \ "timer tick=2" \