use alloc::{borrow::Cow, boxed::Box, collections::VecDeque, sync::Arc, vec::Vec}; use core::{cmp::Ordering, mem}; use spin::RwLock; use crate::{ arch::{interrupt::InterruptStack, paging::PAGE_SIZE}, common::{aligned_box::AlignedBox, unique::Unique}, context::{self, arch, file::FileDescriptor, memory::AddrSpace}, ipi::{ipi, IpiKind, IpiTarget}, memory::{Frame, RaiiFrame}, paging::{RmmA, RmmArch}, scheme::{CallerCtx, FileHandle, SchemeNamespace}, sync::WaitMap, LogicalCpuId, LogicalCpuSet, }; use crate::syscall::{ data::SigAction, error::{Error, Result, EAGAIN, ESRCH}, flag::{SigActionFlags, SIG_DFL}, }; /// Unique identifier for a context (i.e. `pid`). use ::core::sync::atomic::AtomicUsize; use super::memory::GrantFileRef; int_like!(ContextId, AtomicContextId, usize, AtomicUsize); /// The status of a context - used for scheduling /// See `syscall::process::waitpid` and the `sync` module for examples of usage #[derive(Clone, Debug)] pub enum Status { Runnable, // TODO: Rename to SoftBlocked and move status_reason to this variant. /// Not currently runnable, typically due to some blocking syscall, but it can be trivially /// unblocked by e.g. signals. Blocked, /// Not currently runnable, and cannot be runnable until manually unblocked, depending on what /// reason. HardBlocked { reason: HardBlockedReason, }, Stopped(usize), Exited(usize), } impl Status { pub fn is_runnable(&self) -> bool { matches!(self, Self::Runnable) } pub fn is_soft_blocked(&self) -> bool { matches!(self, Self::Blocked) } } #[derive(Clone, Debug)] pub enum HardBlockedReason { AwaitingMmap { file_ref: GrantFileRef }, // TODO: PageFaultOom? // TODO: NotYetStarted/ManuallyBlocked (when new contexts are created) // TODO: ptrace_stop? } #[derive(Copy, Clone, Debug)] pub struct WaitpidKey { pub pid: Option, pub pgid: Option, } impl Ord for WaitpidKey { fn cmp(&self, other: &WaitpidKey) -> Ordering { // If both have pid set, compare that if let Some(s_pid) = self.pid { if let Some(o_pid) = other.pid { return s_pid.cmp(&o_pid); } } // If both have pgid set, compare that if let Some(s_pgid) = self.pgid { if let Some(o_pgid) = other.pgid { return s_pgid.cmp(&o_pgid); } } // If either has pid set, it is greater if self.pid.is_some() { return Ordering::Greater; } if other.pid.is_some() { return Ordering::Less; } // If either has pgid set, it is greater if self.pgid.is_some() { return Ordering::Greater; } if other.pgid.is_some() { return Ordering::Less; } // If all pid and pgid are None, they are equal Ordering::Equal } } impl PartialOrd for WaitpidKey { fn partial_cmp(&self, other: &WaitpidKey) -> Option { Some(self.cmp(other)) } } impl PartialEq for WaitpidKey { fn eq(&self, other: &WaitpidKey) -> bool { self.cmp(other) == Ordering::Equal } } impl Eq for WaitpidKey {} /// A context, which identifies either a process or a thread #[derive(Debug)] pub struct Context { /// The ID of this context pub id: ContextId, /// The group ID of this context pub pgid: ContextId, /// The ID of the parent context pub ppid: ContextId, /// The ID of the session pub session_id: ContextId, /// The real user id pub ruid: u32, /// The real group id pub rgid: u32, /// The real namespace id pub rns: SchemeNamespace, /// The effective user id pub euid: u32, /// The effective group id pub egid: u32, /// The effective namespace id pub ens: SchemeNamespace, /// Signal mask pub sigmask: [u64; 2], /// Process umask pub umask: usize, /// Status of context pub status: Status, pub status_reason: &'static str, /// Context running or not pub running: bool, /// Current CPU ID pub cpu_id: Option, /// Time this context was switched to pub switch_time: u128, /// Amount of CPU time used pub cpu_time: u128, /// Scheduler CPU affinity. If set, [`cpu_id`] can except [`None`] never be anything else than /// this value. // TODO: bitmask (selection of multiple allowed CPUs)? pub sched_affinity: LogicalCpuSet, /// Current system call pub syscall: Option<(usize, usize, usize, usize, usize, usize)>, /// Head buffer to use when system call buffers are not page aligned // TODO: Store in user memory? pub syscall_head: Option, /// Tail buffer to use when system call buffers are not page aligned // TODO: Store in user memory? pub syscall_tail: Option, /// Context is being waited on pub waitpid: Arc>, /// Context should handle pending signals pub pending: VecDeque, /// Context should wake up at specified time pub wake: Option, /// The architecture specific context pub arch: arch::Context, /// Kernel FX - used to store SIMD and FPU registers on context switch pub kfx: AlignedBox<[u8], { arch::KFX_ALIGN }>, /// Kernel stack pub kstack: Option>, /// Kernel signal backup: Registers, Kernel FX, Kernel Stack, Signal number pub ksig: Option<( arch::Context, AlignedBox<[u8], { arch::KFX_ALIGN }>, Option>, u8, )>, /// Restore ksig context on next switch pub ksig_restore: bool, /// Address space containing a page table lock, and grants. Normally this will have a value, /// but can be None while the context is being reaped or when a new context is created but has /// not yet had its address space changed. Note that these are only for user mappings; kernel /// mappings are universal and independent on address spaces or contexts. pub addr_space: Option>>, /// The name of the context // TODO: fixed size ArrayString? pub name: Cow<'static, str>, /// The open files in the scheme pub files: Arc>>>, /// Signal actions pub actions: Arc>>, /// The pointer to the user-space registers, saved after certain /// interrupts. This pointer is somewhere inside kstack, and the /// kstack address at the time of creation is the first element in /// this tuple. pub regs: Option<(usize, Unique)>, /// A somewhat hacky way to initially stop a context when creating /// a new instance of the proc: scheme, entirely separate from /// signals or any other way to restart a process. pub ptrace_stop: bool, /// A pointer to the signal stack. If this is unset, none of the sigactions can be anything /// else than SIG_DFL, otherwise signals will not be delivered. Userspace is responsible for /// setting this. pub sigstack: Option, /// An even hackier way to pass the return entry point and stack pointer to new contexts while /// implementing clone. Before a context has returned to userspace, its IntRegisters cannot be /// set since there is no interrupt stack (unless the kernel stack is copied, but that is in my /// opinion hackier and less efficient than this (and UB to do in Rust)). pub clone_entry: Option<[usize; 2]>, pub fmap_ret: Option, } impl Context { pub fn new(id: ContextId) -> Result { let this = Context { id, pgid: id, ppid: ContextId::from(0), session_id: ContextId::from(0), ruid: 0, rgid: 0, rns: SchemeNamespace::from(0), euid: 0, egid: 0, ens: SchemeNamespace::from(0), sigmask: [0; 2], umask: 0o022, status: Status::Blocked, status_reason: "", running: false, cpu_id: None, switch_time: 0, cpu_time: 0, sched_affinity: LogicalCpuSet::all(), syscall: None, syscall_head: Some(RaiiFrame::allocate()?), syscall_tail: Some(RaiiFrame::allocate()?), waitpid: Arc::new(WaitMap::new()), pending: VecDeque::new(), wake: None, arch: arch::Context::new(), kfx: AlignedBox::<[u8], { arch::KFX_ALIGN }>::try_zeroed_slice(crate::arch::kfx_size())?, kstack: None, ksig: None, ksig_restore: false, addr_space: None, name: Cow::Borrowed(""), files: Arc::new(RwLock::new(Vec::new())), actions: Self::empty_actions(), regs: None, ptrace_stop: false, sigstack: None, clone_entry: None, fmap_ret: None, }; Ok(this) } /// Block the context, and return true if it was runnable before being blocked pub fn block(&mut self, reason: &'static str) -> bool { if self.status.is_runnable() { self.status = Status::Blocked; self.status_reason = reason; true } else { false } } pub fn hard_block(&mut self, reason: HardBlockedReason) -> bool { if self.status.is_runnable() { self.status = Status::HardBlocked { reason }; true } else { false } } /// Unblock context, and return true if it was blocked before being marked runnable pub fn unblock(&mut self) -> bool { if self.unblock_no_ipi() { if let Some(cpu_id) = self.cpu_id { if cpu_id != crate::cpu_id() { // Send IPI if not on current CPU ipi(IpiKind::Wakeup, IpiTarget::Other); } } true } else { false } } /// Unblock context without IPI, and return true if it was blocked before being marked runnable pub fn unblock_no_ipi(&mut self) -> bool { if self.status.is_soft_blocked() { self.status = Status::Runnable; self.status_reason = ""; true } else { false } } /// Add a file to the lowest available slot. /// Return the file descriptor number or None if no slot was found pub fn add_file(&self, file: FileDescriptor) -> Option { self.add_file_min(file, 0) } /// Add a file to the lowest available slot greater than or equal to min. /// Return the file descriptor number or None if no slot was found pub fn add_file_min(&self, file: FileDescriptor, min: usize) -> Option { let mut files = self.files.write(); for (i, file_option) in files.iter_mut().enumerate() { if file_option.is_none() && i >= min { *file_option = Some(file); return Some(FileHandle::from(i)); } } let len = files.len(); if len < super::CONTEXT_MAX_FILES { if len >= min { files.push(Some(file)); Some(FileHandle::from(len)) } else { drop(files); self.insert_file(FileHandle::from(min), file) } } else { None } } /// Get a file pub fn get_file(&self, i: FileHandle) -> Option { let files = self.files.read(); if i.get() < files.len() { files[i.get()].clone() } else { None } } /// Insert a file with a specific handle number. This is used by dup2 /// Return the file descriptor number or None if the slot was not empty, or i was invalid pub fn insert_file(&self, i: FileHandle, file: FileDescriptor) -> Option { let mut files = self.files.write(); if i.get() < super::CONTEXT_MAX_FILES { while i.get() >= files.len() { files.push(None); } if files[i.get()].is_none() { files[i.get()] = Some(file); Some(i) } else { None } } else { None } } /// Remove a file // TODO: adjust files vector to smaller size if possible pub fn remove_file(&self, i: FileHandle) -> Option { let mut files = self.files.write(); if i.get() < files.len() { files[i.get()].take() } else { None } } pub fn addr_space(&self) -> Result<&Arc>> { self.addr_space.as_ref().ok_or(Error::new(ESRCH)) } pub fn set_addr_space( &mut self, addr_space: Arc>, ) -> Option>> { if self.id == super::context_id() { unsafe { addr_space.read().table.utable.make_current(); } } self.addr_space.replace(addr_space) } pub fn empty_actions() -> Arc>> { Arc::new(RwLock::new(vec![ ( SigAction { sa_handler: unsafe { mem::transmute(SIG_DFL) }, sa_mask: [0; 2], sa_flags: SigActionFlags::empty(), }, 0 ); 128 ])) } pub fn caller_ctx(&self) -> CallerCtx { CallerCtx { pid: self.id.into(), uid: self.euid, gid: self.egid, } } } /// Wrapper struct for borrowing the syscall head or tail buf. #[derive(Debug)] pub struct BorrowedHtBuf { inner: Option, head_and_not_tail: bool, } impl BorrowedHtBuf { pub fn head() -> Result { Ok(Self { inner: Some( context::current()? .write() .syscall_head .take() .ok_or(Error::new(EAGAIN))?, ), head_and_not_tail: true, }) } pub fn tail() -> Result { Ok(Self { inner: Some( context::current()? .write() .syscall_tail .take() .ok_or(Error::new(EAGAIN))?, ), head_and_not_tail: false, }) } pub fn buf(&self) -> &[u8; PAGE_SIZE] { unsafe { &*(RmmA::phys_to_virt( self.inner .as_ref() .expect("must succeed") .get() .start_address(), ) .data() as *const [u8; PAGE_SIZE]) } } pub fn buf_mut(&mut self) -> &mut [u8; PAGE_SIZE] { unsafe { &mut *(RmmA::phys_to_virt( self.inner .as_mut() .expect("must succeed") .get() .start_address(), ) .data() as *mut [u8; PAGE_SIZE]) } } pub fn frame(&self) -> Frame { self.inner.as_ref().expect("must succeed").get() } /* pub fn use_for_slice(&mut self, raw: UserSlice) -> Result> { if raw.len() > self.buf().len() { return Ok(None); } raw.copy_to_slice(&mut self.buf_mut()[..raw.len()])?; Ok(Some(&self.buf()[..raw.len()])) } pub fn use_for_string(&mut self, raw: UserSlice) -> Result<&str> { let slice = self.use_for_slice(raw)?.ok_or(Error::new(ENAMETOOLONG))?; core::str::from_utf8(slice).map_err(|_| Error::new(EINVAL)) } pub unsafe fn use_for_struct(&mut self) -> Result<&mut T> { if mem::size_of::() > PAGE_SIZE || mem::align_of::() > PAGE_SIZE { return Err(Error::new(EINVAL)); } self.buf_mut().fill(0_u8); Ok(unsafe { &mut *self.buf_mut().as_mut_ptr().cast() }) } */ } impl Drop for BorrowedHtBuf { fn drop(&mut self) { let Ok(context) = context::current() else { return; }; let Some(inner) = self.inner.take() else { return; }; match context.write() { mut context => { (if self.head_and_not_tail { &mut context.syscall_head } else { &mut context.syscall_tail }) .get_or_insert(inner); } } } }