579 lines
17 KiB
Rust
579 lines
17 KiB
Rust
#![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<Process, { crate::NPROC }> = 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<usize> = 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::<Context>(),
|
|
);
|
|
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<Pagetable, ProcessError> {
|
|
// 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<i32, ProcessError> {
|
|
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<i32, ProcessError> {
|
|
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::<i32>(),
|
|
) < 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);
|
|
}
|
|
}
|
|
}
|