Task scheduler
This commit is contained in:
12
Makefile
12
Makefile
@@ -38,7 +38,7 @@ TARGET = BOOTX64.EFI
|
|||||||
TARGET_SO = bootx64.so
|
TARGET_SO = bootx64.so
|
||||||
OBJ = $(BUILD_DIR)/main.o
|
OBJ = $(BUILD_DIR)/main.o
|
||||||
KERNEL_TARGET = kernel.elf
|
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
|
KERNEL_LD = kernel.ld
|
||||||
|
|
||||||
# QEMU settings
|
# QEMU settings
|
||||||
@@ -166,6 +166,16 @@ $(BUILD_DIR)/memory.o: $(SRC_DIR)/memory.c
|
|||||||
$(CC) -ffreestanding -fno-stack-protector -fno-pic -fshort-wchar \
|
$(CC) -ffreestanding -fno-stack-protector -fno-pic -fshort-wchar \
|
||||||
-mno-red-zone -Wall -Wextra $(EFI_INCLUDES) -c $< -o $@
|
-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
|
# Link kernel ELF
|
||||||
$(BUILD_DIR)/$(KERNEL_TARGET): $(KERNEL_OBJS) $(KERNEL_LD)
|
$(BUILD_DIR)/$(KERNEL_TARGET): $(KERNEL_OBJS) $(KERNEL_LD)
|
||||||
@echo "Linking kernel ELF..."
|
@echo "Linking kernel ELF..."
|
||||||
|
|||||||
201
README.md
201
README.md
@@ -1,14 +1,16 @@
|
|||||||
# Simple UEFI Operating System
|
# 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
|
## 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
|
- **Console I/O**: Interactive keyboard input and screen output
|
||||||
- **System Information**: Displays firmware details
|
- **Interrupt Handling**: IDT setup with CPU exception handlers (vectors 0–31) and hardware IRQ dispatch
|
||||||
- **Kernel Loader**: Loads an ELF64 kernel from the EFI partition
|
- **Memory Management**: Bitmap-based physical memory manager (PMM), 4-level x86-64 paging, and a first-fit heap allocator with block coalescing
|
||||||
- **Simple Commands**: Minimal interactive command interface
|
- **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
|
## Requirements
|
||||||
|
|
||||||
@@ -28,30 +30,30 @@ make install-deps
|
|||||||
Or manually:
|
Or manually:
|
||||||
|
|
||||||
```bash
|
```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:
|
### On Arch Linux:
|
||||||
|
|
||||||
```bash
|
```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:
|
### On Fedora/RHEL:
|
||||||
|
|
||||||
```bash
|
```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
|
## Building
|
||||||
|
|
||||||
Build the UEFI application and kernel:
|
Build the UEFI loader and kernel:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
make
|
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
|
## Running
|
||||||
|
|
||||||
@@ -61,63 +63,151 @@ Test with QEMU (no graphics mode):
|
|||||||
make run
|
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).
|
The loader expects `kernel.elf` at the root of the EFI partition (next to the `EFI/` directory).
|
||||||
|
|
||||||
### QEMU Controls:
|
### QEMU Controls:
|
||||||
- **Exit QEMU**: Press `Ctrl+A`, then `X`
|
- **Exit QEMU**: Press `Ctrl+A`, then `X`
|
||||||
- **Shutdown OS**: Type `shutdown` in the OS
|
- **Shutdown OS**: Type `shutdown` in the OS
|
||||||
|
|
||||||
## Commands in the OS
|
## Commands
|
||||||
|
|
||||||
Once the OS boots, you can use these commands:
|
Once the OS boots, you can use these commands:
|
||||||
|
|
||||||
- `help` - Display all available commands
|
| Command | Description |
|
||||||
- `man <command>` - Display detailed help for a command
|
|---------|-------------|
|
||||||
- `shutdown` - Shutdown the system
|
| `help` | Display all available commands |
|
||||||
- `clear` - Clear the screen
|
| `man <command>` | Display detailed help for a command |
|
||||||
- `about` - Display system information
|
| `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
|
-> help
|
||||||
-> man shutdown
|
|
||||||
-> clear
|
|
||||||
-> about
|
-> about
|
||||||
|
-> mem
|
||||||
|
-> memtest
|
||||||
|
-> spawn worker1
|
||||||
|
-> spawn worker2
|
||||||
|
-> ps
|
||||||
|
-> tasktest
|
||||||
-> shutdown
|
-> 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
|
## Project Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
.
|
.
|
||||||
├── main.c # UEFI loader (reads and loads kernel ELF)
|
├── main.c # UEFI loader (reads and loads kernel ELF)
|
||||||
├── kernel.c # Kernel entry point and main loop
|
├── kernel.c # Kernel entry point and interactive shell loop
|
||||||
├── commands.c # Command registry and handlers
|
├── kernel.ld # Kernel linker script (base address 0x100000)
|
||||||
├── commands.h # Command interface definitions
|
├── boot_info.h # Shared boot interface (BootInfo struct)
|
||||||
├── string_utils.c # String utility functions
|
├── commands.c # Command registry and handler implementations
|
||||||
├── string_utils.h # String utility declarations
|
├── commands.h # Command interface definitions
|
||||||
├── kernel.ld # Kernel linker script
|
├── string_utils.c # String utility functions (trim, compare, etc.)
|
||||||
├── boot_info.h # Shared boot interface
|
├── string_utils.h # String utility declarations
|
||||||
├── Makefile # Build configuration
|
├── idt.c # IDT setup, exception handlers, PIC EOI
|
||||||
└── README.md # This file
|
├── idt.h # IDT types (ISRFrame) and init declaration
|
||||||
|
├── isr.S # ISR stub table (vectors 0–255) 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
|
### Boot Flow
|
||||||
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
|
|
||||||
|
|
||||||
**Command System Architecture:**
|
1. **UEFI Entry** — `efi_main()` in `main.c` is called by firmware
|
||||||
- Commands are registered in a static array with metadata (name, description, usage, handler)
|
2. **GNU-EFI Init** — library initialized with the system table
|
||||||
- The `execute_command()` function parses input, splits command and arguments, and dispatches to handlers
|
3. **Kernel Load** — `kernel.elf` is read from the EFI partition, ELF64 PT_LOAD segments are mapped into memory
|
||||||
- The `man` command provides detailed help by looking up command metadata
|
4. **BootInfo Setup** — function pointers for console I/O, shutdown, and page allocation are packaged into a `BootInfo` struct
|
||||||
- New commands can be easily added by implementing a handler and registering it
|
5. **Kernel Entry** — `kmain()` is called with the `BootInfo` pointer
|
||||||
|
|
||||||
|
### Kernel Initialization
|
||||||
|
|
||||||
|
1. **IDT** — CPU exception vectors (0–31) 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
|
## Building for Real Hardware
|
||||||
|
|
||||||
@@ -166,30 +256,23 @@ Ensure you have QEMU with x86_64 support and OVMF firmware installed.
|
|||||||
- **Architecture**: x86_64
|
- **Architecture**: x86_64
|
||||||
- **Firmware Interface**: UEFI 2.x
|
- **Firmware Interface**: UEFI 2.x
|
||||||
- **Development Library**: GNU-EFI
|
- **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
|
- **Boot Protocol**: UEFI Boot Services
|
||||||
|
- **Memory Model**: Identity-mapped (UEFI), 4-level paging (kernel)
|
||||||
|
- **Scheduling**: Cooperative round-robin multitasking
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This is a minimal educational example. Feel free to use and modify as needed.
|
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`
|
1. Open `commands.c`
|
||||||
2. Create a new handler function: `static void cmd_yourcommand(BootInfo *Boot, CHAR16 *Args)`
|
2. Add a forward declaration: `static void cmd_yourcommand(BootInfo *Boot, CHAR16 *Args);`
|
||||||
3. Add your command to the `commands[]` array with name, description, usage, and handler
|
3. Add an entry to the `commands[]` array (before the sentinel)
|
||||||
4. Rebuild with `make`
|
4. Implement the handler function
|
||||||
|
5. Rebuild with `make`
|
||||||
**Other Ideas for expansion:**
|
|
||||||
- File system support
|
|
||||||
- Memory management
|
|
||||||
- Graphics output
|
|
||||||
- Network stack
|
|
||||||
- Device drivers
|
|
||||||
- Multi-tasking
|
|
||||||
|
|
||||||
## Resources
|
## Resources
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ typedef struct {
|
|||||||
EFI_STATUS (*clear_screen)(void);
|
EFI_STATUS (*clear_screen)(void);
|
||||||
EFI_STATUS (*set_attribute)(UINTN Attribute);
|
EFI_STATUS (*set_attribute)(UINTN Attribute);
|
||||||
EFI_STATUS (*read_key)(EFI_INPUT_KEY *Key);
|
EFI_STATUS (*read_key)(EFI_INPUT_KEY *Key);
|
||||||
|
EFI_STATUS (*try_read_key)(EFI_INPUT_KEY *Key); /* non-blocking */
|
||||||
void (*shutdown)(void);
|
void (*shutdown)(void);
|
||||||
EFI_STATUS (*alloc_pages)(UINTN pages, EFI_PHYSICAL_ADDRESS *addr);
|
EFI_STATUS (*alloc_pages)(UINTN pages, EFI_PHYSICAL_ADDRESS *addr);
|
||||||
EFI_STATUS (*free_pages)(EFI_PHYSICAL_ADDRESS addr, UINTN pages);
|
EFI_STATUS (*free_pages)(EFI_PHYSICAL_ADDRESS addr, UINTN pages);
|
||||||
|
|||||||
213
commands.c
213
commands.c
@@ -2,6 +2,7 @@
|
|||||||
#include "commands.h"
|
#include "commands.h"
|
||||||
#include "string_utils.h"
|
#include "string_utils.h"
|
||||||
#include "memory.h"
|
#include "memory.h"
|
||||||
|
#include "task.h"
|
||||||
|
|
||||||
#define SAFE_PRINT(Boot, ...) \
|
#define SAFE_PRINT(Boot, ...) \
|
||||||
do { \
|
do { \
|
||||||
@@ -17,6 +18,10 @@ static void cmd_man(BootInfo *Boot, CHAR16 *Args);
|
|||||||
static void cmd_clear(BootInfo *Boot, CHAR16 *Args);
|
static void cmd_clear(BootInfo *Boot, CHAR16 *Args);
|
||||||
static void cmd_about(BootInfo *Boot, CHAR16 *Args);
|
static void cmd_about(BootInfo *Boot, CHAR16 *Args);
|
||||||
static void cmd_mem(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
|
// Command registry
|
||||||
static Command commands[] = {
|
static Command commands[] = {
|
||||||
@@ -56,6 +61,30 @@ static Command commands[] = {
|
|||||||
L"Usage: mem\n\r Shows physical memory, heap, and paging information.",
|
L"Usage: mem\n\r Shows physical memory, heap, and paging information.",
|
||||||
cmd_mem
|
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
|
{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" - Console I/O\n\r");
|
||||||
SAFE_PRINT(Boot, L" - ELF64 Kernel Loader\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" - 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" - Interactive Command Interface\n\r");
|
||||||
SAFE_PRINT(Boot, L"\n\r");
|
SAFE_PRINT(Boot, L"\n\r");
|
||||||
SAFE_PRINT(Boot, L"Type 'help' for available commands.\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);
|
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)
|
void show_help(BootInfo *Boot)
|
||||||
{
|
{
|
||||||
UINTN i = 0;
|
UINTN i = 0;
|
||||||
|
|||||||
44
context_switch.S
Normal file
44
context_switch.S
Normal 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
|
||||||
14
kernel.c
14
kernel.c
@@ -4,6 +4,7 @@
|
|||||||
#include "commands.h"
|
#include "commands.h"
|
||||||
#include "idt.h"
|
#include "idt.h"
|
||||||
#include "memory.h"
|
#include "memory.h"
|
||||||
|
#include "task.h"
|
||||||
|
|
||||||
#define SAFE_PRINT(Boot, ...) \
|
#define SAFE_PRINT(Boot, ...) \
|
||||||
do { \
|
do { \
|
||||||
@@ -38,6 +39,7 @@ void kmain(BootInfo *Boot)
|
|||||||
|
|
||||||
idt_init(Boot);
|
idt_init(Boot);
|
||||||
memory_init(Boot);
|
memory_init(Boot);
|
||||||
|
task_init(Boot);
|
||||||
|
|
||||||
SAFE_PRINT(Boot, L"================================================\n\r");
|
SAFE_PRINT(Boot, L"================================================\n\r");
|
||||||
SAFE_PRINT(Boot, L" Welcome to Simple UEFI Operating System!\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"-> ");
|
SAFE_PRINT(Boot, L"-> ");
|
||||||
|
|
||||||
while (TRUE) {
|
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");
|
SAFE_PRINT(Boot, L"Console input unavailable.\n\r");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
Status = Boot->read_key(&Key);
|
|
||||||
if (EFI_ERROR(Status)) {
|
if (EFI_ERROR(Status)) {
|
||||||
read_errors++;
|
read_errors++;
|
||||||
if (read_errors == 1 || (read_errors % 64) == 0) {
|
if (read_errors == 1 || (read_errors % 64) == 0) {
|
||||||
|
|||||||
6
main.c
6
main.c
@@ -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);
|
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)
|
static void loader_shutdown(void)
|
||||||
{
|
{
|
||||||
uefi_call_wrapper(RT->ResetSystem, 4, EfiResetShutdown, EFI_SUCCESS, 0, NULL);
|
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.clear_screen = loader_clear_screen;
|
||||||
Boot.set_attribute = loader_set_attribute;
|
Boot.set_attribute = loader_set_attribute;
|
||||||
Boot.read_key = loader_read_key;
|
Boot.read_key = loader_read_key;
|
||||||
|
Boot.try_read_key = loader_try_read_key;
|
||||||
Boot.shutdown = loader_shutdown;
|
Boot.shutdown = loader_shutdown;
|
||||||
Boot.alloc_pages = loader_alloc_pages;
|
Boot.alloc_pages = loader_alloc_pages;
|
||||||
Boot.free_pages = loader_free_pages;
|
Boot.free_pages = loader_free_pages;
|
||||||
|
|||||||
351
task.c
Normal file
351
task.c
Normal 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
56
task.h
Normal 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
|
||||||
Reference in New Issue
Block a user