Files
Operator-system/task.c

391 lines
11 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* task.c Cooperative multitasking: PCB pool, scheduler, yield/exit.
*
* Task 0 is the always-present kernel core thread (uses the boot stack).
* Additional tasks are created with task_create(), which allocates a
* stack from the PMM and sets up a fake context-switch frame so that
* context_switch() can "return" into a trampoline that calls the real
* entry function.
*
* Scheduling is purely cooperative: tasks must call task_yield() to
* let others run. The scheduler uses round-robin selection among
* READY tasks.
*/
#include "task.h"
#include "memory.h"
/* Null-safe print helper used throughout the kernel. */
#define SAFE_PRINT(Boot, ...) \
do { \
if ((Boot) != NULL && (Boot)->print != NULL) { \
(Boot)->print(__VA_ARGS__); \
} \
} while (0)
/* ================================================================
* Module state
* ================================================================ */
static Task tasks[TASK_MAX]; /* PCB pool (static array) */
static Task *current_task = NULL;
static UINT32 next_pid = 0;
static BootInfo *task_boot = NULL;
static BOOLEAN task_ready = FALSE;
/* Forward declaration */
static void task_trampoline(void);
/* ----------------------------------------------------------------
* Helpers
* ---------------------------------------------------------------- */
/* Copy a wide string with a maximum length (including NUL). */
static void wstrcpy16(CHAR16 *dst, const CHAR16 *src, UINTN max)
{
UINTN i = 0;
while (i < max - 1 && src != NULL && src[i] != L'\0') {
dst[i] = src[i];
i++;
}
dst[i] = L'\0';
}
/* ----------------------------------------------------------------
* Initialisation make the current (kernel) thread task 0
* ---------------------------------------------------------------- */
/*
* Initialise the scheduler: clear all PCB slots and register the
* currently running kernel core thread as task 0.
*/
void task_init(BootInfo *Boot)
{
UINTN i;
task_boot = Boot;
/* Clear all PCB slots */
for (i = 0; i < TASK_MAX; i++) {
tasks[i].state = TASK_STATE_FREE;
tasks[i].pid = 0;
tasks[i].saved_rsp = 0;
tasks[i].stack_base = 0;
tasks[i].stack_pages = 0;
tasks[i].entry = NULL;
tasks[i].arg = NULL;
tasks[i].switches = 0;
tasks[i].name[0] = L'\0';
}
/*
* Task 0 = the currently running kernel core thread.
* It already has a stack (the kernel's boot stack), so we don't
* allocate one. Its saved_rsp will be filled in during the
* first context_switch call in task_yield().
*/
tasks[0].pid = next_pid++;
tasks[0].state = TASK_STATE_RUNNING;
tasks[0].switches = 1;
wstrcpy16(tasks[0].name, L"core", TASK_NAME_LEN);
current_task = &tasks[0];
task_ready = TRUE;
SAFE_PRINT(Boot, L" Tasks: scheduler ready (max %d tasks)\n\r",
(UINTN)TASK_MAX);
}
/* ----------------------------------------------------------------
* Trampoline first code a new task executes after context_switch
* returns into it. It calls the real entry function and then
* performs a clean task_exit().
* ---------------------------------------------------------------- */
static void task_trampoline(void)
{
Task *t = task_current();
if (t != NULL && t->entry != NULL) {
t->entry(t->arg);
}
task_exit();
/* Should never reach here, but just in case: */
for (;;) {
__asm__ __volatile__("hlt");
}
}
/* ----------------------------------------------------------------
* Create a new task
* ---------------------------------------------------------------- */
Task *task_create(const CHAR16 *name, TaskEntryFn entry, void *arg)
{
Task *t = NULL;
UINTN i;
UINT64 stack_phys;
UINT64 *sp;
if (!task_ready || entry == NULL) {
return NULL;
}
/* Find a free PCB slot */
for (i = 0; i < TASK_MAX; i++) {
if (tasks[i].state == TASK_STATE_FREE) {
t = &tasks[i];
break;
}
}
if (t == NULL) {
return NULL; /* out of slots */
}
/* Allocate stack pages from the physical memory manager */
stack_phys = pmm_alloc_pages(TASK_STACK_PAGES);
if (stack_phys == 0) {
return NULL; /* out of memory */
}
/* Fill in the PCB */
t->pid = next_pid++;
t->state = TASK_STATE_READY;
t->entry = entry;
t->arg = arg;
t->switches = 0;
t->stack_base = stack_phys;
t->stack_pages = TASK_STACK_PAGES;
wstrcpy16(t->name, name != NULL ? name : L"unnamed", TASK_NAME_LEN);
/*
* Set up the initial stack frame so that context_switch() can
* "return" into task_trampoline().
*
* context_switch saves/restores (low → high on stack):
* flags, r15, r14, r13, r12, rbx, rbp (pushes)
* then `ret` pops the return address (→ trampoline)
*
* Above the return address we place a safety-net address
* (task_exit) so that if the trampoline or entry function does
* a bare `ret`, it lands in task_exit().
*/
sp = (UINT64 *)(stack_phys + TASK_STACK_SIZE);
/* Align stack top to 16 bytes */
sp = (UINT64 *)((UINT64)sp & ~0xFULL);
/* Safety-net return address for the trampoline */
*(--sp) = (UINT64)(UINTN)task_exit;
/* Return address for context_switch's `ret` → trampoline */
*(--sp) = (UINT64)(UINTN)task_trampoline;
/* Callee-saved registers all zero for fresh task */
*(--sp) = 0; /* rbp */
*(--sp) = 0; /* rbx */
*(--sp) = 0; /* r12 */
*(--sp) = 0; /* r13 */
*(--sp) = 0; /* r14 */
*(--sp) = 0; /* r15 */
/* RFLAGS interrupts enabled (IF = bit 9) */
*(--sp) = 0x202; /* flags */
t->saved_rsp = (UINT64)(UINTN)sp;
return t;
}
/* ----------------------------------------------------------------
* Schedule pick the next READY task (round-robin)
* ---------------------------------------------------------------- */
static Task *schedule_next(void)
{
UINTN start, idx, i;
if (current_task == NULL) {
return &tasks[0];
}
/* Find current task's index in the array */
start = (UINTN)(current_task - tasks);
/* Round-robin: scan from (current+1) wrapping around */
for (i = 1; i <= TASK_MAX; i++) {
idx = (start + i) % TASK_MAX;
if (tasks[idx].state == TASK_STATE_READY) {
return &tasks[idx];
}
}
/* No other ready task stay with current if still runnable */
if (current_task->state == TASK_STATE_RUNNING ||
current_task->state == TASK_STATE_READY) {
return current_task;
}
/* Fallback to task 0 (kernel / shell) */
return &tasks[0];
}
/* ----------------------------------------------------------------
* Yield voluntarily give up the CPU to the next ready task
* ---------------------------------------------------------------- */
void task_yield(void)
{
Task *prev, *next;
if (!task_ready) {
return;
}
prev = current_task;
next = schedule_next();
if (next == prev) {
return; /* nothing else to switch to */
}
/* Mark the previous task as READY (still runnable) */
if (prev->state == TASK_STATE_RUNNING) {
prev->state = TASK_STATE_READY;
}
next->state = TASK_STATE_RUNNING;
next->switches++;
current_task = next;
/*
* context_switch saves callee-saved regs + flags on prev's stack,
* stores prev's RSP into prev->saved_rsp, loads next->saved_rsp
* into RSP, restores regs + flags, and `ret`s into next's code.
*/
context_switch(&prev->saved_rsp, next->saved_rsp);
}
/* ----------------------------------------------------------------
* Exit terminate the current task and switch away
* ---------------------------------------------------------------- */
void task_exit(void)
{
Task *prev, *next;
if (!task_ready) {
return;
}
prev = current_task;
prev->state = TASK_STATE_TERMINATED;
/* Free the stack memory back to the PMM */
if (prev->stack_base != 0 && prev->stack_pages != 0) {
pmm_free_pages(prev->stack_base, prev->stack_pages);
prev->stack_base = 0;
prev->stack_pages = 0;
}
/* Mark the PCB slot as free for reuse */
prev->state = TASK_STATE_FREE;
next = schedule_next();
if (next == prev) {
/* Shouldn't happen if task 0 (kernel) is always alive */
next = &tasks[0];
}
next->state = TASK_STATE_RUNNING;
next->switches++;
current_task = next;
/* One-way switch: we never return to the exited task */
context_switch(&prev->saved_rsp, next->saved_rsp);
/* Should never reach here */
for (;;) {
__asm__ __volatile__("hlt");
}
}
/* ----------------------------------------------------------------
* Accessors
* ---------------------------------------------------------------- */
Task *task_current(void)
{
return current_task;
}
UINTN task_count(void)
{
UINTN i, count = 0;
for (i = 0; i < TASK_MAX; i++) {
if (tasks[i].state != TASK_STATE_FREE) {
count++;
}
}
return count;
}
/* ----------------------------------------------------------------
* Wait for another task to finish
* ---------------------------------------------------------------- */
void task_wait(Task *t)
{
if (!task_ready || t == NULL) {
return;
}
/*
* Busy-wait cooperatively until the target task's PCB slot has
* been recycled back to FREE by task_exit().
*/
while (t->state != TASK_STATE_FREE) {
task_yield();
}
}
/* ----------------------------------------------------------------
* Print task list (implements the `ps` command)
* ---------------------------------------------------------------- */
static const CHAR16 *state_str(TaskState s)
{
switch (s) {
case TASK_STATE_FREE: return L"FREE";
case TASK_STATE_READY: return L"READY";
case TASK_STATE_RUNNING: return L"RUNNING";
case TASK_STATE_TERMINATED: return L"ENDED";
default: return L"???";
}
}
void task_print_list(BootInfo *Boot)
{
UINTN i;
SAFE_PRINT(Boot, L"\n\r");
SAFE_PRINT(Boot, L" PID STATE SWITCHES NAME\n\r");
SAFE_PRINT(Boot, L" --- ---------- -------- ----\n\r");
for (i = 0; i < TASK_MAX; i++) {
if (tasks[i].state == TASK_STATE_FREE) {
continue;
}
SAFE_PRINT(Boot, L" %3d %-10s %8d %s\n\r",
tasks[i].pid,
state_str(tasks[i].state),
tasks[i].switches,
tasks[i].name);
}
SAFE_PRINT(Boot, L"\n\r");
SAFE_PRINT(Boot, L" Active tasks: %d / %d\n\r",
task_count(), (UINTN)TASK_MAX);
SAFE_PRINT(Boot, L"\n\r");
}