From bbf184f9fc5830dfa896e5a989bfac517c669eab Mon Sep 17 00:00:00 2001 From: Microindole Date: Sat, 9 May 2026 17:07:09 +0800 Subject: [PATCH] feat(sched): add x86 context switch and round-robin scheduling --- arch/x86/include/arch/switch.h | 8 ++ arch/x86/kernel/Makefile | 1 + arch/x86/kernel/switch.S | 22 +++++ docs/agents/tasks/04-time-scheduler.md | 10 +- docs/agents/tasks/README.md | 4 +- docs/roadmap.md | 4 +- include/tianole/sched.h | 3 + kernel/main.c | 4 +- kernel/sched/thread.c | 123 +++++++++++++++++++++++++ scripts/checks/boot.sh | 14 +++ 10 files changed, 183 insertions(+), 10 deletions(-) create mode 100644 arch/x86/include/arch/switch.h create mode 100644 arch/x86/kernel/switch.S diff --git a/arch/x86/include/arch/switch.h b/arch/x86/include/arch/switch.h new file mode 100644 index 0000000..4263866 --- /dev/null +++ b/arch/x86/include/arch/switch.h @@ -0,0 +1,8 @@ +#ifndef ARCH_X86_SWITCH_H +#define ARCH_X86_SWITCH_H + +#include + +void arch_context_switch(uintptr_t *prev_sp, uintptr_t next_sp); + +#endif diff --git a/arch/x86/kernel/Makefile b/arch/x86/kernel/Makefile index d63b019..c34ffe1 100644 --- a/arch/x86/kernel/Makefile +++ b/arch/x86/kernel/Makefile @@ -8,4 +8,5 @@ ARCH_KERNEL_SRCS := \ ARCH_KERNEL_OBJS := \ $(BUILD_DIR)/arch/kernel/entry.o \ $(BUILD_DIR)/arch/kernel/exception_entry.o \ + $(BUILD_DIR)/arch/kernel/switch.o \ $(patsubst $(ARCH_DIR)/kernel/%.c,$(BUILD_DIR)/arch/kernel/%.o,$(ARCH_KERNEL_SRCS)) diff --git a/arch/x86/kernel/switch.S b/arch/x86/kernel/switch.S new file mode 100644 index 0000000..ee7d7df --- /dev/null +++ b/arch/x86/kernel/switch.S @@ -0,0 +1,22 @@ +.section .text + +.global arch_context_switch +.type arch_context_switch, @function +arch_context_switch: + pushq %rbp + pushq %rbx + pushq %r12 + pushq %r13 + pushq %r14 + pushq %r15 + + movq %rsp, (%rdi) + movq %rsi, %rsp + + popq %r15 + popq %r14 + popq %r13 + popq %r12 + popq %rbx + popq %rbp + ret diff --git a/docs/agents/tasks/04-time-scheduler.md b/docs/agents/tasks/04-time-scheduler.md index 604976f..5b43ded 100644 --- a/docs/agents/tasks/04-time-scheduler.md +++ b/docs/agents/tasks/04-time-scheduler.md @@ -54,15 +54,17 @@ - 已建立最小 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`。 +- 已建立 x86 上下文切换入口,保存/恢复 callee-saved 寄存器和栈指针。 +- 已建立线程 trampoline,新线程能从独立内核栈进入自己的入口函数。 +- 已建立协作式 round-robin,两个 kernel thread 能通过 `sched_yield()` 轮转运行。 +- `scripts/check.sh` 已验证 `timer initialized`、`timer tick=1/2/3`、`scheduler initialized`、`kernel thread selftest ok` 和 `thread 1/2` 轮转日志。 后续扩展: - 把 IRQ 分发扩展为可注册 handler 的表,而不是只处理 timer。 -- 建立上下文切换入口。 -- 建立 run queue 和最小 round-robin scheduler。 +- 把协作式调度接入 timer tick,演进为抢占式调度。 - 建立 `yield()`、`sleep()`、timer wakeup 和 wait queue。 下一阶段: -- 继续在 `04-time-scheduler.md` 内推进上下文切换入口和最小 round-robin scheduler。 +- 继续在 `04-time-scheduler.md` 内推进 timer 驱动的抢占式调度、`sleep()` 和 wait queue。 diff --git a/docs/agents/tasks/README.md b/docs/agents/tasks/README.md index 1fe3beb..588c5bc 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 对象、内核栈和 run queue 基础;上下文切换、调度和等待队列未完成。 | +| `04-time-scheduler.md` | 进行中 | 已有 PIT timer interrupt、通用 tick 计数、kernel thread、内核栈、上下文切换和协作式 round-robin;抢占式调度和等待队列未完成。 | | `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` 内:引入上下文切换入口,再做最小 round-robin 调度循环。 +当前最合适的下一步仍在 `04-time-scheduler.md` 内:把调度接入 timer tick,演进到抢占式调度,然后做 `sleep()` 和 wait queue。 ## Linux 级能力缺口路由 diff --git a/docs/roadmap.md b/docs/roadmap.md index 6ea48de..e555ed5 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -25,6 +25,7 @@ - 内核堆基础:已提供 `kmalloc/kfree`,并接入启动 selftest。 - timer 基础:已接入 x86 PIC/PIT,timer IRQ 能进入通用 tick 入口。 - kernel thread 基础:已建立线程对象、内核栈和 run queue。 +- 调度基础:已建立 x86 上下文切换入口和协作式 round-robin。 - 构建系统已拆成根 Makefile、`arch/x86/Makefile` 和目录 Makefile。 - `scripts/check.sh` 已验证启动日志、串口日志和 invalid opcode 异常路径,GitHub Actions 已接入。 - `.clang-format` 已用于强制当前 C 代码风格。 @@ -44,7 +45,8 @@ 目标: -- 建立上下文切换入口和最小 round-robin 调度循环。 +- 把调度接入 timer tick,演进到抢占式调度。 +- 建立 `sleep()`、timer wakeup 和 wait queue。 ## 任务路由 diff --git a/include/tianole/sched.h b/include/tianole/sched.h index c483901..ddcaaf5 100644 --- a/include/tianole/sched.h +++ b/include/tianole/sched.h @@ -16,6 +16,7 @@ enum thread_state { struct thread { uint64_t id; enum thread_state state; + uintptr_t stack_pointer; kernel_thread_entry_t entry; void *arg; void *stack_base; @@ -28,5 +29,7 @@ struct thread { void sched_init(void); struct thread *kernel_thread_create( const char *name, kernel_thread_entry_t entry, void *arg); +void sched_start(void) __attribute__((noreturn)); +void sched_yield(void); #endif diff --git a/kernel/main.c b/kernel/main.c index ab384e6..d0ae4c0 100644 --- a/kernel/main.c +++ b/kernel/main.c @@ -24,7 +24,5 @@ void kernel_main(const boot_info_t *boot_info) *(volatile uint64_t *)(uintptr_t)0xffffff1000000000ull = 1; #endif - for (;;) { - __asm__ volatile("hlt"); - } + sched_start(); } diff --git a/kernel/sched/thread.c b/kernel/sched/thread.c index e8907ac..fa7d51c 100644 --- a/kernel/sched/thread.c +++ b/kernel/sched/thread.c @@ -1,6 +1,8 @@ #include #include +#include + #include #include #include @@ -10,9 +12,13 @@ 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 void thread_trampoline(void) __attribute__((noreturn)); + static uintptr_t align_down_uintptr(uintptr_t value, uintptr_t alignment) { return value & ~(alignment - 1); @@ -50,6 +56,22 @@ static void enqueue_thread(struct thread *thread) run_queue_tail = thread; } +static uintptr_t prepare_initial_stack(uintptr_t stack_top) +{ + uintptr_t *stack = (uintptr_t *)stack_top; + + *--stack = 0; + *--stack = (uintptr_t)thread_trampoline; + *--stack = 0; + *--stack = 0; + *--stack = 0; + *--stack = 0; + *--stack = 0; + *--stack = 0; + + return (uintptr_t)stack; +} + struct thread *kernel_thread_create( const char *name, kernel_thread_entry_t entry, void *arg) { @@ -78,6 +100,7 @@ struct thread *kernel_thread_create( thread->entry = entry; thread->arg = arg; thread->stack_top = align_down_uintptr(stack_top, STACK_ALIGNMENT); + thread->stack_pointer = prepare_initial_stack(thread->stack_top); thread->stack_size = KERNEL_STACK_SIZE; thread->next = 0; copy_thread_name(thread->name, sizeof(thread->name), name); @@ -87,6 +110,74 @@ struct thread *kernel_thread_create( return 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) { + return thread; + } + thread = thread->next; + } + + for (thread = run_queue_head; thread != start; thread = thread->next) { + if (thread->state == THREAD_READY) { + return thread; + } + } + + return 0; +} + +void sched_yield(void) +{ + struct thread *prev = current_thread; + struct thread *next = next_runnable_thread(); + + if (next == 0 || next == prev) { + return; + } + + if (prev != 0 && prev->state == THREAD_RUNNING) { + prev->state = THREAD_READY; + } + + next->state = THREAD_RUNNING; + current_thread = next; + + if (prev == 0) { + arch_context_switch(&boot_stack_pointer, next->stack_pointer); + return; + } + + arch_context_switch(&prev->stack_pointer, next->stack_pointer); +} + +static void thread_trampoline(void) +{ + struct thread *thread = current_thread; + + if (thread == 0 || thread->entry == 0) { + panic("kernel thread entered without entry"); + } + + thread->entry(thread->arg); + thread->state = THREAD_DEAD; + + for (;;) { + sched_yield(); + } +} + static void thread_selftest_entry(void *arg) { (void)arg; @@ -121,6 +212,21 @@ static void scheduler_selftest(void) 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("thread "); + early_log_u64_decimal(id); + early_log_puts(" step="); + early_log_u64_decimal(step); + early_log_puts("\n"); + sched_yield(); + } +} + void sched_init(void) { if (scheduler_ready != 0) { @@ -134,3 +240,20 @@ void sched_init(void) 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); + + if (first == 0 || second == 0) { + panic("scheduler demo thread creation failed"); + } + + early_log_puts("scheduler starting\n"); + sched_yield(); + + panic("scheduler returned to boot context"); +} diff --git a/scripts/checks/boot.sh b/scripts/checks/boot.sh index 7d3a0ae..3a46988 100755 --- a/scripts/checks/boot.sh +++ b/scripts/checks/boot.sh @@ -26,6 +26,13 @@ check_lines build/debug.log \ "scheduler initialized" \ "kernel thread selftest ok" \ "timer initialized" \ + "scheduler starting" \ + "thread 1 step=1" \ + "thread 2 step=1" \ + "thread 1 step=2" \ + "thread 2 step=2" \ + "thread 1 step=3" \ + "thread 2 step=3" \ "timer tick=1" \ "timer tick=2" \ "timer tick=3" @@ -46,6 +53,13 @@ check_lines build/serial.log \ "scheduler initialized" \ "kernel thread selftest ok" \ "timer initialized" \ + "scheduler starting" \ + "thread 1 step=1" \ + "thread 2 step=1" \ + "thread 1 step=2" \ + "thread 2 step=2" \ + "thread 1 step=3" \ + "thread 2 step=3" \ "timer tick=1" \ "timer tick=2" \ "timer tick=3"