use alloc::sync::Arc;
#[cfg(axtest)]
use core::sync::atomic::{AtomicBool, AtomicUsize};
use core::sync::atomic::{AtomicPtr, Ordering};
mod reader_epoch;
use reader_epoch::ReaderEpoch;
use super::ProcessData;
use crate::{
mm::{MmHandle, MmPin, TransparentHugePageMode},
sync::{IrqMutex, PreemptGuard},
task::futex::FutexDomain,
};
struct ProcessMemoryOwner {
mm: MmHandle,
private_futexes: Arc<FutexDomain>,
}
struct ProcessMemoryOwnerCell<T> {
current: AtomicPtr<T>,
readers: ReaderEpoch,
writer: IrqMutex<()>,
#[cfg(axtest)]
locked_snapshots: AtomicUsize,
}
impl<T> ProcessMemoryOwnerCell<T> {
fn new(current: Arc<T>) -> Self {
Self {
current: AtomicPtr::new(Arc::into_raw(current).cast_mut()),
readers: ReaderEpoch::new(),
writer: IrqMutex::new(()),
#[cfg(axtest)]
locked_snapshots: AtomicUsize::new(0),
}
}
fn snapshot(&self) -> Arc<T> {
self.snapshot_after_load(|| {})
}
fn replace(&self, next: Arc<T>) -> Arc<T> {
self.replace_after_publish(next, || {})
}
fn snapshot_after_load(&self, after_load: impl FnOnce()) -> Arc<T> {
let _reader_pin = PreemptGuard::new();
loop {
let Some(epoch) = self.readers.enter() else {
continue;
};
let current = self.current.load(Ordering::Acquire);
debug_assert!(!current.is_null());
after_load();
let snapshot = unsafe {
Arc::increment_strong_count(current);
Arc::from_raw(current)
};
self.readers.leave(epoch);
return snapshot;
}
}
fn replace_after_publish(&self, next: Arc<T>, after_publish: impl FnOnce()) -> Arc<T> {
let writer = self.writer.lock();
let next = Arc::into_raw(next).cast_mut();
let previous = self.current.swap(next, Ordering::AcqRel);
let previous_epoch = self.readers.advance();
after_publish();
while !self.readers.is_quiescent(previous_epoch) {
core::hint::spin_loop();
}
let previous = unsafe { Arc::from_raw(previous) };
drop(writer);
previous
}
#[cfg(axtest)]
fn locked_snapshot_count(&self) -> usize {
self.locked_snapshots.load(Ordering::Relaxed)
}
}
impl<T> Drop for ProcessMemoryOwnerCell<T> {
fn drop(&mut self) {
debug_assert!(self.readers.is_quiescent(0));
debug_assert!(self.readers.is_quiescent(1));
let current = *self.current.get_mut();
debug_assert!(!current.is_null());
unsafe { drop(Arc::from_raw(current)) };
}
}
impl ProcessMemoryOwner {
fn new(mm: MmHandle, shared: Option<ProcessMemoryShare>) -> Self {
let private_futexes = shared.map_or_else(
|| Arc::new(FutexDomain::new_private()),
|shared| {
assert_eq!(mm.id(), shared.0.mm.id());
shared.private_futexes()
},
);
Self {
mm,
private_futexes,
}
}
}
#[derive(Clone)]
pub(crate) struct ProcessMemoryShare(Arc<ProcessMemoryOwner>);
impl ProcessMemoryShare {
pub(crate) fn aspace(&self) -> MmPin {
self.0
.mm
.pin()
.expect("a live syscall must retain a live MM")
}
pub(crate) fn private_futexes(&self) -> Arc<FutexDomain> {
self.0.private_futexes.clone()
}
pub(crate) fn private_futexes_ref(&self) -> &Arc<FutexDomain> {
&self.0.private_futexes
}
pub(crate) fn retire(&self) {
if let Some(permit) = self.0.mm.release_user_ref() {
crate::mm::enqueue_retire(permit);
}
}
}
pub(crate) fn scheduler_address_space(
mm: &MmHandle,
) -> Result<ax_runtime::thread::TaskAddressSpace, ax_runtime::task::thread::TaskError> {
mm.scheduler_address_space()
}
pub(crate) struct PreparedProcessMemory(Arc<ProcessMemoryOwner>);
impl PreparedProcessMemory {
pub(crate) fn new(mm: MmHandle) -> Self {
Self(Arc::new(ProcessMemoryOwner::new(mm, None)))
}
}
pub(super) struct ProcessMemoryState {
owner: ProcessMemoryOwnerCell<ProcessMemoryOwner>,
}
impl ProcessMemoryState {
pub(super) fn new(mm: MmHandle, shared: Option<ProcessMemoryShare>) -> Self {
Self {
owner: ProcessMemoryOwnerCell::new(Arc::new(ProcessMemoryOwner::new(mm, shared))),
}
}
}
impl ProcessData {
pub fn retire_mm_owner(&self) {
self.memory_share().retire();
}
pub fn clone_aspace_user_ref(&self) -> Result<MmHandle, crate::mm::CloneUserRefError> {
self.memory.owner.snapshot().mm.clone_user_ref()
}
pub fn pin_aspace(&self) -> crate::StarryResult<MmPin> {
self.memory
.owner
.snapshot()
.mm
.pin()
.map_err(|_| crate::StarryError::BadState)
}
pub fn aspace(&self) -> MmPin {
self.pin_aspace()
.expect("operation requires a live process MM")
}
pub fn transparent_huge_page_mode(&self) -> TransparentHugePageMode {
self.memory.owner.snapshot().mm.transparent_huge_page_mode()
}
pub fn set_transparent_huge_page_mode(
&self,
mode: TransparentHugePageMode,
) -> crate::StarryResult<()> {
self.pin_aspace()?.set_transparent_huge_page_mode(mode);
Ok(())
}
pub(crate) fn memory_share(&self) -> ProcessMemoryShare {
ProcessMemoryShare(self.memory.owner.snapshot())
}
pub(crate) fn scheduler_address_space(
&self,
) -> Result<ax_runtime::thread::TaskAddressSpace, ax_runtime::task::thread::TaskError> {
self.memory.owner.snapshot().mm.scheduler_address_space()
}
#[must_use = "retire the old MM only after committing the new active MM"]
pub(crate) fn stage_memory_replacement(
&self,
prepared: PreparedProcessMemory,
) -> ProcessMemoryShare {
ProcessMemoryShare(self.memory.owner.replace(prepared.0))
}
}
#[cfg(axtest)]
fn memory_owner_snapshot_avoids_irq_lock_for_test() -> bool {
let owner = ProcessMemoryOwnerCell::new(Arc::new(7usize));
let snapshot = owner.snapshot();
*snapshot == 7 && owner.locked_snapshot_count() == 0
}
#[cfg(axtest)]
fn memory_owner_replacement_preserves_pinned_snapshot_for_test() -> bool {
let Ok(cpu_count) = ax_runtime::task::sched::cpu_topology_len() else {
return false;
};
if cpu_count < 2 {
return false;
}
let mut reader_affinity = ax_runtime::task::sched::CpuSet::empty(cpu_count);
reader_affinity.insert(ax_runtime::task::sched::CpuId::new(1));
let mut writer_affinity = ax_runtime::task::sched::CpuSet::empty(cpu_count);
writer_affinity.insert(ax_runtime::task::sched::CpuId::new(0));
let owner = Arc::new(ProcessMemoryOwnerCell::new(Arc::new(7usize)));
let reader_loaded = Arc::new(AtomicBool::new(false));
let writer_published = Arc::new(AtomicBool::new(false));
let reader_value = Arc::new(AtomicUsize::new(0));
let previous_value = Arc::new(AtomicUsize::new(0));
let reader = {
let owner = owner.clone();
let reader_loaded = reader_loaded.clone();
let writer_published = writer_published.clone();
let reader_value = reader_value.clone();
ax_std::thread::spawn(move || {
ax_runtime::task::thread::current::set_current_thread_affinity(reader_affinity)
.expect("the snapshot reader must be pinned to its test CPU");
let snapshot = owner.snapshot_after_load(|| {
reader_loaded.store(true, Ordering::Release);
while !writer_published.load(Ordering::Acquire) {
core::hint::spin_loop();
}
});
reader_value.store(*snapshot, Ordering::Release);
})
};
while !reader_loaded.load(Ordering::Acquire) {
ax_std::thread::yield_now();
}
let writer = {
let owner = owner.clone();
let writer_published = writer_published.clone();
let previous_value = previous_value.clone();
ax_std::thread::spawn(move || {
ax_runtime::task::thread::current::set_current_thread_affinity(writer_affinity)
.expect("the replacement writer must be pinned to its test CPU");
let previous = owner.replace_after_publish(Arc::new(9), || {
writer_published.store(true, Ordering::Release);
});
previous_value.store(*previous, Ordering::Release);
})
};
while !writer_published.load(Ordering::Acquire) {
ax_std::thread::yield_now();
}
reader.join().unwrap();
writer.join().unwrap();
let published = owner.snapshot();
*published == 9
&& reader_value.load(Ordering::Acquire) == 7
&& previous_value.load(Ordering::Acquire) == 7
&& *owner.snapshot() == 9
}
#[cfg(axtest)]
fn thread_page_table_lease_follows_task_lifetime_for_test() -> bool {
let Ok(mut aspace) = crate::mm::new_user_aspace_empty() else {
return false;
};
if crate::mm::copy_from_kernel(&mut aspace).is_err() {
return false;
}
let mm = MmHandle::from_arc(Arc::new(crate::sync::Mutex::new(aspace))).unwrap();
let task_aspace = match scheduler_address_space(&mm) {
Ok(task_aspace) => task_aspace,
Err(_) => return false,
};
let before_detach = mm.kernel_pins() == 1;
let no_early_retirement = mm.release_user_ref().is_none();
let retiring = mm.state() == crate::mm::MmState::Retiring;
drop(task_aspace);
before_detach && no_early_retirement && retiring && mm.kernel_pins() == 0
}
#[cfg(all(test, axtest))]
mod axtests {
#[axtest::axtest]
fn memory_owner_snapshot_avoids_irq_lock() {
assert!(super::memory_owner_snapshot_avoids_irq_lock_for_test());
}
#[axtest::axtest]
fn memory_owner_replacement_preserves_pinned_snapshot() {
assert!(super::memory_owner_replacement_preserves_pinned_snapshot_for_test());
}
#[axtest::axtest]
fn thread_page_table_lease_follows_task_lifetime() {
assert!(super::thread_page_table_lease_follows_task_lifetime_for_test());
}
}