From e8a14dbc157e342f0b64e196201e6f7f3c443c24 Mon Sep 17 00:00:00 2001 From: Microindole Date: Sat, 9 May 2026 17:19:26 +0800 Subject: [PATCH] feat(sched): add timer-driven scheduling and wait queues --- docs/agents/tasks/04-time-scheduler.md | 14 ++- docs/agents/tasks/README.md | 4 +- docs/roadmap.md | 6 +- include/tianole/sched.h | 14 +++ kernel/sched/thread.c | 161 ++++++++++++++++++++++++- kernel/time/timer.c | 3 + scripts/checks/boot.sh | 32 +++-- 7 files changed, 209 insertions(+), 25 deletions(-) diff --git a/docs/agents/tasks/04-time-scheduler.md b/docs/agents/tasks/04-time-scheduler.md index 5b43ded..74803b9 100644 --- a/docs/agents/tasks/04-time-scheduler.md +++ b/docs/agents/tasks/04-time-scheduler.md @@ -57,14 +57,20 @@ - 已建立 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` 轮转日志。 +- 已把调度接入 `timer_tick()`,timer tick 会唤醒到期 sleep 线程并触发 round-robin。 +- 已建立 idle thread,所有普通线程 sleep/wait 时由 idle 承接 CPU。 +- 已提供 `sched_sleep()`,线程可以睡眠指定 tick 数并被 timer 唤醒。 +- 已提供 `wait_queue_init()`、`wait_queue_sleep()`、`wait_queue_wake_one()` 和 `wait_queue_wake_all()`。 +- `scripts/check.sh` 已验证 `timer initialized`、`timer tick=1/2/3`、`scheduler initialized`、`kernel thread selftest ok`、timer 驱动线程轮转、`sched_sleep()` 和 wait queue wakeup。 后续扩展: - 把 IRQ 分发扩展为可注册 handler 的表,而不是只处理 timer。 -- 把协作式调度接入 timer tick,演进为抢占式调度。 -- 建立 `yield()`、`sleep()`、timer wakeup 和 wait queue。 +- 把当前直接在 timer IRQ 内触发调度的路径收敛为更明确的 interrupt-exit reschedule 模型。 +- 建立基础 spinlock 或 interrupt-safe lock。 +- 为 wait queue 增加条件等待、超时等待和状态检查。 +- 为线程退出增加资源回收路径。 下一阶段: -- 继续在 `04-time-scheduler.md` 内推进 timer 驱动的抢占式调度、`sleep()` 和 wait queue。 +- 继续在 `04-time-scheduler.md` 内推进 interrupt-safe lock、线程退出回收和更严格的 interrupt-exit reschedule。 diff --git a/docs/agents/tasks/README.md b/docs/agents/tasks/README.md index 588c5bc..35c41bf 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、内核栈、上下文切换和协作式 round-robin;抢占式调度和等待队列未完成。 | +| `04-time-scheduler.md` | 进行中 | 已有 PIT timer interrupt、通用 tick、kernel thread、内核栈、上下文切换、timer 驱动 round-robin、sleep 和 wait 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` 内:把调度接入 timer tick,演进到抢占式调度,然后做 `sleep()` 和 wait queue。 +当前最合适的下一步仍在 `04-time-scheduler.md` 内:补 interrupt-safe lock、线程退出回收,并把调度路径收敛为更明确的 interrupt-exit reschedule。 ## Linux 级能力缺口路由 diff --git a/docs/roadmap.md b/docs/roadmap.md index e555ed5..51cf1e9 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -26,6 +26,7 @@ - timer 基础:已接入 x86 PIC/PIT,timer IRQ 能进入通用 tick 入口。 - kernel thread 基础:已建立线程对象、内核栈和 run queue。 - 调度基础:已建立 x86 上下文切换入口和协作式 round-robin。 +- sleep/wait 基础:timer tick 能唤醒 sleep 线程,wait queue 支持 sleep 和 wake。 - 构建系统已拆成根 Makefile、`arch/x86/Makefile` 和目录 Makefile。 - `scripts/check.sh` 已验证启动日志、串口日志和 invalid opcode 异常路径,GitHub Actions 已接入。 - `.clang-format` 已用于强制当前 C 代码风格。 @@ -45,8 +46,9 @@ 目标: -- 把调度接入 timer tick,演进到抢占式调度。 -- 建立 `sleep()`、timer wakeup 和 wait queue。 +- 建立 interrupt-safe lock。 +- 建立线程退出资源回收。 +- 把调度路径收敛为更明确的 interrupt-exit reschedule。 ## 任务路由 diff --git a/include/tianole/sched.h b/include/tianole/sched.h index ddcaaf5..9710434 100644 --- a/include/tianole/sched.h +++ b/include/tianole/sched.h @@ -10,9 +10,15 @@ enum thread_state { THREAD_READY, THREAD_RUNNING, THREAD_SLEEPING, + THREAD_WAITING, THREAD_DEAD, }; +struct wait_queue { + struct thread *head; + struct thread *tail; +}; + struct thread { uint64_t id; enum thread_state state; @@ -22,7 +28,9 @@ struct thread { void *stack_base; uintptr_t stack_top; size_t stack_size; + uint64_t wake_tick; struct thread *next; + struct thread *wait_next; char name[32]; }; @@ -30,6 +38,12 @@ 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_tick(uint64_t tick); void sched_yield(void); +void sched_sleep(uint64_t ticks); +void wait_queue_init(struct wait_queue *queue); +void wait_queue_sleep(struct wait_queue *queue); +void wait_queue_wake_one(struct wait_queue *queue); +void wait_queue_wake_all(struct wait_queue *queue); #endif diff --git a/kernel/sched/thread.c b/kernel/sched/thread.c index fa7d51c..2d2ddf3 100644 --- a/kernel/sched/thread.c +++ b/kernel/sched/thread.c @@ -6,6 +6,7 @@ #include #include #include +#include #define KERNEL_STACK_SIZE (PAGE_SIZE * 4u) #define STACK_ALIGNMENT 16u @@ -16,6 +17,8 @@ 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 struct thread *idle_thread; static void thread_trampoline(void) __attribute__((noreturn)); @@ -102,7 +105,9 @@ struct thread *kernel_thread_create( 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->wake_tick = 0; thread->next = 0; + thread->wait_next = 0; copy_thread_name(thread->name, sizeof(thread->name), name); enqueue_thread(thread); @@ -123,36 +128,60 @@ static struct thread *next_runnable_thread(void) thread = start; while (thread != 0) { - if (thread->state == THREAD_READY) { + 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) { + 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 = current_thread; struct thread *next = next_runnable_thread(); + if (schedule_locked != 0) { + return; + } + 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); @@ -162,6 +191,90 @@ void sched_yield(void) 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) { + 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); + } +} + static void thread_trampoline(void) { struct thread *thread = current_thread; @@ -218,12 +331,42 @@ static void scheduler_demo_entry(void *arg) uint64_t step; for (step = 1; step <= 3; step++) { - early_log_puts("thread "); + early_log_puts("preempt thread "); early_log_u64_decimal(id); early_log_puts(" step="); early_log_u64_decimal(step); early_log_puts("\n"); - sched_yield(); + 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"); } } @@ -235,6 +378,7 @@ void sched_init(void) run_queue_head = 0; run_queue_tail = 0; + idle_thread = 0; scheduler_ready = 1; early_log_puts("scheduler initialized\n"); @@ -247,11 +391,18 @@ void sched_start(void) "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) { + 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(); diff --git a/kernel/time/timer.c b/kernel/time/timer.c index 6d21360..b8c3002 100644 --- a/kernel/time/timer.c +++ b/kernel/time/timer.c @@ -1,6 +1,7 @@ #include #include +#include #include static uint64_t tick_count; @@ -14,6 +15,8 @@ void timer_tick(void) early_log_u64_decimal(tick_count); early_log_puts("\n"); } + + sched_tick(tick_count); } uint64_t timer_ticks(void) diff --git a/scripts/checks/boot.sh b/scripts/checks/boot.sh index 3a46988..89609bd 100755 --- a/scripts/checks/boot.sh +++ b/scripts/checks/boot.sh @@ -27,12 +27,16 @@ check_lines build/debug.log \ "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" \ + "preempt thread 1 step=1" \ + "preempt thread 2 step=1" \ + "waiter sleeping" \ + "waker sleeping" \ + "preempt thread 1 step=2" \ + "preempt thread 2 step=2" \ + "preempt thread 1 step=3" \ + "preempt thread 2 step=3" \ + "waker wake_one" \ + "waiter woke" \ "timer tick=1" \ "timer tick=2" \ "timer tick=3" @@ -54,12 +58,16 @@ check_lines build/serial.log \ "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" \ + "preempt thread 1 step=1" \ + "preempt thread 2 step=1" \ + "waiter sleeping" \ + "waker sleeping" \ + "preempt thread 1 step=2" \ + "preempt thread 2 step=2" \ + "preempt thread 1 step=3" \ + "preempt thread 2 step=3" \ + "waker wake_one" \ + "waiter woke" \ "timer tick=1" \ "timer tick=2" \ "timer tick=3"