Compare commits

...

2 Commits

Author SHA1 Message Date
9b1a70e3a5 Enhance command execution with improved concurrency and documentation updates. Refined the CommandTaskContext structure for better task management and clarified usage instructions for 'memtest' and 'tasktest' commands in README.md. 2026-02-27 20:14:24 +00:00
7ecf26cbd9 Add command task execution and improve command documentation
Introduced a new CommandTaskContext structure to facilitate command execution in separate tasks. Updated the execute_command function to spawn tasks for commands, enhancing concurrency. Improved documentation for the 'memtest' and 'tasktest' commands in README.md to clarify their functionality and usage. Updated the interactive prompt reference to 'starling>'.
2026-02-27 20:14:11 +00:00
6 changed files with 306 additions and 74 deletions

View File

@@ -104,7 +104,7 @@ The loader expects `kernel.elf` at the root of the EFI partition (next to the `E
### Shell Commands ### Shell Commands
Once the OS boots, an interactive prompt (`->`) is displayed. The following commands are available: Once the OS boots, the Starling Terminal displays an interactive prompt (`starling>`). The following built-in commands are available:
| Command | Description | | Command | Description |
|---------|-------------| |---------|-------------|
@@ -115,10 +115,17 @@ Once the OS boots, an interactive prompt (`->`) is displayed. The following comm
| `about` | Display system information | | `about` | Display system information |
| `mem` | Display memory statistics (PMM, heap, paging) | | `mem` | Display memory statistics (PMM, heap, paging) |
| `ps` | List all active tasks with PID, state, and name | | `ps` | List all active tasks with PID, state, and name |
| `spawn [name]` | Create a demo background task | | `spawn [name]` | Create a demo background task (optional argument sets the task name) |
| `memtest` | Run memory allocation/deallocation tests | | `memtest` | Run memory allocation/deallocation and PMM tests |
| `tasktest` | Spawn multiple concurrent tasks to test the scheduler | | `tasktest` | Spawn multiple concurrent tasks to test the scheduler |
In addition, the Starling Terminal itself recognises:
| Command | Description |
|---------|-------------|
| `starling` | Spawn a nested Starling Terminal at increased depth |
| `exit` | Exit the current nested Starling Terminal (not allowed at depth 0) |
### Example Session ### Example Session
``` ```
@@ -185,8 +192,8 @@ UEFI Firmware
└─ kmain(&Boot) [kernel.c] jump to kernel └─ kmain(&Boot) [kernel.c] jump to kernel
├─ idt_init() install exception handlers ├─ idt_init() install exception handlers
├─ memory_init() PMM → paging → heap ├─ memory_init() PMM → paging → heap
├─ task_init() scheduler + task 0 ├─ task_init() scheduler + task 0
└─ shell loop read-eval-print with cooperative yield └─ Starling Terminal task interactive read-eval-print loop
``` ```
### Memory Management ### Memory Management

View File

@@ -40,6 +40,32 @@ static void cmd_spawn(BootInfo *Boot, CHAR16 *Args);
static void cmd_memtest(BootInfo *Boot, CHAR16 *Args); static void cmd_memtest(BootInfo *Boot, CHAR16 *Args);
static void cmd_tasktest(BootInfo *Boot, CHAR16 *Args); static void cmd_tasktest(BootInfo *Boot, CHAR16 *Args);
/* Small helper struct used to pass arguments into per-command tasks. */
typedef struct {
BootInfo *Boot;
CommandHandlerFn handler;
CHAR16 args[128];
} CommandTaskContext;
static void command_task_entry(void *arg);
/* Local string copy helper (wide-char, bounded). */
static void wstrcpy16_local(CHAR16 *dst, const CHAR16 *src, UINTN max)
{
UINTN i = 0;
if (dst == NULL || max == 0) {
return;
}
while (src != NULL && src[i] != L'\0' && i < (max - 1)) {
dst[i] = src[i];
i++;
}
dst[i] = L'\0';
}
/* ================================================================ /* ================================================================
* Command registry * Command registry
* ================================================================ */ * ================================================================ */
@@ -96,13 +122,22 @@ static Command commands[] = {
{ {
L"memtest", L"memtest",
L"Test memory allocation and deallocation", L"Test memory allocation and deallocation",
L"Usage: memtest\n\r Allocates and frees heap and page memory to verify\n\r the memory manager is working correctly.", L"Usage: memtest\n\r"
L" Run memory tests in four phases:\n\r"
L" 1) Heap allocation of 8 blocks (164096 bytes) via kmalloc()\n\r"
L" 2) Heap free and coalescing verification via kfree()\n\r"
L" 3) Single-page PMM allocate/free via pmm_alloc_page()/pmm_free_page()\n\r"
L" 4) Multi-page (4-page) PMM allocate/free via pmm_alloc_pages()/pmm_free_pages()\n\r",
cmd_memtest cmd_memtest
}, },
{ {
L"tasktest", L"tasktest",
L"Test task scheduler with multiple tasks", L"Test task scheduler with multiple tasks",
L"Usage: tasktest\n\r Spawns several concurrent tasks that run cooperatively\n\r and report their progress, demonstrating context switching.", L"Usage: tasktest\n\r"
L" Spawns three worker tasks (worker-A/B/C) that run cooperatively,\n\r"
L" each printing three progress steps and yielding between them.\n\r"
L" After the workers finish, prints the final task list to\n\r"
L" demonstrate the cooperative round-robin scheduler.",
cmd_tasktest cmd_tasktest
}, },
{NULL, NULL, NULL, NULL} /* sentinel */ {NULL, NULL, NULL, NULL} /* sentinel */
@@ -448,22 +483,29 @@ void show_help(BootInfo *Boot)
/* /*
* Parse a line of user input into command + arguments and dispatch * Parse a line of user input into command + arguments and dispatch
* to the matching handler. Unknown commands print an error. * by spawning a dedicated task for the matching handler. Unknown
* commands print an error.
*
* Returns:
* - Pointer to the spawned Task for the command, or NULL if the
* command was not found or had to run synchronously.
*/ */
void execute_command(BootInfo *Boot, CHAR16 *Input) Task *execute_command(BootInfo *Boot, CHAR16 *Input)
{ {
CHAR16 *cmd_start = NULL; CHAR16 *cmd_start = NULL;
CHAR16 *args_start = NULL; CHAR16 *args_start = NULL;
UINTN i = 0; UINTN i = 0;
CommandTaskContext *ctx;
Task *t;
if (Boot == NULL || Input == NULL) { if (Boot == NULL || Input == NULL) {
return; return NULL;
} }
trim_spaces_inplace(Input); trim_spaces_inplace(Input);
if (Input[0] == L'\0') { if (Input[0] == L'\0') {
return; return NULL;
} }
/* Split input into command and argument strings */ /* Split input into command and argument strings */
@@ -489,12 +531,57 @@ void execute_command(BootInfo *Boot, CHAR16 *Input)
/* Look up and dispatch the command */ /* Look up and dispatch the command */
for (i = 0; commands[i].name != NULL; i++) { for (i = 0; commands[i].name != NULL; i++) {
if (ascii_streq_ci(cmd_start, commands[i].name)) { if (ascii_streq_ci(cmd_start, commands[i].name)) {
commands[i].handler(Boot, args_start); /* Allocate a context block for the command task. */
return; ctx = (CommandTaskContext *)kmalloc(sizeof(CommandTaskContext));
if (ctx == NULL) {
SAFE_PRINT(Boot, L"Failed to allocate command context; running in core thread.\n\r");
commands[i].handler(Boot, args_start);
return NULL;
}
ctx->Boot = Boot;
ctx->handler = commands[i].handler;
wstrcpy16_local(ctx->args, args_start, sizeof(ctx->args) / sizeof(ctx->args[0]));
t = task_create(commands[i].name, command_task_entry, ctx);
if (t == NULL) {
SAFE_PRINT(Boot, L"Failed to create task for command '%s'; running in core thread.\n\r",
commands[i].name);
kfree(ctx);
commands[i].handler(Boot, args_start);
return NULL;
}
SAFE_PRINT(Boot, L"[starling] spawned '%s' as PID %d\n\r", t->name, t->pid);
return t;
} }
} }
/* Command not found */ /* Command not found */
SAFE_PRINT(Boot, L"Unknown command: %s\n\r", cmd_start); SAFE_PRINT(Boot, L"Unknown command: %s\n\r", cmd_start);
SAFE_PRINT(Boot, L"Type 'help' for a list of available commands.\n\r"); SAFE_PRINT(Boot, L"Type 'help' for a list of available commands.\n\r");
return NULL;
}
/* ----------------------------------------------------------------
* Command task entry executes one command in its own task context
* ---------------------------------------------------------------- */
static void command_task_entry(void *arg)
{
CommandTaskContext *ctx = (CommandTaskContext *)arg;
BootInfo *Boot = NULL;
if (ctx == NULL) {
return;
}
Boot = ctx->Boot;
if (ctx->handler != NULL) {
ctx->handler(Boot, ctx->args);
}
/* Context was heap-allocated in execute_command. */
kfree(ctx);
} }

View File

@@ -9,6 +9,7 @@
#define COMMANDS_H #define COMMANDS_H
#include "boot_info.h" #include "boot_info.h"
#include "task.h"
/* Handler function signature: receives BootInfo and any argument text. */ /* Handler function signature: receives BootInfo and any argument text. */
typedef void (*CommandHandlerFn)(BootInfo *Boot, CHAR16 *Args); typedef void (*CommandHandlerFn)(BootInfo *Boot, CHAR16 *Args);
@@ -26,8 +27,13 @@ typedef struct {
CommandHandlerFn handler; /* function that executes the cmd */ CommandHandlerFn handler; /* function that executes the cmd */
} Command; } Command;
/* Parse and dispatch a line of user input. */ /* Parse and dispatch a line of user input.
void execute_command(BootInfo *Boot, CHAR16 *Input); *
* Returns:
* - Pointer to a Task representing the spawned command process,
* or NULL if the command was not found or ran synchronously.
*/
Task *execute_command(BootInfo *Boot, CHAR16 *Input);
/* Print a formatted list of all registered commands. */ /* Print a formatted list of all registered commands. */
void show_help(BootInfo *Boot); void show_help(BootInfo *Boot);

220
kernel.c
View File

@@ -1,12 +1,13 @@
/* /*
* kernel.c Kernel entry point and interactive shell loop. * kernel.c Kernel entry point and Starling Terminal task.
* *
* kmain() is called by the loader (main.c) after the ELF kernel has been * kmain() is called by the loader (main.c) after the ELF kernel has been
* mapped into memory. It initialises subsystems (IDT, memory, tasks), * mapped into memory. It initialises subsystems (IDT, memory, tasks),
* prints a welcome banner, and enters an interactive read-eval-print * prints a welcome banner, and then spawns the Starling Terminal as a
* dedicated task. The terminal task runs the interactive read-eval-print
* loop that dispatches typed commands via commands.c. * loop that dispatches typed commands via commands.c.
* *
* While waiting for keyboard input, the shell yields to the cooperative * While the terminal waits for keyboard input it yields to the cooperative
* scheduler so that background tasks can make progress. * scheduler so that background tasks can make progress.
*/ */
@@ -16,6 +17,7 @@
#include "idt.h" #include "idt.h"
#include "memory.h" #include "memory.h"
#include "task.h" #include "task.h"
#include "string_utils.h"
/* Null-safe print helper used throughout the kernel. */ /* Null-safe print helper used throughout the kernel. */
#define SAFE_PRINT(Boot, ...) \ #define SAFE_PRINT(Boot, ...) \
@@ -32,15 +34,143 @@
#define COLOR_BLACK 0x0 #define COLOR_BLACK 0x0
#define COLOR_LIGHTGREEN 0xA #define COLOR_LIGHTGREEN 0xA
/* Simple context passed to each Starling Terminal instance. */
typedef struct {
BootInfo *Boot;
UINTN depth; /* 0 = top-level, 1+ = nested shells */
} StarlingContext;
/* ================================================================
* Starling Terminal task interactive command loop
* ================================================================ */
static void starling_terminal_task(void *arg)
{
StarlingContext *ctx = (StarlingContext *)arg;
BootInfo *Boot = NULL;
KeyEvent Key;
KSTATUS Status;
UINTN read_errors = 0;
CHAR16 line[128];
UINTN len = 0;
UINTN depth = 0;
if (ctx == NULL || ctx->Boot == NULL) {
return;
}
Boot = ctx->Boot;
depth = ctx->depth;
SAFE_PRINT(Boot, L"\n\r[Starling Terminal depth %d] ready.\n\r\n\r", depth);
SAFE_PRINT(Boot, L"starling> ");
while (TRUE) {
/* Try non-blocking read first; yield to other tasks while idle */
if (Boot->try_read_key != NULL) {
Status = Boot->try_read_key(&Key);
if (Status != 0) {
task_yield();
continue;
}
} else if (Boot->read_key != NULL) {
Status = Boot->read_key(&Key);
} else {
SAFE_PRINT(Boot, L"Console input unavailable.\n\r");
break;
}
if (Status != 0) {
read_errors++;
if (read_errors == 1 || (read_errors % 64) == 0) {
SAFE_PRINT(Boot, L"read_key failed (status=%ld)\n\r",
(UINT64)Status);
}
continue;
}
read_errors = 0;
if (Key.unicode_char == L'\r' || Key.unicode_char == L'\n') {
/* Enter pressed: execute the buffered command */
line[len] = L'\0';
SAFE_PRINT(Boot, L"\n\r");
trim_spaces_inplace(line);
/* Built-in terminal controls: exit / spawn nested Starling. */
if (ascii_streq_ci(line, L"exit")) {
if (depth == 0) {
SAFE_PRINT(Boot, L"Starling: cannot exit top-level terminal.\n\r");
SAFE_PRINT(Boot, L"starling> ");
} else {
SAFE_PRINT(Boot, L"Exiting Starling Terminal depth %d...\n\r", depth);
break;
}
} else if (ascii_streq_ci(line, L"starling")) {
StarlingContext *child_ctx;
Task *child_task;
child_ctx = (StarlingContext *)kmalloc(sizeof(StarlingContext));
if (child_ctx == NULL) {
SAFE_PRINT(Boot, L"Starling: failed to allocate nested terminal context.\n\r");
SAFE_PRINT(Boot, L"starling> ");
} else {
child_ctx->Boot = Boot;
child_ctx->depth = depth + 1;
child_task = task_create(L"starling-term", starling_terminal_task, child_ctx);
if (child_task == NULL) {
SAFE_PRINT(Boot, L"Starling: failed to spawn nested terminal.\n\r");
kfree(child_ctx);
SAFE_PRINT(Boot, L"starling> ");
} else {
SAFE_PRINT(Boot, L"[starling] spawned nested terminal (PID %d, depth %d)\n\r",
child_task->pid, child_ctx->depth);
/* Block this shell until the child terminal exits. */
task_wait(child_task);
SAFE_PRINT(Boot, L"[starling] returned from nested terminal (depth %d)\n\r", depth);
SAFE_PRINT(Boot, L"starling> ");
}
}
} else {
Task *cmd_task = execute_command(Boot, line);
/* If a command task was spawned, wait for it to finish. */
if (cmd_task != NULL) {
task_wait(cmd_task);
}
/* Reset for next command */
len = 0;
SAFE_PRINT(Boot, L"starling> ");
}
} else if (Key.scan_code == 0x08 || Key.unicode_char == L'\b' || Key.unicode_char == 0x7F) {
/* Backspace */
if (len > 0) {
len--;
SAFE_PRINT(Boot, L"\b \b");
}
} else if (Key.unicode_char >= 32 && Key.unicode_char < 127) {
/* Printable ASCII */
if (len < (sizeof(line) / sizeof(line[0]) - 1)) {
line[len++] = Key.unicode_char;
SAFE_PRINT(Boot, L"%c", Key.unicode_char);
}
}
}
/* Free our context on exit (allocated by the spawner). */
kfree(ctx);
}
/* ================================================================ /* ================================================================
* Kernel entry point * Kernel entry point
* ================================================================ */ * ================================================================ */
void kmain(BootInfo *Boot) void kmain(BootInfo *Boot)
{ {
KeyEvent Key;
KSTATUS Status; KSTATUS Status;
UINTN read_errors = 0; Task *terminal_task = NULL;
StarlingContext *ctx = NULL;
if (Boot == NULL) { if (Boot == NULL) {
return; return;
@@ -84,61 +214,41 @@ void kmain(BootInfo *Boot)
SAFE_PRINT(Boot, L"Available Services:\n\r"); SAFE_PRINT(Boot, L"Available Services:\n\r");
SAFE_PRINT(Boot, L" - Console Input/Output: %s\n\r", SAFE_PRINT(Boot, L" - Console Input/Output: %s\n\r",
(Boot->read_key != NULL && Boot->print != NULL) ? L"Active" : L"Unavailable"); (Boot->read_key != NULL && Boot->print != NULL) ? L"Active" : L"Unavailable");
SAFE_PRINT(Boot, L" - Terminal: Starling Terminal\n\r");
SAFE_PRINT(Boot, L"\n\r"); SAFE_PRINT(Boot, L"\n\r");
SAFE_PRINT(Boot, L"Type 'help' for a list of commands.\n\r\n\r"); SAFE_PRINT(Boot, L"Type 'help' for a list of commands.\n\r\n\r");
/* ---- Interactive shell loop ---- */ /* ---- Spawn Starling Terminal as its own task ---- */
CHAR16 line[128]; ctx = (StarlingContext *)kmalloc(sizeof(StarlingContext));
UINTN len = 0; if (ctx == NULL) {
SAFE_PRINT(Boot, L"Failed to allocate Starling Terminal context; starting inline.\n\r");
StarlingContext inline_ctx;
inline_ctx.Boot = Boot;
inline_ctx.depth = 0;
starling_terminal_task(&inline_ctx);
return;
}
SAFE_PRINT(Boot, L"-> "); ctx->Boot = Boot;
ctx->depth = 0;
terminal_task = task_create(L"starling-term", starling_terminal_task, ctx);
if (terminal_task == NULL) {
SAFE_PRINT(Boot, L"Failed to start Starling Terminal task; falling back to kernel loop.\n\r");
/*
* Fall back to running the terminal loop directly in the core
* thread so the system remains usable even if task creation
* fails for some reason.
*/
starling_terminal_task(Boot);
return;
}
SAFE_PRINT(Boot, L"[core] Started Starling Terminal (PID %d).\n\r", terminal_task->pid);
/* Core thread becomes an idle loop, yielding to the terminal and others. */
while (TRUE) { while (TRUE) {
/* Try non-blocking read first; yield to other tasks while idle */ task_yield();
if (Boot->try_read_key != NULL) {
Status = Boot->try_read_key(&Key);
if (Status != 0) {
task_yield();
continue;
}
} else if (Boot->read_key != NULL) {
Status = Boot->read_key(&Key);
} else {
SAFE_PRINT(Boot, L"Console input unavailable.\n\r");
break;
}
if (Status != 0) {
read_errors++;
if (read_errors == 1 || (read_errors % 64) == 0) {
SAFE_PRINT(Boot, L"read_key failed (status=%ld)\n\r",
(UINT64)Status);
}
continue;
}
read_errors = 0;
if (Key.unicode_char == L'\r' || Key.unicode_char == L'\n') {
/* Enter pressed: execute the buffered command */
line[len] = L'\0';
SAFE_PRINT(Boot, L"\n\r");
execute_command(Boot, line);
/* Reset for next command */
len = 0;
SAFE_PRINT(Boot, L"-> ");
} else if (Key.scan_code == 0x08 || Key.unicode_char == L'\b' || Key.unicode_char == 0x7F) {
/* Backspace */
if (len > 0) {
len--;
SAFE_PRINT(Boot, L"\b \b");
}
} else if (Key.unicode_char >= 32 && Key.unicode_char < 127) {
/* Printable ASCII */
if (len < (sizeof(line) / sizeof(line[0]) - 1)) {
line[len++] = Key.unicode_char;
SAFE_PRINT(Boot, L"%c", Key.unicode_char);
}
}
} }
} }

27
task.c
View File

@@ -1,7 +1,7 @@
/* /*
* task.c Cooperative multitasking: PCB pool, scheduler, yield/exit. * task.c Cooperative multitasking: PCB pool, scheduler, yield/exit.
* *
* Task 0 is the kernel/shell thread (uses the boot stack). * Task 0 is the always-present kernel core thread (uses the boot stack).
* Additional tasks are created with task_create(), which allocates a * Additional tasks are created with task_create(), which allocates a
* stack from the PMM and sets up a fake context-switch frame so that * 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 * context_switch() can "return" into a trampoline that calls the real
@@ -57,7 +57,7 @@ static void wstrcpy16(CHAR16 *dst, const CHAR16 *src, UINTN max)
/* /*
* Initialise the scheduler: clear all PCB slots and register the * Initialise the scheduler: clear all PCB slots and register the
* currently running kernel thread as task 0. * currently running kernel core thread as task 0.
*/ */
void task_init(BootInfo *Boot) void task_init(BootInfo *Boot)
{ {
@@ -79,7 +79,7 @@ void task_init(BootInfo *Boot)
} }
/* /*
* Task 0 = the currently running kernel thread (the shell). * Task 0 = the currently running kernel core thread.
* It already has a stack (the kernel's boot stack), so we don't * 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 * allocate one. Its saved_rsp will be filled in during the
* first context_switch call in task_yield(). * first context_switch call in task_yield().
@@ -87,7 +87,7 @@ void task_init(BootInfo *Boot)
tasks[0].pid = next_pid++; tasks[0].pid = next_pid++;
tasks[0].state = TASK_STATE_RUNNING; tasks[0].state = TASK_STATE_RUNNING;
tasks[0].switches = 1; tasks[0].switches = 1;
wstrcpy16(tasks[0].name, L"kernel", TASK_NAME_LEN); wstrcpy16(tasks[0].name, L"core", TASK_NAME_LEN);
current_task = &tasks[0]; current_task = &tasks[0];
task_ready = TRUE; task_ready = TRUE;
@@ -329,6 +329,25 @@ UINTN task_count(void)
return 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) * Print task list (implements the `ps` command)
* ---------------------------------------------------------------- */ * ---------------------------------------------------------------- */

3
task.h
View File

@@ -74,6 +74,9 @@ Task *task_current(void); /* return the running task's PCB */
UINTN task_count(void); /* number of non-FREE tasks */ UINTN task_count(void); /* number of non-FREE tasks */
void task_print_list(BootInfo *Boot); /* print task table (for `ps`) */ void task_print_list(BootInfo *Boot); /* print task table (for `ps`) */
/* Block the current task until the target task has finished. */
void task_wait(Task *t);
/* Assembly context switch (defined in context_switch.S). */ /* Assembly context switch (defined in context_switch.S). */
extern void context_switch(UINT64 *old_rsp, UINT64 new_rsp); extern void context_switch(UINT64 *old_rsp, UINT64 new_rsp);