feat(sched): add irq-exit reschedule and thread reaping

This commit is contained in:
Microindole 2026-05-10 11:03:06 +08:00
parent e8a14dbc15
commit 20cdf179d7
No known key found for this signature in database
GPG Key ID: 22FB34CD2133ACA1
11 changed files with 205 additions and 21 deletions

View File

@ -31,7 +31,7 @@ include kernel/Makefile
all: $(BOOT_EFI) $(KERNEL_ELF) all: $(BOOT_EFI) $(KERNEL_ELF)
dirs: 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 $(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 $@ $(CC) $(CFLAGS) -c $< -o $@

View File

@ -3,7 +3,9 @@
#include <arch/io.h> #include <arch/io.h>
#include <arch/traps.h> #include <arch/traps.h>
#include <tianole/arch.h>
#include <tianole/early_log.h> #include <tianole/early_log.h>
#include <tianole/irq.h>
#include <tianole/timer.h> #include <tianole/timer.h>
#define PIC1_COMMAND 0x20 #define PIC1_COMMAND 0x20
@ -24,11 +26,14 @@
#define IRQ_BASE 32u #define IRQ_BASE 32u
#define IRQ_TIMER 0u #define IRQ_TIMER 0u
#define IRQ_COUNT 16u
static void enable_interrupts(void) struct irq_action {
{ irq_handler_t handler;
__asm__ volatile("sti"); void *data;
} };
static struct irq_action irq_actions[IRQ_COUNT];
static void pic_remap(void) static void pic_remap(void)
{ {
@ -77,19 +82,63 @@ static void pit_init(void)
outb(PIT_CHANNEL0, (uint8_t)(divisor >> 8)); 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(); timer_tick();
pic_send_eoi(IRQ_TIMER);
} }
void handle_irq(struct trap_frame *frame) void handle_irq(struct trap_frame *frame)
{ {
uint64_t irq = frame->vector - IRQ_BASE; uint64_t irq = frame->vector - IRQ_BASE;
struct irq_action *action;
if (irq == IRQ_TIMER) { if (irq < IRQ_COUNT) {
handle_timer_irq(); action = &irq_actions[irq];
return; if (action->handler != 0) {
action->handler((uint8_t)irq, action->data);
pic_send_eoi((uint8_t)irq);
return;
}
} }
early_log_puts("unexpected irq="); early_log_puts("unexpected irq=");
@ -102,7 +151,10 @@ void arch_timer_init(void)
{ {
pic_remap(); pic_remap();
pic_mask_all_except_timer(); pic_mask_all_except_timer();
if (irq_register(IRQ_TIMER, timer_irq_handler, 0) != 0) {
panic("timer irq registration failed");
}
pit_init(); pit_init();
early_log_puts("timer initialized\n"); early_log_puts("timer initialized\n");
enable_interrupts(); arch_irq_restore(1ull << 9);
} }

View File

@ -2,6 +2,7 @@
#include <tianole/arch.h> #include <tianole/arch.h>
#include <tianole/early_log.h> #include <tianole/early_log.h>
#include <tianole/sched.h>
#include "cpu.h" #include "cpu.h"
@ -59,6 +60,7 @@ void trap_dispatch(struct trap_frame *frame)
if (vector >= 32 && vector < 48) { if (vector >= 32 && vector < 48) {
handle_irq(frame); handle_irq(frame);
sched_irq_exit();
return; return;
} }

View File

@ -50,26 +50,30 @@
- 已接入 PIT periodic timer当前频率为 100Hz。 - 已接入 PIT periodic timer当前频率为 100Hz。
- 已建立通用 `timer_tick()` 入口和 `timer_ticks()` 计数接口。 - 已建立通用 `timer_tick()` 入口和 `timer_ticks()` 计数接口。
- 已在 trap dispatch 中区分 CPU exception 和外部 IRQ。 - 已在 trap dispatch 中区分 CPU exception 和外部 IRQ。
- 已对 timer IRQ0 发送 EOI避免中断只触发一次。 - 已建立 IRQ handler 注册表timer IRQ0 通过 `irq_register()` 接入分发路径。
- 已对已处理 IRQ 发送 EOI避免中断只触发一次。
- 已建立最小 kernel thread 对象,包含 id、状态、入口、参数、内核栈和 run queue 链接。 - 已建立最小 kernel thread 对象,包含 id、状态、入口、参数、内核栈和 run queue 链接。
- 已建立动态 run queue不固定写死线程数量。 - 已建立动态 run queue不固定写死线程数量。
- 已能通过 `kernel_thread_create()` 动态分配线程对象和内核栈。 - 已能通过 `kernel_thread_create()` 动态分配线程对象和内核栈。
- 已建立 x86 上下文切换入口,保存/恢复 callee-saved 寄存器和栈指针。 - 已建立 x86 上下文切换入口,保存/恢复 callee-saved 寄存器和栈指针。
- 已建立线程 trampoline新线程能从独立内核栈进入自己的入口函数。 - 已建立线程 trampoline新线程能从独立内核栈进入自己的入口函数。
- 已建立协作式 round-robin两个 kernel thread 能通过 `sched_yield()` 轮转运行。 - 已建立协作式 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。 - 已建立 idle thread所有普通线程 sleep/wait 时由 idle 承接 CPU。
- 已提供 `sched_sleep()`,线程可以睡眠指定 tick 数并被 timer 唤醒。 - 已提供 `sched_sleep()`,线程可以睡眠指定 tick 数并被 timer 唤醒。
- 已提供 `wait_queue_init()`、`wait_queue_sleep()`、`wait_queue_wake_one()` 和 `wait_queue_wake_all()` - 已提供 `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。 - `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。 - 把当前 `sched_irq_exit()` 继续收敛为更严格的 trap-frame aware interrupt-exit reschedule 模型,避免把普通线程栈切换入口长期当成完整抢占式切换。
- 把当前直接在 timer IRQ 内触发调度的路径收敛为更明确的 interrupt-exit reschedule 模型。 - 继续扩大 interrupt-safe lock 覆盖范围,明确可睡眠路径和不可睡眠路径的锁规则。
- 建立基础 spinlock 或 interrupt-safe lock。
- 为 wait queue 增加条件等待、超时等待和状态检查。 - 为 wait queue 增加条件等待、超时等待和状态检查。
- 为线程退出增加资源回收路径 - 为线程退出增加更完整的生命周期状态、引用规则和最终释放约束
下一阶段: 下一阶段:

View File

@ -6,6 +6,8 @@
void arch_early_log_init(void); void arch_early_log_init(void);
void arch_early_log_putc(char ch); void arch_early_log_putc(char ch);
void arch_halt_forever(void) __attribute__((noreturn)); 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); int arch_page_table_uses_page(uint64_t page);
void arch_traps_init(void); void arch_traps_init(void);
void arch_timer_init(void); void arch_timer_init(void);

10
include/tianole/irq.h Normal file
View File

@ -0,0 +1,10 @@
#ifndef TIANOLE_IRQ_H
#define TIANOLE_IRQ_H
#include <stdint.h>
typedef void (*irq_handler_t)(uint8_t irq, void *data);
int irq_register(uint8_t irq, irq_handler_t handler, void *data);
#endif

View File

@ -39,6 +39,7 @@ struct thread *kernel_thread_create(
const char *name, kernel_thread_entry_t entry, void *arg); const char *name, kernel_thread_entry_t entry, void *arg);
void sched_start(void) __attribute__((noreturn)); void sched_start(void) __attribute__((noreturn));
void sched_tick(uint64_t tick); void sched_tick(uint64_t tick);
void sched_irq_exit(void);
void sched_yield(void); void sched_yield(void);
void sched_sleep(uint64_t ticks); void sched_sleep(uint64_t ticks);
void wait_queue_init(struct wait_queue *queue); void wait_queue_init(struct wait_queue *queue);

View File

@ -0,0 +1,18 @@
#ifndef TIANOLE_SPINLOCK_H
#define TIANOLE_SPINLOCK_H
#include <stdint.h>
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

View File

@ -2,6 +2,7 @@ KERNEL_SRCS := \
kernel/main.c \ kernel/main.c \
kernel/boot_report.c \ kernel/boot_report.c \
kernel/early_log.c \ kernel/early_log.c \
kernel/locking/spinlock.c \
kernel/sched/thread.c \ kernel/sched/thread.c \
kernel/time/timer.c kernel/time/timer.c

32
kernel/locking/spinlock.c Normal file
View File

@ -0,0 +1,32 @@
#include <stdint.h>
#include <tianole/arch.h>
#include <tianole/early_log.h>
#include <tianole/spinlock.h>
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);
}

View File

@ -6,6 +6,7 @@
#include <tianole/early_log.h> #include <tianole/early_log.h>
#include <tianole/mm.h> #include <tianole/mm.h>
#include <tianole/sched.h> #include <tianole/sched.h>
#include <tianole/spinlock.h>
#include <tianole/timer.h> #include <tianole/timer.h>
#define KERNEL_STACK_SIZE (PAGE_SIZE * 4u) #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 uint64_t next_thread_id = 1;
static int scheduler_ready; static int scheduler_ready;
static int schedule_locked; static int schedule_locked;
static int need_resched;
static struct thread *idle_thread; static struct thread *idle_thread;
static struct spinlock scheduler_lock = SPINLOCK_INITIALIZER;
static void thread_trampoline(void) __attribute__((noreturn)); static void thread_trampoline(void) __attribute__((noreturn));
@ -59,6 +62,40 @@ static void enqueue_thread(struct thread *thread)
run_queue_tail = 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) static uintptr_t prepare_initial_stack(uintptr_t stack_top)
{ {
uintptr_t *stack = (uintptr_t *)stack_top; uintptr_t *stack = (uintptr_t *)stack_top;
@ -80,6 +117,7 @@ struct thread *kernel_thread_create(
{ {
struct thread *thread; struct thread *thread;
uintptr_t stack_top; uintptr_t stack_top;
uint64_t flags;
if (entry == 0) { if (entry == 0) {
return 0; return 0;
@ -98,7 +136,6 @@ struct thread *kernel_thread_create(
stack_top = (uintptr_t)thread->stack_base + KERNEL_STACK_SIZE; stack_top = (uintptr_t)thread->stack_base + KERNEL_STACK_SIZE;
thread->id = next_thread_id++;
thread->state = THREAD_READY; thread->state = THREAD_READY;
thread->entry = entry; thread->entry = entry;
thread->arg = arg; thread->arg = arg;
@ -110,7 +147,10 @@ struct thread *kernel_thread_create(
thread->wait_next = 0; thread->wait_next = 0;
copy_thread_name(thread->name, sizeof(thread->name), name); copy_thread_name(thread->name, sizeof(thread->name), name);
spin_lock_irqsave(&scheduler_lock, &flags);
thread->id = next_thread_id++;
enqueue_thread(thread); enqueue_thread(thread);
spin_unlock_irqrestore(&scheduler_lock, flags);
return thread; return thread;
} }
@ -162,13 +202,18 @@ static void wake_sleeping_threads(uint64_t tick)
void sched_yield(void) void sched_yield(void)
{ {
struct thread *prev = current_thread; struct thread *prev;
struct thread *next = next_runnable_thread(); struct thread *next;
if (schedule_locked != 0) { if (schedule_locked != 0) {
return; return;
} }
reap_dead_threads();
prev = current_thread;
next = next_runnable_thread();
if (next == 0 || next == prev) { if (next == 0 || next == prev) {
return; return;
} }
@ -196,10 +241,20 @@ void sched_tick(uint64_t tick)
wake_sleeping_threads(tick); wake_sleeping_threads(tick);
if (current_thread != 0 && current_thread->state == THREAD_RUNNING) { 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) void sched_sleep(uint64_t ticks)
{ {
uint64_t now; uint64_t now;
@ -298,10 +353,14 @@ static void thread_selftest_entry(void *arg)
static void scheduler_selftest(void) static void scheduler_selftest(void)
{ {
struct spinlock test_lock;
struct thread *first = struct thread *first =
kernel_thread_create("worker-a", thread_selftest_entry, 0); kernel_thread_create("worker-a", thread_selftest_entry, 0);
struct thread *second = struct thread *second =
kernel_thread_create("worker-b", thread_selftest_entry, 0); kernel_thread_create("worker-b", thread_selftest_entry, 0);
uint64_t flags;
test_lock.locked = 0;
if (first == 0 || second == 0 || first == second) { if (first == 0 || second == 0 || first == second) {
panic("kernel thread selftest allocation failed"); panic("kernel thread selftest allocation failed");
@ -322,6 +381,9 @@ static void scheduler_selftest(void)
panic("kernel thread selftest run queue failed"); 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"); early_log_puts("kernel thread selftest ok\n");
} }