Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
ae6465c
feat: add bounded process identity and lifecycle
ultronstudio Oct 24, 2026
8358abf
fix: enforce legal process lifecycle transitions
ultronstudio Oct 24, 2026
c2aba26
arch: constrain boot mappings to kernel half
ultronstudio Oct 24, 2026
bdea418
feat: add process address spaces
ultronstudio Oct 24, 2026
d3ed3db
feat: add userspace stack and rsp0 switching support
ultronstudio Oct 24, 2026
70ba46f
feat: add checked static userspace elf loader
ultronstudio Oct 24, 2026
81a8be3
feat: prepare userspace process tasks
ultronstudio Oct 24, 2026
335614c
feat: add audited process CR3 activation
ultronstudio Oct 24, 2026
9a5c028
feat: add process switch plans and cr3 handoff
ultronstudio Oct 24, 2026
b6405c0
feat: preempt userspace processes
ultronstudio Oct 24, 2026
2496f9c
feat: add process syscalls and safe exit
ultronstudio Oct 24, 2026
3bef28e
feat: add checked userspace memory copying
ultronstudio Oct 24, 2026
04596d5
feat: contain userspace faults
ultronstudio Oct 24, 2026
8827b7a
test: validate process isolation accounting and capacity
ultronstudio Oct 24, 2026
cbd2f79
docs: document pvKernel 0.0.9 process foundation
ultronstudio Oct 24, 2026
000d051
test: require userspace SIMD state isolation
ultronstudio Oct 24, 2026
50fc9da
fix: disable unsaved user execution state
ultronstudio Oct 24, 2026
de9eec0
fix: release paging lock before cr3 handoff
ultronstudio Oct 24, 2026
30af43b
fix: block unsaved avx state in user tasks
ultronstudio Oct 24, 2026
b1476c5
docs: record process execution-state policy
ultronstudio Oct 24, 2026
490a62e
docs: align process fault and syscall status
ultronstudio Oct 24, 2026
7c09565
fix: satisfy strict kernel clippy checks
ultronstudio Oct 24, 2026
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
154 changes: 79 additions & 75 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -1,62 +1,55 @@
# Architecture

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

## Components

```
```text
UEFI firmware (OVMF)
│
▼
rust-osdev bootloader (external crate, not part of pvKernel)
│ loads the pvKernel ELF, exits UEFI boot services,
│ builds an initial page table mapping (incl. a physical-memory
│ offset mapping), and passes BootInfo
▼
pvKernel ELF (kernel_main)
│
├── drivers::serial – COM1 UART diagnostics (kprint!/kprintln!)
├── arch::x86_64 – GDT, TSS, IDT, exception + IRQ handlers, PIC, PIT
├── arch::x86_64::memory – physical frame allocator, active page table
│ access, map/unmap/permission operations
├── arch::x86_64::syscall – provisional int 0x80 syscall ABI
├── arch::x86_64::userspace – test-only CPL0 → CPL3 (iretq) entry
├── graphics::framebuffer – centered blue "P" logo
└── halt – fatal halt vs. interrupt-enabled idle loop
|
rust-osdev bootloader
| upper-half dynamic mapping arena, BootInfo, framebuffer, memory map
v
pvKernel kernel_main
|-- drivers::serial, graphics::framebuffer, halt
|-- arch::x86_64
| |-- GDT/TSS/RSP0, IDT, PIC/PIT, exception and timer entry
| |-- memory: physical allocator, audited kernel root, AddressSpace
| |-- syscall: experimental scalar int 0x80 ABI
|-- task: bounded scheduler, kernel/user Task contexts, SwitchPlan
|-- process: ProcessId/PID table, lifecycle, ELF64 loader, snapshots
```

## 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.
The root `task` module owns the bounded Task table, runnable ring, context
selection, and kernel/user execution-context state. `process` owns generation-
safe ProcessId identity, boot-lifetime PIDs, process lifecycle, AddressSpace
ownership, and process metadata. `arch::x86_64::memory` owns the kernel root,
inactive process-root construction, mapping audit, and CR3 activation.
`arch::x86_64::task` owns task-stack mapping, the saved frame layout, and the
timer-entry/trampoline assembly. Scheduler policy remains separate from
process policy, and the PIT hot path does not allocate or parse executable data.

* **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,
establishes an initial 4-level page table mapping and stack, collects the
boot memory map and framebuffer information into `BootInfo`, and transfers
control to `kernel_main`. pvKernel does not currently own or replace this
page table mapping.
* **pvKernel**: everything from `kernel_main` onward. This includes serial
diagnostics, the GDT/TSS, the IDT and its exception/IRQ handlers, the
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 and ELF userspace loading remain future roadmap items (see
[ROADMAP.md](ROADMAP.md)); pvKernel does not
currently support them.

* **rust-osdev bootloader**: loads the pvKernel ELF, exits UEFI boot services,
creates the initial 4-level mappings and stack, and passes `BootInfo`.
pvKernel audits required mappings and constrains dynamic boot mappings to
the upper canonical half before it can activate process CR3 roots.
* **Kernel Tasks**: bounded preemptive single-core ring-0 contexts with
dedicated supervisor-only guarded stacks.
* **Process Tasks**: one CPL3 Task per process in 0.0.9, each with an isolated
lower-half address space, dedicated USER RW/NX stack and supervisor kernel
entry stack. Timer IRQs can preempt CPL3 execution and switch CR3/RSP0.
* **Userspace ABI**: experimental `int 0x80` scalar `getpid` and terminal
`exit`. The checked static ELF64 loader accepts only validated `ET_EXEC`
`PT_LOAD` images. CPL3 #PF/#GP/#UD terminate the affected process and retain
scalar fault metadata. No stable ABI is promised before 0.1.0.
* **Future work**: VFS/initrd, `/bin/init`, FDs, fork/general exec, dynamic
linking, user heap/mmap, signals, IPC, general frame reclamation, storage,
networking, SMP, and GUI remain out of scope.
## Initialization order

1. `kernel_main` receives `BootInfo` from the bootloader.
Expand All @@ -72,9 +65,10 @@ shared across all kernel tasks.
(`x86_64::instructions::interrupts::disable()`).
2. The kernel-owned GDT and TSS are built and loaded
(`arch::x86_64::gdt`), including the dedicated double-fault IST stack,
ring-3 (CPL3) code/data segment descriptors, and a dedicated
TSS.RSP0 ring-transition kernel stack (see
[docs/architecture/userspace-entry.md](docs/architecture/userspace-entry.md)).
ring-3 (CPL3) code/data segment descriptors, and a kernel-lifetime
fallback TSS.RSP0 stack. User Task kernel-stack tops are installed
before returning to CPL3 (see
[docs/architecture/processes.md](docs/architecture/processes.md)).
3. The kernel-owned IDT is built and loaded (`arch::x86_64::interrupts`),
including handlers for all 16 remapped IRQ vectors (32-47) and the
`int 0x80` syscall gate (see
Expand Down Expand Up @@ -121,13 +115,17 @@ shared across all kernel tasks.
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.
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)).
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
13. The process subsystem audits required boot mappings, initializes its
bounded table, and can construct inactive process address spaces while
preserving audited supervisor-only kernel mappings.
14. Process-owned tasks use checked ELF loading, dedicated user and kernel
stacks, and timer-driven CPL3 preemption. On ordinary boots an embedded
static smoke ELF exercises CPL3 entry, `getpid`, and terminal `exit`;
QEMU features select deterministic process and legacy boundary scenarios.
15. The timer entry saves the complete interrupt context and can schedule
both CPL0 kernel tasks and process-owned CPL3 tasks, switching CR3 and
TSS.RSP0 according to the selected context.
16. 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 All @@ -152,10 +150,11 @@ items.
* a TSS descriptor, wrapping a kernel-owned `TaskStateSegment`;
* a dedicated double-fault stack (5 × 4 KiB), referenced by IST index
`DOUBLE_FAULT_IST_INDEX`;
* ring-3 (CPL3) code and data segment descriptors, and a dedicated,
writable TSS.RSP0 ring-transition kernel stack (4 × 4 KiB), used only
when entering CPL3 (see
[docs/architecture/userspace-entry.md](docs/architecture/userspace-entry.md)).
* ring-3 (CPL3) code and data segment descriptors, and a kernel-lifetime
fallback RSP0 stack for bootstrap/kernel execution. Before returning to
CPL3, the scheduler installs the selected user Task's guarded supervisor
kernel-stack top in TSS.RSP0 (see
[docs/architecture/processes.md](docs/architecture/processes.md)).

The GDT and TSS are stored in `spin::Once` cells with `'static` storage
duration, loaded once, and never replaced during the kernel's lifetime.
Expand All @@ -173,9 +172,9 @@ handler-level detail.

* `BootFrameAllocator` — a monotonic physical frame allocator built once
from the bootloader's memory map;
* `KernelAddressSpace` — the single active x86_64 page table (an
`OffsetPageTable` over the CR3-resident level-4 table, reached through the
bootloader's physical-memory offset mapping), paired with the frame
* the active kernel root (`KernelAddressSpace`) and process-owned inactive
`AddressSpace` roots. Page-table frames are reached through the audited
bootloader physical-memory offset mapping and paired with the frame
allocator.

Both are stored together behind a `spin::Mutex`, itself behind a
Expand All @@ -202,29 +201,34 @@ the heap for the kernel lifetime and are not reclaimed by `Box`, `Vec`, or
[docs/architecture/virtual-memory.md](docs/architecture/virtual-memory.md)
for the layout and safety invariants.

## Syscall and userspace entry ownership (0.0.6)
## Syscall and userspace entry ownership (0.0.9)

`arch::x86_64::syscall` owns a minimal, provisional, scalar-only `int 0x80`
syscall ABI: a naked-asm entry stub installed into the IDT's vector `0x80`,
a `SyscallFrame` register-frame layout built on the RSP0 stack, and a
Rust-side dispatcher. `arch::x86_64::userspace` owns the (test-only, behind
Cargo features) code that maps a hand-written payload into the reserved
userspace virtual-address range and transitions into it via `iretq`. See
`arch::x86_64::syscall` owns the experimental scalar `int 0x80` ABI: a
naked-asm entry stub at vector `0x80`, the `SyscallFrame` saved on the
selected user's supervisor RSP0 stack, and a Rust dispatcher for `getpid`
and terminal `exit`. The separate legacy `arch::x86_64::userspace` module
retains bootstrap CPL3 boundary-test payloads. Process-owned user Tasks use
the normal checked ELF loader and synthetic scheduler context; ordinary
boots run an embedded static smoke executable until VFS/initrd is available.
See
[docs/architecture/syscalls.md](docs/architecture/syscalls.md) and
[docs/architecture/userspace-entry.md](docs/architecture/userspace-entry.md)
for the full design.
[docs/architecture/processes.md](docs/architecture/processes.md) for the
current details.

## Fatal exception behavior

Breakpoint exceptions log the event and return normally. Invalid opcode,
general protection fault, page fault, and double fault are treated as fatal:
Breakpoint exceptions log the event and return normally. CPL0 invalid opcode,
general protection fault, page fault, and double fault remain kernel-fatal:
they log structured diagnostics (exception name, error code where relevant,
faulting address for page faults, the CPL the fault originated from, and the
interrupt stack frame) and then call `halt::halt_forever` directly (or,
under the `qemu-test` feature, exit QEMU with a failure status instead),
without invoking `panic!`. The exceptions are deliberately triggered faults
under the `fault-tests`, `userspace-privilege-fault-test`, and
`userspace-supervisor-fault-test` features: when one matches its expected
signature, it is treated as a passed test rather than a fatal condition (see
signature, it is treated as a passed test rather than a fatal condition.
CPL3 #UD/#GP/#PF from a process-owned Task instead capture scalar fault
metadata and use the terminal scheduler handoff to terminate that process;
the kernel remains active. See
[docs/development/testing.md](docs/development/testing.md) and
[docs/development/userspace-testing.md](docs/development/userspace-testing.md)).
[docs/development/userspace-testing.md](docs/development/userspace-testing.md).
31 changes: 31 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,36 @@
# Changelog

## [0.0.9] - Unreleased

### Added

* Generation-safe `ProcessId` handles distinct from monotonic boot-lifetime
PIDs, a bounded process table, checked parent references, and a Creating /
Alive / Exiting / Zombie lifecycle with internal reap.
* Per-process four-level address spaces built from audited upper-half kernel
mappings, inactive-root construction, and CR3-aware task switching.
* Dedicated 32 KiB user stacks with unmapped guards, supervisor-only guarded
kernel entry stacks per user Task, and checked TSS.RSP0 updates.
* True PIT preemption while CPL3 processes run, with a fixed scheduler/task
capacity and explicit terminal exit/fault handoffs.
* A checked static ELF64 little-endian x86_64 `ET_EXEC` loader for `PT_LOAD`
segments, with W^X enforcement and zeroed BSS/new user pages.
* Experimental `getpid` and non-returning `exit` syscalls plus central,
page-chunked `copy_from_user`/`copy_to_user` APIs.
* CPL3 #PF/#GP/#UD/#NM containment with scalar fault metadata, deferred Zombie
completion after stack handoff, process CPU/page accounting, and a bounded
process snapshot API.
* Embedded static userspace smoke execution on ordinary boots and deterministic
QEMU scenarios for process preemption, same-VA isolation, capacity, exit,
re-use, ELF validation, user-copy, guard faults, and fault containment.

Until per-task floating-point state is implemented, x87/legacy SIMD and AVX
are disabled, and inherited SYSCALL/SYSENTER entry targets are cleared. This
remains an experimental single-core userspace foundation. VFS, initrd,
`/bin/init`, file descriptors, fork/general exec, dynamic linking, signals,
IPC, user heap/mmap, general physical-frame reclamation, storage, networking,
SMP and GUI support are not included.

## [0.0.8] - 2026-10-24

### 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.

14 changes: 13 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.8"
version = "0.0.9"
license = "MIT OR Apache-2.0"
edition = "2024"
repository = "https://github.com/ultronstudio/pvkernel"
Expand Down Expand Up @@ -56,6 +56,18 @@ 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"]
process-preemption-test = ["kernel/process-preemption-test"]
process-smoke-test = ["kernel/process-smoke-test"]
process-exit-test = ["kernel/process-exit-test"]
process-id-reuse-test = ["kernel/process-id-reuse-test"]
user-copy-test = ["kernel/user-copy-test"]
user-fault-containment-test = ["kernel/user-fault-containment-test"]
address-space-isolation-test = ["kernel/address-space-isolation-test"]
process-capacity-test = ["kernel/process-capacity-test"]
user-stack-guard-fault-test = ["kernel/user-stack-guard-fault-test"]
user-kernel-isolation-test = ["kernel/user-kernel-isolation-test"]
elf-loader-test = ["kernel/elf-loader-test"]
elf-malformed-test = ["kernel/elf-malformed-test"]
task-stack-guard-fault-test = ["kernel/task-stack-guard-fault-test"]
task-capacity-test = ["kernel/task-capacity-test"]

Expand Down
Loading
Loading