feat(sched): add x86 context switch and round-robin scheduling

This commit is contained in:
Microindole 2026-05-09 17:07:09 +08:00
parent 76a9363e3c
commit bbf184f9fc
No known key found for this signature in database
GPG Key ID: 22FB34CD2133ACA1
10 changed files with 183 additions and 10 deletions

View File

@ -0,0 +1,8 @@
#ifndef ARCH_X86_SWITCH_H
#define ARCH_X86_SWITCH_H
#include <stdint.h>
void arch_context_switch(uintptr_t *prev_sp, uintptr_t next_sp);
#endif

View File

@ -8,4 +8,5 @@ ARCH_KERNEL_SRCS := \
ARCH_KERNEL_OBJS := \ ARCH_KERNEL_OBJS := \
$(BUILD_DIR)/arch/kernel/entry.o \ $(BUILD_DIR)/arch/kernel/entry.o \
$(BUILD_DIR)/arch/kernel/exception_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)) $(patsubst $(ARCH_DIR)/kernel/%.c,$(BUILD_DIR)/arch/kernel/%.o,$(ARCH_KERNEL_SRCS))

22
arch/x86/kernel/switch.S Normal file
View File

@ -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

View File

@ -54,15 +54,17 @@
- 已建立最小 kernel thread 对象,包含 id、状态、入口、参数、内核栈和 run queue 链接。 - 已建立最小 kernel thread 对象,包含 id、状态、入口、参数、内核栈和 run queue 链接。
- 已建立动态 run queue不固定写死线程数量。 - 已建立动态 run queue不固定写死线程数量。
- 已能通过 `kernel_thread_create()` 动态分配线程对象和内核栈。 - 已能通过 `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。 - 把 IRQ 分发扩展为可注册 handler 的表,而不是只处理 timer。
- 建立上下文切换入口。 - 把协作式调度接入 timer tick演进为抢占式调度。
- 建立 run queue 和最小 round-robin scheduler。
- 建立 `yield()`、`sleep()`、timer wakeup 和 wait queue。 - 建立 `yield()`、`sleep()`、timer wakeup 和 wait queue。
下一阶段: 下一阶段:
- 继续在 `04-time-scheduler.md` 内推进上下文切换入口和最小 round-robin scheduler - 继续在 `04-time-scheduler.md` 内推进 timer 驱动的抢占式调度、`sleep()` 和 wait queue

View File

@ -28,7 +28,7 @@
| `01-early-debug.md` | 基础完成 | 已有 early log 前端、QEMU debug port、COM1 串口和最小 `panic()`。 | | `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 回归测试。 | | `02-cpu-interrupts.md` | 基础完成 | 已有 GDT/TSS/IDT、exception vector 0-31、`trap_frame` 和 invalid opcode 回归测试。 |
| `03-memory.md` | 基础完成 | 已有最小物理页 allocator、页表 map/unmap/query、page fault 诊断和内核堆。 | | `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` | 未开始 | 需要输入事件模型和键盘接入。 | | `05-input-events.md` | 未开始 | 需要输入事件模型和键盘接入。 |
| `06-storage-vfs.md` | 未开始 | 需要块层、缓存、VFS 和基础文件系统。 | | `06-storage-vfs.md` | 未开始 | 需要块层、缓存、VFS 和基础文件系统。 |
| `07-user-mode.md` | 未开始 | 需要 syscall、进程、用户地址空间和 ELF 加载。 | | `07-user-mode.md` | 未开始 | 需要 syscall、进程、用户地址空间和 ELF 加载。 |
@ -36,7 +36,7 @@
| `09-driver-expansion.md` | 未开始 | 需要设备模型、PCI/ACPI、存储、网络等驱动扩展。 | | `09-driver-expansion.md` | 未开始 | 需要设备模型、PCI/ACPI、存储、网络等驱动扩展。 |
| `10-real-machine.md` | 未开始 | 需要 U 盘真机启动验证和硬件差异记录。 | | `10-real-machine.md` | 未开始 | 需要 U 盘真机启动验证和硬件差异记录。 |
当前最合适的下一步仍在 `04-time-scheduler.md` 内:引入上下文切换入口,再做最小 round-robin 调度循环 当前最合适的下一步仍在 `04-time-scheduler.md` 内:把调度接入 timer tick演进到抢占式调度然后做 `sleep()` 和 wait queue
## Linux 级能力缺口路由 ## Linux 级能力缺口路由

View File

@ -25,6 +25,7 @@
- 内核堆基础:已提供 `kmalloc/kfree`,并接入启动 selftest。 - 内核堆基础:已提供 `kmalloc/kfree`,并接入启动 selftest。
- timer 基础:已接入 x86 PIC/PITtimer IRQ 能进入通用 tick 入口。 - timer 基础:已接入 x86 PIC/PITtimer IRQ 能进入通用 tick 入口。
- kernel thread 基础:已建立线程对象、内核栈和 run queue。 - kernel thread 基础:已建立线程对象、内核栈和 run queue。
- 调度基础:已建立 x86 上下文切换入口和协作式 round-robin。
- 构建系统已拆成根 Makefile、`arch/x86/Makefile` 和目录 Makefile。 - 构建系统已拆成根 Makefile、`arch/x86/Makefile` 和目录 Makefile。
- `scripts/check.sh` 已验证启动日志、串口日志和 invalid opcode 异常路径GitHub Actions 已接入。 - `scripts/check.sh` 已验证启动日志、串口日志和 invalid opcode 异常路径GitHub Actions 已接入。
- `.clang-format` 已用于强制当前 C 代码风格。 - `.clang-format` 已用于强制当前 C 代码风格。
@ -44,7 +45,8 @@
目标: 目标:
- 建立上下文切换入口和最小 round-robin 调度循环。 - 把调度接入 timer tick演进到抢占式调度。
- 建立 `sleep()`、timer wakeup 和 wait queue。
## 任务路由 ## 任务路由

View File

@ -16,6 +16,7 @@ enum thread_state {
struct thread { struct thread {
uint64_t id; uint64_t id;
enum thread_state state; enum thread_state state;
uintptr_t stack_pointer;
kernel_thread_entry_t entry; kernel_thread_entry_t entry;
void *arg; void *arg;
void *stack_base; void *stack_base;
@ -28,5 +29,7 @@ struct thread {
void sched_init(void); void sched_init(void);
struct thread *kernel_thread_create( 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_yield(void);
#endif #endif

View File

@ -24,7 +24,5 @@ void kernel_main(const boot_info_t *boot_info)
*(volatile uint64_t *)(uintptr_t)0xffffff1000000000ull = 1; *(volatile uint64_t *)(uintptr_t)0xffffff1000000000ull = 1;
#endif #endif
for (;;) { sched_start();
__asm__ volatile("hlt");
}
} }

View File

@ -1,6 +1,8 @@
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include <arch/switch.h>
#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>
@ -10,9 +12,13 @@
static struct thread *run_queue_head; static struct thread *run_queue_head;
static struct thread *run_queue_tail; 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 uint64_t next_thread_id = 1;
static int scheduler_ready; static int scheduler_ready;
static void thread_trampoline(void) __attribute__((noreturn));
static uintptr_t align_down_uintptr(uintptr_t value, uintptr_t alignment) static uintptr_t align_down_uintptr(uintptr_t value, uintptr_t alignment)
{ {
return value & ~(alignment - 1); return value & ~(alignment - 1);
@ -50,6 +56,22 @@ static void enqueue_thread(struct thread *thread)
run_queue_tail = 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( 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)
{ {
@ -78,6 +100,7 @@ struct thread *kernel_thread_create(
thread->entry = entry; thread->entry = entry;
thread->arg = arg; thread->arg = arg;
thread->stack_top = align_down_uintptr(stack_top, STACK_ALIGNMENT); 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->stack_size = KERNEL_STACK_SIZE;
thread->next = 0; thread->next = 0;
copy_thread_name(thread->name, sizeof(thread->name), name); copy_thread_name(thread->name, sizeof(thread->name), name);
@ -87,6 +110,74 @@ struct thread *kernel_thread_create(
return thread; 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) static void thread_selftest_entry(void *arg)
{ {
(void)arg; (void)arg;
@ -121,6 +212,21 @@ static void scheduler_selftest(void)
early_log_puts("kernel thread selftest ok\n"); 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) void sched_init(void)
{ {
if (scheduler_ready != 0) { if (scheduler_ready != 0) {
@ -134,3 +240,20 @@ void sched_init(void)
early_log_puts("scheduler initialized\n"); early_log_puts("scheduler initialized\n");
scheduler_selftest(); 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");
}

View File

@ -26,6 +26,13 @@ check_lines build/debug.log \
"scheduler initialized" \ "scheduler initialized" \
"kernel thread selftest ok" \ "kernel thread selftest ok" \
"timer initialized" \ "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=1" \
"timer tick=2" \ "timer tick=2" \
"timer tick=3" "timer tick=3"
@ -46,6 +53,13 @@ check_lines build/serial.log \
"scheduler initialized" \ "scheduler initialized" \
"kernel thread selftest ok" \ "kernel thread selftest ok" \
"timer initialized" \ "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=1" \
"timer tick=2" \ "timer tick=2" \
"timer tick=3" "timer tick=3"