diff --git a/Cargo.lock b/Cargo.lock index 69d95b967..7f59ad4e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1757,7 +1757,7 @@ dependencies = [ [[package]] name = "xarray" version = "0.1.0" -source = "git+https://github.com/asterinas/xarray?rev=72a4067#72a4067a65e8f94cfc193f5f19ebc981c1de9de7" +source = "git+https://github.com/asterinas/xarray#1dad5d9b74aac30193bd242a97077b4c54933830" dependencies = [ "smallvec", ] diff --git a/kernel/aster-nix/src/vm/vmo/dyn_cap.rs b/kernel/aster-nix/src/vm/vmo/dyn_cap.rs index c1a00eef0..0aa548764 100644 --- a/kernel/aster-nix/src/vm/vmo/dyn_cap.rs +++ b/kernel/aster-nix/src/vm/vmo/dyn_cap.rs @@ -5,72 +5,14 @@ use core::ops::Range; use aster_rights::{Rights, TRights}; use ostd::mm::{Frame, VmIo}; -use super::{ - options::{VmoCowChild, VmoSliceChild}, - CommitFlags, Vmo, VmoChildOptions, VmoRightsOp, -}; +use super::{CommitFlags, Vmo, VmoRightsOp}; use crate::prelude::*; impl Vmo { - /// Creates a new slice VMO through a set of VMO child options. - /// - /// # Example - /// - /// ``` - /// let parent = VmoOptions::new(PAGE_SIZE).alloc().unwrap(); - /// let child_size = parent.size(); - /// let child = parent.new_slice_child(0..child_size).alloc().unwrap(); - /// assert!(child.size() == child_size); - /// ``` - /// - /// For more details on the available options, see `VmoChildOptions`. - /// - /// # Access rights - /// - /// This method requires the Dup right. - /// - /// The new VMO child will be of the same capability flavor as the parent; - /// so are the access rights. - pub fn new_slice_child( - &self, - range: Range, - ) -> Result> { - let dup_self = self.dup()?; - Ok(VmoChildOptions::new_slice_rights(dup_self, range)) - } - - /// Creates a new COW VMO through a set of VMO child options. - /// - /// # Example - /// - /// ``` - /// let parent = VmoOptions::new(PAGE_SIZE).alloc().unwrap(); - /// let child_size = 2 * parent.size(); - /// let child = parent.new_cow_child(0..child_size).alloc().unwrap(); - /// assert!(child.size() == child_size); - /// ``` - /// - /// For more details on the available options, see `VmoChildOptions`. - /// - /// # Access rights - /// - /// This method requires the Dup right. - /// - /// The new VMO child will be of the same capability flavor as the parent. - /// The child will be given the access rights of the parent - /// plus the Write right. - pub fn new_cow_child( - &self, - range: Range, - ) -> Result> { - let dup_self = self.dup()?; - Ok(VmoChildOptions::new_cow(dup_self, range)) - } - - /// commit a page at specific offset + /// Commits a page at specific offset pub fn commit_page(&self, offset: usize) -> Result { self.check_rights(Rights::WRITE)?; - self.0.commit_page(offset, false) + self.0.commit_page(offset) } /// Commits the pages specified in the range (in bytes). @@ -137,6 +79,29 @@ impl Vmo { Ok(Self(self.0.clone(), self.1)) } + /// Creates a new VMO that replicates the original capability, initially representing + /// the same physical pages. + /// Changes to the permissions and commits/replacements of internal pages in the original VMO + /// and the new VMO will not affect each other. + /// + /// # Access rights + /// + /// The method requires the Dup right. + pub fn dup_independent(&self) -> Result { + self.check_rights(Rights::DUP | Rights::WRITE)?; + Ok(Vmo(Arc::new(super::Vmo_::clone(&self.0)), self.1)) + } + + /// Replaces the page at the `page_idx` in the VMO with the input `page`. + /// + /// # Access rights + /// + /// The method requires the Write right. + pub fn replace(&self, page: Frame, page_idx: usize) -> Result<()> { + self.check_rights(Rights::WRITE)?; + self.0.replace(page, page_idx) + } + /// Restricts the access rights given the mask. pub fn restrict(mut self, mask: Rights) -> Self { self.1 |= mask; diff --git a/kernel/aster-nix/src/vm/vmo/mod.rs b/kernel/aster-nix/src/vm/vmo/mod.rs index f66b26cfc..96c296797 100644 --- a/kernel/aster-nix/src/vm/vmo/mod.rs +++ b/kernel/aster-nix/src/vm/vmo/mod.rs @@ -10,7 +10,7 @@ use core::ops::Range; use align_ext::AlignExt; use aster_rights::Rights; use ostd::{ - collections::xarray::{CursorMut, XArray, XMark}, + collections::xarray::{CursorMut, XArray}, mm::{Frame, FrameAllocOptions, VmReader, VmWriter}, }; @@ -21,11 +21,9 @@ mod options; mod pager; mod static_cap; -pub use options::{VmoChildOptions, VmoOptions}; +pub use options::VmoOptions; pub use pager::Pager; -use self::options::ChildType; - /// Virtual Memory Objects (VMOs) are a type of capability that represents a /// range of memory pages. /// @@ -35,12 +33,6 @@ use self::options::ChildType; /// memory pages that it contain. /// * **On-demand paging.** The memory pages of a VMO (except for _contiguous_ /// VMOs) are allocated lazily when the page is first accessed. -/// * **Tree structure.** Given a VMO, one can create a child VMO from it. -/// The child VMO can only access a subset of the parent's memory, -/// which is a good thing for the perspective of access control. -/// * **Copy-on-write (COW).** A child VMO may be created with COW semantics, -/// which prevents any writes on the child from affecting the parent -/// by duplicating memory pages only upon the first writes. /// * **Access control.** As capabilities, VMOs restrict the /// accessible range of memory and the allowed I/O operations. /// * **Device driver support.** If specified upon creation, VMOs will be @@ -69,15 +61,13 @@ use self::options::ChildType; /// /// # Examples /// -/// For creating root VMOs, see `VmoOptions`.` -/// -/// For creating child VMOs, see `VmoChildOptions`. +/// For creating root VMOs, see [`VmoOptions`]. /// /// # Implementation /// /// `Vmo` provides high-level APIs for address space management by wrapping -/// around its low-level counterpart `ostd::vm::VmFrames`. -/// Compared with `VmFrames`, +/// around its low-level counterpart [`ostd::mm::Frame`]. +/// Compared with `Frame`, /// `Vmo` is easier to use (by offering more powerful APIs) and /// harder to misuse (thanks to its nature of being capability). /// @@ -135,41 +125,31 @@ bitflags! { } } -/// Marks used for the `XArray` in `Vmo_`. -#[derive(Copy, Clone)] -pub(super) enum VmoMark { - /// Marks used for the VMO's `pages` which is managed by `XArray`. - /// The VMO whose `pages` is marked as `CowVmo` may require a Copy-On-Write (COW) operation - /// when performing a write action. - CowVmo, - /// Marks used for the `Frame` stored within the pages marked as `CowVmo`, - /// `Frame`s marked as `ExclusivePage` are newly created through the COW mechanism - /// and do not require further COW operations. - ExclusivePage, -} - -impl From for XMark { - fn from(val: VmoMark) -> Self { - match val { - VmoMark::CowVmo => XMark::Mark0, - VmoMark::ExclusivePage => XMark::Mark1, - } - } -} - /// `Pages` is the struct that manages the `Frame`s stored in `Vmo_`. pub(super) enum Pages { /// `Pages` that cannot be resized. This kind of `Pages` will have a constant size. - Nonresizable(Arc>>, usize), - /// `Pages` that can be resized and have a variable size, and such `Pages` cannot - /// be shared between different VMOs. - Resizable(Mutex<(XArray, usize)>), + Nonresizable(Mutex>, usize), + /// `Pages` that can be resized and have a variable size. + Resizable(Mutex<(XArray, usize)>), +} + +impl Clone for Pages { + fn clone(&self) -> Self { + match self { + Self::Nonresizable(_, _) => { + self.with(|pages, size| Self::Nonresizable(Mutex::new(pages.clone()), size)) + } + Self::Resizable(_) => { + self.with(|pages, size| Self::Resizable(Mutex::new((pages.clone(), size)))) + } + } + } } impl Pages { fn with(&self, func: F) -> R where - F: FnOnce(&mut XArray, usize) -> R, + F: FnOnce(&mut XArray, usize) -> R, { match self { Self::Nonresizable(pages, size) => func(&mut pages.lock(), *size), @@ -187,167 +167,92 @@ impl Pages { /// 1. File-backed VMO: the VMO backed by a file and resides in the `PageCache`, /// which includes a pager to provide it with actual pages. /// 2. Anonymous VMO: the VMO without a file backup, which does not have a pager. +#[derive(Clone)] pub(super) struct Vmo_ { pager: Option>, /// Flags flags: VmoFlags, - /// The offset of the range of pages corresponding to the VMO within `pages`. - page_idx_offset: usize, /// The virtual pages where the VMO resides. pages: Pages, } -fn clone_page(page: &Frame) -> Result { - let new_page = FrameAllocOptions::new(1).alloc_single()?; - new_page.copy_from(page); - 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; + const WILL_OVERWRITE = 1; } } 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. - /// - /// Based on the type of VMO and the impending operation on the prepared page, there are 3 conditions: - /// 1. For an Anonymous VMO, provide a new page directly. If the VMO requires copy-on-write (COW), - /// the prepared page can be directly set to exclusive. - /// 2. For a File-backed VMO that does not need to trigger the COW mechanism, - /// obtain a page from the pager directly without the need to be set as exclusive. - /// 3. For a File-backed VMO that requires triggering the COW mechanism, obtain a page - /// from the pager and then copy it. This page can be set as exclusive. - fn prepare_page( - &self, - page_idx: usize, - is_cow_vmo: bool, - commit_flags: CommitFlags, - ) -> Result<(Frame, bool)> { - let (page, should_mark_exclusive) = match &self.pager { - None => { - // Condition 1. The new anonymous page only need to be marked as `ExclusivePage` - // when current VMO is a cow VMO, otherwise this mark is meaningless. - (FrameAllocOptions::new(1).alloc_single()?, is_cow_vmo) - } - Some(pager) => { - let page = pager.commit_page(page_idx)?; - // The prerequisite for triggering the COW mechanism here is that the current - // 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 && commit_flags.will_write(); - if trigger_cow { - // Condition 3. - (clone_page(&page)?, true) - } else { - // Condition 2. - (page, false) - } - } - }; - Ok((page, should_mark_exclusive)) + /// Prepares a new `Frame` for the target index in pages, returns this new frame. + fn prepare_page(&self, page_idx: usize) -> Result { + match &self.pager { + None => Ok(FrameAllocOptions::new(1).alloc_single()?), + Some(pager) => pager.commit_page(page_idx), + } } - /// 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)? + /// Prepares a new `Frame` for the target index in the VMO, returns this new frame. + fn prepare_overwrite(&self, page_idx: usize) -> Result { + if let Some(pager) = &self.pager { + pager.commit_overwrite(page_idx) } else { - FrameAllocOptions::new(1).alloc_single()? - }; - Ok(page) + Ok(FrameAllocOptions::new(1).alloc_single()?) + } } fn commit_with_cursor( &self, - cursor: &mut CursorMut<'_, Frame, VmoMark>, - is_cow_vmo: bool, + cursor: &mut CursorMut<'_, Frame>, commit_flags: CommitFlags, ) -> Result { - let (new_page, is_exclusive) = { - let is_exclusive = cursor.is_marked(VmoMark::ExclusivePage); + let new_page = { if let Some(committed_page) = cursor.load() { - // 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 && commit_flags.will_write() && !is_exclusive; - if !trigger_cow { - // Fast path: return the page directly. - return Ok(committed_page.clone()); - } - - if commit_flags.will_overwrite() { - (FrameAllocOptions::new(1).alloc_single()?, true) - } else { - (clone_page(&committed_page)?, true) - } + // Fast path: return the page directly. + return Ok(committed_page.clone()); } 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, - ) + // In this case, the page will be completely overwritten. + self.prepare_overwrite(cursor.index() as usize)? } else { - self.prepare_page(cursor.index() as usize, is_cow_vmo, commit_flags)? + self.prepare_page(cursor.index() as usize)? } }; cursor.store(new_page.clone()); - if is_exclusive { - cursor.set_mark(VmoMark::ExclusivePage).unwrap(); - } Ok(new_page) } - /// Commit the page corresponding to the target offset in the VMO and return that page. + /// Commits the page corresponding to the target offset in the VMO and return that page. /// If the current offset has already been committed, the page will be returned directly. - /// During the commit process, the Copy-On-Write (COW) mechanism may be triggered depending on the circumstances. - pub fn commit_page(&self, offset: usize, will_write: bool) -> Result { - let page_idx = offset / PAGE_SIZE + self.page_idx_offset; + pub fn commit_page(&self, offset: usize) -> Result { + let page_idx = offset / PAGE_SIZE; self.pages.with(|pages, size| { - let is_cow_vmo = pages.is_marked(VmoMark::CowVmo); + if offset >= size { + return_errno_with_message!(Errno::EINVAL, "the offset is outside the VMO"); + } let mut cursor = pages.cursor_mut(page_idx as u64); - let commit_flags = if will_write { - CommitFlags::WILL_WRITE - } else { - CommitFlags::empty() - }; - self.commit_with_cursor(&mut cursor, is_cow_vmo, commit_flags) + self.commit_with_cursor(&mut cursor, CommitFlags::empty()) }) } - /// Decommit the page corresponding to the target offset in the VMO. + /// Decommits the page corresponding to the target offset in the VMO. fn decommit_page(&mut self, offset: usize) -> Result<()> { - let page_idx = offset / PAGE_SIZE + self.page_idx_offset; + let page_idx = offset / PAGE_SIZE; self.pages.with(|pages, size| { - let is_cow_vmo = pages.is_marked(VmoMark::CowVmo); + if offset >= size { + return_errno_with_message!(Errno::EINVAL, "the offset is outside the VMO"); + } let mut cursor = pages.cursor_mut(page_idx as u64); if cursor.remove().is_some() && let Some(pager) = &self.pager - && !is_cow_vmo { pager.decommit_page(page_idx)?; } @@ -355,7 +260,7 @@ impl Vmo_ { }) } - /// Commit a range of pages in the VMO, and perform the operation + /// Commits a range of pages in the VMO, and perform the operation /// on each page in the range in turn. pub fn commit_and_operate( &self, @@ -371,15 +276,10 @@ impl Vmo_ { return_errno_with_message!(Errno::EINVAL, "operated range exceeds the vmo size"); } - let raw_page_idx_range = get_page_idx_range(range); - let page_idx_range = (raw_page_idx_range.start + self.page_idx_offset) - ..(raw_page_idx_range.end + self.page_idx_offset); - - let is_cow_vmo = pages.is_marked(VmoMark::CowVmo); + let page_idx_range = get_page_idx_range(range); 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, commit_flags)?; + let committed_page = self.commit_with_cursor(&mut cursor, commit_flags)?; operate(committed_page); cursor.next(); } @@ -387,15 +287,19 @@ impl Vmo_ { }) } - /// Decommit a range of pages in the VMO. + /// Decommits a range of pages in the VMO. pub fn decommit(&self, range: Range) -> Result<()> { self.pages.with(|pages, size| { + if range.end > size { + return_errno_with_message!(Errno::EINVAL, "operated range exceeds the vmo size"); + } + self.decommit_pages(pages, range)?; Ok(()) }) } - /// Read the specified amount of buffer content starting from the target offset in the VMO. + /// Reads the specified amount of buffer content starting from the target offset in the VMO. pub fn read_bytes(&self, offset: usize, buf: &mut [u8]) -> Result<()> { let read_len = buf.len(); let read_range = offset..(offset + read_len); @@ -410,7 +314,7 @@ impl Vmo_ { self.commit_and_operate(&read_range, read, CommitFlags::empty()) } - /// Write the specified amount of buffer content starting from the target offset in the VMO. + /// Writes the specified amount of buffer content starting from the target offset in the VMO. pub fn write_bytes(&self, offset: usize, buf: &[u8]) -> Result<()> { let write_len = buf.len(); let write_range = offset..(offset + write_len); @@ -423,14 +327,14 @@ impl Vmo_ { }; if write_range.len() < PAGE_SIZE { - self.commit_and_operate(&write_range, write, CommitFlags::WILL_WRITE)?; + self.commit_and_operate(&write_range, write, CommitFlags::empty())?; } 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)?; + self.commit_and_operate(&head_range, &mut write, CommitFlags::empty())?; } if up_align_start != down_align_end { let mid_range = up_align_start..down_align_end; @@ -438,17 +342,12 @@ impl Vmo_ { } 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)?; + self.commit_and_operate(&tail_range, &mut write, CommitFlags::empty())?; } } - let is_cow_vmo = self.is_cow_vmo(); - if let Some(pager) = &self.pager - && !is_cow_vmo - { - let raw_page_idx_range = get_page_idx_range(&write_range); - let page_idx_range = (raw_page_idx_range.start + self.page_idx_offset) - ..(raw_page_idx_range.end + self.page_idx_offset); + if let Some(pager) = &self.pager { + let page_idx_range = get_page_idx_range(&write_range); for page_idx in page_idx_range { pager.update_page(page_idx)?; } @@ -456,118 +355,19 @@ impl Vmo_ { Ok(()) } - /// Clear the target range in current VMO. + /// Clears the target range in current VMO. pub fn clear(&self, range: Range) -> Result<()> { let buffer = vec![0u8; range.end - range.start]; self.write_bytes(range.start, &buffer)?; Ok(()) } - /// Return the size of current VMO. + /// Returns the size of current VMO. pub fn size(&self) -> usize { - self.pages.with(|pages, size| size) + self.pages.with(|_, size| size) } - /// Return the page index offset of current VMO in corresponding pages. - pub fn page_idx_offset(&self) -> usize { - self.page_idx_offset - } - - /// Clone the current `pages` to the child VMO. - /// - /// Depending on the type of the VMO and the child, there are 4 conditions: - /// 1. For a slice child, directly share the current `pages` with that child. - /// 2. For a COW child, and the current VMO requires COW, it is necessary to clear the - /// ExclusivePage mark in the current `pages` and clone a new `pages` to the child. - /// 3. For a COW child, where the current VMO does not require COW and is a File-backed VMO. - /// In this case, a new `pages` needs to be cloned to the child, and the child's `pages` - /// require COW. The current `pages` do not need COW as they need to remain consistent with the pager. - /// 4. For a COW child, where the current VMO does not require COW and is an Anonymous VMO. - /// In this case, a new `pages` needs to be cloned to the child, and both the current `pages` and - /// the child's `pages` require COW. - pub fn clone_pages_for_child( - &self, - child_type: ChildType, - child_flags: VmoFlags, - range: &Range, - ) -> Result { - let child_vmo_start = range.start; - let child_vmo_end = range.end; - debug_assert!(child_vmo_start % PAGE_SIZE == 0); - debug_assert!(child_vmo_end % PAGE_SIZE == 0); - if child_vmo_start % PAGE_SIZE != 0 || child_vmo_end % PAGE_SIZE != 0 { - return_errno_with_message!(Errno::EINVAL, "VMO range does not aligned with PAGE_SIZE"); - } - - match child_type { - ChildType::Slice => { - if child_flags.contains(VmoFlags::RESIZABLE) { - return_errno_with_message!( - Errno::EINVAL, - "a slice child VMO cannot be resizable" - ); - } - - let Pages::Nonresizable(ref pages, size) = self.pages else { - return_errno_with_message!( - Errno::EINVAL, - "a resizable VMO cannot have a slice child" - ); - }; - - // A slice child should be inside parent VMO's range - debug_assert!(child_vmo_end <= size); - if child_vmo_end > size { - return_errno_with_message!( - Errno::EINVAL, - "a slice child VMO cannot exceed its parent VMO's size" - ); - } - // Condition 1. - Ok(Pages::Nonresizable(pages.clone(), range.len())) - } - ChildType::Cow => { - let new_pages = self.pages.with(|pages, size| { - // A Copy-on-Write child should intersect with parent VMO - debug_assert!(child_vmo_start <= size); - if child_vmo_start > size { - return_errno_with_message!( - Errno::EINVAL, - "a COW VMO should overlap with its parent" - ); - } - - let self_is_cow = pages.is_marked(VmoMark::CowVmo); - if self_is_cow { - // Condition 2. - pages.unset_mark_all(VmoMark::ExclusivePage); - return Ok(pages.clone()); - } - - if self.pager.is_some() { - // Condition 3. - let mut cloned_pages = pages.clone(); - cloned_pages.set_mark(VmoMark::CowVmo); - return Ok(cloned_pages); - } - - // Condition 4. - pages.set_mark(VmoMark::CowVmo); - Ok(pages.clone()) - })?; - if child_flags.contains(VmoFlags::RESIZABLE) { - Ok(Pages::Resizable(Mutex::new((new_pages, range.len())))) - } else { - Ok(Pages::Nonresizable( - Arc::new(Mutex::new(new_pages)), - range.len(), - )) - } - } - } - } - - /// Resize current VMO to target size. + /// Resizes current VMO to target size. pub fn resize(&self, new_size: usize) -> Result<()> { assert!(self.flags.contains(VmoFlags::RESIZABLE)); let new_size = new_size.align_up(PAGE_SIZE); @@ -588,20 +388,12 @@ impl Vmo_ { Ok(()) } - fn decommit_pages( - &self, - pages: &mut XArray, - range: Range, - ) -> Result<()> { - let raw_page_idx_range = get_page_idx_range(&range); - let page_idx_range = (raw_page_idx_range.start + self.page_idx_offset) - ..(raw_page_idx_range.end + self.page_idx_offset); - let is_cow_vmo = pages.is_marked(VmoMark::CowVmo); + fn decommit_pages(&self, pages: &mut XArray, range: Range) -> Result<()> { + let page_idx_range = get_page_idx_range(&range); let mut cursor = pages.cursor_mut(page_idx_range.start as u64); for page_idx in page_idx_range { if cursor.remove().is_some() && let Some(pager) = &self.pager - && !is_cow_vmo { pager.decommit_page(page_idx)?; } @@ -610,24 +402,25 @@ impl Vmo_ { Ok(()) } - /// Determine whether a page is committed. + /// Determines whether a page is committed. pub fn is_page_committed(&self, page_idx: usize) -> bool { - self.pages.with(|pages, size| { - pages - .load((page_idx + self.page_idx_offset) as u64) - .is_some() - }) + self.pages + .with(|pages, _| pages.load(page_idx as u64).is_some()) } - /// Return the flags of current VMO. + /// Returns the flags of current VMO. pub fn flags(&self) -> VmoFlags { self.flags } - /// Determine whether the VMO is need COW mechanism. - pub fn is_cow_vmo(&self) -> bool { - self.pages - .with(|pages, size| pages.is_marked(VmoMark::CowVmo)) + fn replace(&self, page: Frame, page_idx: usize) -> Result<()> { + self.pages.with(|pages, size| { + if page_idx >= size / PAGE_SIZE { + return_errno_with_message!(Errno::EINVAL, "the page index is outside of the vmo"); + } + pages.store(page_idx as u64, page); + Ok(()) + }) } } @@ -641,22 +434,9 @@ impl Vmo { pub fn flags(&self) -> VmoFlags { self.0.flags() } - - /// return whether a page is already committed - pub fn is_page_committed(&self, page_idx: usize) -> bool { - self.0.is_page_committed(page_idx) - } - - pub fn get_committed_frame(&self, page_idx: usize, write_page: bool) -> Result { - self.0.commit_page(page_idx * PAGE_SIZE, write_page) - } - - pub fn is_cow_vmo(&self) -> bool { - self.0.is_cow_vmo() - } } -/// get the page index range that contains the offset range of vmo +/// Gets the page index range that contains the offset range of VMO. pub fn get_page_idx_range(vmo_offset_range: &Range) -> Range { let start = vmo_offset_range.start.align_down(PAGE_SIZE); let end = vmo_offset_range.end.align_up(PAGE_SIZE); diff --git a/kernel/aster-nix/src/vm/vmo/options.rs b/kernel/aster-nix/src/vm/vmo/options.rs index 0a50aea4f..0455122d4 100644 --- a/kernel/aster-nix/src/vm/vmo/options.rs +++ b/kernel/aster-nix/src/vm/vmo/options.rs @@ -4,18 +4,14 @@ //! Options for allocating root and child VMOs. -use core::{marker::PhantomData, ops::Range}; - use align_ext::AlignExt; -use aster_rights::{Dup, Rights, TRightSet, TRights, Write}; -use aster_rights_proc::require; +use aster_rights::{Rights, TRightSet, TRights}; use ostd::{ collections::xarray::XArray, mm::{Frame, FrameAllocOptions}, }; -use typeflags_util::{SetExtend, SetExtendOp}; -use super::{Pager, Pages, Vmo, VmoFlags, VmoMark, VmoRightsOp}; +use super::{Pager, Pages, Vmo, VmoFlags}; use crate::{prelude::*, vm::vmo::Vmo_}; /// Options for allocating a root VMO. @@ -131,18 +127,17 @@ fn alloc_vmo_(size: usize, flags: VmoFlags, pager: Option>) -> Re if flags.contains(VmoFlags::RESIZABLE) { Pages::Resizable(Mutex::new((pages, size))) } else { - Pages::Nonresizable(Arc::new(Mutex::new(pages)), size) + Pages::Nonresizable(Mutex::new(pages), size) } }; Ok(Vmo_ { pager, flags, - page_idx_offset: 0, pages, }) } -fn committed_pages_if_continuous(flags: VmoFlags, size: usize) -> Result> { +fn committed_pages_if_continuous(flags: VmoFlags, size: usize) -> Result> { if flags.contains(VmoFlags::CONTIGUOUS) { // if the vmo is continuous, we need to allocate frames for the vmo let frames_num = size / PAGE_SIZE; @@ -163,317 +158,6 @@ fn committed_pages_if_continuous(flags: VmoFlags, size: usize) -> Result = VmoOptions::new(PAGE_SIZE) -/// .alloc() -/// .unwrap(); -/// let child_vmo: Vmo = parent_vmo.new_slice_child(0..PAGE_SIZE) -/// .alloc() -/// .unwrap(); -/// assert!(parent_vmo.rights() == child_vmo.rights()); -/// ``` -/// -/// Normally, a child VMO is initially given the same set of access rights -/// as its parent (as shown above). But there is one exception: -/// if the child VMO is created as a COW child, then it is granted the Write -/// right regardless of whether the parent is writable or not. -/// -/// ``` -/// use aster_nix::vm::{PAGE_SIZE, VmoOptions, VmoChildOptions}; -/// -/// let parent_vmo = VmoOptions::new(PAGE_SIZE) -/// .alloc() -/// .unwrap() -/// .restrict(Rights::DUP | Rights::READ); -/// let child_vmo = parent_vmo.new_cow_child(0..PAGE_SIZE) -/// .alloc() -/// .unwrap(); -/// assert!(child_vmo.rights().contains(Rights::WRITE)); -/// ``` -/// -/// The above rule for COW VMO children also applies to static capabilities. -/// -/// ``` -/// use aster_nix::vm::{PAGE_SIZE, VmoOptions, VmoChildOptions}; -/// -/// let parent_vmo = VmoOptions::::new(PAGE_SIZE) -/// .alloc() -/// .unwrap(); -/// let child_vmo = parent_vmo.new_cow_child(0..PAGE_SIZE) -/// .alloc() -/// .unwrap(); -/// assert!(child_vmo.rights().contains(Rights::WRITE)); -/// ``` -/// -/// One can set VMO flags for a child VMO. Currently, the only flag that is -/// valid when creating VMO children is `VmoFlags::RESIZABLE`. -/// Note that a slice VMO child and its parent cannot not be resizable. -/// -/// ```rust -/// use aster_nix::vm::{PAGE_SIZE, VmoOptions}; -/// -/// let parent_vmo = VmoOptions::new(PAGE_SIZE) -/// .alloc() -/// .unwrap(); -/// let child_vmo = parent_vmo.new_cow_child(0..PAGE_SIZE) -/// // Make the child resizable! -/// .flags(VmoFlags::RESIZABLE) -/// .alloc() -/// .unwrap(); -/// assert!(parent_vmo.rights() == child_vmo.rights()); -/// ``` -pub struct VmoChildOptions { - parent: Vmo, - range: Range, - flags: VmoFlags, - // Specifies whether the child is a slice or a COW - marker: PhantomData, -} - -impl VmoChildOptions, VmoSliceChild> { - /// Creates a default set of options for creating a slice VMO child. - /// - /// A slice child of a VMO, which has direct access to a range of memory - /// pages in the parent VMO. In other words, any updates of the parent will - /// reflect on the child, and vice versa. - /// - /// The range of a child must be within that of the parent. - #[require(R > Dup)] - pub fn new_slice(parent: Vmo>, range: Range) -> Self { - Self { - flags: parent.flags() & Self::PARENT_FLAGS_MASK, - parent, - range, - marker: PhantomData, - } - } -} - -impl VmoChildOptions { - /// Creates a default set of options for creating a slice VMO child. - /// - /// User should ensure parent have dup rights, otherwise this function will panic - /// - /// A slice child of a VMO, which has direct access to a range of memory - /// pages in the parent VMO. In other words, any updates of the parent will - /// reflect on the child, and vice versa. - /// - /// The range of a child must be within that of the parent. - pub fn new_slice_rights(parent: Vmo, range: Range) -> Self { - parent - .check_rights(Rights::DUP) - .expect("function new_slice_rights should called with rights Dup"); - Self { - flags: parent.flags(), - parent, - range, - marker: PhantomData, - } - } -} - -impl VmoChildOptions { - /// Flags that a VMO child inherits from its parent. - pub const PARENT_FLAGS_MASK: VmoFlags = - VmoFlags::from_bits(VmoFlags::CONTIGUOUS.bits | VmoFlags::DMA.bits).unwrap(); - /// Flags that a VMO child may differ from its parent. - pub const CHILD_FLAGS_MASK: VmoFlags = VmoFlags::empty(); - - /// Sets the VMO flags. - /// - /// Only the flags among `Self::CHILD_FLAGS_MASK` may be set through this - /// method. - /// - /// To set `VmoFlags::RESIZABLE`, the child must be COW. - /// - /// The default value is `VmoFlags::empty()`. - pub fn flags(mut self, flags: VmoFlags) -> Self { - let inherited_flags = self.flags & Self::PARENT_FLAGS_MASK; - self.flags = inherited_flags | (flags & Self::CHILD_FLAGS_MASK); - self - } -} - -impl VmoChildOptions { - /// Flags that a VMO child inherits from its parent. - pub const PARENT_FLAGS_MASK: VmoFlags = - VmoFlags::from_bits(VmoFlags::CONTIGUOUS.bits | VmoFlags::DMA.bits).unwrap(); - /// Flags that a VMO child may differ from its parent. - pub const CHILD_FLAGS_MASK: VmoFlags = VmoFlags::RESIZABLE; - /// Creates a default set of options for creating a copy-on-write (COW) - /// VMO child. - /// - /// A COW VMO child behaves as if all its - /// memory pages are copied from the parent VMO upon creation, although - /// the copying is done lazily when the parent's memory pages are updated. - /// - /// The range of a child may go beyond that of the parent. - /// Any pages that are beyond the parent's range are initially all zeros. - pub fn new_cow(parent: Vmo, range: Range) -> Self { - Self { - flags: parent.flags(), - parent, - range, - marker: PhantomData, - } - } - - /// Sets the VMO flags. - /// - /// Only the flags among `Self::CHILD_FLAGS_MASK` may be set through this - /// method. - /// - /// To set `VmoFlags::RESIZABLE`, the child must be COW. - /// - /// The default value is `VmoFlags::empty()`. - pub fn flags(mut self, flags: VmoFlags) -> Self { - let inherited_flags = self.flags & Self::PARENT_FLAGS_MASK; - self.flags = inherited_flags | (flags & Self::CHILD_FLAGS_MASK); - self - } -} - -impl VmoChildOptions { - /// Allocates the child VMO. - /// - /// # Access rights - /// - /// The child VMO is initially assigned all the parent's access rights. - pub fn alloc(self) -> Result> { - let VmoChildOptions { - parent, - range, - flags, - .. - } = self; - let Vmo(parent_vmo_, parent_rights) = parent; - let child_vmo_ = alloc_child_vmo_(parent_vmo_, range, flags, ChildType::Slice)?; - Ok(Vmo(Arc::new(child_vmo_), parent_rights)) - } -} - -impl VmoChildOptions { - /// Allocates the child VMO. - /// - /// # Access rights - /// - /// The child VMO is initially assigned all the parent's access rights. - pub fn alloc(self) -> Result> { - let VmoChildOptions { - parent, - range, - flags, - .. - } = self; - let Vmo(parent_vmo_, parent_rights) = parent; - let child_vmo_ = alloc_child_vmo_(parent_vmo_, range, flags, ChildType::Cow)?; - Ok(Vmo(Arc::new(child_vmo_), parent_rights)) - } -} - -impl VmoChildOptions, VmoSliceChild> { - /// Allocates the child VMO. - /// - /// # Access rights - /// - /// The child VMO is initially assigned all the parent's access rights. - pub fn alloc(self) -> Result>> { - let VmoChildOptions { - parent, - range, - flags, - .. - } = self; - let Vmo(parent_vmo_, parent_rights) = parent; - let child_vmo_ = alloc_child_vmo_(parent_vmo_, range, flags, ChildType::Slice)?; - Ok(Vmo(Arc::new(child_vmo_), parent_rights)) - } -} - -impl VmoChildOptions, VmoCowChild> { - /// Allocates the child VMO. - /// - /// # Access rights - /// - /// The child VMO is initially assigned all the parent's access rights - /// plus the Write right. - pub fn alloc(self) -> Result>>> - where - R: SetExtend, - SetExtendOp: TRights, - { - let VmoChildOptions { - parent, - range, - flags, - .. - } = self; - let Vmo(parent_vmo_, _) = parent; - let child_vmo_ = alloc_child_vmo_(parent_vmo_, range, flags, ChildType::Cow)?; - let right = SetExtendOp::::new(); - Ok(Vmo(Arc::new(child_vmo_), TRightSet(right))) - } -} - -#[derive(Debug, Clone, Copy)] -pub(crate) enum ChildType { - Cow, - Slice, -} - -fn alloc_child_vmo_( - parent_vmo_: Arc, - range: Range, - child_flags: VmoFlags, - child_type: ChildType, -) -> Result { - let parent_page_idx_offset = range.start / PAGE_SIZE; - let child_pages = parent_vmo_.clone_pages_for_child(child_type, child_flags, &range)?; - let new_vmo = Vmo_ { - pager: parent_vmo_.pager.clone(), - flags: child_flags, - pages: child_pages, - page_idx_offset: parent_page_idx_offset + parent_vmo_.page_idx_offset(), - }; - Ok(new_vmo) -} - -/// A type to specify the "type" of a child, which is either a slice or a COW. -pub trait VmoChildType {} - -/// A type to mark a child is slice. -#[derive(Copy, Clone, Debug)] -pub struct VmoSliceChild; -impl VmoChildType for VmoSliceChild {} - -/// A type to mark a child is COW. -#[derive(Copy, Clone, Debug)] -pub struct VmoCowChild; -impl VmoChildType for VmoCowChild {} - #[cfg(ktest)] mod test { use aster_rights::Full; @@ -512,53 +196,6 @@ mod test { assert_eq!(read_val, 0x78563412) } - #[ktest] - fn slice_child() { - let parent = VmoOptions::::new(2 * PAGE_SIZE).alloc().unwrap(); - let parent_dup = parent.dup(); - let slice_child = VmoChildOptions::new_slice(parent_dup, 0..PAGE_SIZE) - .alloc() - .unwrap(); - // write parent, read child - parent.write_val(1, &42u8).unwrap(); - assert_eq!(slice_child.read_val::(1).unwrap(), 42); - // write child, read parent - slice_child.write_val(99, &0x1234u32).unwrap(); - assert_eq!(parent.read_val::(99).unwrap(), 0x1234); - } - - #[ktest] - fn cow_child() { - let parent = VmoOptions::::new(2 * PAGE_SIZE).alloc().unwrap(); - parent.write_val(1, &42u8).unwrap(); - parent.write_val(2, &16u8).unwrap(); - let parent_dup = parent.dup(); - let cow_child = VmoChildOptions::new_cow(parent_dup, 0..10 * PAGE_SIZE) - .alloc() - .unwrap(); - // Read child. - assert_eq!(cow_child.read_val::(1).unwrap(), 42); - assert_eq!(cow_child.read_val::(2).unwrap(), 16); - // Write parent to trigger copy-on-write. read child and parent. - parent.write_val(1, &64u8).unwrap(); - assert_eq!(parent.read_val::(1).unwrap(), 64); - assert_eq!(cow_child.read_val::(1).unwrap(), 42); - // Write child to trigger copy on write, read child and parent - cow_child.write_val(2, &0x1234u32).unwrap(); - assert_eq!(cow_child.read_val::(2).unwrap(), 0x1234); - assert_eq!(cow_child.read_val::(1).unwrap(), 42); - assert_eq!(parent.read_val::(2).unwrap(), 16); - assert_eq!(parent.read_val::(1).unwrap(), 64); - // Write parent on already-copied page - parent.write_val(1, &123u8).unwrap(); - assert_eq!(parent.read_val::(1).unwrap(), 123); - assert_eq!(cow_child.read_val::(1).unwrap(), 42); - // Write parent on not-copied page - parent.write_val(2, &12345u32).unwrap(); - assert_eq!(parent.read_val::(2).unwrap(), 12345); - assert_eq!(cow_child.read_val::(2).unwrap(), 0x1234); - } - #[ktest] fn resize() { let vmo = VmoOptions::::new(PAGE_SIZE) @@ -573,17 +210,4 @@ mod test { vmo.resize(PAGE_SIZE).unwrap(); assert_eq!(vmo.read_val::(10).unwrap(), 42); } - - #[ktest] - fn resize_cow() { - let vmo = VmoOptions::::new(10 * PAGE_SIZE) - .flags(VmoFlags::RESIZABLE) - .alloc() - .unwrap(); - - let cow_child = VmoChildOptions::new_cow(vmo, 0..PAGE_SIZE).alloc().unwrap(); - - cow_child.resize(2 * PAGE_SIZE).unwrap(); - assert_eq!(cow_child.size(), 2 * PAGE_SIZE); - } } diff --git a/kernel/aster-nix/src/vm/vmo/static_cap.rs b/kernel/aster-nix/src/vm/vmo/static_cap.rs index f3a42b1ac..dfd723c21 100644 --- a/kernel/aster-nix/src/vm/vmo/static_cap.rs +++ b/kernel/aster-nix/src/vm/vmo/static_cap.rs @@ -6,74 +6,17 @@ use aster_rights::{Dup, Rights, TRightSet, TRights, Write}; use aster_rights_proc::require; use ostd::mm::{Frame, VmIo}; -use super::{ - options::{VmoCowChild, VmoSliceChild}, - CommitFlags, Vmo, VmoChildOptions, VmoRightsOp, -}; +use super::{CommitFlags, Vmo, VmoRightsOp}; use crate::prelude::*; impl Vmo> { - /// Creates a new slice VMO through a set of VMO child options. - /// - /// # Example - /// - /// ``` - /// let parent = VmoOptions::new(PAGE_SIZE).alloc().unwrap(); - /// let child_size = parent.size(); - /// let child = parent.new_slice_child(0..child_size).alloc().unwrap(); - /// assert!(child.size() == child_size); - /// ``` - /// - /// For more details on the available options, see `VmoChildOptions`. - /// - /// # Access rights - /// - /// This method requires the Dup right. - /// - /// The new VMO child will be of the same capability flavor as the parent; - /// so are the access rights. - #[require(R > Dup)] - pub fn new_slice_child( - &self, - range: Range, - ) -> VmoChildOptions, VmoSliceChild> { - let dup_self = self.dup(); - VmoChildOptions::new_slice(dup_self, range) - } - - /// Creates a new COW VMO through a set of VMO child options. - /// - /// # Example - /// - /// ``` - /// let parent = VmoOptions::new(PAGE_SIZE).alloc().unwrap(); - /// let child_size = 2 * parent.size(); - /// let child = parent.new_cow_child(0..child_size).alloc().unwrap(); - /// assert!(child.size() == child_size); - /// ``` - /// - /// For more details on the available options, see `VmoChildOptions`. - /// - /// # Access rights - /// - /// This method requires the Dup right. - /// - /// The new VMO child will be of the same capability flavor as the parent. - /// The child will be given the access rights of the parent - /// plus the Write right. - #[require(R > Dup)] - pub fn new_cow_child(&self, range: Range) -> VmoChildOptions, VmoCowChild> { - let dup_self = self.dup(); - VmoChildOptions::new_cow(dup_self, range) - } - - /// commit a page at specific offset + /// Commits a page at specific offset. pub fn commit_page(&self, offset: usize) -> Result { self.check_rights(Rights::WRITE)?; - self.0.commit_page(offset, false) + self.0.commit_page(offset) } - /// Commit the pages specified in the range (in bytes). + /// Commits the pages specified in the range (in bytes). /// /// The range must be within the size of the VMO. /// @@ -89,7 +32,7 @@ impl Vmo> { Ok(()) } - /// Decommit the pages specified in the range (in bytes). + /// Decommits the pages specified in the range (in bytes). /// /// The range must be within the size of the VMO. /// @@ -137,6 +80,29 @@ impl Vmo> { Vmo(self.0.clone(), self.1) } + /// Creates a new VMO that replicates the original capability, initially representing + /// the same physical pages. + /// Changes to the permissions and commits/replacements of internal pages in the original VMO + /// and the new VMO will not affect each other. + /// + /// # Access rights + /// + /// The method requires the Dup right. + #[require(R > Dup | Write)] + pub fn dup_independent(&self) -> Self { + Vmo(Arc::new(super::Vmo_::clone(&self.0)), self.1) + } + + /// Replaces the page at the `page_idx` in the VMO with the input `page`. + /// + /// # Access rights + /// + /// The method requires the Write right. + #[require(R > Write)] + pub fn replace(&self, page: Frame, page_idx: usize) -> Result<()> { + self.0.replace(page, page_idx) + } + /// Strict the access rights. #[require(R > R1)] pub fn restrict(self) -> Vmo> { diff --git a/ostd/Cargo.toml b/ostd/Cargo.toml index 9043ce710..e190e7a0e 100644 --- a/ostd/Cargo.toml +++ b/ostd/Cargo.toml @@ -43,7 +43,7 @@ static_assertions = "1.1.0" trapframe = "0.10.0" unwinding = { version = "0.2.2", default-features = false, features = ["fde-gnu-eh-frame-hdr", "hide-trace", "panic", "personality", "unwinder"] } volatile = { version = "0.4.5", features = ["unstable"] } -xarray = { git = "https://github.com/asterinas/xarray", rev = "72a4067", version = "0.1.0" } +xarray = { git = "https://github.com/asterinas/xarray", version = "0.1.0" } [target.x86_64-unknown-none.dependencies] x86_64 = "0.14.2"