diff --git a/libkernel/src/memory/proc_vm/memory_map/mod.rs b/libkernel/src/memory/proc_vm/memory_map/mod.rs index 508f8e66..089ff43e 100644 --- a/libkernel/src/memory/proc_vm/memory_map/mod.rs +++ b/libkernel/src/memory/proc_vm/memory_map/mod.rs @@ -93,12 +93,25 @@ impl MemoryMap { /// Maps a region of memory. pub fn mmap( + &mut self, + requested_address: AddressRequest, + len: usize, + perms: VMAPermissions, + kind: VMAreaKind, + name: String, + ) -> Result { + self.mmap_with_options(requested_address, len, perms, kind, name, false) + } + + /// Maps a region of memory with explicit VMA options. + pub fn mmap_with_options( &mut self, requested_address: AddressRequest, mut len: usize, perms: VMAPermissions, kind: VMAreaKind, name: String, + shared: bool, ) -> Result { if len == 0 { return Err(KernelError::InvalidValue); @@ -150,6 +163,7 @@ impl MemoryMap { let mut new_vma = VMArea::new(region, kind, perms); new_vma.set_name(name); + new_vma.set_shared(shared); self.insert_and_merge(new_vma); @@ -494,8 +508,9 @@ impl MemoryMap { for vma in new_vmas.values() { let mut pte_perms = PtePermissions::from(vma.permissions); - // Mark all writable pages as CoW. - if pte_perms.is_write() { + // Shared mappings stay shared across fork; private writable + // mappings become CoW. + if !vma.is_shared() && pte_perms.is_write() { pte_perms = pte_perms.into_cow(); } diff --git a/libkernel/src/memory/proc_vm/mod.rs b/libkernel/src/memory/proc_vm/mod.rs index 8e0c08d7..8ff05340 100644 --- a/libkernel/src/memory/proc_vm/mod.rs +++ b/libkernel/src/memory/proc_vm/mod.rs @@ -194,6 +194,7 @@ mod tests { kind: VMAreaKind::Anon, // Simplification for test permissions: VMAPermissions::rx(), name: String::new(), + shared: false, }; ProcessVM::from_vma(text_vma).unwrap() @@ -350,6 +351,7 @@ mod tests { kind: VMAreaKind::Anon, permissions: VMAPermissions::ro(), name: String::new(), + shared: false, }; vm.mm.insert_and_merge(obstacle_vma); assert_eq!(vm.mm.vma_count(), 2); diff --git a/libkernel/src/memory/proc_vm/vmarea.rs b/libkernel/src/memory/proc_vm/vmarea.rs index 322cd723..ea864922 100644 --- a/libkernel/src/memory/proc_vm/vmarea.rs +++ b/libkernel/src/memory/proc_vm/vmarea.rs @@ -186,6 +186,7 @@ pub struct VMArea { pub(super) name: String, pub(super) kind: VMAreaKind, pub(super) permissions: VMAPermissions, + pub(super) shared: bool, } impl VMArea { @@ -201,6 +202,7 @@ impl VMArea { kind, permissions, name: String::new(), + shared: false, } } @@ -209,6 +211,11 @@ impl VMArea { self.name = s.as_ref().to_string(); } + /// Marks this VMA as a shared mapping. + pub fn set_shared(&mut self, shared: bool) { + self.shared = shared; + } + /// Creates a file-backed `VMArea` directly from an ELF program header. /// /// This is a convenience function used by the ELF loader. It parses the @@ -264,6 +271,7 @@ impl VMArea { }), permissions, name: String::new(), + shared: false, } } @@ -397,7 +405,7 @@ impl VMArea { /// Merging is possible if permissions are identical and the backing storage /// is of a compatible and contiguous nature. pub(super) fn can_merge_with(&self, other: &VMArea) -> bool { - if self.permissions != other.permissions { + if self.permissions != other.permissions || self.shared != other.shared { return false; } @@ -435,6 +443,11 @@ impl VMArea { matches!(self.kind, VMAreaKind::File(_)) } + /// Returns whether this VMA was created as a shared mapping. + pub fn is_shared(&self) -> bool { + self.shared + } + /// Shrink this VMA's region to `new_region`, recalculating file offsets, /// for file mappings. #[must_use] diff --git a/src/memory/mmap.rs b/src/memory/mmap.rs index 6a312a9a..4577e6f9 100644 --- a/src/memory/mmap.rs +++ b/src/memory/mmap.rs @@ -1,12 +1,15 @@ use core::sync::atomic::{AtomicUsize, Ordering}; -use crate::{process::fd_table::Fd, sched::syscall_ctx::ProcessCtx}; +use crate::{memory::page::ClaimedPage, process::fd_table::Fd, sched::syscall_ctx::ProcessCtx}; use alloc::string::{String, ToString}; use libkernel::{ - error::{KernelError, Result}, + error::{KernelError, MapError, Result}, memory::{ + PAGE_MASK, PAGE_SIZE, address::VA, + paging::permissions::PtePermissions, proc_vm::{ + address_space::UserAddressSpace, memory_map::AddressRequest, vmarea::{VMAPermissions, VMAreaKind}, }, @@ -45,6 +48,47 @@ fn prot_to_perms(prot: u64) -> VMAPermissions { /// # Returns /// A `Result` containing the starting address of the new mapping on success, /// or a `KernelError` on failure. +fn align_mmap_len(len: usize) -> usize { + if len & PAGE_MASK != 0 { + (len & !PAGE_MASK) + PAGE_SIZE + } else { + len + } +} + +async fn populate_shared_anon_mapping( + ctx: &ProcessCtx, + start: VA, + len: usize, + permissions: VMAPermissions, +) -> Result<()> { + let region = VirtMemoryRegion::new(start, align_mmap_len(len)); + + for page_va in region.iter_pages() { + let page = ClaimedPage::alloc_zeroed()?; + let pfn = page.pa().to_pfn(); + + match ctx + .shared() + .vm + .lock_save_irq() + .mm_mut() + .address_space_mut() + .map_page(pfn, page_va, PtePermissions::from(permissions)) + { + Ok(()) => { + page.leak(); + } + Err(KernelError::MappingError(MapError::AlreadyMapped)) => { + drop(page); + } + Err(e) => return Err(e), + } + } + + Ok(()) +} + pub async fn sys_mmap( ctx: &ProcessCtx, addr: u64, @@ -58,13 +102,15 @@ pub async fn sys_mmap( return Err(KernelError::InvalidValue); } - // Ensure mapping sharability has been specified: - if (flags & (MAP_SHARED | MAP_PRIVATE)) == 0 { + let mapping_kind = flags & (MAP_SHARED | MAP_PRIVATE); + if mapping_kind == 0 || mapping_kind == (MAP_SHARED | MAP_PRIVATE) { return Err(KernelError::InvalidValue); } - // TODO: Shared Mappings. - if (flags & MAP_SHARED) != 0 { + let is_shared = (flags & MAP_SHARED) != 0; + let is_anon = (flags & (MAP_ANON | MAP_ANONYMOUS)) != 0; + + if is_shared && !is_anon { return Err(KernelError::NotSupported); } @@ -87,7 +133,10 @@ pub async fn sys_mmap( let requested_len = len as usize; - let (kind, name) = if (flags & (MAP_ANON | MAP_ANONYMOUS)) != 0 { + let (kind, name) = if is_anon { + if offset != 0 { + return Err(KernelError::InvalidValue); + } (VMAreaKind::Anon, String::new()) } else { // File-backed mapping: require a valid fd and use the provided offset. @@ -127,14 +176,23 @@ pub async fn sys_mmap( }; // Lock the task and call the core memory manager to perform the mapping. - let new_mapping_addr = ctx.shared().vm.lock_save_irq().mm_mut().mmap( + let new_mapping_addr = ctx.shared().vm.lock_save_irq().mm_mut().mmap_with_options( address_request, requested_len, permissions, kind, name, + is_shared, )?; + if is_shared + && let Err(e) = + populate_shared_anon_mapping(ctx, new_mapping_addr, requested_len, permissions).await + { + let _ = sys_munmap(ctx, new_mapping_addr, align_mmap_len(requested_len)).await; + return Err(e); + } + Ok(new_mapping_addr.value()) }