//! Process data structures use core::{ fmt, mem::size_of, pin::Pin, sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering}, task::{Context, Poll}, }; use abi::{ error::Error, process::{ExitCode, Signal, ThreadSpawnOptions}, }; use alloc::{ collections::{BTreeMap, VecDeque}, string::String, sync::Arc, vec::Vec, }; use futures_util::Future; use kernel_util::util::OneTimeInit; use vfs::VnodeRef; use crate::{ mem::{ phys, pointer::{PhysicalRef, PhysicalRefMut}, process::ProcessAddressSpace, table::MapAttributes, }, proc::{self, io::ProcessIo}, sync::IrqSafeSpinlock, task::context::TaskContextImpl, }; use super::{ runtime::QueueWaker, sync::UserspaceMutex, thread::{Thread, ThreadId, ThreadState}, TaskContext, }; #[derive(PartialEq)] pub enum ProcessState { Running, Terminated(ExitCode), } #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] #[repr(transparent)] pub struct ProcessId(u64); // TLS layout (x86-64): // | mem_size | uthread_size | // | Data .......| self, ??? | // // TLS layout (aarch64): // | uthread_size (0x10?) | mem_size | // | ??? | Data .....| #[derive(Debug)] pub struct ProcessTlsInfo { pub master_copy_base: usize, pub layout: ProcessTlsLayout, } #[derive(Debug)] pub struct ProcessTlsLayout { pub data_offset: usize, pub uthread_offset: usize, pub ptr_offset: usize, pub data_size: usize, pub mem_size: usize, pub full_size: usize, } #[cfg(target_arch = "aarch64")] impl ProcessTlsLayout { pub fn new(align: usize, data_size: usize, mem_size: usize) -> Self { debug_assert!(align.is_power_of_two()); let tls_block0_offset = (size_of::() * 2 + align - 1) & !(align - 1); let full_size = (tls_block0_offset + mem_size + align - 1) & !(align - 1); Self { data_offset: tls_block0_offset, uthread_offset: 0, ptr_offset: 0, data_size, mem_size, full_size, } } } #[cfg(target_arch = "x86_64")] impl ProcessTlsLayout { pub fn new(align: usize, data_size: usize, mem_size: usize) -> Self { // The static TLS blocks are placed below TP // TP points to the TCB debug_assert!(align.is_power_of_two()); let back_size = (mem_size + align - 1) & !(align - 1); // Self-pointer let forward_size = size_of::(); let full_size = back_size + forward_size; Self { data_offset: 0, uthread_offset: back_size, ptr_offset: back_size, data_size, mem_size, full_size, } } } pub struct ProcessImage { pub entry: usize, pub tls: Option, } struct ProcessInner { state: ProcessState, session_id: ProcessId, group_id: ProcessId, session_terminal: Option, threads: Vec>, mutexes: BTreeMap>, } pub struct Process { name: String, id: ProcessId, space: Arc, inner: IrqSafeSpinlock, image: Option, exit_waker: QueueWaker, pub io: IrqSafeSpinlock, } static PROCESSES: IrqSafeSpinlock>> = IrqSafeSpinlock::new(BTreeMap::new()); impl Process { pub fn new_with_main>( name: S, space: Arc, context: TaskContext, image: Option, ) -> (Arc, Arc) { let name = name.into(); let id = ProcessId::next(); let process = Arc::new(Self { name, id, image, space: space.clone(), inner: IrqSafeSpinlock::new(ProcessInner { state: ProcessState::Running, session_id: id, group_id: id, session_terminal: None, threads: Vec::new(), mutexes: BTreeMap::new(), }), exit_waker: QueueWaker::new(), io: IrqSafeSpinlock::new(ProcessIo::new()), }); // Create "main" thread let thread = Thread::new_uthread(process.clone(), space, context); process.inner.lock().threads.push(thread.clone()); PROCESSES.lock().insert(id, process.clone()); (process, thread) } pub fn spawn_thread(self: &Arc, options: &ThreadSpawnOptions) -> Result { debugln!( "Spawn thread in {} with options: {:#x?}", self.id(), options ); let tls_address = if let Some(image) = self.image.as_ref() { proc::elf::clone_tls(&self.space, image)? } else { 0 }; let space = self.space.clone(); let context = TaskContext::user( options.entry as _, options.argument as _, space.as_address_with_asid(), options.stack_top, tls_address, )?; let thread = Thread::new_uthread(self.clone(), space, context); let id = thread.id(); self.inner.lock().threads.push(thread.clone()); thread.enqueue_somewhere(); Ok(id) } pub fn id(&self) -> ProcessId { self.id } pub fn group_id(&self) -> ProcessId { self.inner.lock().group_id } pub fn session_id(&self) -> ProcessId { self.inner.lock().session_id } pub fn name(&self) -> &str { self.name.as_ref() } pub fn set_group_id(&self, id: ProcessId) { self.inner.lock().group_id = id; } pub fn set_session_id(&self, id: ProcessId) { self.inner.lock().session_id = id; } // Resources pub fn session_terminal(&self) -> Option { self.inner.lock().session_terminal.clone() } pub fn set_session_terminal(&self, node: VnodeRef) { self.inner.lock().session_terminal.replace(node); } pub fn clear_session_terminal(&self) -> Option { self.inner.lock().session_terminal.take() } pub fn inherit(&self, parent: &Process) -> Result<(), Error> { let mut our_inner = self.inner.lock(); let their_inner = parent.inner.lock(); our_inner.session_id = their_inner.session_id; our_inner.group_id = their_inner.group_id; our_inner.session_terminal = their_inner.session_terminal.clone(); Ok(()) } // State pub fn get_exit_status(&self) -> Option { match self.inner.lock().state { ProcessState::Running => None, ProcessState::Terminated(x) => Some(x), } } pub fn wait_for_exit(process: Arc) -> impl Future { struct ProcessExitFuture { process: Arc, } impl Future for ProcessExitFuture { type Output = ExitCode; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let process = &self.process; process.exit_waker.register(cx.waker()); if let Some(exit_status) = process.get_exit_status() { process.exit_waker.remove(cx.waker()); Poll::Ready(exit_status) } else { Poll::Pending } } } ProcessExitFuture { process } } pub fn handle_thread_exit(&self, thread: ThreadId, code: ExitCode) { debugln!("Thread {} of process {}: {:?}", thread, self.id, code); let mut inner = self.inner.lock(); // TODO make this cleaner let old_len = inner.threads.len(); inner.threads.retain(|t| t.id() != thread); assert_ne!(inner.threads.len(), old_len); let last_thread = inner.threads.is_empty(); if last_thread { debugln!("Last thread of {} exited", self.id); inner.state = ProcessState::Terminated(code); self.io.lock().handle_exit(); drop(inner); self.exit_waker.wake_all(); } } /// Raises a signal for the specified process pub fn raise_signal(self: &Arc, signal: Signal) { let thread = self.inner.lock().threads[0].clone(); thread.raise_signal(signal); } /// Raises a signal for the specified process group pub fn signal_group(group_id: ProcessId, signal: Signal) { let processes = PROCESSES.lock(); for (_, proc) in processes.iter() { let inner = proc.inner.lock(); if !matches!(inner.state, ProcessState::Terminated(_)) && inner.group_id == group_id { debugln!("Deliver group signal to {}: {:?}", proc.id(), signal); drop(inner); proc.raise_signal(signal); } } } pub fn get_or_insert_mutex(&self, address: usize) -> Arc { let mut inner = self.inner.lock(); inner .mutexes .entry(address) .or_insert_with(|| Arc::new(UserspaceMutex::new(address))) .clone() } // Process list pub fn get(id: ProcessId) -> Option> { PROCESSES.lock().get(&id).cloned() } pub async fn terminate_others(&self, except: ThreadId) { let mut inner = self.inner.lock(); for thread in inner.threads.iter() { if thread.id() == except { continue; } infoln!("Terminate thread {}", thread.id()); thread.terminate().await; } inner.threads.retain(|t| t.id() == except); } } impl fmt::Display for ProcessId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use fmt::Write; write!(f, "", self.0) } } // XXX TODO Remove this impl From for u32 { fn from(value: ProcessId) -> Self { value.0 as _ } } impl From for ProcessId { fn from(value: u32) -> Self { Self(value as _) } } impl ProcessId { pub fn next() -> Self { static COUNTER: AtomicU64 = AtomicU64::new(1); let id = COUNTER.fetch_add(1, Ordering::SeqCst); Self(id) } } // /// 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) -> &ProcessAddressSpace { // self.space.as_ref().unwrap() // } // // /// Returns the address space of the task, if one is set // pub fn get_address_space(&self) -> Option<&ProcessAddressSpace> { // self.space.as_ref() // } // // /// Replaces the task's session terminal device with another one // pub fn set_session_terminal(&self, terminal: VnodeRef) { // self.inner.lock().session_terminal.replace(terminal); // } // // /// Removes the task's current terminal // pub fn clear_session_terminal(&self) -> Option { // } // // /// Returns the current terminal of the task // pub fn session_terminal(&self) -> Option { // self.inner.lock().session_terminal.clone() // } // // /// Sets the session ID of the task // pub fn set_session_id(&self, sid: ProcessId) { // self.inner.lock().session_id = sid; // } // // /// Sets the process group ID of the task // pub fn set_group_id(&self, mut gid: ProcessId) { // if gid == 0 { // gid = self.id(); // } // self.inner.lock().group_id = gid; // } // // /// Returns the process group ID of the task // pub fn group_id(&self) -> ProcessId { // self.inner.lock().group_id // } // // /// Returns the CPU number this task in running on (or the last one) // pub fn cpu_id(&self) -> u32 { // self.cpu_id.load(Ordering::Acquire) // } // // /// 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: Arc) -> 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: Arc, queue: &'static CpuQueue) { // let _irq = IrqGuard::acquire(); // // { // let mut inner = self.inner.lock(); // let old_queue = inner.queue.replace(queue); // if old_queue.is_some() { // // Already in some queue // return; // } // } // match self.state.compare_exchange( // ProcessState::Suspended, // ProcessState::Ready, // Ordering::SeqCst, // Ordering::Relaxed, // ) { // Err(ProcessState::Terminated) => { // // Process might've been killed while `await`ing in a `block!` // debugln!( // "Process {} {:?} already terminated, dropping", // self.id(), // self.name() // ); // } // Err(state) => { // todo!("Unexpected process state when enqueueing: {:?}", state) // } // Ok(_) => unsafe { // queue.enqueue(self); // }, // } // } // // /// Returns an exit code if the process exited, [None] if it didn't // pub fn get_exit_status(&self) -> Option { // if self.state() == ProcessState::Terminated { // Some(ExitCode::from(self.inner.lock().exit_status)) // } else { // None // } // } // // /// Returns the [Process] currently executing on local CPU, None if idling. // pub fn get_current() -> Option { // let queue = Cpu::local().queue(); // queue.current_process() // } // // /// Returns a process by its ID // pub fn get(pid: ProcessId) -> Option> { // PROCESSES.lock().get(pid).cloned() // } // // /// 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() -> CurrentProcess { // Self::get_current().unwrap() // } // // /// Handles the cleanup of an exited process // pub fn handle_exit(&self) { // } // // /// Inherits the data from a parent process. Meant to be called from SpawnProcess handler. // pub fn inherit(&self, parent: &Arc) -> Result<(), Error> { // } // // // pub fn wait_for_exit(process: Arc) -> impl Future { // } // } // // impl ArcWake for Process { // fn wake_by_ref(arc_self: &Arc) { // arc_self.clone().enqueue_somewhere(); // } // } // // impl Drop for Process { // fn drop(&mut self) { // infoln!("Drop process!"); // } // } // // impl CurrentProcess { // /// Wraps a process in this structure. // /// // /// # Safety // /// // /// Only meant to be called from [Process::current] or [CpuQueue::current_process]. // pub unsafe fn new(inner: Arc, guard: IrqGuard) -> Self { // Self(inner, guard) // } // // /// Configures signal entry information for the process // pub fn set_signal_entry(&self, entry: usize, stack: usize) { // let mut inner = self.inner.lock(); // inner.signal_entry.replace(SignalEntry { entry, stack }); // } // // pub fn suspend(&self) -> Result<(), Error> { // self.dequeue(ProcessState::Suspended); // // let inner = self.inner.lock(); // if !inner.signal_stack.is_empty() { // return Err(Error::Interrupted); // } // // Ok(()) // } // // /// Terminate the current process // pub fn exit(&self, status: ExitCode) { // self.inner.lock().exit_status = status.into(); // debugln!("Process {} exited with code {:?}", self.id(), status); // // self.handle_exit(); // self.dequeue(ProcessState::Terminated); // } // // } // // impl Deref for CurrentProcess { // type Target = Arc; // // fn deref(&self) -> &Self::Target { // &self.0 // } // }