From d87c7cc4bdfe2021affcca04ec3cffcc4d4d55ec Mon Sep 17 00:00:00 2001 From: wilkie Date: Sat, 27 Dec 2025 19:25:56 -0500 Subject: [PATCH] Add process management with address space switching - Implement Process structure representing a virtual address space - Add process table with PID allocation (max 64 processes) - Initialize new process page tables with kernel mappings copied - Implement address space switching via CR3 manipulation - Process 0 reserved for kernel, uses boot page table In XOmB's exokernel model, a process is fundamentally its page table. The kernel maintains minimal state - just enough to securely multiplex hardware resources via paging. --- src/lib.rs | 31 +++++ src/process/mod.rs | 323 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 354 insertions(+) create mode 100644 src/process/mod.rs diff --git a/src/lib.rs b/src/lib.rs index 53915e7..5105755 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -82,6 +82,7 @@ extern crate alloc; pub mod arch; pub mod boot_info; pub mod memory; +pub mod process; pub mod serial; #[cfg(feature = "multiboot2")] @@ -151,6 +152,36 @@ pub fn kernel_init(info: &BootInfo) -> ! { writeln!(serial, ">>> Initializing interrupt handling...").ok(); arch::x86_64::interrupts::init(); + // Initialize process subsystem + writeln!(serial, "").ok(); + writeln!(serial, ">>> Initializing process subsystem...").ok(); + process::init(); + writeln!(serial, " Process 0 (kernel) initialized").ok(); + writeln!(serial, " Active processes: {}", process::count()).ok(); + + // Test creating a new process and switching address spaces + writeln!(serial, "").ok(); + writeln!(serial, ">>> Testing process creation...").ok(); + match process::create() { + Ok(pid) => { + writeln!(serial, " Created process {} (page table: {:#x})", + pid, process::get(pid).unwrap().page_table).ok(); + + // Test address space switch + unsafe { + if process::switch_address_space(pid).is_ok() { + writeln!(serial, " Switched to process {} address space", pid).ok(); + process::switch_to_kernel(); + writeln!(serial, " Returned to kernel address space").ok(); + } + } + writeln!(serial, " Active processes: {}", process::count()).ok(); + } + Err(e) => { + writeln!(serial, " Failed to create process: {:?}", e).ok(); + } + } + // Test allocating a few frames writeln!(serial, "").ok(); writeln!(serial, ">>> Testing frame allocator...").ok(); diff --git a/src/process/mod.rs b/src/process/mod.rs new file mode 100644 index 0000000..19532e8 --- /dev/null +++ b/src/process/mod.rs @@ -0,0 +1,323 @@ +//! Process management for XOmB exokernel +//! +//! In XOmB, a process is fundamentally a virtual address space represented +//! by a page table. The kernel maintains minimal state - just enough to +//! securely multiplex hardware resources via paging. +//! +//! Each process owns its page table structure and can map resources into +//! its address space via kernel primitives. + +use crate::memory::{PhysAddr, VirtAddr}; +use crate::memory::frame::allocate_frame; +use crate::memory::paging::{self, flags}; + +/// Maximum number of processes supported +pub const MAX_PROCESSES: usize = 64; + +/// Process ID type +pub type Pid = u16; + +/// Process states +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ProcessState { + /// Process slot is unused + Free, + /// Process is being created + Creating, + /// Process is ready to run + Ready, + /// Process is currently running + Running, + /// Process has exited + Exited, +} + +/// A process in XOmB +/// +/// In the exokernel model, a process is primarily its virtual address space. +/// The page table (PML4) is the fundamental data structure representing a process. +#[derive(Debug)] +pub struct Process { + /// Process ID + pub pid: Pid, + /// Current state + pub state: ProcessState, + /// Physical address of the PML4 (page table root) + /// This is what gets loaded into CR3 to switch to this process + pub page_table: PhysAddr, + /// Saved stack pointer (for context switching) + pub rsp: u64, + /// Saved instruction pointer (for context switching) + pub rip: u64, + /// Saved RFLAGS + pub rflags: u64, +} + +impl Process { + /// Create an empty/free process slot + pub const fn empty() -> Self { + Self { + pid: 0, + state: ProcessState::Free, + page_table: PhysAddr::new(0), + rsp: 0, + rip: 0, + rflags: 0, + } + } + + /// Check if this process slot is free + pub fn is_free(&self) -> bool { + self.state == ProcessState::Free + } +} + +/// The process table +/// +/// This is a simple static array of process slots. Process 0 is reserved +/// for the kernel. +pub struct ProcessTable { + processes: [Process; MAX_PROCESSES], + /// Number of active processes + count: usize, +} + +impl ProcessTable { + /// Create a new empty process table + pub const fn new() -> Self { + Self { + processes: [const { Process::empty() }; MAX_PROCESSES], + count: 0, + } + } + + /// Allocate a new process ID + fn allocate_pid(&mut self) -> Option { + // Find first free slot (skip 0, reserved for kernel) + for i in 1..MAX_PROCESSES { + if self.processes[i].is_free() { + return Some(i as Pid); + } + } + None + } + + /// Get a process by PID + pub fn get(&self, pid: Pid) -> Option<&Process> { + let idx = pid as usize; + if idx < MAX_PROCESSES && !self.processes[idx].is_free() { + Some(&self.processes[idx]) + } else { + None + } + } + + /// Get a mutable reference to a process by PID + pub fn get_mut(&mut self, pid: Pid) -> Option<&mut Process> { + let idx = pid as usize; + if idx < MAX_PROCESSES && !self.processes[idx].is_free() { + Some(&mut self.processes[idx]) + } else { + None + } + } + + /// Create a new process with its own address space + /// + /// Returns the PID of the new process, or None if creation failed. + pub fn create(&mut self) -> Result { + // Allocate a PID + let pid = self.allocate_pid().ok_or(ProcessError::TooManyProcesses)?; + + // Allocate a frame for the new PML4 + let pml4_frame = allocate_frame().map_err(|_| ProcessError::OutOfMemory)?; + let pml4_phys = pml4_frame.start_address(); + + // Initialize the new page table with kernel mappings + init_page_table(pml4_phys)?; + + // Note: Frame is Copy and doesn't have a destructor, so it stays + // allocated. The frame is now owned by the process's page table. + + // Initialize the process + let process = &mut self.processes[pid as usize]; + process.pid = pid; + process.state = ProcessState::Creating; + process.page_table = pml4_phys; + process.rsp = 0; + process.rip = 0; + process.rflags = 0x200; // Interrupts enabled + + self.count += 1; + + Ok(pid) + } + + /// Mark a process as ready to run + pub fn set_ready(&mut self, pid: Pid) -> Result<(), ProcessError> { + let process = self.get_mut(pid).ok_or(ProcessError::InvalidPid)?; + if process.state != ProcessState::Creating { + return Err(ProcessError::InvalidState); + } + process.state = ProcessState::Ready; + Ok(()) + } + + /// Mark a process as running + pub fn set_running(&mut self, pid: Pid) -> Result<(), ProcessError> { + let process = self.get_mut(pid).ok_or(ProcessError::InvalidPid)?; + if process.state != ProcessState::Ready { + return Err(ProcessError::InvalidState); + } + process.state = ProcessState::Running; + Ok(()) + } + + /// Get the number of active processes + pub fn count(&self) -> usize { + self.count + } +} + +/// Process-related errors +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ProcessError { + /// Too many processes already exist + TooManyProcesses, + /// Out of memory for process structures + OutOfMemory, + /// Invalid process ID + InvalidPid, + /// Invalid state transition + InvalidState, + /// Page table initialization failed + PageTableError, +} + +/// Temporary virtual address for mapping new page tables during setup +/// Uses an address in the kernel temporary mapping region (PML4[509]) +const TEMP_MAP_ADDR: u64 = 0xFFFFFE8000001000; + +/// Initialize a new process's page table +/// +/// This maps the new PML4 temporarily, clears user-space entries, +/// and copies kernel-space entries from the current page table. +fn init_page_table(pml4_phys: PhysAddr) -> Result<(), ProcessError> { + let temp_virt = VirtAddr::new(TEMP_MAP_ADDR); + + // Map the new PML4 at our temporary address + paging::map_4kb(temp_virt, pml4_phys, flags::KERNEL_DATA) + .map_err(|_| ProcessError::PageTableError)?; + + // Access the new PML4 through the temporary mapping + let pml4_ptr = temp_virt.as_u64() as *mut u64; + + unsafe { + // Clear user-space entries (0-255) + for i in 0..256 { + core::ptr::write_volatile(pml4_ptr.add(i), 0); + } + + // Copy kernel-space entries (256-511) from current PML4 + // These include the recursive mapping (510) and kernel mapping (511) + for i in 256..512 { + let entry = paging::read_pml4(i); + core::ptr::write_volatile(pml4_ptr.add(i), entry.bits()); + } + } + + // Unmap the temporary mapping (don't free the frame - it's the new page table!) + // We need to manually clear the mapping without freeing + paging::unmap_4kb(temp_virt).map_err(|_| ProcessError::PageTableError)?; + + Ok(()) +} + +// Global process table +use core::cell::UnsafeCell; + +struct SyncProcessTable(UnsafeCell); +unsafe impl Sync for SyncProcessTable {} + +static PROCESS_TABLE: SyncProcessTable = SyncProcessTable(UnsafeCell::new(ProcessTable::new())); + +/// Initialize the process subsystem +/// +/// Sets up process 0 as the kernel process using the current page table. +pub fn init() { + let table = unsafe { &mut *PROCESS_TABLE.0.get() }; + + // Process 0 is the kernel - it uses the current page table + let kernel_process = &mut table.processes[0]; + kernel_process.pid = 0; + kernel_process.state = ProcessState::Running; + + // Get current CR3 as the kernel's page table + let cr3: u64; + unsafe { + core::arch::asm!("mov {}, cr3", out(reg) cr3, options(nostack, preserves_flags)); + } + kernel_process.page_table = PhysAddr::new(cr3 & !0xFFF); // Mask off flags + + table.count = 1; +} + +/// Create a new process +pub fn create() -> Result { + let table = unsafe { &mut *PROCESS_TABLE.0.get() }; + table.create() +} + +/// Get a process by PID +pub fn get(pid: Pid) -> Option<&'static Process> { + let table = unsafe { &*PROCESS_TABLE.0.get() }; + table.get(pid) +} + +/// Get the current process count +pub fn count() -> usize { + let table = unsafe { &*PROCESS_TABLE.0.get() }; + table.count() +} + +/// Get the current running process (for now, always kernel) +pub fn current() -> &'static Process { + let table = unsafe { &*PROCESS_TABLE.0.get() }; + &table.processes[0] +} + +/// Switch to a process's address space by loading its page table +/// +/// # Safety +/// The process must have a valid, properly initialized page table. +/// The kernel mappings must be present in the new page table. +pub unsafe fn switch_address_space(pid: Pid) -> Result<(), ProcessError> { + let table = unsafe { &*PROCESS_TABLE.0.get() }; + let process = table.get(pid).ok_or(ProcessError::InvalidPid)?; + + // Load the new page table into CR3 + let new_cr3 = process.page_table.as_u64(); + unsafe { + core::arch::asm!( + "mov cr3, {}", + in(reg) new_cr3, + options(nostack, preserves_flags) + ); + } + + Ok(()) +} + +/// Switch back to the kernel's address space +pub fn switch_to_kernel() { + let table = unsafe { &*PROCESS_TABLE.0.get() }; + let kernel = &table.processes[0]; + + unsafe { + core::arch::asm!( + "mov cr3, {}", + in(reg) kernel.page_table.as_u64(), + options(nostack, preserves_flags) + ); + } +}