diff --git a/src/context/memory.rs b/src/context/memory.rs index f0fd4f73349a2db4b0e2b109ebc728d1555fb528..53e99e2cf1ce37e61b3b59c44dd1eae941b95da5 100644 --- a/src/context/memory.rs +++ b/src/context/memory.rs @@ -110,7 +110,7 @@ impl AddrSpace { (), false, // is_pinned_userscheme_borrow )?, - Provider::Allocated { ref cow_file_ref } => Grant::cow( + Provider::Allocated { ref cow_file_ref } => Grant::copy_mappings( Arc::clone(&self_arc), grant_base, grant_base, @@ -120,7 +120,20 @@ impl AddrSpace { new_mapper, &mut this_flusher, (), - cow_file_ref.clone(), + CopyMappingsMode::Owned { cow_file_ref: cow_file_ref.clone() }, + )?, + // TODO: Merge Allocated and AllocatedShared, and make CopyMappingsMode a field? + Provider::AllocatedShared => Grant::copy_mappings( + Arc::clone(&self_arc), + grant_base, + grant_base, + grant_info.page_count, + grant_info.flags, + this_mapper, + new_mapper, + &mut this_flusher, + (), + CopyMappingsMode::Borrowed, )?, // MAP_SHARED grants are retained by reference, across address space clones (across @@ -134,7 +147,6 @@ impl AddrSpace { (), false, )?, - // TODO: "clone grant using fmap" Provider::FmapBorrowed { .. } => continue, }; @@ -610,14 +622,30 @@ pub struct GrantInfo { pub(crate) provider: Provider, } +/// Enumeration of various types of grants. #[derive(Debug)] pub enum Provider { - /// The grant was initialized with (lazy) zeroed memory, and any changes will make it owned by - /// the frame allocator. + /// The grant is owned, but possibly CoW-shared. + /// + /// The pages this grant spans, need not necessarily be initialized right away, and can be + /// populated either from zeroed frames, the CoW zeroed frame, or from a scheme fmap call, if + /// mapped with MAP_LAZY. All frames must have an available PageInfo. Allocated { cow_file_ref: Option<GrantFileRef> }, + /// The grant is owned, but possibly shared. + /// + /// The pages may only be lazily initialized, if the address space has not yet been cloned (when forking). + /// + /// This type of grants is obtained from MAP_SHARED anonymous or `memory:` mappings, i.e. + /// allocated memory that remains shared after address space clones. + AllocatedShared, + /// The grant is not owned, but borrowed from physical memory frames that do not belong to the /// frame allocator. + /// + /// This is true for MMIO, or where the frames are managed externally (UserScheme head/tail + /// buffers). + /// // TODO: Stop using PhysBorrowed for head/tail pages when doing scheme calls! Force userspace // to provide it, perhaps from relibc? PhysBorrowed { base: Frame, is_pinned_userscheme_borrow: bool }, @@ -627,9 +655,9 @@ pub enum Provider { /// The memory is MAP_SHARED borrowed from a scheme. /// - /// Since the address space is not tracked here, all nonpresent pages (all pages must be - /// present before the fmap operation completes, unless MAP_LAZY is specified) are tracked - /// using PageInfo, or treated as PhysBorrowed if any frame lacks a PageInfo. + /// Since the address space is not tracked here, all nonpresent pages must be present before + /// the fmap operation completes, unless MAP_LAZY is specified. They are tracked using + /// PageInfo, or treated as PhysBorrowed if any frame lacks a PageInfo. FmapBorrowed { file_ref: GrantFileRef }, } @@ -645,6 +673,8 @@ pub struct GrantFileRef { pub base_offset: usize, } +// TODO: When using this frame, keep in mind AllocatedShared must not be able to obtain writable +// mappings to it. static THE_ZEROED_FRAME: Once<Frame> = Once::new(); impl Grant { @@ -661,7 +691,7 @@ impl Grant { }, }) } - pub fn zeroed(span: PageSpan, flags: PageFlags<RmmA>, mapper: &mut PageMapper, mut flusher: impl Flusher<RmmA>) -> Result<Grant, Enomem> { + pub fn zeroed(span: PageSpan, flags: PageFlags<RmmA>, mapper: &mut PageMapper, mut flusher: impl Flusher<RmmA>, shared: bool) -> Result<Grant, Enomem> { //let the_frame = THE_ZEROED_FRAME.get().expect("expected the zeroed frame to be available").start_address(); // TODO: O(n) readonly map with zeroed page, or O(1) no-op and then lazily map? @@ -686,7 +716,11 @@ impl Grant { page_count: span.count, flags, mapped: true, - provider: Provider::Allocated { cow_file_ref: None }, + provider: if shared { + Provider::AllocatedShared + } else { + Provider::Allocated { cow_file_ref: None } + }, }, }) } @@ -750,7 +784,7 @@ impl Grant { } unsafe { - flusher.consume(mapper.map_phys(dst_page.start_address(), frame.start_address(), new_flags.write(!is_cow)).unwrap()); + flusher.consume(mapper.map_phys(dst_page.start_address(), frame.start_address(), new_flags.write(new_flags.has_write() && !is_cow)).unwrap()); } } } @@ -828,7 +862,7 @@ impl Grant { }) } // TODO: This is limited to one grant. Should it be (if some magic new proc: API is introduced)? - pub fn cow( + pub fn copy_mappings( src_address_space_lock: Arc<RwLock<AddrSpace>>, src_base: Page, dst_base: Page, @@ -838,27 +872,46 @@ impl Grant { dst_mapper: &mut PageMapper, mut src_flusher: impl Flusher<RmmA>, mut dst_flusher: impl Flusher<RmmA>, - cow_file_ref: Option<GrantFileRef>, + mode: CopyMappingsMode, ) -> Result<Grant, Enomem> { + let is_cow = match mode { + CopyMappingsMode::Owned { .. } => true, + CopyMappingsMode::Borrowed => false, + }; + // TODO: Page table iterator for page_idx in 0..page_count { let src_page = src_base.next_by(page_idx); let dst_page = dst_base.next_by(page_idx).start_address(); - let Some((_old_flags, src_phys, flush)) = (unsafe { src_mapper.remap_with(src_page.start_address(), |flags| flags.write(false)) }) else { - // Page is not mapped, let the page fault handler take care of that (initializing - // it to zero). - // - // TODO: If eager, allocate zeroed page if writable, or use *the* zeroed page (also - // for read-only)? - continue; + let src_frame = if is_cow { + let Some((_, phys, flush)) = (unsafe { src_mapper.remap_with(src_page.start_address(), |flags| flags.write(false)) }) else { + // Page is not mapped, let the page fault handler take care of that (initializing + // it to zero). + // + // TODO: If eager, allocate zeroed page if writable, or use *the* zeroed page (also + // for read-only)? + continue; + }; + src_flusher.consume(flush); + + Frame::containing_address(phys) + } else { + if let Some((phys, _)) = src_mapper.translate(src_page.start_address()) { + Frame::containing_address(phys) + } else { + let new_frame = init_frame(2, 2).expect("TODO: handle OOM"); + let src_flush = unsafe { src_mapper.map_phys(src_page.start_address(), new_frame.start_address(), flags).expect("TODO: handle OOM") }; + src_flusher.consume(src_flush); + + new_frame + } }; - let src_frame = Frame::containing_address(src_phys); let src_page_info = get_page_info(src_frame).expect("allocated page was not present in the global page array"); - src_page_info.add_ref(true); + src_page_info.add_ref(is_cow); - let Some(map_result) = (unsafe { dst_mapper.map_phys(dst_page, src_frame.start_address(), flags.write(false)) }) else { + let Some(map_result) = (unsafe { dst_mapper.map_phys(dst_page, src_frame.start_address(), flags.write(flags.has_write() && !is_cow)) }) else { break; }; @@ -871,8 +924,11 @@ impl Grant { page_count, flags, mapped: true, - provider: Provider::Allocated { cow_file_ref }, - }, + provider: match mode { + CopyMappingsMode::Owned { cow_file_ref } => Provider::Allocated { cow_file_ref }, + CopyMappingsMode::Borrowed => Provider::AllocatedShared, + }, + } }) } /// Move a grant between two address spaces. @@ -925,21 +981,20 @@ impl Grant { }; let frame = Frame::containing_address(phys); - let (is_cow_opt, require_info) = match self.info.provider { - Provider::Allocated { .. } => (Some(true), true), - Provider::External { .. } => (Some(false), false), - Provider::PhysBorrowed { .. } => (None, false), - Provider::FmapBorrowed { .. } => (Some(false), false), + let (is_cow, require_info) = match self.info.provider { + Provider::Allocated { .. } => (true, true), + Provider::AllocatedShared => (false, true), + Provider::External { .. } => (false, false), + Provider::PhysBorrowed { .. } => (false, false), + Provider::FmapBorrowed { .. } => (false, false), }; - if let Some(is_cow) = is_cow_opt { - if let Some(info) = get_page_info(frame) { - if info.remove_ref(is_cow) == 0 { - deallocate_frames(frame, 1); - }; - } else { - assert!(!require_info, "allocated frame did not have an associated PageInfo"); - } + if let Some(info) = get_page_info(frame) { + if info.remove_ref(is_cow) == 0 { + deallocate_frames(frame, 1); + }; + } else { + assert!(!require_info, "allocated frame did not have an associated PageInfo"); } @@ -996,6 +1051,7 @@ impl Grant { is_pinned_userscheme_borrow: false, }, Provider::Allocated { ref cow_file_ref } => Provider::Allocated { cow_file_ref: cow_file_ref.clone() }, + Provider::AllocatedShared => Provider::AllocatedShared, Provider::PhysBorrowed { base, .. } => Provider::PhysBorrowed { base: base.clone(), is_pinned_userscheme_borrow: false }, Provider::FmapBorrowed { ref file_ref } => Provider::FmapBorrowed { file_ref: file_ref.clone() } } @@ -1007,7 +1063,7 @@ impl Grant { match self.info.provider { Provider::PhysBorrowed { ref mut base, .. } => *base = base.next_by(middle_page_offset), Provider::FmapBorrowed { ref mut file_ref } | Provider::Allocated { cow_file_ref: Some(ref mut file_ref) } => file_ref.base_offset += middle_page_offset * PAGE_SIZE, - Provider::Allocated { cow_file_ref: None } | Provider::External { .. } => (), + Provider::Allocated { cow_file_ref: None } | Provider::AllocatedShared | Provider::External { .. } => (), } @@ -1019,6 +1075,7 @@ impl Grant { page_count: span.count, provider: match self.info.provider { Provider::Allocated { cow_file_ref: None } => Provider::Allocated { cow_file_ref: None }, + Provider::AllocatedShared => Provider::AllocatedShared, Provider::Allocated { cow_file_ref: Some(ref file_ref) } => Provider::Allocated { cow_file_ref: Some(GrantFileRef { base_offset: file_ref.base_offset + this_span.count * PAGE_SIZE, description: Arc::clone(&file_ref.description), @@ -1103,6 +1160,9 @@ impl GrantInfo { // !GRANT_SHARED is equivalent to "GRANT_PRIVATE" flags.set(GrantFlags::GRANT_SCHEME, cow_file_ref.is_some()); } + Provider::AllocatedShared => { + flags |= GrantFlags::GRANT_SHARED; + } Provider::PhysBorrowed { is_pinned_userscheme_borrow, .. } => { flags |= GrantFlags::GRANT_SHARED | GrantFlags::GRANT_PHYS; flags.set(GrantFlags::GRANT_PINNED, is_pinned_userscheme_borrow); @@ -1244,6 +1304,7 @@ pub enum AccessMode { InstrFetch, } +#[derive(Debug)] pub enum PfError { Segv, Oom, @@ -1259,24 +1320,24 @@ fn cow(dst_mapper: &mut PageMapper, page: Page, old_frame: Frame, info: &PageInf return Ok(old_frame); } - let new_frame = init_frame()?; + let new_frame = init_frame(1, 0)?; unsafe { copy_frame_to_frame_directly(new_frame, old_frame); } Ok(new_frame) } -fn init_frame() -> Result<Frame, PfError> { +fn init_frame(init_rc: usize, init_borrowed_rc: usize) -> Result<Frame, PfError> { let new_frame = crate::memory::allocate_frames(1).ok_or(PfError::Oom)?; let page_info = get_page_info(new_frame).expect("all allocated frames need an associated page info"); - page_info.refcount.store(1, Ordering::Relaxed); - page_info.borrowed_refcount.store(0, Ordering::Relaxed); + page_info.refcount.store(init_rc, Ordering::Relaxed); + page_info.borrowed_refcount.store(init_borrowed_rc, Ordering::Relaxed); Ok(new_frame) } fn map_zeroed(mapper: &mut PageMapper, page: Page, page_flags: PageFlags<RmmA>, _writable: bool) -> Result<Frame, PfError> { - let new_frame = init_frame()?; + let new_frame = init_frame(1, 0)?; unsafe { mapper.map_phys(page.start_address(), new_frame.start_address(), page_flags).ok_or(PfError::Oom)?.ignore(); @@ -1357,7 +1418,7 @@ fn correct_inner<'l>(addr_space_lock: &'l RwLock<AddrSpace>, mut addr_space_guar let mut debug = false; let frame = match grant_info.provider { - Provider::Allocated { .. } if access == AccessMode::Write => { + Provider::Allocated { .. } | Provider::AllocatedShared if access == AccessMode::Write => { match faulting_pageinfo_opt { Some((_, None)) => unreachable!("allocated page needs frame to be valid"), Some((frame, Some(info))) => if info.owned_refcount() == 1 { @@ -1368,10 +1429,14 @@ fn correct_inner<'l>(addr_space_lock: &'l RwLock<AddrSpace>, mut addr_space_guar _ => map_zeroed(&mut addr_space.table.utable, faulting_page, grant_flags, true)?, } } - Provider::Allocated { .. } => { + + Provider::Allocated { .. } | Provider::AllocatedShared => { match faulting_pageinfo_opt { Some((_, None)) => unreachable!("allocated page needs frame to be valid"), Some((frame, Some(page_info))) => { + // Keep in mind that alloc_writable must always be true if this code is reached + // for AllocatedShared, since shared pages cannot be mapped lazily (without + // using AddrSpace backrefs). allow_writable = page_info.owned_refcount() == 1; frame @@ -1495,3 +1560,8 @@ pub fn handle_notify_files(notify_files: Vec<UnmapResult>) { let _ = file.unmap(); } } + +pub enum CopyMappingsMode { + Owned { cow_file_ref: Option<GrantFileRef> }, + Borrowed, +} diff --git a/src/scheme/memory.rs b/src/scheme/memory.rs index a76cc4e5a3b1456c25f1b89953bdf16f5efda21b..beb1fd99813960a15a02d4a4295d3eef1a9d561d 100644 --- a/src/scheme/memory.rs +++ b/src/scheme/memory.rs @@ -12,6 +12,7 @@ use crate::paging::VirtualAddress; use crate::paging::entry::EntryFlags; use crate::syscall::data::{Map, StatVfs}; +use crate::syscall::flag::MapFlags; use crate::syscall::error::*; use crate::syscall::scheme::Scheme; use crate::syscall::usercopy::UserSliceWo; @@ -65,7 +66,7 @@ impl MemoryScheme { let page = addr_space .write() .mmap((map.address != 0).then_some(span.base), page_count, map.flags, &mut notify_files, |dst_page, flags, mapper, flusher| { - Ok(Grant::zeroed(PageSpan::new(dst_page, page_count.get()), flags, mapper, flusher)?) + Ok(Grant::zeroed(PageSpan::new(dst_page, page_count.get()), flags, mapper, flusher, map.flags.contains(MapFlags::MAP_SHARED))?) })?; handle_notify_files(notify_files); diff --git a/src/syscall/process.rs b/src/syscall/process.rs index 4737b1623007650fe2ee98c1a3542392d7d84cee..dcd7a91f163075f6e004e267b4578061be63d883 100644 --- a/src/syscall/process.rs +++ b/src/syscall/process.rs @@ -546,6 +546,7 @@ pub unsafe fn usermode_bootstrap(bootstrap: &Bootstrap) -> ! { PageFlags::new().user(true).write(true).execute(true), &mut addr_space.table.utable, PageFlushAll::new(), + false, // is_shared ).expect("failed to physmap bootstrap memory")); }