starry-kernel 0.10.0

A Linux-compatible OS kernel built on ArceOS unikernel
Documentation
//! User task management.

mod allocation;
mod bounded_stack;
mod cgroup_exit_invariant;
mod cred;
pub mod futex;
pub mod future;
mod interruption;
mod job_control;
mod ops;
mod pid;
pub mod posix_timer;
mod process;
mod process_accounting;
mod process_cgroup;
mod process_identity;
mod process_image;
mod process_memory;
mod process_policy;
mod process_ptrace;
mod process_wait;
mod resources;
mod scheduler_identity;
mod scheduler_task;
mod seccomp;
mod signal;
mod signal_publication;
mod stat;
mod thread;
mod timer;
#[cfg(target_arch = "loongarch64")]
mod unaligned;
mod user;
#[cfg(target_arch = "aarch64")]
mod user_cpu_features;
mod user_memory_access;
mod user_wait;

use alloc::sync::Arc;

pub(crate) use process_ptrace::{PtraceAttachMode, PtraceWaitAction, PtraceWaitStop};
pub use process_ptrace::{PtraceStopFpData, SyscallTraceState};
use starry_signal::{
    Signo,
    api::{ProcessSignalManager, SignalActions},
};

pub use self::{
    cred::*, futex::*, job_control::JobStatus, ops::*, posix_timer::PosixTimerTable, process::*,
    process_image::ProcessImage, process_wait::wait_on_pollset, resources::*, scheduler_task::*,
    seccomp::*, signal::*, stat::*, thread::Thread, timer::*, user::*,
};
use self::{
    job_control::ProcessJobControl, process_accounting::ProcessAccountingState,
    process_cgroup::ProcessCgroupState, process_image::ProcessImageState,
    process_memory::ProcessMemoryState, process_policy::ProcessPolicyState,
    process_ptrace::ProcessPtraceState, process_wait::ProcessWaitState,
};
pub(crate) use self::{
    pid::*,
    process_identity::*,
    process_memory::{PreparedProcessMemory, scheduler_address_space},
};
use crate::{
    mm::MmHandle,
    namespace::NsProxy,
    sync::{IrqMutex, Mutex, MutexGuard, RawSpinLock},
};

/// Resources shared by every thread in one Linux process generation.
pub struct ProcessData {
    /// TGID role ownership transferred into the zombie at final exit.
    /// Keep this before the identity pins: failed clone can leave deferred
    /// task reclamation as the last owner after PID reservation rollback.
    tgid_lease: IrqMutex<Option<PidRoleLease<Tgid>>>,
    /// Process topology object.
    pub proc: Arc<Process>,
    /// Stable identity shared by PID namespaces, pidfds, and observers.
    identity: Arc<PidIdentity>,
    /// Executable metadata independently synchronized for exec and procfs.
    image: ProcessImageState,
    /// Address-space publication and release state.
    memory: ProcessMemoryState,
    /// Per-process uprobe manager.
    pub uprobe_manager: crate::kprobe::KprobeManager,
    /// Per-process uprobe point list.
    pub uprobe_point_list: Mutex<crate::kprobe::KprobePointList>,
    /// Immutable namespace snapshot published under a short IRQ-safe lock.
    pub(crate) nsproxy: IrqMutex<Arc<NsProxy>>,
    /// Sleepable writer transaction gate for namespace replacement.
    namespace_update: Mutex<()>,
    /// Authoritative cgroup membership and exit serialization.
    cgroup: ProcessCgroupState,
    /// Resource limits and process-wide compatibility policy.
    policy: ProcessPolicyState,
    /// Exit metadata, wait channels, and vfork completion.
    wait: ProcessWaitState,
    /// Process signal manager.
    pub signal: Arc<ProcessSignalManager>,
    /// CPU accounting and process-owned timer tables.
    accounting: ProcessAccountingState,
    /// Ptrace stop/resume state and architecture register snapshots.
    ptrace: ProcessPtraceState,
    /// Job-control stop state and parent-report delivery.
    job_control: ProcessJobControl,
}

/// Fallible resources prepared before publishing one process generation.
pub struct ProcessDataInit {
    image: ProcessImage,
    aspace: MmHandle,
    signal_actions: Arc<RawSpinLock<SignalActions>>,
    nsproxy: NsProxy,
    cgroup: Arc<ax_cgroup::CgroupNode>,
    exit_signal: Option<Signo>,
    wait_parent_tid: TidNumber,
    shared_memory: Option<process_memory::ProcessMemoryShare>,
}

impl ProcessDataInit {
    /// Collects the resources that become owned by one process identity.
    pub fn new(
        image: ProcessImage,
        aspace: MmHandle,
        signal_actions: Arc<RawSpinLock<SignalActions>>,
        nsproxy: NsProxy,
        exit_signal: Option<Signo>,
        wait_parent_tid: TidNumber,
    ) -> Self {
        Self {
            image,
            aspace,
            signal_actions,
            nsproxy,
            cgroup: crate::cgroup::root(),
            exit_signal,
            wait_parent_tid,
            shared_memory: None,
        }
    }

    /// Makes a non-thread clone share the parent's Linux mm generation.
    pub(crate) fn with_shared_memory(mut self, parent: &ProcessData) -> Self {
        self.shared_memory = Some(parent.memory_share());
        self
    }

    /// Selects inherited membership for a prepared non-thread child.
    pub fn with_cgroup(mut self, cgroup: Arc<ax_cgroup::CgroupNode>) -> Self {
        self.cgroup = cgroup;
        self
    }
}

impl ProcessData {
    /// Creates one process aggregate around the already-reserved PID identity.
    pub fn new(
        proc: Arc<Process>,
        identity: Arc<PidIdentity>,
        tgid_lease: PidRoleLease<Tgid>,
        init: ProcessDataInit,
    ) -> Arc<Self> {
        let ProcessDataInit {
            image,
            aspace,
            signal_actions,
            nsproxy,
            cgroup,
            exit_signal,
            wait_parent_tid,
            shared_memory,
        } = init;
        let wait = ProcessWaitState::new(exit_signal, wait_parent_tid);
        let exit_event = wait.exit_event_arc();
        let this = Arc::new(Self {
            proc: proc.clone(),
            identity: identity.clone(),
            tgid_lease: IrqMutex::new(Some(tgid_lease)),
            image: ProcessImageState::new(image),
            memory: ProcessMemoryState::new(aspace, shared_memory),
            wait,
            uprobe_manager: crate::kprobe::KprobeManager::new(),
            uprobe_point_list: Mutex::new(crate::kprobe::KprobePointList::new()),
            policy: ProcessPolicyState::new(),
            accounting: ProcessAccountingState::new(),
            signal: Arc::new(ProcessSignalManager::new(
                signal_actions,
                crate::config::SIGNAL_TRAMPOLINE,
                proc.group_exit_state(),
            )),
            nsproxy: IrqMutex::new(Arc::new(nsproxy)),
            namespace_update: Mutex::new(()),
            cgroup: ProcessCgroupState::new(&identity, cgroup),
            ptrace: ProcessPtraceState::new(),
            job_control: ProcessJobControl::new(),
        });
        identity.bind_process(proc, exit_event, Arc::downgrade(&this));
        this
    }

    /// Returns this process generation's stable PID identity.
    pub(crate) fn identity(&self) -> Arc<PidIdentity> {
        self.identity.clone()
    }

    /// Transfers the process-owned TGID lease into its immutable zombie.
    pub(crate) fn take_tgid_lease(&self) -> PidRoleLease<Tgid> {
        self.tgid_lease
            .lock()
            .take()
            .expect("process TGID lease transferred twice")
    }

    /// Returns the current or final cgroup membership snapshot.
    pub(crate) fn cgroup_node(&self) -> Arc<ax_cgroup::CgroupNode> {
        self.cgroup.current()
    }

    /// Moves this live process under its per-generation transaction.
    pub(crate) fn migrate_cgroup(
        &self,
        target: Arc<ax_cgroup::CgroupNode>,
    ) -> ax_cgroup::CgroupResult<()> {
        self.cgroup.migrate(target)
    }

    /// Reserve one child task charge under this process transaction.
    pub(crate) fn begin_cgroup_task(
        &self,
        child: ax_cgroup::ProcessId,
        child_kind: ax_cgroup::CgroupChildKind,
    ) -> ax_cgroup::CgroupResult<ax_cgroup::CgroupForkGuard> {
        self.cgroup.begin_task(child, child_kind)
    }

    /// Retire one thread-group entry and its cgroup charge as one transaction.
    pub(crate) fn finish_thread_exit(
        &self,
        task: ax_cgroup::ProcessId,
        transition: impl FnOnce() -> ThreadExit,
    ) -> (ThreadExit, ax_cgroup::CgroupResult<()>) {
        self.cgroup.finish_thread_exit(task, transition)
    }

    /// Rename one exact task charge after Linux de-threading.
    pub(crate) fn rename_cgroup_task(
        &self,
        old_task: ax_cgroup::ProcessId,
        new_task: ax_cgroup::ProcessId,
    ) -> ax_cgroup::CgroupResult<()> {
        self.cgroup.rename_task(old_task, new_task)
    }

    /// Returns a stable namespace aggregate without retaining the raw lock.
    pub(crate) fn namespace_snapshot(&self) -> Arc<NsProxy> {
        self.nsproxy.lock().clone()
    }

    /// Serializes one process-wide namespace mutation or replacement.
    pub(crate) fn namespace_update(&self) -> ProcessNamespaceUpdate<'_> {
        ProcessNamespaceUpdate {
            publication: &self.nsproxy,
            _guard: self.namespace_update.lock(),
        }
    }

    /// Releases the process-owned cgroup namespace after final exit.
    pub(crate) fn release_cgroup_namespace(&self) {
        let update = self.namespace_update();
        let mut replacement = update.snapshot().clone_for_unshare();
        replacement.release_cgroup_namespace();
        update.publish(replacement);
    }
}

impl Drop for ProcessData {
    fn drop(&mut self) {
        self.retire_mm_owner();
    }
}

/// Serialized copy-on-write namespace publication.
pub(crate) struct ProcessNamespaceUpdate<'a> {
    publication: &'a IrqMutex<Arc<NsProxy>>,
    _guard: MutexGuard<'a, ()>,
}

impl ProcessNamespaceUpdate<'_> {
    pub(crate) fn snapshot(&self) -> Arc<NsProxy> {
        self.publication.lock().clone()
    }

    pub(crate) fn publish(self, replacement: NsProxy) {
        let replacement = Arc::new(replacement);
        let previous = {
            let mut current = self.publication.lock();
            core::mem::replace(&mut *current, replacement)
        };
        drop(self);
        drop(previous);
    }
}

#[cfg(axtest)]
pub(crate) fn new_test_process_data(
    identity: Arc<PidIdentity>,
    tgid: PidRoleLease<Tgid>,
) -> Arc<ProcessData> {
    let parent = TidNumber::from(identity.root_number());
    ProcessData::new(
        Process::new_for_axtest(identity.clone()),
        identity,
        tgid,
        ProcessDataInit::new(
            ProcessImage::new(
                Default::default(),
                Arc::default(),
                Arc::default(),
                Default::default(),
                Default::default(),
                Default::default(),
            ),
            MmHandle::from_arc(Arc::new(Mutex::new(
                crate::mm::AddrSpace::new_empty(0x10000.into(), 0x10000).unwrap(),
            )))
            .unwrap(),
            Arc::default(),
            NsProxy::new_root(),
            None,
            parent,
        ),
    )
}

#[cfg(all(test, not(axtest)))]
mod tests {
    use super::ProcessData;
    use crate::{namespace::NsProxy, sync::IrqMutex};

    #[test]
    fn namespace_publication_lock_only_contains_a_shared_snapshot() {
        fn assert_snapshot_lock(_: &IrqMutex<alloc::sync::Arc<NsProxy>>) {}
        fn assert_process_lock_type(process: &ProcessData) {
            assert_snapshot_lock(&process.nsproxy);
        }

        let _ = assert_process_lock_type as fn(&ProcessData);
    }
}