Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions libkernel/src/memory/proc_vm/memory_map/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,25 @@ impl<AS: UserAddressSpace> MemoryMap<AS> {

/// Maps a region of memory.
pub fn mmap(
&mut self,
requested_address: AddressRequest,
len: usize,
perms: VMAPermissions,
kind: VMAreaKind,
name: String,
) -> Result<VA> {
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<VA> {
if len == 0 {
return Err(KernelError::InvalidValue);
Expand Down Expand Up @@ -150,6 +163,7 @@ impl<AS: UserAddressSpace> MemoryMap<AS> {
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);

Expand Down Expand Up @@ -494,8 +508,9 @@ impl<AS: UserAddressSpace> MemoryMap<AS> {
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();
}

Expand Down
2 changes: 2 additions & 0 deletions libkernel/src/memory/proc_vm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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);
Expand Down
15 changes: 14 additions & 1 deletion libkernel/src/memory/proc_vm/vmarea.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -201,6 +202,7 @@ impl VMArea {
kind,
permissions,
name: String::new(),
shared: false,
}
}

Expand All @@ -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
Expand Down Expand Up @@ -264,6 +271,7 @@ impl VMArea {
}),
permissions,
name: String::new(),
shared: false,
}
}

Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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]
Expand Down
74 changes: 66 additions & 8 deletions src/memory/mmap.rs
Original file line number Diff line number Diff line change
@@ -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},
},
Expand Down Expand Up @@ -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,
Expand All @@ -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);
}

Expand All @@ -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.
Expand Down Expand Up @@ -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())
}

Expand Down
Loading