Task scheduler
This commit is contained in:
12
Makefile
12
Makefile
@@ -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
201
README.md
@@ -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 0–31) 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 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
|
||||
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 (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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
213
commands.c
213
commands.c
@@ -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
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 "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
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);
|
||||
}
|
||||
|
||||
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
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