Skip to content
This repository has been archived by the owner on Sep 14, 2024. It is now read-only.

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
equation314 committed Jul 26, 2024
0 parents commit 5d76ffc
Show file tree
Hide file tree
Showing 8 changed files with 722 additions and 0 deletions.
55 changes: 55 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
name: CI

on: [push, pull_request]

jobs:
ci:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
rust-toolchain: [nightly]
targets: [x86_64-unknown-linux-gnu, x86_64-unknown-none, riscv64gc-unknown-none-elf, aarch64-unknown-none-softfloat]
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly
with:
toolchain: ${{ matrix.rust-toolchain }}
components: rust-src, clippy, rustfmt
targets: ${{ matrix.targets }}
- name: Check rust version
run: rustc --version --verbose
- name: Check code format
run: cargo fmt --all -- --check
- name: Clippy
run: cargo clippy --target ${{ matrix.targets }} --all-features -- -A clippy::new_without_default
- name: Build
run: cargo build --target ${{ matrix.targets }} --all-features
- name: Unit test
if: ${{ matrix.targets == 'x86_64-unknown-linux-gnu' }}
run: cargo test --target ${{ matrix.targets }} -- --nocapture

doc:
runs-on: ubuntu-latest
strategy:
fail-fast: false
permissions:
contents: write
env:
default-branch: ${{ format('refs/heads/{0}', github.event.repository.default_branch) }}
RUSTDOCFLAGS: -D rustdoc::broken_intra_doc_links -D missing-docs
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly
- name: Build docs
continue-on-error: ${{ github.ref != env.default-branch && github.event_name != 'pull_request' }}
run: |
cargo doc --no-deps --all-features
printf '<meta http-equiv="refresh" content="0;url=%s/index.html">' $(cargo tree | head -1 | cut -d' ' -f1) > target/doc/index.html
- name: Deploy to Github Pages
if: ${{ github.ref == env.default-branch }}
uses: JamesIves/github-pages-deploy-action@v4
with:
single-commit: true
branch: gh-pages
folder: target/doc
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/target
/.vscode
.DS_Store
Cargo.lock
15 changes: 15 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "memory_set"
version = "0.1.0"
edition = "2021"
authors = ["Yuekai Jia <equation618@gmail.com>"]
description = "Data structures and operations for managing memory mappings"
license = "GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0"
homepage = "https://github.com/rcore-os/arceos"
repository = "https://github.com/arceos-org/memory_set"
documentation = "https://docs.rs/memory_set"
keywords = ["arceos", "virtual-memory", "memory-area", "mmap"]
categories = ["os", "memory-management", "no-std"]

[dependencies]
memory_addr = "0.2"
69 changes: 69 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# memory_set

[![Crates.io](https://img.shields.io/crates/v/memory_set)](https://crates.io/crates/memory_set)
[![Docs.rs](https://docs.rs/memory_set/badge.svg)](https://docs.rs/memory_set)
[![CI](https://github.com/arceos-org/memory_set/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/arceos-org/memory_set/actions/workflows/ci.yml)

Data structures and operations for managing memory mappings.

It is useful to implement [`mmap`][1] and [`munmap`][1].

[1]: https://man7.org/linux/man-pages/man2/mmap.2.html

## Examples

```rust
use memory_addr::{va, va_range, VirtAddr};
use memory_set::{MappingBackend, MemoryArea, MemorySet};

const MAX_ADDR: usize = 0x10000;

/// A mock memory flags.
type MockFlags = u8;
/// A mock page table, which is a simple array that maps addresses to flags.
type MockPageTable = [MockFlags; MAX_ADDR];

/// A mock mapping backend that manipulates the page table on `map` and `unmap`.
#[derive(Clone)]
struct MockBackend;

let mut pt = [0; MAX_ADDR];
let mut memory_set = MemorySet::<MockFlags, MockPageTable, MockBackend>::new();

// Map [0x1000..0x5000).
memory_set.map(
/* area: */ MemoryArea::new(va!(0x1000), 0x4000, 1, MockBackend),
/* page_table: */ &mut pt,
/* unmap_overlap */ false,
).unwrap();
// Unmap [0x2000..0x4000), will split the area into two parts.
memory_set.unmap(va!(0x2000), 0x2000, &mut pt).unwrap();

let areas = memory_set.iter().collect::<Vec<_>>();
assert_eq!(areas.len(), 2);
assert_eq!(areas[0].va_range(), va_range!(0x1000..0x2000));
assert_eq!(areas[1].va_range(), va_range!(0x4000..0x5000));

// Underlying operations to do when manipulating mappings.
impl MappingBackend<MockFlags, MockPageTable> for MockBackend {
fn map(&self, start: VirtAddr, size: usize, flags: MockFlags, pt: &mut MockPageTable) -> bool {
for entry in pt.iter_mut().skip(start.as_usize()).take(size) {
if *entry != 0 {
return false;
}
*entry = flags;
}
true
}

fn unmap(&self, start: VirtAddr, size: usize, pt: &mut MockPageTable) -> bool {
for entry in pt.iter_mut().skip(start.as_usize()).take(size) {
if *entry == 0 {
return false;
}
*entry = 0;
}
true
}
}
```
157 changes: 157 additions & 0 deletions src/area.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
use core::fmt;
use core::marker::PhantomData;

use memory_addr::{VirtAddr, VirtAddrRange};

use crate::{MappingError, MappingResult};

/// Underlying operations to do when manipulating mappings within the specific
/// [`MemoryArea`].
///
/// The backend can be different for different memory areas. e.g., for linear
/// mappings, the target physical address is known when it is added to the page
/// table. For lazy mappings, an empty mapping needs to be added to the page table
/// to trigger a page fault.
pub trait MappingBackend<F: Copy, P>: Clone {
/// What to do when mapping a region within the area with the given flags.
fn map(&self, start: VirtAddr, size: usize, flags: F, page_table: &mut P) -> bool;
/// What to do when unmaping a memory region within the area.
fn unmap(&self, start: VirtAddr, size: usize, page_table: &mut P) -> bool;
}

/// A memory area represents a continuous range of virtual memory with the same
/// flags.
///
/// The target physical memory frames are determined by [`MappingBackend`] and
/// may not be contiguous.
pub struct MemoryArea<F: Copy, P, B: MappingBackend<F, P>> {
va_range: VirtAddrRange,
flags: F,
backend: B,
_phantom: PhantomData<(F, P)>,
}

impl<F: Copy, P, B: MappingBackend<F, P>> MemoryArea<F, P, B> {
/// Creates a new memory area.
pub const fn new(start: VirtAddr, size: usize, flags: F, backend: B) -> Self {
Self {
va_range: VirtAddrRange::from_start_size(start, size),
flags,
backend,
_phantom: PhantomData,
}
}

/// Returns the virtual address range.
pub const fn va_range(&self) -> VirtAddrRange {
self.va_range
}

/// Returns the memory flags, e.g., the permission bits.
pub const fn flags(&self) -> F {
self.flags
}

/// Returns the start address of the memory area.
pub const fn start(&self) -> VirtAddr {
self.va_range.start
}

/// Returns the end address of the memory area.
pub const fn end(&self) -> VirtAddr {
self.va_range.end
}

/// Returns the size of the memory area.
pub const fn size(&self) -> usize {
self.va_range.size()
}

/// Returns the mapping backend of the memory area.
pub const fn backend(&self) -> &B {
&self.backend
}
}

impl<F: Copy, P, B: MappingBackend<F, P>> MemoryArea<F, P, B> {
/// Maps the whole memory area in the page table.
pub(crate) fn map_area(&self, page_table: &mut P) -> MappingResult {
self.backend
.map(self.start(), self.size(), self.flags, page_table)
.then_some(())
.ok_or(MappingError::BadState)
}

/// Unmaps the whole memory area in the page table.
pub(crate) fn unmap_area(&self, page_table: &mut P) -> MappingResult {
self.backend
.unmap(self.start(), self.size(), page_table)
.then_some(())
.ok_or(MappingError::BadState)
}

/// Shrinks the memory area at the left side.
///
/// The start address of the memory area is increased by `new_size`. The
/// shrunk part is unmapped.
pub(crate) fn shrink_left(&mut self, new_size: usize, page_table: &mut P) -> MappingResult {
let unmap_size = self.size() - new_size;
if !self.backend.unmap(self.start(), unmap_size, page_table) {
return Err(MappingError::BadState);
}
self.va_range.start += unmap_size;
Ok(())
}

/// Shrinks the memory area at the right side.
///
/// The end address of the memory area is decreased by `new_size`. The
/// shrunk part is unmapped.
pub(crate) fn shrink_right(&mut self, new_size: usize, page_table: &mut P) -> MappingResult {
let unmap_size = self.size() - new_size;
if !self
.backend
.unmap(self.start() + new_size, unmap_size, page_table)
{
return Err(MappingError::BadState);
}
self.va_range.end -= unmap_size;
Ok(())
}

/// Splits the memory area at the given position.
///
/// The original memory area is shrunk to the left part, and the right part
/// is returned.
///
/// Returns `None` if the given position is not in the memory area, or one
/// of the parts is empty after splitting.
pub(crate) fn split(&mut self, pos: VirtAddr) -> Option<Self> {
let start = self.start();
let end = self.end();
if start < pos && pos < end {
let new_area = Self::new(
pos,
end.as_usize() - pos.as_usize(),
self.flags,
self.backend.clone(),
);
self.va_range.end = pos;
Some(new_area)
} else {
None
}
}
}

impl<F, P, B: MappingBackend<F, P>> fmt::Debug for MemoryArea<F, P, B>
where
F: fmt::Debug + Copy,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("MemoryArea")
.field("va_range", &self.va_range)
.field("flags", &self.flags)
.finish()
}
}
27 changes: 27 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#![cfg_attr(not(test), no_std)]
#![doc = include_str!("../README.md")]

extern crate alloc;

mod area;
mod set;

#[cfg(test)]
mod tests;

pub use self::area::{MappingBackend, MemoryArea};
pub use self::set::MemorySet;

/// Error type for memory mapping operations.
#[derive(Debug, Eq, PartialEq)]
pub enum MappingError {
/// Invalid parameter (e.g., `addr`, `size`, `flags`, etc.)
InvalidParam,
/// The given range overlaps with an existing mapping.
AlreadyExists,
/// The backend page table is in a bad state.
BadState,
}

/// A [`Result`] type with [`MappingError`] as the error type.
pub type MappingResult<T = ()> = Result<T, MappingError>;
Loading

0 comments on commit 5d76ffc

Please sign in to comment.