Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ jobs:
- "qemu-test heap-test"
- "qemu-test heap-oom-test"
- "qemu-test heap-guard-fault-test"
- scheduler-test
- scheduler-preemption-test
- task-stack-guard-fault-test
- task-capacity-test
steps:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand Down Expand Up @@ -267,3 +271,55 @@ jobs:
description: >-
Reads the unmapped supervisor heap guard page and validates the
expected non-present page fault.

qemu-scheduler:
name: QEMU / kernel scheduler smoke
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/qemu-scenario
with:
features: "qemu-test scheduler-test"
description: >-
Runs two kernel tasks on distinct guarded stacks, validates their
lifecycle, and confirms bootstrap idle resumes after task exit.

qemu-scheduler-preemption:
name: QEMU / timer-driven task preemption
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/qemu-scenario
with:
features: scheduler-preemption-test
description: >-
Uses two CPU-bound tasks with no voluntary yields; each can finish
only after timer preemption has run the other task.

qemu-task-stack-guard:
name: QEMU / task stack guard page
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/qemu-scenario
with:
features: task-stack-guard-fault-test
description: >-
Deliberately reads a kernel task's unmapped lower stack guard and
validates the CPL0 non-present read fault.

qemu-task-capacity:
name: QEMU / task capacity and reuse
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: ./.github/actions/qemu-scenario
with:
features: "qemu-test task-capacity-test"
description: >-
Fills the fixed task table, checks deterministic exhaustion,
reuses an exited slot with a new generation, and rejects the old ID.
34 changes: 23 additions & 11 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Architecture

This document describes pvKernel's current architecture as implemented in
version 0.0.7. It does not describe planned or aspirational architecture; see
version 0.0.8. It does not describe planned or aspirational architecture; see
[ROADMAP.md](ROADMAP.md) for that.

## Components
Expand Down Expand Up @@ -29,6 +29,11 @@ pvKernel ELF (kernel_main)

## Responsibility boundary

The root `task` module owns the fixed task table and runnable ring.
`arch::x86_64::task` owns task stack mapping, the saved frame layout, and the
small timer-entry/trampoline assembly. Policy stays in Rust; CR3 remains
shared across all kernel tasks.

* **UEFI firmware**: powers on the machine, initializes basic platform
devices, and runs the boot manager.
* **rust-osdev bootloader**: locates and loads the pvKernel ELF binary,
Expand All @@ -41,12 +46,15 @@ pvKernel ELF (kernel_main)
legacy 8259 PIC and PIT drivers, the physical frame allocator and active
page-table access, a provisional `int 0x80` syscall ABI, and (test-only)
CPL0 → CPL3 userspace entry, and framebuffer rendering.
* **Kernel tasks**: 0.0.8 adds bounded, preemptive, single-core ring-0 tasks
with separate guarded stacks. It does not schedule CPL3 payloads or switch
CR3; see [docs/architecture/kernel-tasks.md](docs/architecture/kernel-tasks.md).
* **Future pvOS userspace**: 0.0.6 adds only a minimal, test-only CPL0 →
CPL3 transition and bootstrap syscall ABI (see
[docs/architecture/userspace-entry.md](docs/architecture/userspace-entry.md)
and [docs/architecture/syscalls.md](docs/architecture/syscalls.md)); a
process model, scheduler, and ELF userspace loading remain future
roadmap items (see [ROADMAP.md](ROADMAP.md)); pvKernel does not
process model and ELF userspace loading remain future roadmap items (see
[ROADMAP.md](ROADMAP.md)); pvKernel does not
currently support them.

## Initialization order
Expand Down Expand Up @@ -99,23 +107,27 @@ pvKernel ELF (kernel_main)
0.0.1: dark background, centered blue `P` (`#00B7EF`).
7. A controlled `int3` breakpoint test verifies the IDT is functioning and
execution resumes correctly.
8. A boot-time timer verification unmasks PIT IRQ0, enables maskable
hardware interrupts, and confirms the tick counter advances (see
[docs/architecture/timer.md](docs/architecture/timer.md)).
9. If the `fault-tests` Cargo feature is enabled, a deliberate page fault is
8. `task::init` reserves and verifies task stacks. Two finite smoke tasks
are created, but not run yet, before IRQ0 is unmasked.
9. PIT IRQ0 is unmasked, maskable interrupts are enabled, the two tasks run
under preemption and return to bootstrap, and timer progress is confirmed
(see [docs/architecture/timer.md](docs/architecture/timer.md)).
10. If the `fault-tests` Cargo feature is enabled, a deliberate page fault is
triggered and matched against its expected faulting address.
10. If the `mem-fault-tests` Cargo feature is enabled, a temporary page is
11. If the `mem-fault-tests` Cargo feature is enabled, a temporary page is
mapped, written to, unmapped, and deliberately re-accessed, matching the
resulting page fault against its expected address (see
[docs/development/memory-testing.md](docs/development/memory-testing.md)).
11. If `supervisor-write-protection-test` is enabled, a CPL0 write to a
12. If `supervisor-write-protection-test` is enabled, a CPL0 write to a
supervisor-only read-only page verifies CR0.WP behavior through an
expected protection-violation page fault.
12. If exactly one `userspace-*-test` Cargo feature is enabled, a hand-written
13. If exactly one `userspace-*-test` Cargo feature is enabled, a hand-written
test payload is mapped into the reserved userspace range and entered at
CPL3 via `iretq`; the scenario ends deterministically (see
[docs/development/userspace-testing.md](docs/development/userspace-testing.md)).
13. The CPU enters an interrupt-enabled idle loop (`halt::idle_loop`), unless
14. The timer entry saves the complete interrupt context, schedules CPL0
contexts only, and resumes bootstrap after both smoke tasks exit.
15. The CPU enters an interrupt-enabled idle loop (`halt::idle_loop`), unless
the `qemu-test` Cargo feature is enabled, in which case QEMU exits
deterministically instead (see
[docs/development/testing.md](docs/development/testing.md)).
Expand Down
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
# Changelog

## [0.0.8] - Unreleased

### Added

* Bounded ring-0 kernel tasks with generation-aware `TaskId`s, eight
pre-mapped 16 KiB supervisor RW/NX stacks, and unmapped guards on both
ends.
* A dedicated x86_64 PIT IRQ entry that saves the complete GPR and long-mode
interrupt frame, preserves CPL3 returns without scheduling, and performs
allocation-free round-robin decisions.
* A bounded ready ring, safe task-return/exit lifecycle, and deterministic
QEMU coverage for task execution, timer preemption, capacity/reuse, stale
IDs, and task stack guard faults.

## [0.0.7] - Unreleased

### Added
Expand Down
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ members = ["kernel"]
# docs/security/vulnerability-management.md and the release workflows for
# why that consistency matters at release time.
[workspace.package]
version = "0.0.7"
version = "0.0.8"
license = "MIT OR Apache-2.0"
edition = "2024"
repository = "https://github.com/ultronstudio/pvkernel"
Expand Down Expand Up @@ -54,6 +54,10 @@ userspace-supervisor-fault-test = ["kernel/userspace-supervisor-fault-test"]
heap-test = ["kernel/heap-test"]
heap-oom-test = ["kernel/heap-oom-test"]
heap-guard-fault-test = ["kernel/heap-guard-fault-test"]
scheduler-test = ["kernel/scheduler-test"]
scheduler-preemption-test = ["kernel/scheduler-preemption-test"]
task-stack-guard-fault-test = ["kernel/task-stack-guard-fault-test"]
task-capacity-test = ["kernel/task-capacity-test"]

[build-dependencies]
kernel = { path = "kernel", artifact = "bin", target = "x86_64-unknown-none" }
Expand Down
15 changes: 12 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ written in Rust and developed as the foundation of pvOS.

## Current milestone

Version: `0.0.7` — Kernel Heap and Rust `alloc`
Version: `0.0.8` — Scheduler + Kernel Tasks

Implemented:

Expand Down Expand Up @@ -59,6 +59,14 @@ Implemented:
coalescing of adjacent free allocation units
* Rust `alloc` support for `Box`, growing `Vec`, and `String`
* deterministic heap success, OOM, and guard-page QEMU tests
* bounded single-core preemptive round-robin scheduling for ring-0 kernel
tasks, with generation-aware IDs and deterministic task lifecycle
* eight centrally reserved 16 KiB kernel task stacks, supervisor-only RW/NX,
with unmapped guard pages above and below
* dedicated timer IRQ context switching with complete general-purpose
register and interrupt-frame preservation; CPL3 timer IRQs resume in place
* deterministic QEMU scheduler, CPU-bound preemption, stack-guard, capacity,
and stale-ID/reuse coverage
* structured serial diagnostics
* panic handler
* an interrupt-enabled idle loop (plus a separate fatal halt path)
Expand All @@ -70,8 +78,9 @@ Not implemented:
* general physical-frame deallocation/reuse (heap backing frames stay owned
by the heap for the kernel lifetime)
* dynamic heap growth, per-process heaps, or a slab allocator
* guard pages for stacks or other subsystems (the kernel heap has two)
* processes, threads, or a scheduler
* dynamic task stack growth, task join, blocking, sleep, or priorities
* APIC, SMP, and multicore scheduling
* userspace process scheduling, processes, or per-process address spaces
* `SYSCALL`/`SYSRET`, a process model, or general (non-test) userspace
execution
* ELF userspace loading
Expand Down
29 changes: 15 additions & 14 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,26 +99,27 @@ freeing heap objects returns memory to the heap allocator, not the physical
frame allocator. Dynamic heap growth and general physical-frame reclamation
remain future work.

## 0.0.8 — Kernel Tasks and Scheduler
## 0.0.8 — Scheduler + Kernel Tasks

* kernel-level tasks
* context switching
* a preemptive scheduler
* bounded, single-core, ring-0 kernel tasks with dedicated guarded stacks
* generation-aware task IDs and safe task lifecycle/slot reuse
* complete x86_64 timer-interrupt context switching and deterministic
round-robin preemption
* QEMU coverage for scheduler operation, true timer preemption, stack guards,
and capacity/reuse

## 0.0.9 — ELF Userspace Loader and Init

* loading ELF userspace binaries
* a minimal init process
* a process model built on top of 0.0.6's ring-3/syscall foundation and
0.0.8's scheduler (superseding the standalone "Ring 3 and System Calls"
milestone originally planned here, since that foundation now exists
earlier)
## 0.0.9 — Processes and Userspace Address Spaces

* process model and process lifecycle foundation
* per-process address spaces and CR3 switching
* userspace ELF execution foundation
* builds on the existing provisional CPL3 entry/syscall boundary; does not
imply a stable userspace ABI

## 0.0.10 — VFS and Initial Ramdisk

* a virtual filesystem layer
* initial ramdisk support
* VFS foundation
* initrd and root filesystem foundation

## 0.1.0 — First pvOS-Ready Kernel Foundation

Expand Down
20 changes: 13 additions & 7 deletions docs/architecture/boot.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,18 @@ crate; it is not part of pvKernel.
`P` logo (`kernel/src/graphics/framebuffer.rs`).
13. pvKernel verifies breakpoint handling with a controlled `int3` call and
confirms execution resumes afterward.
14. pvKernel unmasks PIT IRQ0, enables maskable hardware interrupts, and
confirms the tick counter advances by at least 5 ticks (see
[docs/architecture/timer.md](timer.md)).
15. If the `fault-tests` Cargo feature is enabled, pvKernel triggers a
14. Before enabling IRQ0, pvKernel initializes the bounded kernel scheduler,
pre-maps and verifies its guarded task stacks, and prepares two finite
kernel-task smoke entries.
15. pvKernel unmasks PIT IRQ0, enables maskable hardware interrupts, runs the
two tasks under timer preemption, confirms bootstrap resumes after task
exit, and verifies the tick counter advances (see
[docs/architecture/timer.md](timer.md) and
[docs/architecture/kernel-tasks.md](kernel-tasks.md)).
16. If the `fault-tests` Cargo feature is enabled, pvKernel triggers a
deliberate page fault and matches it against its expected faulting
address.
16. pvKernel enters an interrupt-enabled idle loop
17. pvKernel enters an interrupt-enabled idle loop
(`kernel/src/halt.rs::idle_loop`), unless the `qemu-test` Cargo feature
is enabled, in which case it exits QEMU deterministically instead (see
[docs/development/testing.md](../development/testing.md)).
Expand All @@ -51,10 +56,11 @@ crate; it is not part of pvKernel.
management is a future milestone (see [ROADMAP.md](../../ROADMAP.md)).
* Steps 10-11 (PIC/PIT setup) happen before the framebuffer is initialized,
but every IRQ line stays masked and maskable interrupts stay disabled
until step 14, so no hardware interrupt can be delivered before the IDT,
until step 15, so no hardware interrupt can be delivered before the IDT,
PIC, and PIT are all fully configured.
* The boot flow above matches `cargo run`. `cargo run --features
fault-tests` performs one additional step after the timer verification: a
fault-tests` performs one additional step after the scheduler/timer
verification: a
deliberate page fault, described in
[docs/development/debugging.md](../development/debugging.md).
* `cargo run --features qemu-test -- --headless-test` (optionally combined
Expand Down
2 changes: 1 addition & 1 deletion docs/architecture/interrupts.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ release; every other line stays masked for its entire lifetime.
| General protection fault | `general_protection_fault_handler` | Logs the event, the CPL it originated from, error code, and stack frame, then calls `fatal_exit` — unless it matches an armed `userspace-privilege-fault-test` expectation, in which case it exits QEMU successfully instead (see [docs/development/userspace-testing.md](../development/userspace-testing.md)). |
| Page fault | `page_fault_handler` | Logs CR2 (faulting address), the CPL it originated from, the page-fault error flags, and the stack frame. Under `qemu-test`, an expected deliberate fault (including the 0.0.6 `userspace-supervisor-fault-test` scenario) exits QEMU successfully instead; otherwise calls `fatal_exit`. |
| Double fault | `double_fault_handler` | Runs on the dedicated IST stack; logs the event, error code, and stack frame, then calls `fatal_exit`. Return type is `!`. |
| Timer (IRQ0 / vector 32) | `timer_interrupt_handler` | Records a tick (`pit::record_tick`) and sends EOI. No logging, to avoid stalling or recursing while interrupting arbitrary kernel state. |
| Timer (IRQ0 / vector 32) | `arch::x86_64::task::timer_irq_entry` | Assembly saves every GPR and the long-mode interrupt frame, calls the bounded Rust round-robin decision path for CPL0 frames, records one tick, and sends one EOI. CPL3 frames are returned unchanged. No allocation, ordinary logging, or blocking occurs in the IRQ path. |
| `Lpt1`/IRQ7 (vector 39) | `master_spurious_interrupt_handler` | Checks the master's in-service register; sends EOI only if the interrupt was real, and records it as unexpected either way. |
| `SecondaryAta`/IRQ15 (vector 47) | `slave_spurious_interrupt_handler` | Checks the slave's in-service register; sends master-only EOI if spurious, full slave+master EOI if real, and records real ones as unexpected. |
| Every other remapped IRQ (1-6, 8-14, vectors 33-38, 40-46) | generated by `generic_irq_handler!` | Records the IRQ as unexpected and sends EOI. These lines stay masked in 0.0.4, so reaching one of these handlers indicates a spurious or misbehaving device, not normal operation. |
Expand Down
Loading
Loading