mirror of
https://github.com/asterinas/asterinas.git
synced 2025-06-08 21:06:48 +00:00
328 lines
12 KiB
Rust
328 lines
12 KiB
Rust
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
//! Frame (physical memory page) management.
|
|
//!
|
|
//! A frame is an aligned, contiguous range of bytes in physical memory. The
|
|
//! sizes of base frames and huge frames (that are mapped as "huge pages") are
|
|
//! architecture-dependent. A frame can be mapped to virtual address spaces
|
|
//! using the page table.
|
|
//!
|
|
//! Frames can be accessed through frame handles, namely, [`Frame`]. A frame
|
|
//! handle is a reference-counted pointer to a frame. When all handles to a
|
|
//! frame are dropped, the frame is released and can be reused. Contiguous
|
|
//! frames are managed with [`Segment`].
|
|
//!
|
|
//! There are various kinds of frames. The top-level grouping of frame kinds
|
|
//! are "typed" frames and "untyped" frames. Typed frames host Rust objects
|
|
//! that must follow the visibility, lifetime and borrow rules of Rust, thus
|
|
//! not being able to be directly manipulated. Untyped frames are raw memory
|
|
//! that can be manipulated directly. So only untyped frames can be
|
|
//! - safely shared to external entities such as device drivers or user-space
|
|
//! applications.
|
|
//! - or directly manipulated with readers and writers that neglect Rust's
|
|
//! "alias XOR mutability" rule.
|
|
//!
|
|
//! The kind of a frame is determined by the type of its metadata. Untyped
|
|
//! frames have its metadata type that implements the [`UntypedFrameMeta`]
|
|
//! trait, while typed frames don't.
|
|
//!
|
|
//! Frames can have dedicated metadata, which is implemented in the [`meta`]
|
|
//! module. The reference count and usage of a frame are stored in the metadata
|
|
//! as well, leaving the handle only a pointer to the metadata slot. Users
|
|
//! can create custom metadata types by implementing the [`AnyFrameMeta`] trait.
|
|
|
|
pub mod allocator;
|
|
pub mod linked_list;
|
|
pub mod meta;
|
|
pub mod segment;
|
|
pub mod unique;
|
|
pub mod untyped;
|
|
|
|
mod frame_ref;
|
|
pub use frame_ref::FrameRef;
|
|
|
|
#[cfg(ktest)]
|
|
mod test;
|
|
|
|
use core::{
|
|
marker::PhantomData,
|
|
mem::ManuallyDrop,
|
|
sync::atomic::{AtomicUsize, Ordering},
|
|
};
|
|
|
|
pub use allocator::GlobalFrameAllocator;
|
|
use meta::{mapping, AnyFrameMeta, GetFrameError, MetaSlot, REF_COUNT_UNUSED};
|
|
pub use segment::Segment;
|
|
use untyped::{AnyUFrameMeta, UFrame};
|
|
|
|
use super::{PagingLevel, PAGE_SIZE};
|
|
use crate::mm::{Paddr, PagingConsts, Vaddr};
|
|
|
|
static MAX_PADDR: AtomicUsize = AtomicUsize::new(0);
|
|
|
|
/// Returns the maximum physical address that is tracked by frame metadata.
|
|
pub(in crate::mm) fn max_paddr() -> Paddr {
|
|
let max_paddr = MAX_PADDR.load(Ordering::Relaxed) as Paddr;
|
|
debug_assert_ne!(max_paddr, 0);
|
|
max_paddr
|
|
}
|
|
|
|
/// A smart pointer to a frame.
|
|
///
|
|
/// A frame is a contiguous range of bytes in physical memory. The [`Frame`]
|
|
/// type is a smart pointer to a frame that is reference-counted.
|
|
///
|
|
/// Frames are associated with metadata. The type of the metadata `M` is
|
|
/// determines the kind of the frame. If `M` implements [`AnyUFrameMeta`], the
|
|
/// frame is a untyped frame. Otherwise, it is a typed frame.
|
|
#[repr(transparent)]
|
|
pub struct Frame<M: AnyFrameMeta + ?Sized> {
|
|
ptr: *const MetaSlot,
|
|
_marker: PhantomData<M>,
|
|
}
|
|
|
|
unsafe impl<M: AnyFrameMeta + ?Sized> Send for Frame<M> {}
|
|
|
|
unsafe impl<M: AnyFrameMeta + ?Sized> Sync for Frame<M> {}
|
|
|
|
impl<M: AnyFrameMeta + ?Sized> core::fmt::Debug for Frame<M> {
|
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
|
write!(f, "Frame({:#x})", self.start_paddr())
|
|
}
|
|
}
|
|
|
|
impl<M: AnyFrameMeta> Frame<M> {
|
|
/// Gets a [`Frame`] with a specific usage from a raw, unused page.
|
|
///
|
|
/// The caller should provide the initial metadata of the page.
|
|
///
|
|
/// If the provided frame is not truly unused at the moment, it will return
|
|
/// an error. If wanting to acquire a frame that is already in use, use
|
|
/// [`Frame::from_in_use`] instead.
|
|
pub fn from_unused(paddr: Paddr, metadata: M) -> Result<Self, GetFrameError> {
|
|
Ok(Self {
|
|
ptr: MetaSlot::get_from_unused(paddr, metadata, false)?,
|
|
_marker: PhantomData,
|
|
})
|
|
}
|
|
|
|
/// Gets the metadata of this page.
|
|
pub fn meta(&self) -> &M {
|
|
// SAFETY: The type is tracked by the type system.
|
|
unsafe { &*self.slot().as_meta_ptr::<M>() }
|
|
}
|
|
}
|
|
|
|
impl Frame<dyn AnyFrameMeta> {
|
|
/// Gets a dynamically typed [`Frame`] from a raw, in-use page.
|
|
///
|
|
/// If the provided frame is not in use at the moment, it will return an error.
|
|
///
|
|
/// The returned frame will have an extra reference count to the frame.
|
|
pub fn from_in_use(paddr: Paddr) -> Result<Self, GetFrameError> {
|
|
Ok(Self {
|
|
ptr: MetaSlot::get_from_in_use(paddr)?,
|
|
_marker: PhantomData,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl<M: AnyFrameMeta + ?Sized> Frame<M> {
|
|
/// Gets the physical address of the start of the frame.
|
|
pub fn start_paddr(&self) -> Paddr {
|
|
self.slot().frame_paddr()
|
|
}
|
|
|
|
/// Gets the map level of this page.
|
|
///
|
|
/// This is the level of the page table entry that maps the frame,
|
|
/// which determines the size of the frame.
|
|
///
|
|
/// Currently, the level is always 1, which means the frame is a regular
|
|
/// page frame.
|
|
pub const fn map_level(&self) -> PagingLevel {
|
|
1
|
|
}
|
|
|
|
/// Gets the size of this page in bytes.
|
|
pub const fn size(&self) -> usize {
|
|
PAGE_SIZE
|
|
}
|
|
|
|
/// Gets the dyncamically-typed metadata of this frame.
|
|
///
|
|
/// If the type is known at compile time, use [`Frame::meta`] instead.
|
|
pub fn dyn_meta(&self) -> &dyn AnyFrameMeta {
|
|
// SAFETY: The metadata is initialized and valid.
|
|
unsafe { &*self.slot().dyn_meta_ptr() }
|
|
}
|
|
|
|
/// Gets the reference count of the frame.
|
|
///
|
|
/// It returns the number of all references to the frame, including all the
|
|
/// existing frame handles ([`Frame`], [`Frame<dyn AnyFrameMeta>`]), and all
|
|
/// the mappings in the page table that points to the frame.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// The function is safe to call, but using it requires extra care. The
|
|
/// reference count can be changed by other threads at any time including
|
|
/// potentially between calling this method and acting on the result.
|
|
pub fn reference_count(&self) -> u64 {
|
|
let refcnt = self.slot().ref_count.load(Ordering::Relaxed);
|
|
debug_assert!(refcnt < meta::REF_COUNT_MAX);
|
|
refcnt
|
|
}
|
|
|
|
/// Borrows a reference from the given frame.
|
|
pub fn borrow(&self) -> FrameRef<'_, M> {
|
|
// SAFETY: Both the lifetime and the type matches `self`.
|
|
unsafe { FrameRef::borrow_paddr(self.start_paddr()) }
|
|
}
|
|
|
|
/// Forgets the handle to the frame.
|
|
///
|
|
/// This will result in the frame being leaked without calling the custom dropper.
|
|
///
|
|
/// A physical address to the frame is returned in case the frame needs to be
|
|
/// restored using [`Frame::from_raw`] later. This is useful when some architectural
|
|
/// data structures need to hold the frame handle such as the page table.
|
|
pub(in crate::mm) fn into_raw(self) -> Paddr {
|
|
let this = ManuallyDrop::new(self);
|
|
this.start_paddr()
|
|
}
|
|
|
|
/// Restores a forgotten [`Frame`] from a physical address.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// The caller should only restore a `Frame` that was previously forgotten using
|
|
/// [`Frame::into_raw`].
|
|
///
|
|
/// And the restoring operation should only be done once for a forgotten
|
|
/// [`Frame`]. Otherwise double-free will happen.
|
|
///
|
|
/// Also, the caller ensures that the usage of the frame is correct. There's
|
|
/// no checking of the usage in this function.
|
|
pub(in crate::mm) unsafe fn from_raw(paddr: Paddr) -> Self {
|
|
let vaddr = mapping::frame_to_meta::<PagingConsts>(paddr);
|
|
let ptr = vaddr as *const MetaSlot;
|
|
|
|
Self {
|
|
ptr,
|
|
_marker: PhantomData,
|
|
}
|
|
}
|
|
|
|
fn slot(&self) -> &MetaSlot {
|
|
// SAFETY: `ptr` points to a valid `MetaSlot` that will never be
|
|
// mutably borrowed, so taking an immutable reference to it is safe.
|
|
unsafe { &*self.ptr }
|
|
}
|
|
}
|
|
|
|
impl<M: AnyFrameMeta + ?Sized> Clone for Frame<M> {
|
|
fn clone(&self) -> Self {
|
|
// SAFETY: We have already held a reference to the frame.
|
|
unsafe { self.slot().inc_ref_count() };
|
|
|
|
Self {
|
|
ptr: self.ptr,
|
|
_marker: PhantomData,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<M: AnyFrameMeta + ?Sized> Drop for Frame<M> {
|
|
fn drop(&mut self) {
|
|
let last_ref_cnt = self.slot().ref_count.fetch_sub(1, Ordering::Release);
|
|
debug_assert!(last_ref_cnt != 0 && last_ref_cnt != REF_COUNT_UNUSED);
|
|
|
|
if last_ref_cnt == 1 {
|
|
// A fence is needed here with the same reasons stated in the implementation of
|
|
// `Arc::drop`: <https://doc.rust-lang.org/std/sync/struct.Arc.html#method.drop>.
|
|
core::sync::atomic::fence(Ordering::Acquire);
|
|
|
|
// SAFETY: this is the last reference and is about to be dropped.
|
|
unsafe { self.slot().drop_last_in_place() };
|
|
|
|
allocator::get_global_frame_allocator().dealloc(self.start_paddr(), PAGE_SIZE);
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<M: AnyFrameMeta> TryFrom<Frame<dyn AnyFrameMeta>> for Frame<M> {
|
|
type Error = Frame<dyn AnyFrameMeta>;
|
|
|
|
/// Tries converting a [`Frame<dyn AnyFrameMeta>`] into the statically-typed [`Frame`].
|
|
///
|
|
/// If the usage of the frame is not the same as the expected usage, it will
|
|
/// return the dynamic frame itself as is.
|
|
fn try_from(dyn_frame: Frame<dyn AnyFrameMeta>) -> Result<Self, Self::Error> {
|
|
if (dyn_frame.dyn_meta() as &dyn core::any::Any).is::<M>() {
|
|
// SAFETY: The metadata is coerceable and the struct is transmutable.
|
|
Ok(unsafe { core::mem::transmute::<Frame<dyn AnyFrameMeta>, Frame<M>>(dyn_frame) })
|
|
} else {
|
|
Err(dyn_frame)
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<M: AnyFrameMeta> From<Frame<M>> for Frame<dyn AnyFrameMeta> {
|
|
fn from(frame: Frame<M>) -> Self {
|
|
// SAFETY: The metadata is coerceable and the struct is transmutable.
|
|
unsafe { core::mem::transmute(frame) }
|
|
}
|
|
}
|
|
|
|
impl<M: AnyUFrameMeta> From<Frame<M>> for UFrame {
|
|
fn from(frame: Frame<M>) -> Self {
|
|
// SAFETY: The metadata is coerceable and the struct is transmutable.
|
|
unsafe { core::mem::transmute(frame) }
|
|
}
|
|
}
|
|
|
|
impl From<UFrame> for Frame<dyn AnyFrameMeta> {
|
|
fn from(frame: UFrame) -> Self {
|
|
// SAFETY: The metadata is coerceable and the struct is transmutable.
|
|
unsafe { core::mem::transmute(frame) }
|
|
}
|
|
}
|
|
|
|
impl TryFrom<Frame<dyn AnyFrameMeta>> for UFrame {
|
|
type Error = Frame<dyn AnyFrameMeta>;
|
|
|
|
/// Tries converting a [`Frame<dyn AnyFrameMeta>`] into [`UFrame`].
|
|
///
|
|
/// If the usage of the frame is not the same as the expected usage, it will
|
|
/// return the dynamic frame itself as is.
|
|
fn try_from(dyn_frame: Frame<dyn AnyFrameMeta>) -> Result<Self, Self::Error> {
|
|
if dyn_frame.dyn_meta().is_untyped() {
|
|
// SAFETY: The metadata is coerceable and the struct is transmutable.
|
|
Ok(unsafe { core::mem::transmute::<Frame<dyn AnyFrameMeta>, UFrame>(dyn_frame) })
|
|
} else {
|
|
Err(dyn_frame)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Increases the reference count of the frame by one.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// The caller should ensure the following conditions:
|
|
/// 1. The physical address must represent a valid frame;
|
|
/// 2. The caller must have already held a reference to the frame.
|
|
pub(in crate::mm) unsafe fn inc_frame_ref_count(paddr: Paddr) {
|
|
debug_assert!(paddr % PAGE_SIZE == 0);
|
|
debug_assert!(paddr < max_paddr());
|
|
|
|
let vaddr: Vaddr = mapping::frame_to_meta::<PagingConsts>(paddr);
|
|
// SAFETY: `vaddr` points to a valid `MetaSlot` that will never be mutably borrowed, so taking
|
|
// an immutable reference to it is always safe.
|
|
let slot = unsafe { &*(vaddr as *const MetaSlot) };
|
|
|
|
// SAFETY: We have already held a reference to the frame.
|
|
unsafe { slot.inc_ref_count() };
|
|
}
|