Compare commits
2 Commits
45693c96b5
...
4ff227bc75
@@ -17,9 +17,11 @@ BOOT_OBJ = build/boot.o
|
|||||||
|
|
||||||
KERNEL_CPP = kernel/entry.cpp kernel/main.cpp kernel/serial.cpp kernel/fs.cpp \
|
KERNEL_CPP = kernel/entry.cpp kernel/main.cpp kernel/serial.cpp kernel/fs.cpp \
|
||||||
kernel/memory/heap.cpp kernel/memory/pmm.cpp \
|
kernel/memory/heap.cpp kernel/memory/pmm.cpp \
|
||||||
|
kernel/scheduler/scheduler.cpp \
|
||||||
graphics/context.cpp graphics/draw.cpp \
|
graphics/context.cpp graphics/draw.cpp \
|
||||||
fonts/pixel_font.cpp
|
fonts/pixel_font.cpp
|
||||||
KERNEL_OBJ = $(KERNEL_CPP:%.cpp=build/%.o)
|
KERNEL_ASM = kernel/scheduler/context_switch.S
|
||||||
|
KERNEL_OBJ = $(KERNEL_CPP:%.cpp=build/%.o) $(KERNEL_ASM:%.S=build/%.o)
|
||||||
|
|
||||||
EFI_TOP_C = $(wildcard efi/lib/*.c)
|
EFI_TOP_C = $(wildcard efi/lib/*.c)
|
||||||
EFI_TOP_S = $(wildcard efi/lib/*.S)
|
EFI_TOP_S = $(wildcard efi/lib/*.S)
|
||||||
@@ -44,6 +46,7 @@ all: _bd $(EFI_OBJ) $(BOOT_OBJ) $(KERNEL_OBJ)
|
|||||||
|
|
||||||
_bd:
|
_bd:
|
||||||
@mkdir -p build/graphics build/kernel build/fonts build/kernel/memory \
|
@mkdir -p build/graphics build/kernel build/fonts build/kernel/memory \
|
||||||
|
build/kernel/scheduler \
|
||||||
build/efi/lib build/efi/lib/x86_64 build/efi/lib/runtime build/efi/gnuefi
|
build/efi/lib build/efi/lib/x86_64 build/efi/lib/runtime build/efi/gnuefi
|
||||||
|
|
||||||
$(EFI_CRT0_OBJ): efi/gnuefi/crt0-efi-x86_64.S | _bd
|
$(EFI_CRT0_OBJ): efi/gnuefi/crt0-efi-x86_64.S | _bd
|
||||||
@@ -86,6 +89,15 @@ build/kernel/memory/%.o: kernel/memory/%.cpp | _bd
|
|||||||
@echo "Compile CPP $<"
|
@echo "Compile CPP $<"
|
||||||
@g++ $(KERNEL_CXXFLAGS) -c $< -o $@
|
@g++ $(KERNEL_CXXFLAGS) -c $< -o $@
|
||||||
|
|
||||||
|
build/kernel/scheduler/%.o: kernel/scheduler/%.cpp | _bd
|
||||||
|
@echo "Compile CPP $<"
|
||||||
|
@g++ $(KERNEL_CXXFLAGS) -c $< -o $@
|
||||||
|
|
||||||
|
build/kernel/scheduler/%.o: kernel/scheduler/%.S | _bd
|
||||||
|
@echo "Compile AS $<"
|
||||||
|
@gcc -Iinclude -Iefi/inc -ffreestanding -fno-stack-protector -fno-stack-check \
|
||||||
|
-fshort-wchar -mno-red-zone -fcf-protection=none -c $< -o $@
|
||||||
|
|
||||||
build/graphics/%.o: graphics/%.cpp | _bd
|
build/graphics/%.o: graphics/%.cpp | _bd
|
||||||
@echo "Compile CPP $<"
|
@echo "Compile CPP $<"
|
||||||
@g++ $(KERNEL_CXXFLAGS) -c $< -o $@
|
@g++ $(KERNEL_CXXFLAGS) -c $< -o $@
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <efi.h>
|
||||||
|
#include <memory/pmm.h>
|
||||||
|
#include <common.h>
|
||||||
|
|
||||||
|
#define TASK_STACK_SIZE (PAGE_SIZE * 4) // 16 KB kernel stack per task
|
||||||
|
#define TASK_MAX 32
|
||||||
|
#define TASK_NAME_LEN 32
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
TASK_STATE_READY,
|
||||||
|
TASK_STATE_RUNNING,
|
||||||
|
TASK_STATE_TERMINATED,
|
||||||
|
} task_state_t;
|
||||||
|
|
||||||
|
typedef struct task {
|
||||||
|
UINT64 rsp; // saved stack pointer (for context switch)
|
||||||
|
UINT32 id;
|
||||||
|
task_state_t state;
|
||||||
|
char name[TASK_NAME_LEN];
|
||||||
|
void* stack_base; // base address of kernel stack
|
||||||
|
struct task* next; // circular linked list
|
||||||
|
} task_t;
|
||||||
|
|
||||||
|
// Create a new task. Returns task pointer or NULL on failure.
|
||||||
|
task_t* task_create(const char* name, void (*entry)(void));
|
||||||
|
|
||||||
|
// Yield CPU to next ready task (cooperative)
|
||||||
|
void yield(void);
|
||||||
|
|
||||||
|
// Start the scheduler — does not return. Picks first READY task and runs it.
|
||||||
|
void scheduler_run(void);
|
||||||
|
|
||||||
|
// Called by a task when it finishes — marks as TERMINATED and yields
|
||||||
|
void task_exit(void);
|
||||||
|
|
||||||
|
// Get current running task
|
||||||
|
task_t* scheduler_current(void);
|
||||||
+36
-2
@@ -6,6 +6,7 @@
|
|||||||
#include <common.h>
|
#include <common.h>
|
||||||
#include <memory/pmm.h>
|
#include <memory/pmm.h>
|
||||||
#include <memory/heap.h>
|
#include <memory/heap.h>
|
||||||
|
#include <scheduler.h>
|
||||||
#include <fs.h>
|
#include <fs.h>
|
||||||
|
|
||||||
extern EFI_SYSTEM_TABLE *ST;
|
extern EFI_SYSTEM_TABLE *ST;
|
||||||
@@ -105,7 +106,40 @@ extern "C" void kernel_main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pf_print("Welcome to Sylva OS!\n");
|
pf_print("Welcome to Sylva OS!\n");
|
||||||
serial_write(" Kernel prepared well, start loop.\n");
|
serial_write(" Kernel prepared well.\n");
|
||||||
|
|
||||||
while (1) ASM ("hlt"); // 《30天》看多了 (doge
|
// --- Multitasking demo ---
|
||||||
|
serial_write("Sylva: creating tasks...\n");
|
||||||
|
|
||||||
|
// Task A: prints a message 3 times, yielding between each
|
||||||
|
task_create("taskA", []() {
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
serial_write("[taskA] running iteration ");
|
||||||
|
serial_write_hex(i);
|
||||||
|
serial_write("\n");
|
||||||
|
yield();
|
||||||
|
}
|
||||||
|
serial_write("[taskA] done\n");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Task B: prints a message 5 times
|
||||||
|
task_create("taskB", []() {
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
serial_write("[taskB] hello from taskB #");
|
||||||
|
serial_write_hex(i);
|
||||||
|
serial_write("\n");
|
||||||
|
yield();
|
||||||
|
}
|
||||||
|
serial_write("[taskB] done\n");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Task C: short task
|
||||||
|
task_create("taskC", []() {
|
||||||
|
serial_write("[taskC] quick task\n");
|
||||||
|
yield();
|
||||||
|
serial_write("[taskC] finished\n");
|
||||||
|
});
|
||||||
|
|
||||||
|
serial_write("Sylva: starting scheduler\n");
|
||||||
|
scheduler_run(); // never returns
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
.intel_syntax noprefix
|
||||||
|
|
||||||
|
// void context_switch(UINT64* old_rsp, UINT64 new_rsp)
|
||||||
|
// rdi = &old_rsp (pointer to save current RSP)
|
||||||
|
// rsi = new_rsp (value of new stack pointer)
|
||||||
|
//
|
||||||
|
// Saves/restores callee-saved registers only.
|
||||||
|
// On first switch into a new task, ret lands on task_entry_trampoline.
|
||||||
|
.global context_switch
|
||||||
|
context_switch:
|
||||||
|
push rbx
|
||||||
|
push rbp
|
||||||
|
push r12
|
||||||
|
push r13
|
||||||
|
push r14
|
||||||
|
push r15
|
||||||
|
|
||||||
|
mov [rdi], rsp // save current RSP
|
||||||
|
mov rsp, rsi // switch to new stack
|
||||||
|
|
||||||
|
pop r15
|
||||||
|
pop r14
|
||||||
|
pop r13
|
||||||
|
pop r12
|
||||||
|
pop rbp
|
||||||
|
pop rbx
|
||||||
|
|
||||||
|
ret
|
||||||
|
|
||||||
|
.section .note.GNU-stack,"",@progbits
|
||||||
@@ -0,0 +1,190 @@
|
|||||||
|
#include <scheduler.h>
|
||||||
|
#include <memory/heap.h>
|
||||||
|
#include <memory/pmm.h>
|
||||||
|
#include <common.h>
|
||||||
|
#include <serial.h>
|
||||||
|
|
||||||
|
// Assembly: context_switch(UINT64* old_rsp, UINT64 new_rsp)
|
||||||
|
extern "C" void context_switch(UINT64* old_rsp, UINT64 new_rsp);
|
||||||
|
|
||||||
|
static task_t g_tasks[TASK_MAX];
|
||||||
|
static UINT32 g_task_count = 0;
|
||||||
|
static task_t* g_current = NULL;
|
||||||
|
static task_t* g_task_list = NULL; // circular linked list head
|
||||||
|
|
||||||
|
// Trampoline: first thing a new task runs after context_switch.
|
||||||
|
// The entry function pointer is stored in the task's name field
|
||||||
|
// (we repurpose a slot — actually we store it in a simple global array).
|
||||||
|
static void (*g_task_entries[TASK_MAX])(void);
|
||||||
|
|
||||||
|
extern "C" void task_entry_trampoline() {
|
||||||
|
task_t* cur = scheduler_current();
|
||||||
|
if (cur && g_task_entries[cur->id]) {
|
||||||
|
g_task_entries[cur->id](); // call the user function
|
||||||
|
}
|
||||||
|
task_exit(); // clean up when done
|
||||||
|
}
|
||||||
|
|
||||||
|
task_t* task_create(const char* name, void (*entry)(void)) {
|
||||||
|
if (g_task_count >= TASK_MAX) {
|
||||||
|
serial_write("SCHED: task limit reached\n");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
UINT32 id = g_task_count++;
|
||||||
|
task_t* task = &g_tasks[id];
|
||||||
|
|
||||||
|
// Store entry function for the trampoline
|
||||||
|
g_task_entries[id] = entry;
|
||||||
|
|
||||||
|
// Allocate kernel stack
|
||||||
|
UINTN stack_pages = TASK_STACK_SIZE / PAGE_SIZE;
|
||||||
|
void* stack = pmm_alloc_pages(stack_pages);
|
||||||
|
if (!stack) {
|
||||||
|
serial_write("SCHED: stack alloc failed for task ");
|
||||||
|
serial_write(name);
|
||||||
|
serial_write("\n");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill task struct
|
||||||
|
task->id = id;
|
||||||
|
task->state = TASK_STATE_READY;
|
||||||
|
task->stack_base = stack;
|
||||||
|
|
||||||
|
// Copy name
|
||||||
|
const char* s = name;
|
||||||
|
char* d = task->name;
|
||||||
|
for (int i = 0; i < TASK_NAME_LEN - 1 && *s; i++) {
|
||||||
|
*d++ = *s++;
|
||||||
|
}
|
||||||
|
*d = '\0';
|
||||||
|
|
||||||
|
// Set up initial stack for first context_switch into this task.
|
||||||
|
// Stack grows downward. context_switch will pop 6 regs then ret.
|
||||||
|
//
|
||||||
|
// Layout (high addr -> low addr):
|
||||||
|
// [stack + TASK_STACK_SIZE] <- top
|
||||||
|
// return addr = task_entry_trampoline (ret goes here)
|
||||||
|
// rbx = 0
|
||||||
|
// rbp = 0
|
||||||
|
// r12 = 0
|
||||||
|
// r13 = 0
|
||||||
|
// r14 = 0
|
||||||
|
// r15 = 0 <- RSP points here initially
|
||||||
|
//
|
||||||
|
UINT64* sp = (UINT64*)((UINT8*)stack + TASK_STACK_SIZE);
|
||||||
|
|
||||||
|
// Push return address (task_entry_trampoline)
|
||||||
|
*--sp = (UINT64)task_entry_trampoline;
|
||||||
|
|
||||||
|
// Push callee-saved registers (all zero)
|
||||||
|
*--sp = 0; // rbx
|
||||||
|
*--sp = 0; // rbp
|
||||||
|
*--sp = 0; // r12
|
||||||
|
*--sp = 0; // r13
|
||||||
|
*--sp = 0; // r14
|
||||||
|
*--sp = 0; // r15
|
||||||
|
|
||||||
|
task->rsp = (UINT64)sp;
|
||||||
|
|
||||||
|
// Insert into circular linked list
|
||||||
|
if (g_task_list == NULL) {
|
||||||
|
task->next = task; // points to itself (single element circle)
|
||||||
|
g_task_list = task;
|
||||||
|
} else {
|
||||||
|
// Insert after current tail (g_task_list is the "last" in circle)
|
||||||
|
task->next = g_task_list->next;
|
||||||
|
g_task_list->next = task;
|
||||||
|
g_task_list = task; // new tail
|
||||||
|
}
|
||||||
|
|
||||||
|
serial_write("SCHED: created task '");
|
||||||
|
serial_write(task->name);
|
||||||
|
serial_write("' id=");
|
||||||
|
serial_write_hex(id);
|
||||||
|
serial_write("\n");
|
||||||
|
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
void yield(void) {
|
||||||
|
if (g_current == NULL || g_task_list == NULL) return;
|
||||||
|
|
||||||
|
task_t* cur = g_current;
|
||||||
|
task_t* next = cur->next;
|
||||||
|
|
||||||
|
// Skip terminated tasks
|
||||||
|
while (next->state == TASK_STATE_TERMINATED && next != cur) {
|
||||||
|
next = next->next;
|
||||||
|
}
|
||||||
|
if (next->state == TASK_STATE_TERMINATED) return; // all terminated
|
||||||
|
|
||||||
|
if (next == cur) return; // only one task, nothing to do
|
||||||
|
|
||||||
|
if (cur->state != TASK_STATE_TERMINATED)
|
||||||
|
cur->state = TASK_STATE_READY;
|
||||||
|
next->state = TASK_STATE_RUNNING;
|
||||||
|
g_current = next;
|
||||||
|
|
||||||
|
context_switch(&cur->rsp, next->rsp);
|
||||||
|
}
|
||||||
|
|
||||||
|
void scheduler_run(void) {
|
||||||
|
if (g_task_list == NULL) {
|
||||||
|
serial_write("SCHED: no tasks to run\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find first READY task
|
||||||
|
task_t* start = g_task_list->next; // head of circle
|
||||||
|
task_t* t = start;
|
||||||
|
do {
|
||||||
|
if (t->state == TASK_STATE_READY) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
t = t->next;
|
||||||
|
} while (t != start);
|
||||||
|
|
||||||
|
if (t->state != TASK_STATE_READY) {
|
||||||
|
serial_write("SCHED: no READY tasks\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_current = t;
|
||||||
|
t->state = TASK_STATE_RUNNING;
|
||||||
|
|
||||||
|
serial_write("SCHED: starting task '");
|
||||||
|
serial_write(t->name);
|
||||||
|
serial_write("'\n");
|
||||||
|
|
||||||
|
// First context switch — no old RSP to save (we're still in scheduler_run)
|
||||||
|
// Just switch to the task's stack directly.
|
||||||
|
// We need a dummy old_rsp to satisfy the API, but we never return here.
|
||||||
|
UINT64 dummy_rsp;
|
||||||
|
context_switch(&dummy_rsp, t->rsp);
|
||||||
|
|
||||||
|
// We only return here when ALL tasks are terminated
|
||||||
|
serial_write("SCHED: all tasks finished\n");
|
||||||
|
while (1) ASM ("hlt");
|
||||||
|
}
|
||||||
|
|
||||||
|
void task_exit(void) {
|
||||||
|
if (g_current == NULL) return;
|
||||||
|
|
||||||
|
serial_write("SCHED: task '");
|
||||||
|
serial_write(g_current->name);
|
||||||
|
serial_write("' exited\n");
|
||||||
|
|
||||||
|
g_current->state = TASK_STATE_TERMINATED;
|
||||||
|
|
||||||
|
// Yield to next task — we won't come back
|
||||||
|
yield();
|
||||||
|
|
||||||
|
// Should never reach here
|
||||||
|
while (1) ASM ("hlt");
|
||||||
|
}
|
||||||
|
|
||||||
|
task_t* scheduler_current(void) {
|
||||||
|
return g_current;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user