starry-kernel 0.5.10

A Linux-compatible OS kernel built on ArceOS unikernel
Documentation
//! Memory mapping backends.
use alloc::{
    boxed::Box,
    string::{String, ToString},
    sync::Arc,
};

use ax_alloc::{UsageKind, global_allocator};
use ax_errno::{AxError, AxResult};
use ax_hal::{
    mem::{phys_to_virt, virt_to_phys},
    paging::{MappingFlags, PageSize, PageTable, PageTableCursor},
};
use ax_memory_addr::{DynPageIter, PAGE_SIZE_4K, PhysAddr, VirtAddr, VirtAddrRange};
use ax_memory_set::MappingBackend;
use ax_sync::Mutex;
use enum_dispatch::enum_dispatch;

mod cow;
mod file;
mod linear;
mod shared;

pub use self::shared::SharedPages;
use super::AddrSpace;

fn divide_page(size: usize, page_size: PageSize) -> usize {
    assert!(page_size.is_aligned(size), "unaligned");
    size >> (page_size as usize).trailing_zeros()
}

fn alloc_frame(zeroed: bool, size: PageSize) -> AxResult<PhysAddr> {
    let page_size = size as usize;
    let num_pages = page_size / PAGE_SIZE_4K;
    let vaddr = VirtAddr::from(
        global_allocator()
            .alloc_pages(num_pages, page_size, UsageKind::VirtMem)
            .map_err(|_| AxError::NoMemory)?,
    );
    if zeroed {
        unsafe { core::ptr::write_bytes(vaddr.as_mut_ptr(), 0, page_size) };
    }
    let paddr = virt_to_phys(vaddr);

    Ok(paddr)
}

fn dealloc_frame(frame: PhysAddr, align: PageSize) {
    let vaddr = phys_to_virt(frame);
    let page_size: usize = align.into();
    let num_pages = page_size / PAGE_SIZE_4K;
    global_allocator().dealloc_pages(vaddr.as_usize(), num_pages, UsageKind::VirtMem);
}

fn pages_in(range: VirtAddrRange, align: PageSize) -> AxResult<DynPageIter<VirtAddr>> {
    DynPageIter::new(range.start, range.end, align as usize).ok_or(AxError::InvalidInput)
}

type PopulateCallback = Box<dyn FnOnce(&mut AddrSpace)>;

#[enum_dispatch]
pub trait BackendOps {
    /// Returns the page size of the backend.
    fn page_size(&self) -> PageSize;

    /// Map a memory region.
    fn map(&self, range: VirtAddrRange, flags: MappingFlags, pt: &mut PageTableCursor) -> AxResult;

    /// Unmap a memory region.
    fn unmap(&self, range: VirtAddrRange, pt: &mut PageTableCursor) -> AxResult;

    /// Called before a memory region is protected.
    fn on_protect(
        &self,
        _range: VirtAddrRange,
        _new_flags: MappingFlags,
        _pt: &mut PageTableCursor,
    ) -> AxResult {
        Ok(())
    }

    /// Populate a memory region and return how many pages now satisfy
    /// `access_flags`.
    ///
    /// If another thread has already mapped the page with sufficient permissions,
    /// treat it as populated.
    fn populate(
        &self,
        _range: VirtAddrRange,
        _flags: MappingFlags,
        _access_flags: MappingFlags,
        _pt: &mut PageTableCursor,
    ) -> AxResult<(usize, Option<PopulateCallback>)> {
        Ok((0, None))
    }

    /// Duplicates this mapping for use in a different page table.
    ///
    /// This differs from `clone`, which is designed for splitting a mapping
    /// within the same table.
    ///
    /// [`BackendOps::map`] will be latter called to the returned backend.
    fn clone_map(
        &self,
        range: VirtAddrRange,
        flags: MappingFlags,
        old_pt: &mut PageTableCursor,
        new_pt: &mut PageTableCursor,
        new_aspace: &Arc<Mutex<AddrSpace>>,
    ) -> AxResult<Backend>;

    /// Splits the backend into two at the given position, and returns the backend for the upper part.
    ///
    /// The original backend is shrunk to the lower part.
    ///
    /// Returns `None` if the given position is not in the memory area, or one
    /// of the parts is empty after splitting.
    fn split(&mut self, align_diff: usize) -> Option<Backend>;

    /// Shrinks the backend from the left by the given size.
    fn shrink_left(&mut self, _shrink_size: usize);

    /// Shrinks the backend from the right by the given size.
    fn shrink_right(&mut self, _shrink_size: usize);
}

/// A unified enum type for different memory mapping backends.
#[derive(Clone)]
#[enum_dispatch(BackendOps)]
pub enum Backend {
    Linear(linear::LinearBackend),
    Cow(cow::CowBackend),
    Shared(shared::SharedBackend),
    File(file::FileBackend),
}

pub struct BackendFileInfo {
    pub path: String,
    pub offset: Option<u64>,
    pub inode: Option<u64>,
    pub dev: Option<u64>,
    pub shared: bool,
}

impl Backend {
    /// Returns the file information if this is a file-backed mapping, or `None` otherwise.
    ///
    /// The returned tuple contains the file name, offset, inode and whether the mapping is shared.
    pub fn file_info(&self) -> AxResult<BackendFileInfo> {
        match self {
            Backend::Cow(b) => b.file_info(),
            Backend::Linear(b) => Ok(BackendFileInfo {
                path: "".to_string(),
                offset: None,
                inode: None,
                dev: None,
                shared: b.is_shared(),
            }),
            Backend::Shared(_) => Ok(BackendFileInfo {
                path: "".to_string(),
                offset: None,
                inode: None,
                dev: None,
                shared: true,
            }),
            Backend::File(b) => b.file_info(),
        }
    }

    /// Clone with a different base address (for mremap moves).
    /// `src_offset` is the distance from the original VMA start to the
    /// mremap source address, used to adjust file/page offsets.
    pub fn relocated(
        &self,
        new_start: VirtAddr,
        src_offset: usize,
        aspace: &Arc<Mutex<AddrSpace>>,
    ) -> AxResult<Self> {
        let adjusted = new_start
            .as_usize()
            .checked_sub(src_offset)
            .map(VirtAddr::from)
            .ok_or(AxError::InvalidInput)?;
        Ok(match self {
            Self::Cow(cb) => Self::Cow(cb.with_start(adjusted)),
            Self::Shared(sb) => Self::Shared(sb.with_start(adjusted)),
            Self::Linear(_) => return Err(AxError::OperationNotSupported),
            Self::File(fb) => Self::File(fb.with_start(adjusted, aspace)),
        })
    }
}

impl MappingBackend for Backend {
    type Addr = VirtAddr;
    type Flags = MappingFlags;
    type PageTable = PageTable;

    fn map(&self, start: VirtAddr, size: usize, flags: MappingFlags, pt: &mut PageTable) -> bool {
        let range = VirtAddrRange::from_start_size(start, size);
        if let Err(err) = BackendOps::map(self, range, flags, &mut pt.cursor()) {
            warn!("Failed to map area: {:?}", err);
            false
        } else {
            true
        }
    }

    fn unmap(&self, start: VirtAddr, size: usize, pt: &mut PageTable) -> bool {
        let range = VirtAddrRange::from_start_size(start, size);
        if let Err(err) = BackendOps::unmap(self, range, &mut pt.cursor()) {
            warn!("Failed to unmap area: {:?}", err);
            false
        } else {
            true
        }
    }

    fn protect(
        &self,
        start: Self::Addr,
        size: usize,
        new_flags: Self::Flags,
        pt: &mut Self::PageTable,
    ) -> bool {
        let range = VirtAddrRange::from_start_size(start, size);
        let mut cursor = pt.cursor();
        if let Err(err) = BackendOps::on_protect(self, range, new_flags, &mut cursor) {
            warn!("Failed to protect area: {:?}", err);
            return false;
        }
        cursor.protect_region(start, size, new_flags).is_ok()
    }

    fn split(&mut self, align_diff: usize) -> Option<Self> {
        BackendOps::split(self, align_diff)
    }

    fn shrink_left(&mut self, shrink_size: usize) {
        BackendOps::shrink_left(self, shrink_size)
    }

    fn shrink_right(&mut self, shrink_size: usize) {
        BackendOps::shrink_right(self, shrink_size)
    }
}