#![allow(clippy::comparison_chain)] use super::{ context::Context, cpu::Cpu, scheduler::{sched, wakeup}, trapframe::Trapframe, }; use crate::{ arch::{ mem::{kstack, Pagetable, PAGE_SIZE, PTE_R, PTE_W, PTE_X, TRAMPOLINE, TRAPFRAME}, trap::{usertrapret, InterruptBlocker}, virtual_memory::{ copyout, mappages, uvmalloc, uvmcopy, uvmcreate, uvmdealloc, uvmfirst, uvmfree, uvmunmap, }, }, fs::{ file::{fileclose, filedup, File}, fsinit, inode::{idup, iput, namei, Inode}, log::LogOperation, FS_INITIALIZED, }, mem::{ kalloc::{kalloc, kfree}, memset, }, sync::spinlock::Spinlock, uprintln, }; use arrayvec::ArrayVec; use core::{ ffi::{c_char, c_void, CStr}, ptr::{addr_of, addr_of_mut, null_mut}, sync::atomic::{AtomicI32, Ordering}, }; extern "C" { // trampoline.S pub static mut trampoline: *mut c_char; } pub static NEXT_PID: AtomicI32 = AtomicI32::new(1); /// Helps ensure that wakeups of wait()ing /// parents are not lost. Helps obey the /// memory model when using p->parent. /// Must be acquired before any p->lock. pub static mut WAIT_LOCK: Spinlock = Spinlock::new(); pub static mut INITPROC: usize = 0; pub static mut PROCESSES: ArrayVec = ArrayVec::new_const(); /// Initialize the proc table. pub unsafe fn procinit() { let mut i = 0; let processes_iter = core::iter::repeat_with(|| { let mut p = Process::new(); p.state = ProcessState::Unused; p.kernel_stack = kstack(i) as u64; i += 1; p }); PROCESSES = processes_iter.take(crate::NPROC).collect(); } /// Set up the first user process. pub unsafe fn userinit() { let p = Process::alloc().unwrap(); INITPROC = addr_of_mut!(*p) as usize; let initcode: &[u8] = &[ 0x17, 0x05, 0x00, 0x00, 0x13, 0x05, 0x45, 0x02, 0x97, 0x05, 0x00, 0x00, 0x93, 0x85, 0x35, 0x02, 0x93, 0x08, 0x70, 0x00, 0x73, 0x00, 0x00, 0x00, 0x93, 0x08, 0x20, 0x00, 0x73, 0x00, 0x00, 0x00, 0xef, 0xf0, 0x9f, 0xff, 0x2f, 0x69, 0x6e, 0x69, 0x74, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; // Allocate one user page and copy initcode's // instructions and data into it. uvmfirst(p.pagetable, initcode.as_ptr().cast_mut(), initcode.len()); p.memory_allocated = PAGE_SIZE as u64; // Prepare for the very first "return" from kernel to user. // User program counter (*p.trapframe).epc = 0; // User stack pointer (*p.trapframe).sp = PAGE_SIZE as u64; p.current_dir = namei( CStr::from_bytes_with_nul(b"/\0") .unwrap() .as_ptr() .cast_mut() .cast(), ); p.state = ProcessState::Runnable; p.lock.unlock(); } #[repr(C)] #[derive(Copy, Clone, Debug, Default, PartialEq)] pub enum ProcessState { #[default] Unused, Used, Sleeping, Runnable, Running, Zombie, } #[derive(Copy, Clone, Debug, PartialEq)] pub enum ProcessError { MaxProcesses, Allocation, NoChildren, Killed, PageError, } /// Per-process state. #[repr(C)] #[derive(Clone)] pub struct Process { pub lock: Spinlock, // p->lock must be held when using these: /// Process state pub state: ProcessState, /// If non-zero, sleeping on chan pub chan: *mut c_void, /// If non-zero, have been killed pub killed: i32, /// Exit status to be returned to parent's wait pub exit_status: i32, /// Process ID pub pid: i32, // WAIT_LOCK must be held when using this: /// Parent process pub parent: *mut Process, // These are private to the process, so p->lock need not be held. /// Virtual address of kernel stack pub kernel_stack: u64, /// Size of process memory (bytes) pub memory_allocated: u64, /// User page table pub pagetable: Pagetable, /// Data page for trampoline.S pub trapframe: *mut Trapframe, /// swtch() here to run process pub context: Context, /// Open files pub open_files: [*mut File; crate::NOFILE], /// Current directory pub current_dir: *mut Inode, } impl Process { pub const fn new() -> Process { Process { lock: Spinlock::new(), state: ProcessState::Unused, chan: null_mut(), killed: 0, exit_status: 0, pid: 0, parent: null_mut(), kernel_stack: 0, memory_allocated: 0, pagetable: null_mut(), trapframe: null_mut(), context: Context::new(), open_files: [null_mut(); crate::NOFILE], current_dir: null_mut(), } } pub fn current() -> Option<&'static mut Process> { let _ = InterruptBlocker::new(); let p = Cpu::current().proc; if p.is_null() { None } else { unsafe { Some(&mut *p) } } } pub fn is_current(&self) -> bool { addr_of!(*self).cast_mut() == Cpu::current().proc } pub fn is_initproc(&self) -> bool { addr_of!(*self) as usize == unsafe { INITPROC } } pub fn alloc_pid() -> i32 { NEXT_PID.fetch_add(1, Ordering::SeqCst) } /// Look in the process table for an UNUSED proc. /// If found, initialize state required to run in the kernel, /// and return with p.lock held. /// If there are no free procs, or a memory allocation fails, return an error. pub unsafe fn alloc() -> Result<&'static mut Process, ProcessError> { let mut index: Option = None; for (i, p) in PROCESSES.iter_mut().enumerate() { p.lock.lock_unguarded(); if p.state == ProcessState::Unused { index = Some(i); break; } else { p.lock.unlock(); } } let Some(index) = index else { return Err(ProcessError::MaxProcesses); }; let p: &mut Process = &mut PROCESSES[index]; p.pid = Process::alloc_pid(); p.state = ProcessState::Used; // Allocate a trapframe page. p.trapframe = kalloc() as *mut Trapframe; if p.trapframe.is_null() { p.free(); p.lock.unlock(); return Err(ProcessError::Allocation); } // An empty user page table. p.pagetable = proc_pagetable(addr_of_mut!(*p)); if p.pagetable.is_null() { p.free(); p.lock.unlock(); return Err(ProcessError::Allocation); } // Set up new context to start executing at forkret, // which returns to userspace. memset( addr_of_mut!(p.context).cast(), 0, core::mem::size_of::(), ); p.context.ra = Process::forkret as usize as u64; p.context.sp = p.kernel_stack + PAGE_SIZE as u64; Ok(p) } /// Free a proc structure and the data hanging from it, including user pages. /// self.lock must be held. pub unsafe fn free(&mut self) { if !self.trapframe.is_null() { kfree(self.trapframe.cast()); } self.trapframe = null_mut(); if !self.pagetable.is_null() { proc_freepagetable(self.pagetable, self.memory_allocated); } self.pagetable = null_mut(); self.memory_allocated = 0; self.pid = 0; self.parent = null_mut(); self.chan = null_mut(); self.killed = 0; self.exit_status = 0; self.state = ProcessState::Unused; } /// Grow or shrink user memory. pub unsafe fn grow_memory(&mut self, num_bytes: i32) -> Result<(), ProcessError> { let mut size = self.memory_allocated; if num_bytes > 0 { size = uvmalloc( self.pagetable, size as usize, size.wrapping_add(num_bytes as u64) as usize, PTE_W, ); if size == 0 { return Err(ProcessError::Allocation); } } else if num_bytes < 0 { size = uvmdealloc( self.pagetable, size as usize, size.wrapping_add(num_bytes as u64) as usize, ); } self.memory_allocated = size; Ok(()) } /// Create a user page table for a given process, /// with no user memory, but with trampoline and trapframe pages. pub unsafe fn alloc_pagetable(&mut self) -> Result { // Create an empty page table. let pagetable: Pagetable = uvmcreate(); if pagetable.is_null() { return Err(ProcessError::Allocation); } // Map the trampoline code (for syscall return) // at the highest user virtual address. // Only the supervisor uses it on the way // to and from user space, so not PTE_U. if mappages( pagetable, TRAMPOLINE, PAGE_SIZE, addr_of!(trampoline) as usize, PTE_R | PTE_X, ) < 0 { uvmfree(pagetable, 0); return Err(ProcessError::Allocation); } // Map the trapframe page just below the trampoline page for trampoline.S. if mappages( pagetable, TRAPFRAME, PAGE_SIZE, self.trapframe as usize, PTE_R | PTE_W, ) < 0 { uvmunmap(pagetable, TRAMPOLINE, 1, false); uvmfree(pagetable, 0); return Err(ProcessError::Allocation); } Ok(pagetable) } /// Free a process's pagetable and free the physical memory it refers to. pub unsafe fn free_pagetable(pagetable: Pagetable, size: usize) { uvmunmap(pagetable, TRAMPOLINE, 1, false); uvmunmap(pagetable, TRAPFRAME, 1, false); uvmfree(pagetable, size) } /// Create a new process, copying the parent. /// Sets up child kernel stack to return as if from fork() syscall. pub unsafe fn fork() -> Result { let parent = Process::current().unwrap(); let child = Process::alloc()?; // Copy user memory from parent to child. if uvmcopy( parent.pagetable, child.pagetable, parent.memory_allocated as usize, ) < 0 { child.free(); child.lock.unlock(); return Err(ProcessError::Allocation); } child.memory_allocated = parent.memory_allocated; // Copy saved user registers. *child.trapframe = *parent.trapframe; // Cause fork to return 0 in the child. (*child.trapframe).a0 = 0; // Increment reference counts on open file descriptors. for (i, file) in parent.open_files.iter().enumerate() { if !file.is_null() { child.open_files[i] = filedup(parent.open_files[i]); } } child.current_dir = idup(parent.current_dir); let pid = child.pid; child.lock.unlock(); { let _guard = WAIT_LOCK.lock(); child.parent = addr_of!(*parent).cast_mut(); } { let _guard = child.lock.lock(); child.state = ProcessState::Runnable; } Ok(pid) } /// A fork child's very first scheduling by /// scheduler() will swtch to forkret. pub unsafe fn forkret() -> ! { // Still holding p->lock from scheduler. Process::current().unwrap().lock.unlock(); if !FS_INITIALIZED { // File system initialization must be run in the context of a // regular process (e.g., because it calls sleep), and thus // cannot be run from main(). FS_INITIALIZED = true; fsinit(crate::ROOTDEV as i32); } usertrapret() } /// Pass p's abandoned children to init. /// Caller must hold WAIT_LOCK. pub unsafe fn reparent(&self) { for p in PROCESSES.iter_mut() { if p.parent == addr_of!(*self).cast_mut() { p.parent = INITPROC as *mut Process; wakeup((INITPROC as *mut Process).cast()); } } } /// Exit the current process. Does not return. /// An exited process remains in the zombie state /// until its parent calls wait(). pub unsafe fn exit(&mut self, status: i32) -> ! { if self.is_initproc() { panic!("init exiting"); } // Close all open files. for file in self.open_files.iter_mut() { if !file.is_null() { fileclose(*file); *file = null_mut(); } } { let _operation = LogOperation::new(); iput(self.current_dir); } self.current_dir = null_mut(); { let _guard = WAIT_LOCK.lock(); // Give any children to init. self.reparent(); // Parent might be sleeping in wait(). wakeup(self.parent.cast()); self.lock.lock_unguarded(); self.exit_status = status; self.state = ProcessState::Zombie; } // Jump into the scheduler, never to return. sched(); unreachable!(); } /// Wait for a child process to exit, and return its pid. pub unsafe fn wait_for_child(&mut self, addr: u64) -> Result { let guard = WAIT_LOCK.lock(); loop { // Scan through the table looking for exited children. let mut has_children = false; for p in PROCESSES.iter_mut() { if p.parent == addr_of_mut!(*self) { has_children = true; // Ensure the child isn't still in exit() or swtch(). p.lock.lock_unguarded(); if p.state == ProcessState::Zombie { // Found an exited child. let pid = p.pid; if addr != 0 && copyout( self.pagetable, addr as usize, addr_of_mut!(p.exit_status).cast(), core::mem::size_of::(), ) < 0 { p.lock.unlock(); return Err(ProcessError::PageError); } p.free(); p.lock.unlock(); return Ok(pid); } p.lock.unlock(); } } if !has_children { return Err(ProcessError::NoChildren); } else if self.is_killed() { return Err(ProcessError::Killed); } // Wait for child to exit. // DOC: wait-sleep guard.sleep(addr_of_mut!(*self).cast()); } } /// Kill the process with the given pid. /// Returns true if the process was killed. /// The victim won't exit until it tries to return /// to user space (see usertrap() in trap.c). pub unsafe fn kill(pid: i32) -> bool { for p in PROCESSES.iter_mut() { let _guard = p.lock.lock(); if p.pid == pid { p.killed = 1; if p.state == ProcessState::Sleeping { // Wake process from sleep(). p.state = ProcessState::Runnable; } return true; } } false } pub fn is_killed(&self) -> bool { let _guard = self.lock.lock(); self.killed > 0 } pub fn set_killed(&mut self, killed: bool) { let _guard = self.lock.lock(); if killed { self.killed = 1; } else { self.killed = 0; } } } /// Return the current struct proc *, or zero if none. #[no_mangle] pub extern "C" fn myproc() -> *mut Process { if let Some(p) = Process::current() { p as *mut Process } else { null_mut() } } #[no_mangle] pub unsafe extern "C" fn proc_pagetable(p: *mut Process) -> Pagetable { (*p).alloc_pagetable().unwrap_or(null_mut()) } #[no_mangle] pub unsafe extern "C" fn proc_freepagetable(pagetable: Pagetable, size: u64) { Process::free_pagetable(pagetable, size as usize) } /// Print a process listing to console for debugging. /// Runs when a user types ^P on console. /// No lock to avoid wedging a stuck machine further. pub unsafe fn procdump() { uprintln!("\nprocdump:"); for p in PROCESSES.iter() { if p.state != ProcessState::Unused { uprintln!(" {}: {:?}", p.pid, p.state); } } }