Skip to content

Commit 7c9bc0b

Browse files
committed
mapping/file_mapping: Implement copy-on-write for writeable file mapped vm regions
Currently, a writeable VMFileMapping will copy the entire contents of the region from the source pages into a read/write copy. This is inefficient in cases where the mapping may be quite large or only sparsely accessed. This commit adds page fault handling to VMFileMapping. The original file pages are mapped as read-only and when a write occurs the page fault is handled and a copy taken of the page where the fault occurred. The copy is stored in a sparse vector in RawAllocMapping. The VMR will update the pagetable for the virtual range to map the new page with the correct access flags. Signed-off-by: Roy Hopkins <[email protected]>
1 parent 1f5bd0b commit 7c9bc0b

File tree

1 file changed

+84
-3
lines changed

1 file changed

+84
-3
lines changed

src/mm/vm/mapping/file_mapping.rs

+84-3
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,16 @@
66

77
extern crate alloc;
88

9+
use core::slice::from_raw_parts_mut;
10+
11+
use alloc::sync::Arc;
912
use alloc::vec::Vec;
1013

11-
use super::{RawAllocMapping, VirtualMapping};
14+
use super::{Mapping, RawAllocMapping, VMPageFaultResolution, VMPhysMem, VirtualMapping};
15+
use crate::address::Address;
1216
use crate::error::SvsmError;
1317
use crate::fs::FileHandle;
18+
use crate::mm::vm::VMR;
1419
use crate::mm::PageRef;
1520
use crate::mm::{pagetable::PageTable, PAGE_SIZE};
1621
use crate::types::PAGE_SHIFT;
@@ -19,6 +24,16 @@ use crate::utils::align_up;
1924
#[derive(Debug)]
2025
struct VMWriteFileMapping(RawAllocMapping);
2126

27+
impl VMWriteFileMapping {
28+
pub fn get_alloc(&self) -> &RawAllocMapping {
29+
&self.0
30+
}
31+
32+
pub fn get_alloc_mut(&mut self) -> &mut RawAllocMapping {
33+
&mut self.0
34+
}
35+
}
36+
2237
impl VirtualMapping for VMWriteFileMapping {
2338
fn mapping_size(&self) -> usize {
2439
self.0.mapping_size()
@@ -57,6 +72,9 @@ pub struct VMFileMapping {
5772

5873
/// A vec containing references to mapped pages within the file
5974
pages: Vec<Option<PageRef>>,
75+
76+
/// A copy of the file pages for mappings with Write permission
77+
write_copy: Option<VMWriteFileMapping>,
6078
}
6179

6280
impl VMFileMapping {
@@ -96,12 +114,22 @@ impl VMFileMapping {
96114
for page_index in 0..count {
97115
pages.push(file.mapping(offset + page_index * PAGE_SIZE));
98116
}
117+
// For ranges with write access we need to take a copy of the ram pages
118+
// to allow them to be written to without modifying the contents of the
119+
// file itself and also to prevent pointer aliasing with any other
120+
// FileHandles that may be open on the same file.
121+
let write_copy = if permission == VMFileMappingPermission::Write {
122+
Some(VMWriteFileMapping(RawAllocMapping::new(size)))
123+
} else {
124+
None
125+
};
99126

100127
Ok(Self {
101128
file,
102129
size,
103130
permission,
104131
pages,
132+
write_copy,
105133
})
106134
}
107135
}
@@ -116,14 +144,67 @@ impl VirtualMapping for VMFileMapping {
116144
if page_index >= self.pages.len() {
117145
return None;
118146
}
147+
if let Some(write_copy) = &self.write_copy {
148+
let write_addr = write_copy.map(offset);
149+
if write_addr.is_some() {
150+
return write_addr;
151+
}
152+
}
119153
self.pages[page_index].as_ref().map(|p| p.phys_addr())
120154
}
121155

122-
fn pt_flags(&self, _offset: usize) -> crate::mm::pagetable::PTEntryFlags {
156+
fn pt_flags(&self, offset: usize) -> crate::mm::pagetable::PTEntryFlags {
123157
match self.permission {
124158
VMFileMappingPermission::Read => PageTable::task_data_ro_flags(),
125-
VMFileMappingPermission::Write => PageTable::task_data_flags(),
159+
VMFileMappingPermission::Write => {
160+
if let Some(write_copy) = &self.write_copy {
161+
if write_copy.get_alloc().present(offset) {
162+
PageTable::task_data_flags()
163+
} else {
164+
PageTable::task_data_ro_flags()
165+
}
166+
} else {
167+
PageTable::task_data_ro_flags()
168+
}
169+
}
126170
VMFileMappingPermission::Execute => PageTable::task_exec_flags(),
127171
}
128172
}
173+
174+
fn handle_page_fault(
175+
&mut self,
176+
vmr: &VMR,
177+
offset: usize,
178+
write: bool,
179+
) -> Result<VMPageFaultResolution, SvsmError> {
180+
let page_size = self.page_size();
181+
if write {
182+
if let Some(write_copy) = self.write_copy.as_mut() {
183+
// This is a writeable region with copy-on-write access. The
184+
// page fault will have occurred because the page has not yet
185+
// been allocated. Allocate a page and copy the readonly source
186+
// page into the new writeable page.
187+
let offset_aligned = offset & !(page_size - 1);
188+
if write_copy
189+
.get_alloc_mut()
190+
.alloc_page(offset_aligned)
191+
.is_ok()
192+
{
193+
let paddr_new_page = write_copy.map(offset_aligned).ok_or(SvsmError::Mem)?;
194+
let temp_map = VMPhysMem::new(paddr_new_page, page_size, true);
195+
let vaddr_new_page = vmr.insert(Arc::new(Mapping::new(temp_map)))?;
196+
let slice =
197+
unsafe { from_raw_parts_mut(vaddr_new_page.bits() as *mut u8, page_size) };
198+
self.file.seek(offset_aligned);
199+
self.file.read(slice)?;
200+
vmr.remove(vaddr_new_page)?;
201+
return Ok(VMPageFaultResolution {
202+
paddr: paddr_new_page,
203+
flags: PageTable::task_data_flags(),
204+
});
205+
}
206+
}
207+
}
208+
Err(SvsmError::Mem)
209+
}
129210
}

0 commit comments

Comments
 (0)