diff --git a/Makefile b/Makefile index df225f6..94a63d8 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)/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)/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/kernel/irq.c b/arch/x86/kernel/irq.c index c01903c..e7700d9 100644 --- a/arch/x86/kernel/irq.c +++ b/arch/x86/kernel/irq.c @@ -3,7 +3,9 @@ #include #include +#include #include +#include #include #define PIC1_COMMAND 0x20 @@ -24,11 +26,14 @@ #define IRQ_BASE 32u #define IRQ_TIMER 0u +#define IRQ_COUNT 16u -static void enable_interrupts(void) -{ - __asm__ volatile("sti"); -} +struct irq_action { + irq_handler_t handler; + void *data; +}; + +static struct irq_action irq_actions[IRQ_COUNT]; static void pic_remap(void) { @@ -77,19 +82,63 @@ static void pit_init(void) outb(PIT_CHANNEL0, (uint8_t)(divisor >> 8)); } -static void handle_timer_irq(void) +uint64_t arch_irq_save(void) { + uint64_t flags; + + __asm__ volatile("pushfq; popq %0; cli" : "=r"(flags) : : "memory"); + return flags; +} + +void arch_irq_restore(uint64_t flags) +{ + if ((flags & (1ull << 9)) != 0) { + __asm__ volatile("sti" : : : "memory"); + } +} + +int irq_register(uint8_t irq, irq_handler_t handler, void *data) +{ + uint64_t flags; + + if (irq >= IRQ_COUNT || handler == 0) { + return -1; + } + + flags = arch_irq_save(); + + if (irq_actions[irq].handler != 0) { + arch_irq_restore(flags); + return -1; + } + + irq_actions[irq].handler = handler; + irq_actions[irq].data = data; + + arch_irq_restore(flags); + return 0; +} + +static void timer_irq_handler(uint8_t irq, void *data) +{ + (void)irq; + (void)data; + timer_tick(); - pic_send_eoi(IRQ_TIMER); } void handle_irq(struct trap_frame *frame) { uint64_t irq = frame->vector - IRQ_BASE; + struct irq_action *action; - if (irq == IRQ_TIMER) { - handle_timer_irq(); - return; + if (irq < IRQ_COUNT) { + action = &irq_actions[irq]; + if (action->handler != 0) { + action->handler((uint8_t)irq, action->data); + pic_send_eoi((uint8_t)irq); + return; + } } early_log_puts("unexpected irq="); @@ -102,7 +151,10 @@ void arch_timer_init(void) { pic_remap(); pic_mask_all_except_timer(); + if (irq_register(IRQ_TIMER, timer_irq_handler, 0) != 0) { + panic("timer irq registration failed"); + } pit_init(); early_log_puts("timer initialized\n"); - enable_interrupts(); + arch_irq_restore(1ull << 9); } diff --git a/arch/x86/kernel/traps.c b/arch/x86/kernel/traps.c index 4cb6dbe..de8b42c 100644 --- a/arch/x86/kernel/traps.c +++ b/arch/x86/kernel/traps.c @@ -2,6 +2,7 @@ #include #include +#include #include "cpu.h" @@ -59,6 +60,7 @@ void trap_dispatch(struct trap_frame *frame) if (vector >= 32 && vector < 48) { handle_irq(frame); + sched_irq_exit(); return; } diff --git a/docs/agents/tasks/04-time-scheduler.md b/docs/agents/tasks/04-time-scheduler.md index 74803b9..880b8b7 100644 --- a/docs/agents/tasks/04-time-scheduler.md +++ b/docs/agents/tasks/04-time-scheduler.md @@ -50,26 +50,30 @@ - 已接入 PIT periodic timer,当前频率为 100Hz。 - 已建立通用 `timer_tick()` 入口和 `timer_ticks()` 计数接口。 - 已在 trap dispatch 中区分 CPU exception 和外部 IRQ。 -- 已对 timer IRQ0 发送 EOI,避免中断只触发一次。 +- 已建立 IRQ handler 注册表,timer IRQ0 通过 `irq_register()` 接入分发路径。 +- 已对已处理 IRQ 发送 EOI,避免中断只触发一次。 - 已建立最小 kernel thread 对象,包含 id、状态、入口、参数、内核栈和 run queue 链接。 - 已建立动态 run queue,不固定写死线程数量。 - 已能通过 `kernel_thread_create()` 动态分配线程对象和内核栈。 - 已建立 x86 上下文切换入口,保存/恢复 callee-saved 寄存器和栈指针。 - 已建立线程 trampoline,新线程能从独立内核栈进入自己的入口函数。 - 已建立协作式 round-robin,两个 kernel thread 能通过 `sched_yield()` 轮转运行。 -- 已把调度接入 `timer_tick()`,timer tick 会唤醒到期 sleep 线程并触发 round-robin。 +- 已把调度接入 `timer_tick()`,timer tick 会唤醒到期 sleep 线程并标记 `need_resched`。 - 已建立 idle thread,所有普通线程 sleep/wait 时由 idle 承接 CPU。 - 已提供 `sched_sleep()`,线程可以睡眠指定 tick 数并被 timer 唤醒。 - 已提供 `wait_queue_init()`、`wait_queue_sleep()`、`wait_queue_wake_one()` 和 `wait_queue_wake_all()`。 +- 已建立单 CPU interrupt-safe lock 基础,当前 `spin_lock_irqsave()` 会保存并关闭中断,`spin_unlock_irqrestore()` 会恢复原中断状态。 +- 已把 `kernel_thread_create()` 中的线程 id 分配和 run queue 入队纳入 interrupt-safe lock 保护。 +- 已建立 `sched_irq_exit()`,timer IRQ 只设置 `need_resched`,trap 的 IRQ 返回边界统一消费调度请求。 +- 已建立最小 DEAD 线程回收路径,调度前会释放非当前 DEAD 线程的内核栈和线程对象。 - `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 IRQ 内触发调度的路径收敛为更明确的 interrupt-exit reschedule 模型。 -- 建立基础 spinlock 或 interrupt-safe lock。 +- 把当前 `sched_irq_exit()` 继续收敛为更严格的 trap-frame aware interrupt-exit reschedule 模型,避免把普通线程栈切换入口长期当成完整抢占式切换。 +- 继续扩大 interrupt-safe lock 覆盖范围,明确可睡眠路径和不可睡眠路径的锁规则。 - 为 wait queue 增加条件等待、超时等待和状态检查。 -- 为线程退出增加资源回收路径。 +- 为线程退出增加更完整的生命周期状态、引用规则和最终释放约束。 下一阶段: diff --git a/include/tianole/arch.h b/include/tianole/arch.h index 5d77103..6263fdd 100644 --- a/include/tianole/arch.h +++ b/include/tianole/arch.h @@ -6,6 +6,8 @@ void arch_early_log_init(void); void arch_early_log_putc(char ch); void arch_halt_forever(void) __attribute__((noreturn)); +uint64_t arch_irq_save(void); +void arch_irq_restore(uint64_t flags); int arch_page_table_uses_page(uint64_t page); void arch_traps_init(void); void arch_timer_init(void); diff --git a/include/tianole/irq.h b/include/tianole/irq.h new file mode 100644 index 0000000..f90271c --- /dev/null +++ b/include/tianole/irq.h @@ -0,0 +1,10 @@ +#ifndef TIANOLE_IRQ_H +#define TIANOLE_IRQ_H + +#include + +typedef void (*irq_handler_t)(uint8_t irq, void *data); + +int irq_register(uint8_t irq, irq_handler_t handler, void *data); + +#endif diff --git a/include/tianole/sched.h b/include/tianole/sched.h index 9710434..5533e21 100644 --- a/include/tianole/sched.h +++ b/include/tianole/sched.h @@ -39,6 +39,7 @@ 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_irq_exit(void); void sched_yield(void); void sched_sleep(uint64_t ticks); void wait_queue_init(struct wait_queue *queue); diff --git a/include/tianole/spinlock.h b/include/tianole/spinlock.h new file mode 100644 index 0000000..15a647e --- /dev/null +++ b/include/tianole/spinlock.h @@ -0,0 +1,18 @@ +#ifndef TIANOLE_SPINLOCK_H +#define TIANOLE_SPINLOCK_H + +#include + +struct spinlock { + int locked; +}; + +#define SPINLOCK_INITIALIZER \ + { \ + 0 \ + } + +void spin_lock_irqsave(struct spinlock *lock, uint64_t *flags); +void spin_unlock_irqrestore(struct spinlock *lock, uint64_t flags); + +#endif diff --git a/kernel/Makefile b/kernel/Makefile index 663ec71..fb1f029 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/locking/spinlock.c \ kernel/sched/thread.c \ kernel/time/timer.c diff --git a/kernel/locking/spinlock.c b/kernel/locking/spinlock.c new file mode 100644 index 0000000..b2ef48c --- /dev/null +++ b/kernel/locking/spinlock.c @@ -0,0 +1,32 @@ +#include + +#include +#include +#include + +void spin_lock_irqsave(struct spinlock *lock, uint64_t *flags) +{ + uint64_t saved_flags; + + if (lock == 0 || flags == 0) { + panic("invalid spinlock acquire"); + } + + saved_flags = arch_irq_save(); + if (lock->locked != 0) { + panic("spinlock recursion or contention"); + } + + lock->locked = 1; + *flags = saved_flags; +} + +void spin_unlock_irqrestore(struct spinlock *lock, uint64_t flags) +{ + if (lock == 0 || lock->locked == 0) { + panic("invalid spinlock release"); + } + + lock->locked = 0; + arch_irq_restore(flags); +} diff --git a/kernel/sched/thread.c b/kernel/sched/thread.c index 2d2ddf3..3782374 100644 --- a/kernel/sched/thread.c +++ b/kernel/sched/thread.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #define KERNEL_STACK_SIZE (PAGE_SIZE * 4u) @@ -18,7 +19,9 @@ 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)); @@ -59,6 +62,40 @@ static void enqueue_thread(struct thread *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; @@ -80,6 +117,7 @@ struct thread *kernel_thread_create( { struct thread *thread; uintptr_t stack_top; + uint64_t flags; if (entry == 0) { return 0; @@ -98,7 +136,6 @@ struct thread *kernel_thread_create( 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; @@ -110,7 +147,10 @@ struct thread *kernel_thread_create( thread->wait_next = 0; copy_thread_name(thread->name, sizeof(thread->name), name); + spin_lock_irqsave(&scheduler_lock, &flags); + thread->id = next_thread_id++; enqueue_thread(thread); + spin_unlock_irqrestore(&scheduler_lock, flags); return thread; } @@ -162,13 +202,18 @@ static void wake_sleeping_threads(uint64_t tick) void sched_yield(void) { - struct thread *prev = current_thread; - struct thread *next = next_runnable_thread(); + 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; } @@ -196,10 +241,20 @@ void sched_tick(uint64_t tick) wake_sleeping_threads(tick); if (current_thread != 0 && current_thread->state == THREAD_RUNNING) { - sched_yield(); + 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; @@ -298,10 +353,14 @@ static void thread_selftest_entry(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"); @@ -322,6 +381,9 @@ static void scheduler_selftest(void) 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"); }