Skip to content

Commit e9671c0

Browse files
mingzi47AsakuraMizu
authored andcommitted
feat: support CopyOn-Write mechanism (#50)
* Add basc COW - support x86_64 refactor: improve page fault handling and page management - Fix whitespace in PageFaultErrorCode flags - Simplify page fault COW logic - Rename page ref count methods for clarity - Move page copy logic to Page struct - Add better documentation for page management refactor: improve copy_with_cow - use `insert_area` - add `MemorySet` patch fmt: fmt chore: update test workflow dependencies - Add memory_set crate dependency - Add memory_addr crate dependency refactor: copy_with_cow and add support alloc contiguous pages - Remove myself git dependencies for memory_set and memory_addr - Update Cargo.toml and Cargo.lock - Adjust page allocation to support contiguous pages - Improve COW handling with page size parameter * Squash merge cow-backend into cow * refactor: replace Arc-based frame tracking with frame info table - Remove Arc and FrameTracker in favor of atomic ref counting - Add frameinfo module for physical frame management - Simplify COW fault handling with direct frame operations - Update backend allocator to use new frame tracking system - only support with x86 * refactor: simplify address space handling logic - Remove redundant backend matching in fork - Reorganize COW fault handling comments - Clean up page table update flow * refactor: improve memory management safety - Replace ok() with expect() for protect/map operations - Simplify page copy with copy_nonoverlapping - Add physical address validation in phys_to_pfn * chore: clean up * refactor: Rename `populate_area` to `ensure_region_mapped` with COW support - Renamed `populate_area` to `ensure_region_mapped` - Refactored `handle_cow_fault` to take a mutable page table reference * chore: Modify according to comment * refactor: replace PageIter4K with PageIterWrapper in AddrSpace - Use align from backend for page iteration - Remove TODO and FIXME comments - Simplify page iteration logic * refactor: simplify frame reference counting - Replace direct frame info access with FrameRefTable API - Remove redundant align parameter from protect() - Rename ensure_region_mapped to populate_area - Simplify COW handling logic - Consolidate frame info operations into FrameRefTable struct * refactor: (populate_area) use a iterator to walk through the area covered by the specified access interval * feat: add COW feature for memory management - Add conditional compilation for COW feature - Refactor clone_or_err into try_clone with COW support - Update frame allocation/deallocation logic - Move frameinfo module behind feature flag - Add new cow feature to Cargo.toml * refactor: simplify COW handling and frame table initialization - Consolidate COW fault handling logic - Replace LazyInit with lazy_static for frame table - Remove redundant frame table initialization - Clean up memory area copying logic * refactor: reorganize memory management modules - Move PageIterWrapper to root module - Clean up unused imports - Simplify cow feature logic - Fix docstring alignment - Remove redundant page size handling * doc: fix doc link
1 parent 8bba5f5 commit e9671c0

File tree

9 files changed

+368
-79
lines changed

9 files changed

+368
-79
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

modules/axdma/src/dma.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@ use core::{alloc::Layout, ptr::NonNull};
22

33
use allocator::{AllocError, AllocResult, BaseAllocator, ByteAllocator};
44
use axalloc::{DefaultByteAllocator, global_allocator};
5-
use axhal::{
6-
mem::virt_to_phys,
7-
paging::{MappingFlags, PageSize},
8-
};
5+
use axhal::{mem::virt_to_phys, paging::MappingFlags};
96
use kspin::SpinNoIrq;
107
use log::{debug, error};
118
use memory_addr::{PAGE_SIZE_4K, VirtAddr, va};
@@ -97,7 +94,7 @@ impl DmaAllocator {
9794
let expand_size = num_pages * PAGE_SIZE_4K;
9895
axmm::kernel_aspace()
9996
.lock()
100-
.protect(vaddr, expand_size, flags, PageSize::Size4K)
97+
.protect(vaddr, expand_size, flags)
10198
.map_err(|e| {
10299
error!("change table flag fail: {e:?}");
103100
AllocError::NoMemory

modules/axmm/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@ documentation = "https://arceos-org.github.io/arceos/axmm/index.html"
1212
[features]
1313
default = []
1414
copy-from = ["page_table_multiarch/copy-from"]
15+
cow = []
1516

1617
[dependencies]
1718
axhal = { workspace = true, features = ["paging"] }
1819
axalloc = { workspace = true }
1920
axconfig = { workspace = true }
2021

22+
lazy_static = { version = "1.5", features = ["spin_no_std"] }
2123
log = "=0.4.21"
2224
axerrno = "0.1"
2325
lazyinit = "0.2"

modules/axmm/src/aspace.rs

Lines changed: 254 additions & 67 deletions
Large diffs are not rendered by default.

modules/axmm/src/backend/alloc.rs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
use crate::backend::page_iter_wrapper::PageIterWrapper;
1+
use crate::page_iter_wrapper::PageIterWrapper;
22
use axalloc::global_allocator;
33
use axhal::mem::{phys_to_virt, virt_to_phys};
44
use axhal::paging::{MappingFlags, PageSize, PageTable};
55
use memory_addr::{PAGE_SIZE_4K, PhysAddr, VirtAddr};
66

7+
#[cfg(feature = "cow")]
8+
use crate::frameinfo::frame_table;
9+
710
use super::Backend;
811

912
/// Allocates a physical frame, with an option to zero it out.
@@ -25,14 +28,18 @@ use super::Backend;
2528
/// - If `zeroed` is `true`, the function uses `unsafe` operations to zero out the memory.
2629
/// - The allocated memory must be accessed via its physical address, which requires
2730
/// conversion using `virt_to_phys`.
28-
fn alloc_frame(zeroed: bool, align: PageSize) -> Option<PhysAddr> {
31+
pub fn alloc_frame(zeroed: bool, align: PageSize) -> Option<PhysAddr> {
2932
let page_size: usize = align.into();
3033
let num_pages = page_size / PAGE_SIZE_4K;
3134
let vaddr = VirtAddr::from(global_allocator().alloc_pages(num_pages, page_size).ok()?);
3235
if zeroed {
3336
unsafe { core::ptr::write_bytes(vaddr.as_mut_ptr(), 0, page_size) };
3437
}
3538
let paddr = virt_to_phys(vaddr);
39+
40+
#[cfg(feature = "cow")]
41+
frame_table().inc_ref(paddr);
42+
3643
Some(paddr)
3744
}
3845

@@ -43,6 +50,9 @@ fn alloc_frame(zeroed: bool, align: PageSize) -> Option<PhysAddr> {
4350
/// The size of the memory to be freed is determined by the `align` parameter,
4451
/// which must be a multiple of 4KiB.
4552
///
53+
/// If `cow` feature is enabled, this function decreases the reference count associated with the frame.
54+
/// When the reference count reaches 1, it actually frees the frame memory.
55+
///
4656
/// # Parameters
4757
/// - `frame`: The physical address of the memory to be freed.
4858
/// - `align`: The alignment requirement for the memory, must be a multiple of 4KiB.
@@ -52,10 +62,15 @@ fn alloc_frame(zeroed: bool, align: PageSize) -> Option<PhysAddr> {
5262
/// otherwise undefined behavior may occur.
5363
/// - If the deallocation fails, the function will call `panic!`. Details about
5464
/// the failure can be obtained from the global memory allocator’s error messages.
55-
fn dealloc_frame(frame: PhysAddr, align: PageSize) {
65+
pub fn dealloc_frame(frame: PhysAddr, align: PageSize) {
66+
#[cfg(feature = "cow")]
67+
if frame_table().dec_ref(frame) > 1 {
68+
return;
69+
}
70+
71+
let vaddr = phys_to_virt(frame);
5672
let page_size: usize = align.into();
5773
let num_pages = page_size / PAGE_SIZE_4K;
58-
let vaddr = phys_to_virt(frame);
5974
global_allocator().dealloc_pages(vaddr.as_usize(), num_pages);
6075
}
6176

modules/axmm/src/backend/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
use axhal::paging::{MappingFlags, PageTable};
44
use memory_addr::VirtAddr;
55
use memory_set::MappingBackend;
6-
pub use page_iter_wrapper::PageIterWrapper;
76
use page_table_multiarch::PageSize;
87

98
mod alloc;
109
mod linear;
11-
mod page_iter_wrapper;
10+
11+
pub use alloc::{alloc_frame, dealloc_frame};
1212

1313
/// A unified enum type for different memory mapping backends.
1414
///

modules/axmm/src/frameinfo.rs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
//! FrameInfo
2+
//!
3+
//! A simple physical FrameInfo manager is provided to track and manage
4+
//! the reference count for every 4KB memory page frame in the system.
5+
//!
6+
//! There is a [`FrameInfo`] struct for each physical page frame
7+
//! that keeps track of its reference count.
8+
//! NOTE: If the page is huge page, its [`FrameInfo`] is placed at the
9+
//! starting physical address.
10+
use core::{
11+
array,
12+
sync::atomic::{AtomicUsize, Ordering},
13+
};
14+
15+
use alloc::boxed::Box;
16+
use lazy_static::lazy_static;
17+
use memory_addr::PhysAddr;
18+
// 4 kb page
19+
const FRAME_SHIFT: usize = 12;
20+
21+
pub const MAX_FRAME_NUM: usize = axconfig::plat::PHYS_MEMORY_SIZE >> FRAME_SHIFT;
22+
23+
lazy_static! {
24+
static ref FRAME_INFO_TABLE: FrameRefTable = FrameRefTable::default();
25+
}
26+
27+
pub(crate) fn frame_table() -> &'static FrameRefTable {
28+
&FRAME_INFO_TABLE
29+
}
30+
31+
pub(crate) struct FrameRefTable {
32+
data: Box<[FrameInfo; MAX_FRAME_NUM]>,
33+
}
34+
35+
impl Default for FrameRefTable {
36+
fn default() -> Self {
37+
FrameRefTable {
38+
data: Box::new(array::from_fn(|_| FrameInfo::default())),
39+
}
40+
}
41+
}
42+
43+
impl FrameRefTable {
44+
fn info(&self, paddr: PhysAddr) -> &FrameInfo {
45+
let index = (paddr.as_usize() - axconfig::plat::PHYS_MEMORY_BASE) >> FRAME_SHIFT;
46+
&self.data[index]
47+
}
48+
49+
/// Increases the reference count of the frame associated with a physical address.
50+
///
51+
/// # Parameters
52+
/// - `paddr`: It must be an aligned physical address; if it's a huge page,
53+
/// it must be the starting physical address.
54+
pub fn inc_ref(&self, paddr: PhysAddr) {
55+
self.info(paddr).ref_count.fetch_add(1, Ordering::SeqCst);
56+
}
57+
58+
/// Decreases the reference count of the frame associated with a physical address.
59+
///
60+
/// - `paddr`: It must be an aligned physical address; if it's a huge page,
61+
/// it must be the starting physical address.
62+
///
63+
/// # Returns
64+
/// The updated reference count after decrementing.
65+
pub fn dec_ref(&self, paddr: PhysAddr) -> usize {
66+
self.info(paddr).ref_count.fetch_sub(1, Ordering::SeqCst)
67+
}
68+
69+
/// Returns the `FrameInfo` structure associated with a given physical address.
70+
///
71+
/// # Parameters
72+
/// - `paddr`: It must be an aligned physical address; if it's a huge page,
73+
/// it must be the starting physical address.
74+
///
75+
/// # Returns
76+
/// A reference to the `FrameInfo` associated with the given physical address.
77+
pub fn ref_count(&self, paddr: PhysAddr) -> usize {
78+
self.info(paddr).ref_count.load(Ordering::SeqCst)
79+
}
80+
}
81+
82+
#[derive(Default)]
83+
pub(crate) struct FrameInfo {
84+
ref_count: AtomicUsize,
85+
}

modules/axmm/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ extern crate alloc;
88

99
mod aspace;
1010
mod backend;
11+
#[cfg(feature = "cow")]
12+
mod frameinfo;
13+
mod page_iter_wrapper;
1114

1215
pub use self::aspace::AddrSpace;
1316
pub use self::backend::Backend;

modules/axmm/src/backend/page_iter_wrapper.rs renamed to modules/axmm/src/page_iter_wrapper.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ impl PageIterWrapper {
5656
PageSize::Size4K => PageIter4K::<VirtAddr>::new(start, end).map(Self::Size4K),
5757
PageSize::Size2M => PageIter2M::<VirtAddr>::new(start, end).map(Self::Size2M),
5858
PageSize::Size1G => PageIter1G::<VirtAddr>::new(start, end).map(Self::Size1G),
59-
_ => None,
6059
}
6160
}
6261
}

0 commit comments

Comments
 (0)