Task scheduler

This commit is contained in:
2026-02-26 21:08:06 +00:00
parent 6658e4314b
commit d449150169
9 changed files with 836 additions and 62 deletions

View File

@@ -38,7 +38,7 @@ TARGET = BOOTX64.EFI
TARGET_SO = bootx64.so
OBJ = $(BUILD_DIR)/main.o
KERNEL_TARGET = kernel.elf
KERNEL_OBJS = $(BUILD_DIR)/kernel.o $(BUILD_DIR)/string_utils.o $(BUILD_DIR)/commands.o $(BUILD_DIR)/idt.o $(BUILD_DIR)/isr.o $(BUILD_DIR)/memory.o
KERNEL_OBJS = $(BUILD_DIR)/kernel.o $(BUILD_DIR)/string_utils.o $(BUILD_DIR)/commands.o $(BUILD_DIR)/idt.o $(BUILD_DIR)/isr.o $(BUILD_DIR)/memory.o $(BUILD_DIR)/task.o $(BUILD_DIR)/context_switch.o
KERNEL_LD = kernel.ld
# QEMU settings
@@ -166,6 +166,16 @@ $(BUILD_DIR)/memory.o: $(SRC_DIR)/memory.c
$(CC) -ffreestanding -fno-stack-protector -fno-pic -fshort-wchar \
-mno-red-zone -Wall -Wextra $(EFI_INCLUDES) -c $< -o $@
$(BUILD_DIR)/task.o: $(SRC_DIR)/task.c
@echo "Compiling task.c..."
$(CC) -ffreestanding -fno-stack-protector -fno-pic -fshort-wchar \
-mno-red-zone -Wall -Wextra $(EFI_INCLUDES) -c $< -o $@
$(BUILD_DIR)/context_switch.o: $(SRC_DIR)/context_switch.S
@echo "Assembling context_switch.S..."
$(CC) -ffreestanding -fno-stack-protector -fno-pic -fshort-wchar \
-mno-red-zone -Wall -Wextra -c $< -o $@
# Link kernel ELF
$(BUILD_DIR)/$(KERNEL_TARGET): $(KERNEL_OBJS) $(KERNEL_LD)
@echo "Linking kernel ELF..."

201
README.md
View File

@@ -1,14 +1,16 @@
# Simple UEFI Operating System
A minimal bootable UEFI operating system written in C that demonstrates basic UEFI functionality.
A minimal bootable UEFI operating system written in C that demonstrates UEFI boot loading, memory management, interrupt handling, and cooperative multitasking.
## Features
- **UEFI Boot**: Boots directly on UEFI firmware
- **UEFI Boot**: Boots directly on UEFI firmware via a PE32+ loader
- **ELF64 Kernel Loader**: Reads and maps a standalone kernel from the EFI partition
- **Console I/O**: Interactive keyboard input and screen output
- **System Information**: Displays firmware details
- **Kernel Loader**: Loads an ELF64 kernel from the EFI partition
- **Simple Commands**: Minimal interactive command interface
- **Interrupt Handling**: IDT setup with CPU exception handlers (vectors 031) and hardware IRQ dispatch
- **Memory Management**: Bitmap-based physical memory manager (PMM), 4-level x86-64 paging, and a first-fit heap allocator with block coalescing
- **Cooperative Multitasking**: Process control blocks, assembly context switching, and a round-robin task scheduler
- **Interactive Shell**: Command registry with `help`, `man`, built-in commands, and test utilities
## Requirements
@@ -28,30 +30,30 @@ make install-deps
Or manually:
```bash
sudo apt-get install gnu-efi qemu-system-x86 ovmf gcc binutils make
sudo apt-get install gnu-efi qemu-system-x86 ovmf gcc binutils make xorriso mtools
```
### On Arch Linux:
```bash
sudo pacman -S gnu-efi qemu-system-x86 edk2-ovmf gcc binutils make
sudo pacman -S gnu-efi qemu-system-x86 edk2-ovmf gcc binutils make xorriso mtools
```
### On Fedora/RHEL:
```bash
sudo dnf install gnu-efi qemu-system-x86 edk2-ovmf gcc binutils make
sudo dnf install gnu-efi qemu-system-x86 edk2-ovmf gcc binutils make xorriso mtools
```
## Building
Build the UEFI application and kernel:
Build the UEFI loader and kernel:
```bash
make
```
This will create `build/EFI/BOOT/BOOTX64.EFI` and `build/kernel.elf`.
This creates `build/EFI/BOOT/BOOTX64.EFI` (UEFI loader) and `build/kernel.elf` (kernel binary).
## Running
@@ -61,63 +63,151 @@ Test with QEMU (no graphics mode):
make run
```
To build and run from an ISO image:
```bash
make runiso
```
The loader expects `kernel.elf` at the root of the EFI partition (next to the `EFI/` directory).
### QEMU Controls:
- **Exit QEMU**: Press `Ctrl+A`, then `X`
- **Shutdown OS**: Type `shutdown` in the OS
## Commands in the OS
## Commands
Once the OS boots, you can use these commands:
- `help` - Display all available commands
- `man <command>` - Display detailed help for a command
- `shutdown` - Shutdown the system
- `clear` - Clear the screen
- `about` - Display system information
| Command | Description |
|---------|-------------|
| `help` | Display all available commands |
| `man <command>` | Display detailed help for a command |
| `shutdown` | Shutdown the system |
| `clear` | Clear the screen |
| `about` | Display system information |
| `mem` | Display memory statistics (PMM, heap, paging) |
| `ps` | List all active tasks with PID, state, and name |
| `spawn [name]` | Create a demo background task |
| `memtest` | Run memory allocation/deallocation tests |
| `tasktest` | Spawn multiple concurrent tasks to test the scheduler |
### Example Session
Example usage:
```
-> help
-> man shutdown
-> clear
-> about
-> mem
-> memtest
-> spawn worker1
-> spawn worker2
-> ps
-> tasktest
-> shutdown
```
### Testing Memory Management
The `memtest` command runs through four test phases:
1. **Heap allocation** — allocates 8 blocks of increasing size (16 to 4096 bytes) via `kmalloc()`
2. **Heap free** — frees all allocated blocks via `kfree()` and verifies coalescing
3. **PMM single page** — allocates and frees a single 4 KB page
4. **PMM multi-page** — allocates and frees 4 contiguous pages
Each phase reports addresses, success/failure, and usage statistics.
### Testing Task Scheduling
The `tasktest` command:
1. Spawns 3 worker tasks (`worker-A`, `worker-B`, `worker-C`)
2. Each worker prints 3 progress steps, yielding between each
3. The shell yields to let workers run in round-robin order
4. Displays the task list after completion
You can also manually test with `spawn` and `ps`:
```
-> spawn myTask
Created task 'myTask' (PID 1)
-> ps
PID STATE SWITCHES NAME
--- ---------- -------- ----
0 RUNNING 5 kernel
1 READY 0 myTask
Active tasks: 2 / 32
```
Background tasks run whenever the shell yields (which happens automatically while waiting for keyboard input).
## Project Structure
```
.
├── main.c # UEFI loader (reads and loads kernel ELF)
├── kernel.c # Kernel entry point and main loop
├── commands.c # Command registry and handlers
├── commands.h # Command interface definitions
├── string_utils.c # String utility functions
├── string_utils.h # String utility declarations
├── kernel.ld # Kernel linker script
├── boot_info.h # Shared boot interface
├── Makefile # Build configuration
── README.md # This file
├── main.c # UEFI loader (reads and loads kernel ELF)
├── kernel.c # Kernel entry point and interactive shell loop
├── kernel.ld # Kernel linker script (base address 0x100000)
├── boot_info.h # Shared boot interface (BootInfo struct)
├── commands.c # Command registry and handler implementations
├── commands.h # Command interface definitions
├── string_utils.c # String utility functions (trim, compare, etc.)
├── string_utils.h # String utility declarations
├── idt.c # IDT setup, exception handlers, PIC EOI
── idt.h # IDT types (ISRFrame) and init declaration
├── isr.S # ISR stub table (vectors 0255) with common handler
├── memory.c # PMM (bitmap), paging (4-level), heap (first-fit)
├── memory.h # Memory subsystem declarations and constants
├── task.c # Task manager: PCB pool, scheduler, yield/exit
├── task.h # Task types (Task, TaskState) and API
├── context_switch.S # Assembly cooperative context switch (x86-64)
├── Makefile # Build configuration
└── README.md # This file
```
## How It Works
## Architecture
1. **UEFI Entry Point**: The `efi_main` function is the entry point called by UEFI firmware
2. **Initialization**: GNU-EFI library is initialized with the system table
3. **Console Setup**: Output is configured and screen is cleared
4. **Kernel Load**: The loader reads `kernel.elf` and maps its loadable segments
5. **Main Loop**: The kernel enters a loop waiting for keyboard input
6. **Command Processing**: User commands are parsed and dispatched to registered handlers
7. **Command Execution**: Each command is executed via its handler function
8. **Shutdown**: UEFI runtime services are used to shutdown the system
### Boot Flow
**Command System Architecture:**
- Commands are registered in a static array with metadata (name, description, usage, handler)
- The `execute_command()` function parses input, splits command and arguments, and dispatches to handlers
- The `man` command provides detailed help by looking up command metadata
- New commands can be easily added by implementing a handler and registering it
1. **UEFI Entry**`efi_main()` in `main.c` is called by firmware
2. **GNU-EFI Init** — library initialized with the system table
3. **Kernel Load**`kernel.elf` is read from the EFI partition, ELF64 PT_LOAD segments are mapped into memory
4. **BootInfo Setup** — function pointers for console I/O, shutdown, and page allocation are packaged into a `BootInfo` struct
5. **Kernel Entry**`kmain()` is called with the `BootInfo` pointer
### Kernel Initialization
1. **IDT** — CPU exception vectors (031) are installed; firmware IRQ handlers are preserved
2. **Memory** — PMM allocates a 16 MB page pool via UEFI, bitmap tracks free/used pages; paging reads CR3; heap carves 256 KB from the PMM
3. **Tasks** — scheduler initializes; the running kernel becomes task 0
4. **Shell** — interactive loop with non-blocking input and cooperative yielding
### Memory Management
| Component | Description |
|-----------|-------------|
| **PMM** | Bitmap-based page-frame allocator managing a 16 MB pool (4096 pages); supports single and contiguous multi-page allocation |
| **Paging** | Walks/creates 4-level x86-64 page tables (PML4 → PDPT → PD → PT); supports map, unmap, and virtual-to-physical translation |
| **Heap** | First-fit free-list allocator with block splitting and bidirectional coalescing; 16-byte alignment; magic number corruption detection |
### Task System
| Component | Description |
|-----------|-------------|
| **PCB** | `Task` struct with PID, state, name, saved RSP, stack, entry function, and scheduling metadata |
| **States** | `FREE``READY``RUNNING``TERMINATED``FREE` |
| **Scheduler** | Round-robin selection among `READY` tasks via `task_yield()` |
| **Context Switch** | Assembly routine saves/restores callee-saved registers (`rbp`, `rbx`, `r12``r15`) and `RFLAGS`, swaps RSP |
| **Stack** | Each task gets 32 KB (8 pages) allocated from PMM; freed on `task_exit()` |
| **Limits** | Up to 32 concurrent tasks; cooperative (voluntary yield) only |
### Command System
- Commands are registered in a static array with metadata (name, description, usage, handler function)
- `execute_command()` parses input, splits command and arguments, and dispatches to the matching handler
- `man` provides detailed help by looking up command metadata
- New commands are added by writing a handler and appending to the `commands[]` array
## Building for Real Hardware
@@ -166,30 +256,23 @@ Ensure you have QEMU with x86_64 support and OVMF firmware installed.
- **Architecture**: x86_64
- **Firmware Interface**: UEFI 2.x
- **Development Library**: GNU-EFI
- **Executable Format**: PE32+ (Portable Executable for EFI)
- **Loader Format**: PE32+ (Portable Executable for EFI)
- **Kernel Format**: ELF64
- **Boot Protocol**: UEFI Boot Services
- **Memory Model**: Identity-mapped (UEFI), 4-level paging (kernel)
- **Scheduling**: Cooperative round-robin multitasking
## License
This is a minimal educational example. Feel free to use and modify as needed.
## Further Development
## Adding New Commands
The codebase is now modular and easy to extend:
**Adding New Commands:**
1. Open `commands.c`
2. Create a new handler function: `static void cmd_yourcommand(BootInfo *Boot, CHAR16 *Args)`
3. Add your command to the `commands[]` array with name, description, usage, and handler
4. Rebuild with `make`
**Other Ideas for expansion:**
- File system support
- Memory management
- Graphics output
- Network stack
- Device drivers
- Multi-tasking
2. Add a forward declaration: `static void cmd_yourcommand(BootInfo *Boot, CHAR16 *Args);`
3. Add an entry to the `commands[]` array (before the sentinel)
4. Implement the handler function
5. Rebuild with `make`
## Resources

View File

@@ -11,6 +11,7 @@ typedef struct {
EFI_STATUS (*clear_screen)(void);
EFI_STATUS (*set_attribute)(UINTN Attribute);
EFI_STATUS (*read_key)(EFI_INPUT_KEY *Key);
EFI_STATUS (*try_read_key)(EFI_INPUT_KEY *Key); /* non-blocking */
void (*shutdown)(void);
EFI_STATUS (*alloc_pages)(UINTN pages, EFI_PHYSICAL_ADDRESS *addr);
EFI_STATUS (*free_pages)(EFI_PHYSICAL_ADDRESS addr, UINTN pages);

View File

@@ -2,6 +2,7 @@
#include "commands.h"
#include "string_utils.h"
#include "memory.h"
#include "task.h"
#define SAFE_PRINT(Boot, ...) \
do { \
@@ -17,6 +18,10 @@ static void cmd_man(BootInfo *Boot, CHAR16 *Args);
static void cmd_clear(BootInfo *Boot, CHAR16 *Args);
static void cmd_about(BootInfo *Boot, CHAR16 *Args);
static void cmd_mem(BootInfo *Boot, CHAR16 *Args);
static void cmd_ps(BootInfo *Boot, CHAR16 *Args);
static void cmd_spawn(BootInfo *Boot, CHAR16 *Args);
static void cmd_memtest(BootInfo *Boot, CHAR16 *Args);
static void cmd_tasktest(BootInfo *Boot, CHAR16 *Args);
// Command registry
static Command commands[] = {
@@ -56,6 +61,30 @@ static Command commands[] = {
L"Usage: mem\n\r Shows physical memory, heap, and paging information.",
cmd_mem
},
{
L"ps",
L"List running tasks",
L"Usage: ps\n\r Displays all active tasks with PID, state, and name.",
cmd_ps
},
{
L"spawn",
L"Spawn a demo background task",
L"Usage: spawn [name]\n\r Creates a cooperative demo task.\n\r Optional argument sets the task name.",
cmd_spawn
},
{
L"memtest",
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.",
cmd_memtest
},
{
L"tasktest",
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.",
cmd_tasktest
},
{NULL, NULL, NULL, NULL} // Sentinel
};
@@ -153,6 +182,7 @@ static void cmd_about(BootInfo *Boot, CHAR16 *Args)
SAFE_PRINT(Boot, L" - Console I/O\n\r");
SAFE_PRINT(Boot, L" - ELF64 Kernel Loader\n\r");
SAFE_PRINT(Boot, L" - Memory Management (PMM + Heap)\n\r");
SAFE_PRINT(Boot, L" - Cooperative Multitasking\n\r");
SAFE_PRINT(Boot, L" - Interactive Command Interface\n\r");
SAFE_PRINT(Boot, L"\n\r");
SAFE_PRINT(Boot, L"Type 'help' for available commands.\n\r");
@@ -165,6 +195,189 @@ static void cmd_mem(BootInfo *Boot, CHAR16 *Args)
memory_print_stats(Boot);
}
/* ----------------------------------------------------------------
* Demo task runs cooperatively, prints progress, then exits
* ---------------------------------------------------------------- */
static void demo_task_fn(void *arg)
{
BootInfo *Boot = (BootInfo *)arg;
Task *self = task_current();
UINTN i;
SAFE_PRINT(Boot, L"[%s:%d] started\n\r", self->name, self->pid);
for (i = 0; i < 5; i++) {
SAFE_PRINT(Boot, L"[%s:%d] tick %d/5\n\r",
self->name, self->pid, i + 1);
task_yield();
}
SAFE_PRINT(Boot, L"[%s:%d] finished\n\r", self->name, self->pid);
}
static void cmd_ps(BootInfo *Boot, CHAR16 *Args)
{
(void)Args;
task_print_list(Boot);
}
static void cmd_spawn(BootInfo *Boot, CHAR16 *Args)
{
Task *t;
const CHAR16 *name;
if (Args != NULL && Args[0] != L'\0') {
trim_spaces_inplace(Args);
name = Args;
} else {
name = L"demo";
}
t = task_create(name, demo_task_fn, Boot);
if (t == NULL) {
SAFE_PRINT(Boot, L"Failed to create task (out of slots or memory).\n\r");
return;
}
SAFE_PRINT(Boot, L"Created task '%s' (PID %d)\n\r", t->name, t->pid);
}
/* ----------------------------------------------------------------
* memtest exercise the heap and PMM allocators
* ---------------------------------------------------------------- */
static void cmd_memtest(BootInfo *Boot, CHAR16 *Args)
{
void *ptrs[8];
UINTN sizes[] = { 16, 64, 128, 256, 512, 1024, 2048, 4096 };
UINTN i;
UINT64 page;
UINTN h_total, h_used, h_free, h_blocks;
(void)Args;
SAFE_PRINT(Boot, L"\n\r");
SAFE_PRINT(Boot, L"Memory Test\n\r");
SAFE_PRINT(Boot, L"================================================\n\r");
SAFE_PRINT(Boot, L"\n\r");
/* --- Heap allocation test --- */
SAFE_PRINT(Boot, L"[1] Heap allocation test\n\r");
for (i = 0; i < 8; i++) {
ptrs[i] = kmalloc(sizes[i]);
if (ptrs[i] != NULL) {
SAFE_PRINT(Boot, L" kmalloc(%4d) = 0x%lx OK\n\r",
sizes[i], (UINT64)(UINTN)ptrs[i]);
} else {
SAFE_PRINT(Boot, L" kmalloc(%4d) = NULL FAIL\n\r", sizes[i]);
}
}
heap_get_stats(&h_total, &h_used, &h_free, &h_blocks);
SAFE_PRINT(Boot, L" Heap after alloc: used=%d free=%d blocks=%d\n\r",
h_used, h_free, h_blocks);
/* --- Heap free test --- */
SAFE_PRINT(Boot, L"\n\r[2] Heap free test\n\r");
for (i = 0; i < 8; i++) {
if (ptrs[i] != NULL) {
kfree(ptrs[i]);
SAFE_PRINT(Boot, L" kfree(0x%lx) OK\n\r",
(UINT64)(UINTN)ptrs[i]);
}
}
heap_get_stats(&h_total, &h_used, &h_free, &h_blocks);
SAFE_PRINT(Boot, L" Heap after free: used=%d free=%d blocks=%d\n\r",
h_used, h_free, h_blocks);
/* --- PMM page allocation test --- */
SAFE_PRINT(Boot, L"\n\r[3] PMM page allocation test\n\r");
SAFE_PRINT(Boot, L" Free pages before: %d\n\r", pmm_get_free_pages());
page = pmm_alloc_page();
if (page != 0) {
SAFE_PRINT(Boot, L" pmm_alloc_page() = 0x%lx OK\n\r", page);
SAFE_PRINT(Boot, L" Free pages after alloc: %d\n\r", pmm_get_free_pages());
pmm_free_page(page);
SAFE_PRINT(Boot, L" pmm_free_page() OK\n\r");
SAFE_PRINT(Boot, L" Free pages after free: %d\n\r", pmm_get_free_pages());
} else {
SAFE_PRINT(Boot, L" pmm_alloc_page() = 0 FAIL (out of pages)\n\r");
}
/* --- Multi-page allocation test --- */
SAFE_PRINT(Boot, L"\n\r[4] PMM multi-page allocation test (4 pages)\n\r");
page = pmm_alloc_pages(4);
if (page != 0) {
SAFE_PRINT(Boot, L" pmm_alloc_pages(4) = 0x%lx OK\n\r", page);
SAFE_PRINT(Boot, L" Free pages after alloc: %d\n\r", pmm_get_free_pages());
pmm_free_pages(page, 4);
SAFE_PRINT(Boot, L" pmm_free_pages() OK\n\r");
SAFE_PRINT(Boot, L" Free pages after free: %d\n\r", pmm_get_free_pages());
} else {
SAFE_PRINT(Boot, L" pmm_alloc_pages(4) = 0 FAIL\n\r");
}
SAFE_PRINT(Boot, L"\n\rAll memory tests completed.\n\r\n\r");
}
/* ----------------------------------------------------------------
* tasktest spawn multiple concurrent tasks to exercise scheduler
* ---------------------------------------------------------------- */
static void worker_task_fn(void *arg)
{
BootInfo *Boot = (BootInfo *)arg;
Task *self = task_current();
UINTN i;
for (i = 0; i < 3; i++) {
SAFE_PRINT(Boot, L" [%s:%d] working... step %d/3\n\r",
self->name, self->pid, i + 1);
task_yield();
}
SAFE_PRINT(Boot, L" [%s:%d] done\n\r", self->name, self->pid);
}
static void cmd_tasktest(BootInfo *Boot, CHAR16 *Args)
{
Task *t1, *t2, *t3;
UINTN i;
(void)Args;
SAFE_PRINT(Boot, L"\n\r");
SAFE_PRINT(Boot, L"Task Scheduler Test\n\r");
SAFE_PRINT(Boot, L"================================================\n\r");
SAFE_PRINT(Boot, L"\n\r");
SAFE_PRINT(Boot, L"Spawning 3 worker tasks...\n\r\n\r");
t1 = task_create(L"worker-A", worker_task_fn, Boot);
t2 = task_create(L"worker-B", worker_task_fn, Boot);
t3 = task_create(L"worker-C", worker_task_fn, Boot);
if (t1 == NULL || t2 == NULL || t3 == NULL) {
SAFE_PRINT(Boot, L"Failed to create one or more tasks.\n\r");
return;
}
SAFE_PRINT(Boot, L"Created: %s (PID %d), %s (PID %d), %s (PID %d)\n\r",
t1->name, t1->pid, t2->name, t2->pid, t3->name, t3->pid);
SAFE_PRINT(Boot, L"\n\rYielding to let workers run:\n\r\n\r");
/* Yield enough times for all workers to complete (3 tasks x 3 steps) */
for (i = 0; i < 12; i++) {
task_yield();
}
SAFE_PRINT(Boot, L"\n\rTask list after test:\n\r");
task_print_list(Boot);
SAFE_PRINT(Boot, L"Task scheduler test completed.\n\r\n\r");
}
void show_help(BootInfo *Boot)
{
UINTN i = 0;

44
context_switch.S Normal file
View File

@@ -0,0 +1,44 @@
/*
* context_switch.S cooperative context switch for x86_64
*
* void context_switch(UINT64 *old_rsp, UINT64 new_rsp);
*
* %rdi = pointer to save current RSP (old task's PCB field)
* %rsi = RSP value to restore (new task's saved RSP)
*
* Saves callee-saved registers and RFLAGS on the current stack,
* stores RSP, loads the new stack, restores registers, and returns
* into the new task's execution context.
*/
.text
.global context_switch
context_switch:
/* Save callee-saved registers and flags on the current stack */
pushq %rbp
pushq %rbx
pushq %r12
pushq %r13
pushq %r14
pushq %r15
pushfq
/* Save current stack pointer into *old_rsp */
movq %rsp, (%rdi)
/* Load new task's stack pointer */
movq %rsi, %rsp
/* Restore callee-saved registers and flags from the new stack */
popfq
popq %r15
popq %r14
popq %r13
popq %r12
popq %rbx
popq %rbp
ret
.section .note.GNU-stack,"",@progbits

View File

@@ -4,6 +4,7 @@
#include "commands.h"
#include "idt.h"
#include "memory.h"
#include "task.h"
#define SAFE_PRINT(Boot, ...) \
do { \
@@ -38,6 +39,7 @@ void kmain(BootInfo *Boot)
idt_init(Boot);
memory_init(Boot);
task_init(Boot);
SAFE_PRINT(Boot, L"================================================\n\r");
SAFE_PRINT(Boot, L" Welcome to Simple UEFI Operating System!\n\r");
@@ -74,12 +76,20 @@ void kmain(BootInfo *Boot)
SAFE_PRINT(Boot, L"-> ");
while (TRUE) {
if (Boot->read_key == NULL) {
/* 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 == EFI_NOT_READY) {
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;
}
Status = Boot->read_key(&Key);
if (EFI_ERROR(Status)) {
read_errors++;
if (read_errors == 1 || (read_errors % 64) == 0) {

6
main.c
View File

@@ -202,6 +202,11 @@ static EFI_STATUS loader_read_key(EFI_INPUT_KEY *Key)
return uefi_call_wrapper(ST->ConIn->ReadKeyStroke, 2, ST->ConIn, Key);
}
static EFI_STATUS loader_try_read_key(EFI_INPUT_KEY *Key)
{
return uefi_call_wrapper(ST->ConIn->ReadKeyStroke, 2, ST->ConIn, Key);
}
static void loader_shutdown(void)
{
uefi_call_wrapper(RT->ResetSystem, 4, EfiResetShutdown, EFI_SUCCESS, 0, NULL);
@@ -251,6 +256,7 @@ efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable)
Boot.clear_screen = loader_clear_screen;
Boot.set_attribute = loader_set_attribute;
Boot.read_key = loader_read_key;
Boot.try_read_key = loader_try_read_key;
Boot.shutdown = loader_shutdown;
Boot.alloc_pages = loader_alloc_pages;
Boot.free_pages = loader_free_pages;

351
task.c Normal file
View File

@@ -0,0 +1,351 @@
#include "task.h"
#include "memory.h"
#define SAFE_PRINT(Boot, ...) \
do { \
if ((Boot) != NULL && (Boot)->print != NULL) { \
(Boot)->print(__VA_ARGS__); \
} \
} while (0)
/* ================================================================
* Task / Process Control Block pool
* ================================================================ */
static Task tasks[TASK_MAX];
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
* ---------------------------------------------------------------- */
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
* ---------------------------------------------------------------- */
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 thread (the shell).
* 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"kernel", 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;
}
/* ----------------------------------------------------------------
* 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");
}

56
task.h Normal file
View File

@@ -0,0 +1,56 @@
#ifndef TASK_H
#define TASK_H
#include <efi.h>
#include "boot_info.h"
/* Maximum number of concurrent tasks */
#define TASK_MAX 32
/* Stack size per task: 32 KB (8 pages) */
#define TASK_STACK_PAGES 8
#define TASK_STACK_SIZE (TASK_STACK_PAGES * 4096)
/* Maximum task name length */
#define TASK_NAME_LEN 32
/* Task states */
typedef enum {
TASK_STATE_FREE = 0, /* PCB slot is unused */
TASK_STATE_READY, /* Runnable, waiting for CPU */
TASK_STATE_RUNNING, /* Currently executing on CPU */
TASK_STATE_TERMINATED /* Finished execution */
} TaskState;
/* Task entry function signature */
typedef void (*TaskEntryFn)(void *arg);
/* ================================================================
* Process Control Block (PCB)
* ================================================================ */
typedef struct Task {
UINT32 pid; /* Process ID */
TaskState state; /* Current state */
CHAR16 name[TASK_NAME_LEN]; /* Human-readable name */
UINT64 saved_rsp; /* Saved stack pointer (context switch) */
UINT64 stack_base; /* Base address of allocated stack */
UINTN stack_pages; /* Stack size in pages */
TaskEntryFn entry; /* Entry function pointer */
void *arg; /* Argument passed to entry */
UINTN switches; /* Number of times scheduled */
} Task;
/* -------- Task API -------- */
void task_init(BootInfo *Boot);
Task *task_create(const CHAR16 *name, TaskEntryFn entry, void *arg);
void task_yield(void);
void task_exit(void);
Task *task_current(void);
UINTN task_count(void);
void task_print_list(BootInfo *Boot);
/* Assembly context switch (defined in context_switch.S) */
extern void context_switch(UINT64 *old_rsp, UINT64 new_rsp);
#endif