kernel/object/capability/memory_mapping/syscall.rs
1//! System calls for MemoryMappingOps capability
2//!
3//! This module implements system calls for memory mapping operations.
4//! ANONYMOUS mappings are handled directly in the syscall for efficiency,
5//! while all other mappings (including FIXED) are delegated to KernelObjects
6//! with MemoryMappingOps capability.
7
8use crate::arch::Trapframe;
9use crate::environment::PAGE_SIZE;
10use crate::mem::page::allocate_raw_pages;
11use crate::task::mytask;
12use crate::vm::vmem::{MemoryArea, VirtualMemoryMap};
13use alloc::boxed::Box;
14use alloc::vec::Vec;
15
16// Memory mapping flags (MAP_*)
17#[allow(dead_code)]
18const MAP_SHARED: usize = 0x01;
19const MAP_ANONYMOUS: usize = 0x20;
20const MAP_PRIVATE: usize = 0x02;
21const MAP_FIXED: usize = 0x10;
22
23// Protection flags (PROT_*)
24const PROT_READ: usize = 0x1;
25const PROT_WRITE: usize = 0x2;
26const PROT_EXEC: usize = 0x4;
27
28/// System call for memory mapping a KernelObject with MemoryMappingOps capability
29/// or creating anonymous mappings
30///
31/// # Arguments
32/// - handle: Handle to the KernelObject (must support MemoryMappingOps) - ignored for ANONYMOUS
33/// - vaddr: Virtual address where to map (0 means kernel chooses)
34/// - length: Length of the mapping in bytes
35/// - prot: Protection flags (PROT_READ, PROT_WRITE, PROT_EXEC)
36/// - flags: Mapping flags (MAP_SHARED, MAP_PRIVATE, MAP_FIXED, MAP_ANONYMOUS, etc.)
37/// - offset: Offset within the object to start mapping from (ignored for ANONYMOUS)
38///
39/// # Returns
40/// - On success: virtual address of the mapping
41/// - On error: usize::MAX
42///
43/// # Design
44/// - ANONYMOUS mappings are handled entirely within this syscall
45/// - All other mappings (including FIXED) are delegated to the KernelObject's MemoryMappingOps
46pub fn sys_memory_map(trapframe: &mut Trapframe) -> usize {
47 let task = match mytask() {
48 Some(task) => task,
49 None => return usize::MAX,
50 };
51
52 let handle = trapframe.get_arg(0) as u32;
53 let vaddr = trapframe.get_arg(1) as usize;
54 let length = trapframe.get_arg(2) as usize;
55 let prot = trapframe.get_arg(3) as usize;
56 let flags = trapframe.get_arg(4) as usize;
57 let offset = trapframe.get_arg(5) as usize;
58
59 // Increment PC to avoid infinite loop if mmap fails
60 trapframe.increment_pc_next(task);
61
62 // Input validation
63 if length == 0 {
64 return usize::MAX;
65 }
66
67 // Round up length to page boundary
68 let aligned_length = (length + PAGE_SIZE - 1) & !(PAGE_SIZE - 1);
69 let num_pages = aligned_length / PAGE_SIZE;
70
71 // Handle ANONYMOUS mappings specially - these are handled entirely in the syscall
72 if (flags & MAP_ANONYMOUS) != 0 {
73 return handle_anonymous_mapping(task, vaddr, aligned_length, num_pages, prot, flags);
74 }
75
76 // All other mappings are handled through the new MemoryMappingOps design
77 let kernel_obj = match task.handle_table.get(handle) {
78 Some(obj) => obj,
79 None => return usize::MAX, // Invalid handle
80 };
81
82 // Check if object supports MemoryMappingOps
83 let memory_mappable = match kernel_obj.as_memory_mappable() {
84 Some(mappable) => mappable,
85 None => return usize::MAX, // Object doesn't support memory mapping operations
86 };
87
88 // Check if the object supports mmap
89 if !memory_mappable.supports_mmap() {
90 return usize::MAX;
91 }
92
93 // Get mapping information from the object.
94 // IMPORTANT: use the page-aligned length, because the VM mapping will be created
95 // with `aligned_length` and must not exceed the object's available range.
96 // Determine is_shared from flags (MAP_SHARED controls sharing semantics)
97 let is_shared = (flags & MAP_SHARED) != 0;
98 let (paddr, obj_permissions, _obj_is_shared) =
99 match memory_mappable.get_mapping_info_with(offset, aligned_length, is_shared) {
100 Ok(info) => info,
101 Err(_) => return usize::MAX,
102 };
103 let is_map_fixed = (flags & MAP_FIXED) != 0;
104
105 // Determine final address
106 let final_vaddr = if vaddr == 0 {
107 match task
108 .vm_manager
109 .find_unmapped_area(aligned_length, PAGE_SIZE)
110 {
111 Some(addr) => addr,
112 None => return usize::MAX,
113 }
114 } else {
115 if vaddr % PAGE_SIZE != 0 {
116 return usize::MAX;
117 }
118 vaddr
119 };
120
121 // Create memory areas
122 let vmarea = MemoryArea::new(final_vaddr, final_vaddr + aligned_length - 1);
123 let pmarea = MemoryArea::new(paddr, paddr + aligned_length - 1);
124
125 // Combine object permissions with requested permissions
126 let final_permissions = obj_permissions & {
127 let mut perm = 0;
128 if (prot & PROT_READ) != 0 {
129 perm |= 0x1;
130 }
131 if (prot & PROT_WRITE) != 0 {
132 perm |= 0x2;
133 }
134 if (prot & PROT_EXEC) != 0 {
135 perm |= 0x4;
136 }
137 perm
138 } | 0x08; // Access from user space
139
140 // If this is a private file-backed mapping, allocate private pages and copy
141 let is_map_private_flag = (flags & MAP_PRIVATE) != 0;
142 if is_map_private_flag && !is_shared {
143 // Allocate private pages
144 let pages = allocate_raw_pages(num_pages);
145 let pages_ptr = pages as usize;
146 let private_pmarea = MemoryArea::new(pages_ptr, pages_ptr + aligned_length - 1);
147
148 let mut vm_map =
149 VirtualMemoryMap::new(private_pmarea, vmarea, final_permissions, false, None);
150
151 // For non-MAP_FIXED, treat vaddr as a hint: if it overlaps, pick a new area.
152 let mut chosen_vaddr = final_vaddr;
153 if !is_map_fixed {
154 if task.vm_manager.add_memory_map(vm_map.clone()).is_err() {
155 chosen_vaddr = match task
156 .vm_manager
157 .find_unmapped_area(aligned_length, PAGE_SIZE)
158 {
159 Some(addr) => addr,
160 None => {
161 crate::mem::page::free_raw_pages(pages, num_pages);
162 return usize::MAX;
163 }
164 };
165 let vmarea = MemoryArea::new(chosen_vaddr, chosen_vaddr + aligned_length - 1);
166 vm_map =
167 VirtualMemoryMap::new(private_pmarea, vmarea, final_permissions, false, None);
168 if task.vm_manager.add_memory_map(vm_map.clone()).is_err() {
169 crate::mem::page::free_raw_pages(pages, num_pages);
170 return usize::MAX;
171 }
172 }
173 }
174
175 let removed_mappings: Vec<VirtualMemoryMap> = if is_map_fixed {
176 match task.vm_manager.add_memory_map_fixed(vm_map.clone()) {
177 Ok(removed) => removed,
178 Err(_) => {
179 crate::mem::page::free_raw_pages(pages, num_pages);
180 return usize::MAX;
181 }
182 }
183 } else {
184 // Non-fixed path already inserted the map via add_memory_map above.
185 Vec::new()
186 };
187
188 {
189 // For private mappings the new mapping uses private pages and the object
190 // does not own those pages; avoid calling on_mapped for the object.
191
192 // Notify owners of removed maps (only for shared mappings)
193 for removed_map in &removed_mappings {
194 if removed_map.is_shared {
195 if let Some(owner_weak) = &removed_map.owner {
196 if let Some(owner) = owner_weak.upgrade() {
197 owner.on_unmapped(removed_map.vmarea.start, removed_map.vmarea.size());
198 }
199 }
200 }
201 }
202
203 // Clean up removed managed pages
204 for removed_map in removed_mappings {
205 if !removed_map.is_shared {
206 let mapping_start = removed_map.vmarea.start;
207 let mapping_end = removed_map.vmarea.end;
208 let num_removed_pages =
209 (mapping_end - mapping_start + 1 + PAGE_SIZE - 1) / PAGE_SIZE;
210 for i in 0..num_removed_pages {
211 let page_vaddr = mapping_start + i * PAGE_SIZE;
212 if let Some(_managed_page) = task.remove_managed_page(page_vaddr) {
213 // freed when dropped
214 }
215 }
216 }
217 }
218
219 // Copy contents from object paddr to private pages
220 for i in 0..num_pages {
221 let src = (paddr + i * PAGE_SIZE) as *const u8;
222 let dst_page = unsafe { (pages as *mut crate::mem::page::Page).add(i) } as *mut u8;
223 unsafe {
224 core::ptr::copy_nonoverlapping(src, dst_page, PAGE_SIZE);
225 }
226 }
227
228 // Add managed pages to task
229 for i in 0..num_pages {
230 let page_vaddr = chosen_vaddr + i * crate::environment::PAGE_SIZE;
231 let page_ptr = unsafe { (pages as *mut crate::mem::page::Page).add(i) };
232 task.add_managed_page(crate::task::ManagedPage {
233 vaddr: page_vaddr,
234 page: unsafe { Box::from_raw(page_ptr) },
235 });
236 }
237
238 return chosen_vaddr;
239 }
240 }
241
242 // Create virtual memory map with weak reference to the object
243 let owner = kernel_obj.as_memory_mappable_weak();
244 let vm_map = VirtualMemoryMap::new(pmarea, vmarea, final_permissions, is_shared, owner);
245
246 // Add the mapping to VM manager
247 if !is_map_fixed {
248 // vaddr != 0 is treated as a hint; if it overlaps, fall back to a fresh area.
249 if task.vm_manager.add_memory_map(vm_map.clone()).is_err() {
250 let chosen_vaddr = match task
251 .vm_manager
252 .find_unmapped_area(aligned_length, PAGE_SIZE)
253 {
254 Some(addr) => addr,
255 None => return usize::MAX,
256 };
257 let vmarea = MemoryArea::new(chosen_vaddr, chosen_vaddr + aligned_length - 1);
258 let pmarea = MemoryArea::new(paddr, paddr + aligned_length - 1);
259 let owner = kernel_obj.as_memory_mappable_weak();
260 let retry_map =
261 VirtualMemoryMap::new(pmarea, vmarea, final_permissions, is_shared, owner);
262 if task.vm_manager.add_memory_map(retry_map).is_err() {
263 return usize::MAX;
264 }
265
266 memory_mappable.on_mapped(chosen_vaddr, paddr, aligned_length, offset);
267 return chosen_vaddr;
268 }
269
270 memory_mappable.on_mapped(final_vaddr, paddr, aligned_length, offset);
271 return final_vaddr;
272 }
273
274 match task.vm_manager.add_memory_map_fixed(vm_map) {
275 Ok(removed_mappings) => {
276 // Notify the object that mapping was created
277 memory_mappable.on_mapped(final_vaddr, paddr, aligned_length, offset);
278
279 // First, notify object owners about removed mappings
280 for removed_map in &removed_mappings {
281 if removed_map.is_shared {
282 if let Some(owner_weak) = &removed_map.owner {
283 if let Some(owner) = owner_weak.upgrade() {
284 owner.on_unmapped(removed_map.vmarea.start, removed_map.vmarea.size());
285 }
286 }
287 }
288 }
289
290 // Then, handle managed page cleanup (MMU cleanup is already handled by VmManager.add_memory_map_fixed)
291 for removed_map in removed_mappings {
292 // Remove managed pages only for private mappings
293 if !removed_map.is_shared {
294 let mapping_start = removed_map.vmarea.start;
295 let mapping_end = removed_map.vmarea.end;
296 let num_pages = (mapping_end - mapping_start + 1 + PAGE_SIZE - 1) / PAGE_SIZE;
297
298 for i in 0..num_pages {
299 let page_vaddr = mapping_start + i * PAGE_SIZE;
300 if let Some(_managed_page) = task.remove_managed_page(page_vaddr) {
301 // The managed page is automatically freed when dropped
302 }
303 }
304 }
305 }
306
307 final_vaddr
308 }
309 Err(_) => usize::MAX,
310 }
311}
312
313/// Handle anonymous memory mapping
314fn handle_anonymous_mapping(
315 task: &crate::task::Task,
316 vaddr: usize,
317 aligned_length: usize,
318 num_pages: usize,
319 prot: usize,
320 flags: usize,
321) -> usize {
322 // For anonymous mappings, decide shared/private based on flags
323 let is_shared = (flags & MAP_SHARED) != 0;
324 let is_map_fixed = (flags & MAP_FIXED) != 0;
325
326 // For anonymous mappings, allocate physical memory directly
327 let pages = allocate_raw_pages(num_pages);
328 let pages_ptr = pages as usize;
329
330 // Convert protection flags to kernel permissions
331 let mut permissions = 0x08; // Access from user space
332 if (prot & PROT_READ) != 0 {
333 permissions |= 0x1; // Readable
334 }
335 if (prot & PROT_WRITE) != 0 {
336 permissions |= 0x2; // Writable
337 }
338 if (prot & PROT_EXEC) != 0 {
339 permissions |= 0x4; // Executable
340 }
341
342 // Determine final address (0 means kernel chooses)
343 let mut chosen_vaddr = vaddr;
344 if chosen_vaddr == 0 {
345 chosen_vaddr = match task
346 .vm_manager
347 .find_unmapped_area(aligned_length, PAGE_SIZE)
348 {
349 Some(addr) => addr,
350 None => {
351 crate::mem::page::free_raw_pages(pages, num_pages);
352 return usize::MAX;
353 }
354 };
355 } else {
356 if chosen_vaddr % PAGE_SIZE != 0 {
357 crate::mem::page::free_raw_pages(pages, num_pages);
358 return usize::MAX;
359 }
360 }
361
362 // Create memory areas
363 let vmarea = MemoryArea::new(chosen_vaddr, chosen_vaddr + aligned_length - 1);
364 let pmarea = MemoryArea::new(pages_ptr, pages_ptr + aligned_length - 1);
365
366 // Create virtual memory map
367 let vm_map = VirtualMemoryMap::new(pmarea, vmarea, permissions, is_shared, None); // Anonymous mappings have no owner
368
369 if !is_map_fixed {
370 // Treat chosen_vaddr as hint; if it overlaps, fall back once.
371 if task.vm_manager.add_memory_map(vm_map.clone()).is_err() {
372 chosen_vaddr = match task
373 .vm_manager
374 .find_unmapped_area(aligned_length, PAGE_SIZE)
375 {
376 Some(addr) => addr,
377 None => {
378 crate::mem::page::free_raw_pages(pages, num_pages);
379 return usize::MAX;
380 }
381 };
382 let vmarea = MemoryArea::new(chosen_vaddr, chosen_vaddr + aligned_length - 1);
383 let retry_map = VirtualMemoryMap::new(pmarea, vmarea, permissions, is_shared, None);
384 if task.vm_manager.add_memory_map(retry_map).is_err() {
385 crate::mem::page::free_raw_pages(pages, num_pages);
386 return usize::MAX;
387 }
388 }
389 } else {
390 match task.vm_manager.add_memory_map_fixed(vm_map) {
391 Ok(removed_mappings) => {
392 // First, process notifications for object owners
393 for removed_map in &removed_mappings {
394 if removed_map.is_shared {
395 if let Some(owner_weak) = &removed_map.owner {
396 if let Some(owner) = owner_weak.upgrade() {
397 owner.on_unmapped(
398 removed_map.vmarea.start,
399 removed_map.vmarea.size(),
400 );
401 }
402 }
403 }
404 }
405
406 // Then, handle managed page cleanup (MMU cleanup is already handled by VmManager.add_memory_map_fixed)
407 for removed_map in removed_mappings {
408 // Remove managed pages only for private mappings
409 if !removed_map.is_shared {
410 let mapping_start = removed_map.vmarea.start;
411 let mapping_end = removed_map.vmarea.end;
412 let num_removed_pages =
413 (mapping_end - mapping_start + 1 + PAGE_SIZE - 1) / PAGE_SIZE;
414
415 for i in 0..num_removed_pages {
416 let page_vaddr = mapping_start + i * PAGE_SIZE;
417 if let Some(_managed_page) = task.remove_managed_page(page_vaddr) {
418 // The managed page is automatically freed when dropped
419 }
420 }
421 }
422 }
423 }
424 Err(_) => {
425 crate::mem::page::free_raw_pages(pages, num_pages);
426 return usize::MAX;
427 }
428 }
429 }
430
431 // Add managed pages for the new anonymous mapping
432 for i in 0..num_pages {
433 let page_vaddr = chosen_vaddr + i * crate::environment::PAGE_SIZE;
434 let page_ptr = unsafe { (pages as *mut crate::mem::page::Page).add(i) };
435 task.add_managed_page(crate::task::ManagedPage {
436 vaddr: page_vaddr,
437 page: unsafe { Box::from_raw(page_ptr) },
438 });
439 }
440
441 chosen_vaddr
442}
443
444/// System call for unmapping memory from a KernelObject or anonymous mapping
445///
446/// # Arguments
447/// - vaddr: Virtual address of the mapping to unmap
448/// - length: Length of the mapping to unmap
449///
450/// # Returns
451/// - On success: 0
452/// - On error: usize::MAX
453pub fn sys_memory_unmap(trapframe: &mut Trapframe) -> usize {
454 let task = match mytask() {
455 Some(task) => task,
456 None => return usize::MAX,
457 };
458
459 let vaddr = trapframe.get_arg(0) as usize;
460 let length = trapframe.get_arg(1) as usize;
461
462 // Increment PC to avoid infinite loop if munmap fails
463 trapframe.increment_pc_next(task);
464
465 // Input validation
466 if length == 0 || vaddr % PAGE_SIZE != 0 {
467 return usize::MAX;
468 }
469
470 // Remove the mapping regardless of whether it's anonymous or object-based
471 if let Some(removed_map) = task.vm_manager.remove_memory_map_by_addr(vaddr) {
472 // Notify the object owner if available (for object-based mappings)
473 if let Some(owner_weak) = &removed_map.owner {
474 if removed_map.is_shared {
475 if let Some(owner) = owner_weak.upgrade() {
476 owner.on_unmapped(vaddr, length);
477 }
478 }
479 }
480
481 // Remove managed pages only for private mappings
482 // Shared mappings should not have their physical pages freed here
483 // as they might be used by other processes
484 // (MMU cleanup is already handled by VmManager.remove_memory_map_by_addr)
485 if !removed_map.is_shared {
486 let mapping_start = removed_map.vmarea.start;
487 let mapping_end = removed_map.vmarea.end;
488 let num_pages = (mapping_end - mapping_start + 1 + PAGE_SIZE - 1) / PAGE_SIZE;
489
490 for i in 0..num_pages {
491 let page_vaddr = mapping_start + i * PAGE_SIZE;
492 if let Some(_managed_page) = task.remove_managed_page(page_vaddr) {
493 // The managed page is automatically freed when dropped
494 }
495 }
496 }
497
498 0
499 } else {
500 usize::MAX // No mapping found at this address
501 }
502}
503
504// TODO: Migrate object-backed MAP_PRIVATE mappings to delayed Copy-On-Write (COW).
505// Motivation:
506// - Currently MAP_PRIVATE file-backed mappings perform an eager (immediate) copy of
507// the mapped region at mmap time. This can be wasteful for large mappings or when
508// the mapping is only read by the process. Delayed COW preserves memory and CPU
509// by copying only on the first write to a page.
510//
511// High-level plan (implementation checklist):
512// 1) Syscall layer: when a user requests MAP_PRIVATE for an object-backed mapping,
513// set a `cow` flag on the VirtualMemoryMap and do NOT perform an immediate copy.
514// - Ensure the mapping is installed with write permission cleared so stores trap.
515// - Preserve the mapping owner (object) for read access until pages are copied.
516//
517// 2) VM representation: add/ensure a boolean `cow` field on VirtualMemoryMap to mark
518// that the mapping uses copy-on-write semantics.
519//
520// 3) Exception/Trap handling: on store-page-faults, detect whether the faulting
521// virtual address belongs to a mapping with cow == true. If so, invoke a per-page
522// COW handler instead of the generic lazy mapping logic.
523//
524// 4) Task::handle_cow_page: implement a handler that:
525// - Allocates a new physical page for the faulting virtual page.
526// - Copies the contents from the original backing paddr (via the owner object
527// or pmarea) to the newly allocated page.
528// - Replaces only the single faulting page in the VM map by inserting a fixed
529// one-page VirtualMemoryMap (owner = None) for that vaddr and maps it immediately
530// (e.g., vm_manager.add_memory_map_fixed + vm_manager.lazy_map_page).
531// - Registers the new page as a managed page of the current Task (so it will be
532// freed on exit).
533//
534// 5) Fork/clone semantics: ensure that when a Task is cloned/forked, the child and parent
535// share the same physical pages (do not eagerly copy) and the `cow` flag is preserved
536// on the mapping entries so that subsequent writes by either side trigger COW.
537// - Ensure managed_pages bookkeeping remains correct (only private copies are managed
538// by the process that holds them).
539//
540// 6) Tests and validation:
541// - Add unit/integration tests that map the same file in two tasks with MAP_PRIVATE,
542// then write from one task and assert the other still sees original content.
543// - Add tests for fork/clone + MAP_PRIVATE behavior.
544// - Add tests for corner cases (partial-page offsets, overlapping mappings, munmap
545// of pages that have been COW'ed).
546//
547// 7) Documentation: update rustdoc and design documentation to describe the COW
548// semantics, the role of the `cow` flag, and the guarantees provided (ownership,
549// notification behavior, and lifecycle of managed pages).
550//
551// Acceptance criteria:
552// - MAP_PRIVATE mappings are created without eager copying (vm_manager installs mapping
553// with cow=true and write cleared).
554// - On first write to a page, only that page is copied and the writer gets a private
555// writable page while others continue sharing the original page.
556// - All added tests pass in the dev environment (cargo make test) and resource leaks
557// (pages) are not introduced.
558//
559// Notes & constraints:
560// - Some object types (e.g., device MMIO) cannot be safely COW'ed; sys_memory_map must
561// detect such objects via supports_mmap / get_mapping_info and either fall back to
562// eager-copy, reject the mapping, or require special flags. Document these cases.
563// - This change requires careful updates to trap handling and the Task-managed page
564// bookkeeping; perform the work incrementally and add tests at each step.