yggdrasil/src/task/process.rs

295 lines
8.9 KiB
Rust
Raw Normal View History

2023-07-18 18:03:45 +03:00
//! Process data structures
use core::sync::atomic::{AtomicU32, Ordering};
use alloc::rc::Rc;
use atomic_enum::atomic_enum;
use crate::{
arch::aarch64::{context::TaskContext, cpu::Cpu},
mem::table::AddressSpace,
proc::{
io::ProcessIo,
2023-07-25 10:49:11 +03:00
wait::{Wait, WaitStatus, PROCESS_EXIT_WAIT},
2023-07-18 18:03:45 +03:00
},
sync::{IrqGuard, IrqSafeSpinlock},
util::OneTimeInit,
};
use super::{sched::CpuQueue, ProcessId, PROCESSES};
/// Represents the states a process can be at some point in time
#[atomic_enum]
#[derive(PartialEq)]
pub enum ProcessState {
/// Process is ready for execution and is present in some CPU's queue
Ready,
/// Process is currently being executed by some CPU
Running,
/// Process is present in a global list, but is not queued for execution until it is resumed
Suspended,
/// Process is terminated and waits to be reaped
Terminated,
}
struct ProcessInner {
pending_wait: Option<&'static Wait>,
wait_status: WaitStatus,
2023-07-25 10:49:11 +03:00
exit_status: i32,
2023-07-18 18:03:45 +03:00
}
/// Process data and state structure
pub struct Process {
context: TaskContext,
// Process state info
id: OneTimeInit<ProcessId>,
state: AtomicProcessState,
cpu_id: AtomicU32,
inner: IrqSafeSpinlock<ProcessInner>,
space: Option<AddressSpace>,
/// I/O state of the task
pub io: IrqSafeSpinlock<ProcessIo>,
}
impl Process {
/// Creates a process from raw architecture-specific [TaskContext].
///
/// # Note
///
/// Has side-effect of allocating a new PID for itself.
pub fn new_with_context(space: Option<AddressSpace>, context: TaskContext) -> Rc<Self> {
let this = Rc::new(Self {
context,
id: OneTimeInit::new(),
state: AtomicProcessState::new(ProcessState::Suspended),
cpu_id: AtomicU32::new(0),
inner: IrqSafeSpinlock::new(ProcessInner {
pending_wait: None,
wait_status: WaitStatus::Done,
2023-07-25 10:49:11 +03:00
exit_status: 0,
2023-07-18 18:03:45 +03:00
}),
space,
io: IrqSafeSpinlock::new(ProcessIo::new()),
});
let id = unsafe { PROCESSES.lock().push(this.clone()) };
this.id.init(id);
this
}
/// Returns a reference to the inner architecture-specific [TaskContext].
pub fn context(&self) -> &TaskContext {
&self.context
}
/// Returns this process' ID
pub fn id(&self) -> ProcessId {
*self.id.get()
}
/// Returns the state of the process.
///
/// # Note
///
/// Maybe I should remove this and make ALL state changes atomic.
pub fn state(&self) -> ProcessState {
self.state.load(Ordering::Acquire)
}
/// Atomically updates the state of the process and returns the previous one.
pub fn set_state(&self, state: ProcessState) -> ProcessState {
self.state.swap(state, Ordering::SeqCst)
}
/// Marks the task as running on the specified CPU.
///
/// # Safety
///
/// Only meant to be called from scheduler routines.
pub unsafe fn set_running(&self, cpu: u32) {
self.cpu_id.store(cpu, Ordering::Release);
self.state.store(ProcessState::Running, Ordering::Release);
}
/// Returns the address space of the task
pub fn address_space(&self) -> &AddressSpace {
self.space.as_ref().unwrap()
}
2023-07-25 10:49:11 +03:00
/// Returns the address space of the task, if one is set
pub fn get_address_space(&self) -> Option<&AddressSpace> {
self.space.as_ref()
}
2023-07-18 18:03:45 +03:00
/// Selects a suitable CPU queue and submits the process for execution.
///
/// # Panics
///
/// Currently, the code will panic if the process is queued/executing on any queue.
pub fn enqueue_somewhere(self: Rc<Self>) -> usize {
// Doesn't have to be precise, so even if something changes, we can still be rebalanced
// to another CPU
let (index, queue) = CpuQueue::least_loaded().unwrap();
self.enqueue_to(queue);
index
}
/// Submits the process to a specific queue.
///
/// # Panics
///
/// Currently, the code will panic if the process is queued/executing on any queue.
pub fn enqueue_to(self: Rc<Self>, queue: &CpuQueue) {
let current_state = self.state.swap(ProcessState::Ready, Ordering::SeqCst);
if current_state != ProcessState::Suspended {
todo!("Handle attempt to enqueue an already queued/running/terminated process");
}
unsafe {
queue.enqueue(self);
}
}
/// Marks the process as suspended, blocking it from being run until it's resumed.
///
/// # Note
///
/// The process may not halt its execution immediately when this function is called, only when
/// this function is called targeting the *current process* running on *local* CPU.
///
/// # TODO
///
/// The code currently does not allow suspension of active processes on either local or other
/// CPUs.
pub fn suspend(&self) {
let _irq = IrqGuard::acquire();
let current_state = self.state.swap(ProcessState::Suspended, Ordering::SeqCst);
match current_state {
// NOTE: I'm not sure if the process could've been queued between the store and this
// but most likely not (if I'm not that bad with atomics)
// Do nothing, its queue will just drop the process
ProcessState::Ready => (),
// Do nothing, not in a queue already
ProcessState::Suspended => (),
ProcessState::Terminated => panic!("Process is terminated"),
ProcessState::Running => {
let cpu_id = self.cpu_id.load(Ordering::Acquire);
let local_cpu_id = Cpu::local_id();
let queue = Cpu::local().queue();
if cpu_id == local_cpu_id {
// Suspending a process running on local CPU
unsafe { queue.yield_cpu() }
} else {
todo!();
}
}
}
}
/// Sets up a pending wait for the process.
///
/// # Safety
///
/// This function is only meant to be called in no-IRQ context and when caller can guarantee
/// the task won't get scheduled to a CPU in such state.
pub unsafe fn setup_wait(&self, wait: &'static Wait) {
let mut inner = self.inner.lock();
inner.pending_wait.replace(wait);
inner.wait_status = WaitStatus::Pending;
}
/// Returns current wait status of the task
pub fn wait_status(&self) -> WaitStatus {
self.inner.lock().wait_status
}
/// Updates the wait status for the task.
///
/// # Safety
///
/// This function is only meant to be called on waiting tasks, otherwise atomicity is not
/// guaranteed.
pub unsafe fn set_wait_status(&self, status: WaitStatus) {
self.inner.lock().wait_status = status;
}
2023-07-25 10:49:11 +03:00
pub fn get_exit_status(&self) -> Option<i32> {
if self.state() == ProcessState::Terminated {
Some(self.inner.lock().exit_status)
} else {
None
}
}
2023-07-18 18:03:45 +03:00
/// Returns the [Process] currently executing on local CPU, None if idling.
pub fn get_current() -> Option<Rc<Self>> {
let queue = Cpu::local().queue();
queue.current_process()
}
2023-07-25 10:49:11 +03:00
pub fn get(pid: ProcessId) -> Option<Rc<Self>> {
PROCESSES.lock().get(pid).cloned()
}
2023-07-18 18:03:45 +03:00
/// Wraps [Process::get_current()] for cases when the caller is absolutely sure there is a
/// running process (e.g. the call itself comes from a process).
pub fn current() -> Rc<Self> {
Self::get_current().unwrap()
}
2023-07-25 10:49:11 +03:00
pub fn handle_exit(&self) {
// Queue lock is still held
assert_eq!(self.state(), ProcessState::Terminated);
// TODO cancel Wait if a process was killed while suspended?
{
let inner = self.inner.lock();
debugln!(
"Handling exit of #{} with status {}",
self.id(),
inner.exit_status
);
// TODO cleanup address space
// if let Some(space) = self.get_address_space() {
// }
self.io.lock().handle_exit();
}
// Notify any waiters we're done
PROCESS_EXIT_WAIT.wakeup_all();
}
2023-07-18 18:03:45 +03:00
/// Terminate a process
2023-07-25 10:49:11 +03:00
pub fn exit(&self, status: i32) {
self.inner.lock().exit_status = status;
2023-07-18 18:03:45 +03:00
let current_state = self.state.swap(ProcessState::Terminated, Ordering::SeqCst);
debugln!("Process {} exited with code {}", self.id(), status);
match current_state {
2023-07-25 10:49:11 +03:00
ProcessState::Suspended => {
todo!();
}
2023-07-18 18:03:45 +03:00
ProcessState::Ready => todo!(),
2023-07-25 10:49:11 +03:00
ProcessState::Running => {
self.handle_exit();
unsafe { Cpu::local().queue().yield_cpu() }
}
2023-07-18 18:03:45 +03:00
ProcessState::Terminated => todo!(),
}
}
}
impl Drop for Process {
fn drop(&mut self) {
infoln!("Drop process!");
}
}