mod cred;
pub mod futex;
mod ops;
pub mod posix_timer;
mod resources;
mod signal;
mod stat;
mod timer;
mod user;
use alloc::{boxed::Box, string::String, sync::Arc, vec::Vec};
use core::{
cell::RefCell,
ops::Deref,
sync::atomic::{AtomicBool, AtomicI32, AtomicU8, AtomicU32, AtomicUsize, Ordering},
};
use ax_hal::time::TimeValue;
use ax_sync::{Mutex, spin::SpinNoIrq};
use ax_task::{TaskExt, TaskInner};
use axpoll::PollSet;
use extern_trait::extern_trait;
use scope_local::{ActiveScope, Scope};
use spin::RwLock;
use starry_process::Process;
use starry_signal::{
Signo,
api::{ProcessSignalManager, SignalActions, ThreadSignalManager},
};
pub use self::{
cred::*, futex::*, ops::*, posix_timer::PosixTimerTable, resources::*, signal::*, stat::*,
timer::*, user::*,
};
#[cfg(feature = "kcov")]
use crate::kcov::KcovThreadState;
use crate::mm::AddrSpace;
#[cfg(target_arch = "x86_64")]
pub const SYSCALL_INSN_LEN: usize = 2;
#[cfg(not(target_arch = "x86_64"))]
pub const SYSCALL_INSN_LEN: usize = 4;
#[repr(transparent)]
pub struct AssumeSync<T>(pub T);
unsafe impl<T> Sync for AssumeSync<T> {}
impl<T> Deref for AssumeSync<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
struct NextSignalCheckBlock(AtomicBool);
impl NextSignalCheckBlock {
const fn new() -> Self {
Self(AtomicBool::new(false))
}
fn block(&self) {
self.0.store(true, Ordering::Release);
}
fn unblock(&self) -> bool {
self.0.swap(false, Ordering::AcqRel)
}
}
pub struct Thread {
pub proc_data: Arc<ProcessData>,
clear_child_tid: AtomicUsize,
robust_list_head: AtomicUsize,
pub signal: Arc<ThreadSignalManager>,
pub time: AssumeSync<RefCell<TimeManager>>,
oom_score_adj: AtomicI32,
pub exit: Arc<AtomicBool>,
accessing_user_memory: AtomicBool,
block_next_signal_check: NextSignalCheckBlock,
pub exit_event: Arc<PollSet>,
rseq_area: AtomicUsize,
pdeathsig: AtomicU32,
cred: SpinNoIrq<Arc<Cred>>,
#[cfg(feature = "kcov")]
kcov: AssumeSync<RefCell<Option<KcovThreadState>>>,
pub fault_dump_signo: AtomicU8,
}
impl Thread {
pub fn new(tid: u32, proc_data: Arc<ProcessData>, parent_cred: Option<Arc<Cred>>) -> Box<Self> {
let cred = parent_cred.unwrap_or_else(|| Arc::new(Cred::root()));
Box::new(Thread {
signal: ThreadSignalManager::new(tid, proc_data.signal.clone()),
proc_data,
clear_child_tid: AtomicUsize::new(0),
robust_list_head: AtomicUsize::new(0),
time: AssumeSync(RefCell::new(TimeManager::new())),
exit: Arc::new(AtomicBool::new(false)),
oom_score_adj: AtomicI32::new(200),
accessing_user_memory: AtomicBool::new(false),
block_next_signal_check: NextSignalCheckBlock::new(),
exit_event: Arc::default(),
rseq_area: AtomicUsize::new(0),
pdeathsig: AtomicU32::new(0),
cred: SpinNoIrq::new(cred),
#[cfg(feature = "kcov")]
kcov: AssumeSync(RefCell::new(None)),
fault_dump_signo: AtomicU8::new(0),
})
}
pub fn clear_child_tid(&self) -> usize {
self.clear_child_tid.load(Ordering::Relaxed)
}
pub fn set_clear_child_tid(&self, clear_child_tid: usize) {
self.clear_child_tid
.store(clear_child_tid, Ordering::Relaxed);
}
pub fn robust_list_head(&self) -> usize {
self.robust_list_head.load(Ordering::SeqCst)
}
pub fn set_robust_list_head(&self, robust_list_head: usize) {
self.robust_list_head
.store(robust_list_head, Ordering::SeqCst);
}
pub fn oom_score_adj(&self) -> i32 {
self.oom_score_adj.load(Ordering::SeqCst)
}
pub fn set_oom_score_adj(&self, value: i32) {
self.oom_score_adj.store(value, Ordering::SeqCst);
}
pub fn pending_exit(&self) -> bool {
self.exit.load(Ordering::Acquire)
}
pub fn set_exit(&self) {
self.exit.store(true, Ordering::Release);
}
pub fn is_accessing_user_memory(&self) -> bool {
self.accessing_user_memory.load(Ordering::Acquire)
}
pub fn set_accessing_user_memory(&self, accessing: bool) {
self.accessing_user_memory
.store(accessing, Ordering::Release);
}
pub fn pdeathsig(&self) -> u32 {
self.pdeathsig.load(Ordering::Relaxed)
}
pub fn set_pdeathsig(&self, sig: u32) {
self.pdeathsig.store(sig, Ordering::Relaxed);
}
#[cfg(feature = "kcov")]
pub fn with_kcov<R>(&self, f: impl FnOnce(Option<&KcovThreadState>) -> R) -> R {
match self.kcov.0.try_borrow() {
Ok(borrow) => f(borrow.as_ref()),
Err(_) => f(None),
}
}
#[cfg(feature = "kcov")]
pub fn set_kcov(&self, state: Option<KcovThreadState>) {
*self.kcov.0.borrow_mut() = state;
}
pub fn cred(&self) -> Arc<Cred> {
self.cred.lock().clone()
}
fn set_cred_single(&self, new_cred: Arc<Cred>) {
*self.cred.lock() = new_cred;
}
pub fn set_cred(&self, new_cred: Cred) {
let new_arc = Arc::new(new_cred);
let mut tids = self.proc_data.proc.threads();
tids.sort_unstable();
for tid in &tids {
if let Ok(task) = ops::get_task(*tid)
&& let Some(thr) = task.try_as_thread()
{
thr.set_cred_single(new_arc.clone());
}
}
}
pub fn rseq_area(&self) -> usize {
self.rseq_area.load(Ordering::SeqCst)
}
pub fn set_rseq_area(&self, addr: usize) {
self.rseq_area.store(addr, Ordering::SeqCst);
}
pub fn block_next_signal_check(&self) {
self.block_next_signal_check.block();
}
pub fn unblock_next_signal_check(&self) -> bool {
self.block_next_signal_check.unblock()
}
}
#[extern_trait]
impl TaskExt for Box<Thread> {
fn on_enter(&self) {
let scope = self.proc_data.scope.read();
unsafe { ActiveScope::set(&scope) };
core::mem::forget(scope);
}
fn on_leave(&self) {
ActiveScope::set_global();
unsafe { self.proc_data.scope.force_read_decrement() };
}
}
pub trait AsThread {
fn try_as_thread(&self) -> Option<&Thread>;
fn as_thread(&self) -> &Thread {
self.try_as_thread().expect("kernel task")
}
}
impl AsThread for TaskInner {
fn try_as_thread(&self) -> Option<&Thread> {
self.task_ext()
.map(|ext| ext.downcast_ref::<Box<Thread>>().as_ref())
}
}
pub struct VforkDone {
done: bool,
wq: Arc<ax_task::WaitQueue>,
}
impl VforkDone {
pub fn new(wq: Arc<ax_task::WaitQueue>) -> Self {
Self { done: false, wq }
}
}
pub struct ProcessData {
pub proc: Arc<Process>,
pub exe_path: RwLock<String>,
pub cmdline: RwLock<Arc<Vec<String>>>,
aspace: SpinNoIrq<Arc<Mutex<AddrSpace>>>,
pub scope: RwLock<Scope>,
heap_top: AtomicUsize,
pub rlim: RwLock<Rlimits>,
pub child_exit_event: Arc<PollSet>,
pub exit_event: Arc<PollSet>,
pub exit_signal: Option<Signo>,
pub signal: Arc<ProcessSignalManager>,
futex_table: Arc<FutexTable>,
vfork_done: SpinNoIrq<Option<VforkDone>>,
umask: AtomicU32,
nice: AtomicI32,
children_cpu_time: SpinNoIrq<(TimeValue, TimeValue)>,
pub posix_timers: Arc<PosixTimerTable>,
vm_aspace_shared: AtomicBool,
aspace_slot_released: AtomicBool,
}
impl ProcessData {
pub fn new(
proc: Arc<Process>,
exe_path: String,
cmdline: Arc<Vec<String>>,
aspace: Arc<Mutex<AddrSpace>>,
signal_actions: Arc<SpinNoIrq<SignalActions>>,
exit_signal: Option<Signo>,
vm_aspace_shared: bool,
) -> Arc<Self> {
let this = Arc::new(Self {
proc,
exe_path: RwLock::new(exe_path),
cmdline: RwLock::new(cmdline),
aspace: SpinNoIrq::new(aspace),
scope: RwLock::new(Scope::new()),
heap_top: AtomicUsize::new(crate::config::USER_HEAP_BASE),
rlim: RwLock::default(),
child_exit_event: Arc::default(),
exit_event: Arc::default(),
exit_signal,
signal: Arc::new(ProcessSignalManager::new(
signal_actions,
crate::config::SIGNAL_TRAMPOLINE,
)),
futex_table: Arc::new(FutexTable::new()),
vfork_done: SpinNoIrq::new(None),
umask: AtomicU32::new(0o022),
nice: AtomicI32::new(0),
children_cpu_time: SpinNoIrq::new((TimeValue::ZERO, TimeValue::ZERO)),
posix_timers: Arc::new(PosixTimerTable::default()),
vm_aspace_shared: AtomicBool::new(vm_aspace_shared),
aspace_slot_released: AtomicBool::new(false),
});
let aspace_arc = this.aspace.lock().clone();
crate::mm::attach_process_slot(&aspace_arc);
this
}
#[inline]
pub fn vm_aspace_shared(&self) -> bool {
self.vm_aspace_shared.load(Ordering::Acquire)
}
#[inline]
pub fn mark_vm_aspace_private_after_exec(&self) {
self.vm_aspace_shared.store(false, Ordering::Release);
}
pub fn release_aspace_slot_if_needed(&self) {
if self.aspace_slot_released.swap(true, Ordering::AcqRel) {
return;
}
let aspace = self.aspace.lock().clone();
crate::mm::release_process_slot(&aspace);
}
pub fn get_heap_top(&self) -> usize {
self.heap_top.load(Ordering::Acquire)
}
pub fn set_heap_top(&self, top: usize) {
self.heap_top.store(top, Ordering::Release)
}
pub fn is_clone_child(&self) -> bool {
self.exit_signal != Some(Signo::SIGCHLD)
}
pub fn umask(&self) -> u32 {
self.umask.load(Ordering::SeqCst)
}
pub fn set_umask(&self, umask: u32) {
self.umask.store(umask, Ordering::SeqCst);
}
pub fn replace_umask(&self, umask: u32) -> u32 {
self.umask.swap(umask, Ordering::SeqCst)
}
pub fn nice(&self) -> i32 {
self.nice.load(Ordering::SeqCst)
}
pub fn set_nice(&self, nice: i32) {
self.nice.store(nice, Ordering::SeqCst);
}
pub fn children_cpu_time(&self) -> (TimeValue, TimeValue) {
*self.children_cpu_time.lock()
}
pub fn add_child_cpu_time(&self, utime: TimeValue, stime: TimeValue) {
let mut time = self.children_cpu_time.lock();
time.0 += utime;
time.1 += stime;
}
pub fn aspace(&self) -> Arc<Mutex<AddrSpace>> {
self.aspace.lock().clone()
}
pub fn replace_aspace(&self, new_aspace: Arc<Mutex<AddrSpace>>) {
let old = {
let mut guard = self.aspace.lock();
core::mem::replace(&mut *guard, new_aspace)
};
crate::mm::release_process_slot(&old);
let aspace_arc = self.aspace.lock().clone();
crate::mm::attach_process_slot(&aspace_arc);
}
pub fn set_vfork_done(&self, wq: Arc<ax_task::WaitQueue>) {
*self.vfork_done.lock() = Some(VforkDone::new(wq));
}
pub fn wait_vfork_done(&self) {
let wq = {
let guard = self.vfork_done.lock();
match guard.as_ref() {
Some(vfork) => vfork.wq.clone(),
None => return, }
};
wq.wait_until(|| {
self.vfork_done
.lock()
.as_ref()
.map(|v| v.done)
.unwrap_or(true)
});
}
pub fn notify_vfork_done(&self) {
let wq = {
let mut guard = self.vfork_done.lock();
match guard.as_mut() {
Some(vfork) => {
vfork.done = true;
vfork.wq.clone()
}
None => return,
}
};
wq.notify_one(true);
}
}
impl Drop for ProcessData {
fn drop(&mut self) {
self.release_aspace_slot_if_needed();
}
}
#[cfg(test)]
mod tests {
use core::sync::atomic::{AtomicBool, Ordering};
use super::NextSignalCheckBlock;
#[test]
fn old_global_signal_check_block_leaks_between_threads() {
static OLD_BLOCK_NEXT_SIGNAL_CHECK: AtomicBool = AtomicBool::new(false);
fn block_next_signal() {
OLD_BLOCK_NEXT_SIGNAL_CHECK.store(true, Ordering::SeqCst);
}
fn unblock_next_signal() -> bool {
OLD_BLOCK_NEXT_SIGNAL_CHECK.swap(false, Ordering::SeqCst)
}
block_next_signal();
assert!(
unblock_next_signal(),
"the old global flag leaks across logical threads"
);
assert!(!unblock_next_signal());
}
#[test]
fn per_thread_signal_check_block_is_isolated() {
let thread_a = NextSignalCheckBlock::new();
let thread_b = NextSignalCheckBlock::new();
thread_a.block();
assert!(
!thread_b.unblock(),
"thread B must not observe thread A's signal-check block"
);
assert!(thread_a.unblock());
assert!(!thread_a.unblock());
}
}