diff --git a/kernel/aster-nix/src/fs/utils/page_cache.rs b/kernel/aster-nix/src/fs/utils/page_cache.rs index d741ed057..252349dff 100644 --- a/kernel/aster-nix/src/fs/utils/page_cache.rs +++ b/kernel/aster-nix/src/fs/utils/page_cache.rs @@ -198,6 +198,15 @@ impl Pager for PageCacheManager { Ok(()) } + + fn commit_overwrite(&self, idx: usize) -> Result { + if let Some(page) = self.pages.lock().get(&idx) { + return Ok(page.frame.clone()); + } + + let page = Page::alloc_zero()?; + Ok(self.pages.lock().get_or_insert(idx, || page).frame.clone()) + } } #[derive(Debug)] diff --git a/kernel/aster-nix/src/vm/vmo/dyn_cap.rs b/kernel/aster-nix/src/vm/vmo/dyn_cap.rs index c02d5c596..15051aff5 100644 --- a/kernel/aster-nix/src/vm/vmo/dyn_cap.rs +++ b/kernel/aster-nix/src/vm/vmo/dyn_cap.rs @@ -7,7 +7,7 @@ use aster_rights::{Rights, TRights}; use super::{ options::{VmoCowChild, VmoSliceChild}, - Vmo, VmoChildOptions, VmoRightsOp, + CommitFlags, Vmo, VmoChildOptions, VmoRightsOp, }; use crate::prelude::*; @@ -84,7 +84,8 @@ impl Vmo { /// The method requires the Write right. pub fn commit(&self, range: Range) -> Result<()> { self.check_rights(Rights::WRITE)?; - self.0.commit_and_operate(&range, |_| {}, false)?; + self.0 + .commit_and_operate(&range, |_| {}, CommitFlags::empty())?; Ok(()) } diff --git a/kernel/aster-nix/src/vm/vmo/mod.rs b/kernel/aster-nix/src/vm/vmo/mod.rs index 1ec65bcd6..12f186c97 100644 --- a/kernel/aster-nix/src/vm/vmo/mod.rs +++ b/kernel/aster-nix/src/vm/vmo/mod.rs @@ -203,6 +203,27 @@ fn clone_page(page: &Frame) -> Result { Ok(new_page) } +bitflags! { + /// Commit Flags. + pub struct CommitFlags: u8 { + /// Set this flag if the page will be written soon. + const WILL_WRITE = 1; + /// Set this flag if the page will be completely overwritten. + /// This flag contains the WILL_WRITE flag. + const WILL_OVERWRITE = 3; + } +} + +impl CommitFlags { + pub fn will_write(&self) -> bool { + self.contains(Self::WILL_WRITE) + } + + pub fn will_overwrite(&self) -> bool { + self.contains(Self::WILL_OVERWRITE) + } +} + impl Vmo_ { /// Prepare a new `Frame` for the target index in pages, returning the new page as well as /// whether this page needs to be marked as exclusive. @@ -218,7 +239,7 @@ impl Vmo_ { &self, page_idx: usize, is_cow_vmo: bool, - will_write: bool, + commit_flags: CommitFlags, ) -> Result<(Frame, bool)> { let (page, should_mark_exclusive) = match &self.pager { None => { @@ -232,7 +253,7 @@ impl Vmo_ { // VMO requires COW and the prepared page is about to undergo a write operation. // At this point, the `Frame` obtained from the pager needs to be cloned to // avoid subsequent modifications affecting the content of the `Frame` in the pager. - let trigger_cow = is_cow_vmo && will_write; + let trigger_cow = is_cow_vmo && commit_flags.will_write(); if trigger_cow { // Condition 3. (clone_page(&page)?, true) @@ -245,11 +266,25 @@ impl Vmo_ { Ok((page, should_mark_exclusive)) } + /// Prepare a new `Frame` for the target index in pages, returning the new page. + /// This function is only used when the new `Frame` will be completely overwritten + /// and we do not care about the content on the page. + fn prepare_overwrite(&self, page_idx: usize, is_cow_vmo: bool) -> Result { + let page = if let Some(pager) = &self.pager + && !is_cow_vmo + { + pager.commit_overwrite(page_idx)? + } else { + FrameAllocOptions::new(1).alloc_single()? + }; + Ok(page) + } + fn commit_with_cursor( &self, cursor: &mut CursorMut<'_, Frame, VmoMark>, is_cow_vmo: bool, - will_write: bool, + commit_flags: CommitFlags, ) -> Result { let (new_page, is_exclusive) = { let is_exclusive = cursor.is_marked(VmoMark::ExclusivePage); @@ -257,15 +292,26 @@ impl Vmo_ { // The necessary and sufficient condition for triggering the COW mechanism is that // the current VMO requires copy-on-write, there is an impending write operation to the page, // and the page is not exclusive. - let trigger_cow = is_cow_vmo && will_write && !is_exclusive; + let trigger_cow = is_cow_vmo && commit_flags.will_write() && !is_exclusive; if !trigger_cow { // Fast path: return the page directly. return Ok(committed_page.clone()); } - (clone_page(&committed_page)?, true) + if commit_flags.will_overwrite() { + (FrameAllocOptions::new(1).alloc_single()?, true) + } else { + (clone_page(&committed_page)?, true) + } + } else if commit_flags.will_overwrite() { + // In this case, the page will be completely overwritten. The page only needs to + // be marked as `ExclusivePage` when the current VMO is a cow VMO. + ( + self.prepare_overwrite(cursor.index() as usize, is_cow_vmo)?, + is_cow_vmo, + ) } else { - self.prepare_page(cursor.index() as usize, is_cow_vmo, will_write)? + self.prepare_page(cursor.index() as usize, is_cow_vmo, commit_flags)? } }; @@ -284,7 +330,12 @@ impl Vmo_ { self.pages.with(|pages, size| { let is_cow_vmo = pages.is_marked(VmoMark::CowVmo); let mut cursor = pages.cursor_mut(page_idx as u64); - self.commit_with_cursor(&mut cursor, is_cow_vmo, will_write) + let commit_flags = if will_write { + CommitFlags::WILL_WRITE + } else { + CommitFlags::empty() + }; + self.commit_with_cursor(&mut cursor, is_cow_vmo, commit_flags) }) } @@ -310,7 +361,7 @@ impl Vmo_ { &self, range: &Range, mut operate: F, - will_write: bool, + commit_flags: CommitFlags, ) -> Result<()> where F: FnMut(Frame), @@ -328,7 +379,7 @@ impl Vmo_ { let mut cursor = pages.cursor_mut(page_idx_range.start as u64); for page_idx in page_idx_range { let committed_page = - self.commit_with_cursor(&mut cursor, is_cow_vmo, will_write)?; + self.commit_with_cursor(&mut cursor, is_cow_vmo, commit_flags)?; operate(committed_page); cursor.next(); } @@ -356,7 +407,7 @@ impl Vmo_ { read_offset = 0; }; - self.commit_and_operate(&read_range, read, false) + self.commit_and_operate(&read_range, read, CommitFlags::empty()) } /// Write the specified amount of buffer content starting from the target offset in the VMO. @@ -366,12 +417,30 @@ impl Vmo_ { let mut write_offset = offset % PAGE_SIZE; let mut buf_reader: VmReader = buf.into(); - let write = move |page: Frame| { + let mut write = move |page: Frame| { page.writer().skip(write_offset).write(&mut buf_reader); write_offset = 0; }; - self.commit_and_operate(&write_range, write, true)?; + if write_range.len() < PAGE_SIZE { + self.commit_and_operate(&write_range, write, CommitFlags::WILL_WRITE)?; + } else { + let temp = write_range.start + PAGE_SIZE - 1; + let up_align_start = temp - temp % PAGE_SIZE; + let down_align_end = write_range.end - write_range.end % PAGE_SIZE; + if write_range.start != up_align_start { + let head_range = write_range.start..up_align_start; + self.commit_and_operate(&head_range, &mut write, CommitFlags::WILL_WRITE)?; + } + if up_align_start != down_align_end { + let mid_range = up_align_start..down_align_end; + self.commit_and_operate(&mid_range, &mut write, CommitFlags::WILL_OVERWRITE)?; + } + if down_align_end != write_range.end { + let tail_range = down_align_end..write_range.end; + self.commit_and_operate(&tail_range, &mut write, CommitFlags::WILL_WRITE)?; + } + } let is_cow_vmo = self.is_cow_vmo(); if let Some(pager) = &self.pager diff --git a/kernel/aster-nix/src/vm/vmo/pager.rs b/kernel/aster-nix/src/vm/vmo/pager.rs index f6d7671ed..09870a231 100644 --- a/kernel/aster-nix/src/vm/vmo/pager.rs +++ b/kernel/aster-nix/src/vm/vmo/pager.rs @@ -50,4 +50,9 @@ pub trait Pager: Send + Sync { /// such an assumption for its correctness; instead, it should simply ignore the /// call or return an error. fn decommit_page(&self, idx: usize) -> Result<()>; + + /// Ask the pager to provide a frame at a specified index. + /// Notify the pager that the frame will be fully overwritten soon, so pager can + /// choose not to initialize it. + fn commit_overwrite(&self, idx: usize) -> Result; } diff --git a/kernel/aster-nix/src/vm/vmo/static_cap.rs b/kernel/aster-nix/src/vm/vmo/static_cap.rs index cc0b77d96..02d273408 100644 --- a/kernel/aster-nix/src/vm/vmo/static_cap.rs +++ b/kernel/aster-nix/src/vm/vmo/static_cap.rs @@ -8,7 +8,7 @@ use aster_rights_proc::require; use super::{ options::{VmoCowChild, VmoSliceChild}, - Vmo, VmoChildOptions, VmoRightsOp, + CommitFlags, Vmo, VmoChildOptions, VmoRightsOp, }; use crate::prelude::*; @@ -84,7 +84,8 @@ impl Vmo> { /// The method requires the Write right. #[require(R > Write)] pub fn commit(&self, range: Range) -> Result<()> { - self.0.commit_and_operate(&range, |_| {}, false)?; + self.0 + .commit_and_operate(&range, |_| {}, CommitFlags::empty())?; Ok(()) }