feat(sched): add irq-exit reschedule and thread reaping
This commit is contained in:
parent
e8a14dbc15
commit
20cdf179d7
2
Makefile
2
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 $@
|
||||
|
||||
@ -3,7 +3,9 @@
|
||||
#include <arch/io.h>
|
||||
#include <arch/traps.h>
|
||||
|
||||
#include <tianole/arch.h>
|
||||
#include <tianole/early_log.h>
|
||||
#include <tianole/irq.h>
|
||||
#include <tianole/timer.h>
|
||||
|
||||
#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,20 +82,64 @@ 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();
|
||||
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=");
|
||||
early_log_u64_decimal(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);
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
#include <tianole/arch.h>
|
||||
#include <tianole/early_log.h>
|
||||
#include <tianole/sched.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
|
||||
@ -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 增加条件等待、超时等待和状态检查。
|
||||
- 为线程退出增加资源回收路径。
|
||||
- 为线程退出增加更完整的生命周期状态、引用规则和最终释放约束。
|
||||
|
||||
下一阶段:
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
10
include/tianole/irq.h
Normal file
10
include/tianole/irq.h
Normal 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
|
||||
@ -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);
|
||||
|
||||
18
include/tianole/spinlock.h
Normal file
18
include/tianole/spinlock.h
Normal 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
|
||||
@ -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
|
||||
|
||||
|
||||
32
kernel/locking/spinlock.c
Normal file
32
kernel/locking/spinlock.c
Normal 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);
|
||||
}
|
||||
@ -6,6 +6,7 @@
|
||||
#include <tianole/early_log.h>
|
||||
#include <tianole/mm.h>
|
||||
#include <tianole/sched.h>
|
||||
#include <tianole/spinlock.h>
|
||||
#include <tianole/timer.h>
|
||||
|
||||
#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");
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user