Improve userspace VM management
Occlum is a single-address-space library OS. Previously, userspace memory are divided for each process. And all the memory are allocated when the process is created, which leads to a lot of wasted space and complicated configuration. In the current implementation, the whole userspace is managed as a memory pool that consists of chunks. There are two kinds of chunks: (1) Single VMA chunk: a chunk with only one VMA. Should be owned by exactly one process. (2) Multi VMA chunk: a chunk with default chunk size and there could be a lot of VMAs in this chunk. Can be used by different processes. This design can help to achieve mainly two goals: (1) Simplify the configuration: Users don't need to configure the process.default_mmap_size anymore. And multiple processes running in the same Occlum instance can use dramatically different sizes of memory. (2) Gain better performance: Two-level management(chunks & VMAs) reduces the time for finding, inserting, deleting, and iterating.
This commit is contained in:
		
							parent
							
								
									9d63d396db
								
							
						
					
					
						commit
						6dd73c64b5
					
				| @ -103,7 +103,6 @@ enclave { | |||||||
|          */ |          */ | ||||||
|         public int occlum_ecall_kill(int pid, int sig); |         public int occlum_ecall_kill(int pid, int sig); | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|         /* |         /* | ||||||
|          * Broadcast interrupts to LibOS threads. |          * Broadcast interrupts to LibOS threads. | ||||||
|          * |          * | ||||||
|  | |||||||
							
								
								
									
										42
									
								
								src/libos/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										42
									
								
								src/libos/Cargo.lock
									
									
									
										generated
									
									
									
								
							| @ -8,11 +8,14 @@ dependencies = [ | |||||||
|  "atomic", |  "atomic", | ||||||
|  "bitflags", |  "bitflags", | ||||||
|  "bitvec", |  "bitvec", | ||||||
|  |  "ctor", | ||||||
|  "derive_builder", |  "derive_builder", | ||||||
|  "goblin", |  "goblin", | ||||||
|  |  "intrusive-collections", | ||||||
|  |  "itertools", | ||||||
|  "lazy_static", |  "lazy_static", | ||||||
|  "log", |  "log", | ||||||
|  "memoffset", |  "memoffset 0.6.1", | ||||||
|  "rcore-fs", |  "rcore-fs", | ||||||
|  "rcore-fs-devfs", |  "rcore-fs-devfs", | ||||||
|  "rcore-fs-mountfs", |  "rcore-fs-mountfs", | ||||||
| @ -110,6 +113,16 @@ dependencies = [ | |||||||
|  "bitflags", |  "bitflags", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "ctor" | ||||||
|  | version = "0.1.16" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "7fbaabec2c953050352311293be5c6aba8e141ba19d6811862b232d6fd020484" | ||||||
|  | dependencies = [ | ||||||
|  |  "quote", | ||||||
|  |  "syn", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "darling" | name = "darling" | ||||||
| version = "0.10.2" | version = "0.10.2" | ||||||
| @ -227,6 +240,24 @@ version = "1.0.1" | |||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "intrusive-collections" | ||||||
|  | version = "0.9.2" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "eb4ed164b4cf1c6bd6e18c097490331a0e58fbb0f39e8f6b5ac7f168006511cd" | ||||||
|  | dependencies = [ | ||||||
|  |  "memoffset 0.5.6", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "itertools" | ||||||
|  | version = "0.10.1" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf" | ||||||
|  | dependencies = [ | ||||||
|  |  "either", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "itoa" | name = "itoa" | ||||||
| version = "0.4.5" | version = "0.4.5" | ||||||
| @ -258,6 +289,15 @@ dependencies = [ | |||||||
|  "cfg-if", |  "cfg-if", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "memoffset" | ||||||
|  | version = "0.5.6" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa" | ||||||
|  | dependencies = [ | ||||||
|  |  "autocfg 1.0.1", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "memoffset" | name = "memoffset" | ||||||
| version = "0.6.1" | version = "0.6.1" | ||||||
|  | |||||||
| @ -27,6 +27,8 @@ serde = { path = "../../deps/serde-sgx/serde", features = ["derive"] } | |||||||
| serde_json = { path = "../../deps/serde-json-sgx" } | serde_json = { path = "../../deps/serde-json-sgx" } | ||||||
| memoffset = "0.6.1" | memoffset = "0.6.1" | ||||||
| scroll = { version = "0.10.2", default-features = false } | scroll = { version = "0.10.2", default-features = false } | ||||||
|  | itertools = { version = "0.10.0", default-features = false, features = ["use_alloc"]  } | ||||||
|  | ctor = "0.1" | ||||||
| 
 | 
 | ||||||
| [patch.'https://github.com/apache/teaclave-sgx-sdk.git'] | [patch.'https://github.com/apache/teaclave-sgx-sdk.git'] | ||||||
| sgx_tstd = { path = "../../deps/rust-sgx-sdk/sgx_tstd" } | sgx_tstd = { path = "../../deps/rust-sgx-sdk/sgx_tstd" } | ||||||
| @ -48,3 +50,4 @@ sgx_tse = { path = "../../deps/rust-sgx-sdk/sgx_tse" } | |||||||
| sgx_tcrypto = { path = "../../deps/rust-sgx-sdk/sgx_tcrypto" } | sgx_tcrypto = { path = "../../deps/rust-sgx-sdk/sgx_tcrypto" } | ||||||
| sgx_cov = { path = "../../deps/rust-sgx-sdk/sgx_cov", optional = true } | sgx_cov = { path = "../../deps/rust-sgx-sdk/sgx_cov", optional = true } | ||||||
| goblin = { version = "0.3.4", default-features = false, features = ["elf64", "elf32", "endian_fd"] } | goblin = { version = "0.3.4", default-features = false, features = ["elf64", "elf32", "endian_fd"] } | ||||||
|  | intrusive-collections = "0.9" | ||||||
|  | |||||||
| @ -15,6 +15,7 @@ use crate::util::log::LevelFilter; | |||||||
| use crate::util::mem_util::from_untrusted::*; | use crate::util::mem_util::from_untrusted::*; | ||||||
| use crate::util::resolv_conf_util::{parse_resolv_conf, write_resolv_conf}; | use crate::util::resolv_conf_util::{parse_resolv_conf, write_resolv_conf}; | ||||||
| use crate::util::sgx::allow_debug as sgx_allow_debug; | use crate::util::sgx::allow_debug as sgx_allow_debug; | ||||||
|  | use crate::vm::USER_SPACE_VM_MANAGER; | ||||||
| use sgx_tse::*; | use sgx_tse::*; | ||||||
| 
 | 
 | ||||||
| pub static mut INSTANCE_DIR: String = String::new(); | pub static mut INSTANCE_DIR: String = String::new(); | ||||||
|  | |||||||
| @ -14,7 +14,7 @@ impl MemInfoINode { | |||||||
| impl ProcINode for MemInfoINode { | impl ProcINode for MemInfoINode { | ||||||
|     fn generate_data_in_bytes(&self) -> vfs::Result<Vec<u8>> { |     fn generate_data_in_bytes(&self) -> vfs::Result<Vec<u8>> { | ||||||
|         let total_ram = USER_SPACE_VM_MANAGER.get_total_size(); |         let total_ram = USER_SPACE_VM_MANAGER.get_total_size(); | ||||||
|         let free_ram = USER_SPACE_VM_MANAGER.get_free_size(); |         let free_ram = current!().vm().get_free_size(); | ||||||
|         Ok(format!( |         Ok(format!( | ||||||
|             "MemTotal:       {} kB\n\ |             "MemTotal:       {} kB\n\ | ||||||
|              MemFree:        {} kB\n\ |              MemFree:        {} kB\n\ | ||||||
|  | |||||||
| @ -9,6 +9,7 @@ | |||||||
| #![feature(alloc_layout_extra)] | #![feature(alloc_layout_extra)] | ||||||
| #![feature(concat_idents)] | #![feature(concat_idents)] | ||||||
| #![feature(trace_macros)] | #![feature(trace_macros)] | ||||||
|  | #![feature(drain_filter)] | ||||||
| // for !Send in rw_lock
 | // for !Send in rw_lock
 | ||||||
| #![feature(negative_impls)] | #![feature(negative_impls)] | ||||||
| // for may_dangle in rw_lock
 | // for may_dangle in rw_lock
 | ||||||
| @ -54,6 +55,8 @@ extern crate serde; | |||||||
| extern crate serde_json; | extern crate serde_json; | ||||||
| #[macro_use] | #[macro_use] | ||||||
| extern crate memoffset; | extern crate memoffset; | ||||||
|  | extern crate ctor; | ||||||
|  | extern crate intrusive_collections; | ||||||
| extern crate resolv_conf; | extern crate resolv_conf; | ||||||
| 
 | 
 | ||||||
| use sgx_trts::libc; | use sgx_trts::libc; | ||||||
|  | |||||||
| @ -26,7 +26,7 @@ pub fn do_sysinfo() -> Result<sysinfo_t> { | |||||||
|     let info = sysinfo_t { |     let info = sysinfo_t { | ||||||
|         uptime: time::up_time::get().unwrap().as_secs() as i64, // Duration can't be negative
 |         uptime: time::up_time::get().unwrap().as_secs() as i64, // Duration can't be negative
 | ||||||
|         totalram: USER_SPACE_VM_MANAGER.get_total_size() as u64, |         totalram: USER_SPACE_VM_MANAGER.get_total_size() as u64, | ||||||
|         freeram: USER_SPACE_VM_MANAGER.get_free_size() as u64, |         freeram: current!().vm().get_free_size() as u64, | ||||||
|         procs: table::get_all_processes().len() as u16, |         procs: table::get_all_processes().len() as u16, | ||||||
|         mem_unit: 1, |         mem_unit: 1, | ||||||
|         ..Default::default() |         ..Default::default() | ||||||
|  | |||||||
| @ -2,6 +2,7 @@ pub use sgx_trts::libc; | |||||||
| pub use sgx_trts::libc::off_t; | pub use sgx_trts::libc::off_t; | ||||||
| pub use sgx_types::*; | pub use sgx_types::*; | ||||||
| 
 | 
 | ||||||
|  | pub use core::intrinsics::unreachable; | ||||||
| use std; | use std; | ||||||
| pub use std::cell::{Cell, RefCell}; | pub use std::cell::{Cell, RefCell}; | ||||||
| pub use std::cmp::{max, min}; | pub use std::cmp::{max, min}; | ||||||
|  | |||||||
| @ -9,6 +9,7 @@ use super::{table, ProcessRef, TermStatus, ThreadRef, ThreadStatus}; | |||||||
| use crate::prelude::*; | use crate::prelude::*; | ||||||
| use crate::signal::{KernelSignal, SigNum}; | use crate::signal::{KernelSignal, SigNum}; | ||||||
| use crate::syscall::CpuContext; | use crate::syscall::CpuContext; | ||||||
|  | use crate::vm::USER_SPACE_VM_MANAGER; | ||||||
| 
 | 
 | ||||||
| pub fn do_exit_group(status: i32, curr_user_ctxt: &mut CpuContext) -> Result<isize> { | pub fn do_exit_group(status: i32, curr_user_ctxt: &mut CpuContext) -> Result<isize> { | ||||||
|     if is_vforked_child_process() { |     if is_vforked_child_process() { | ||||||
| @ -103,6 +104,8 @@ fn exit_process(thread: &ThreadRef, term_status: TermStatus) { | |||||||
|     }; |     }; | ||||||
|     // Lock the current process
 |     // Lock the current process
 | ||||||
|     let mut process_inner = process.inner(); |     let mut process_inner = process.inner(); | ||||||
|  |     // Clean used VM
 | ||||||
|  |     USER_SPACE_VM_MANAGER.free_chunks_when_exit(thread); | ||||||
| 
 | 
 | ||||||
|     // The parent is the idle process
 |     // The parent is the idle process
 | ||||||
|     if parent_inner.is_none() { |     if parent_inner.is_none() { | ||||||
| @ -201,6 +204,9 @@ fn exit_process_for_execve( | |||||||
| 
 | 
 | ||||||
|     // Lock the current process
 |     // Lock the current process
 | ||||||
|     let mut process_inner = process.inner(); |     let mut process_inner = process.inner(); | ||||||
|  |     // Clean used VM
 | ||||||
|  |     USER_SPACE_VM_MANAGER.free_chunks_when_exit(thread); | ||||||
|  | 
 | ||||||
|     let mut new_parent_inner = new_parent_ref.inner(); |     let mut new_parent_inner = new_parent_ref.inner(); | ||||||
|     let pid = process.pid(); |     let pid = process.pid(); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -710,7 +710,7 @@ fn do_syscall(user_context: &mut CpuContext) { | |||||||
|             retval |             retval | ||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
|     trace!("Retval = {:?}", retval); |     trace!("Retval = 0x{:x}", retval); | ||||||
| 
 | 
 | ||||||
|     // Put the return value into user_context.rax, except for syscalls that may
 |     // Put the return value into user_context.rax, except for syscalls that may
 | ||||||
|     // modify user_context directly. Currently, there are three such syscalls:
 |     // modify user_context directly. Currently, there are three such syscalls:
 | ||||||
|  | |||||||
							
								
								
									
										239
									
								
								src/libos/src/vm/chunk.rs
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										239
									
								
								src/libos/src/vm/chunk.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,239 @@ | |||||||
|  | use super::*; | ||||||
|  | 
 | ||||||
|  | use super::vm_area::VMArea; | ||||||
|  | use super::vm_chunk_manager::ChunkManager; | ||||||
|  | use super::vm_perms::VMPerms; | ||||||
|  | use super::vm_util::*; | ||||||
|  | use crate::process::ProcessRef; | ||||||
|  | use crate::process::ThreadRef; | ||||||
|  | use std::cmp::Ordering; | ||||||
|  | use std::collections::HashSet; | ||||||
|  | use std::hash::{Hash, Hasher}; | ||||||
|  | 
 | ||||||
|  | // For single VMA chunk, the vma struct doesn't need to update the pid field. Because all the chunks are recorded by the process VM already.
 | ||||||
|  | pub const DUMMY_CHUNK_PROCESS_ID: pid_t = 0; | ||||||
|  | // Default chunk size: 32MB
 | ||||||
|  | pub const CHUNK_DEFAULT_SIZE: usize = 32 * 1024 * 1024; | ||||||
|  | 
 | ||||||
|  | pub type ChunkID = usize; | ||||||
|  | pub type ChunkRef = Arc<Chunk>; | ||||||
|  | 
 | ||||||
|  | pub struct Chunk { | ||||||
|  |     range: VMRange, | ||||||
|  |     internal: ChunkType, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Hash for Chunk { | ||||||
|  |     fn hash<H: Hasher>(&self, state: &mut H) { | ||||||
|  |         self.range.hash(state); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Ord for Chunk { | ||||||
|  |     fn cmp(&self, other: &Self) -> Ordering { | ||||||
|  |         self.range.start().cmp(&other.range.start()) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl PartialOrd for Chunk { | ||||||
|  |     fn partial_cmp(&self, other: &Self) -> Option<Ordering> { | ||||||
|  |         Some(self.cmp(other)) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl PartialEq for Chunk { | ||||||
|  |     fn eq(&self, other: &Self) -> bool { | ||||||
|  |         self.range == other.range | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Eq for Chunk {} | ||||||
|  | 
 | ||||||
|  | impl Debug for Chunk { | ||||||
|  |     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||||||
|  |         match self.internal() { | ||||||
|  |             ChunkType::SingleVMA(vma) => write!(f, "Single VMA chunk: {:?}", vma), | ||||||
|  |             ChunkType::MultiVMA(internal_manager) => write!(f, "default chunk: {:?}", self.range()), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Chunk { | ||||||
|  |     pub fn range(&self) -> &VMRange { | ||||||
|  |         &self.range | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn internal(&self) -> &ChunkType { | ||||||
|  |         &self.internal | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn free_size(&self) -> usize { | ||||||
|  |         match self.internal() { | ||||||
|  |             ChunkType::SingleVMA(vma) => 0, // for single VMA chunk, there is no free space
 | ||||||
|  |             ChunkType::MultiVMA(internal_manager) => internal_manager.lock().unwrap().free_size(), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn new_default_chunk(vm_range: VMRange) -> Result<Self> { | ||||||
|  |         let internal_manager = ChunkInternal::new(vm_range)?; | ||||||
|  |         Ok(Self { | ||||||
|  |             range: vm_range, | ||||||
|  |             internal: ChunkType::MultiVMA(SgxMutex::new(internal_manager)), | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn new_single_vma_chunk(vm_range: VMRange, options: &VMMapOptions) -> Self { | ||||||
|  |         let writeback_file = options.writeback_file().clone(); | ||||||
|  |         let vm_area = VMArea::new( | ||||||
|  |             vm_range.clone(), | ||||||
|  |             *options.perms(), | ||||||
|  |             writeback_file, | ||||||
|  |             DUMMY_CHUNK_PROCESS_ID, | ||||||
|  |         ); | ||||||
|  |         // Initialize the memory of the new range
 | ||||||
|  |         unsafe { | ||||||
|  |             let buf = vm_range.as_slice_mut(); | ||||||
|  |             options.initializer().init_slice(buf); | ||||||
|  |         } | ||||||
|  |         // Set memory permissions
 | ||||||
|  |         if !options.perms().is_default() { | ||||||
|  |             VMPerms::apply_perms(&vm_area, vm_area.perms()); | ||||||
|  |         } | ||||||
|  |         Self::new_chunk_with_vma(vm_area) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn new_chunk_with_vma(vma: VMArea) -> Self { | ||||||
|  |         Self { | ||||||
|  |             range: vma.range().clone(), | ||||||
|  |             internal: ChunkType::SingleVMA(SgxMutex::new(vma)), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn add_process(&self, current: &ThreadRef) { | ||||||
|  |         match self.internal() { | ||||||
|  |             ChunkType::SingleVMA(vma) => unreachable!(), | ||||||
|  |             ChunkType::MultiVMA(internal_manager) => { | ||||||
|  |                 internal_manager | ||||||
|  |                     .lock() | ||||||
|  |                     .unwrap() | ||||||
|  |                     .add_process(current.process().pid()); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn mmap(&self, options: &VMMapOptions) -> Result<usize> { | ||||||
|  |         debug_assert!(!self.is_single_vma()); | ||||||
|  |         trace!("try allocate in chunk: {:?}", self); | ||||||
|  |         let mut internal_manager = if let ChunkType::MultiVMA(internal_manager) = &self.internal { | ||||||
|  |             internal_manager.lock().unwrap() | ||||||
|  |         } else { | ||||||
|  |             unreachable!(); | ||||||
|  |         }; | ||||||
|  |         if internal_manager.chunk_manager.free_size() < options.size() { | ||||||
|  |             return_errno!(ENOMEM, "no enough size without trying. try other chunks"); | ||||||
|  |         } | ||||||
|  |         return internal_manager.chunk_manager.mmap(options); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn try_mmap(&self, options: &VMMapOptions) -> Result<usize> { | ||||||
|  |         debug_assert!(!self.is_single_vma()); | ||||||
|  |         // Try lock ChunkManager. If it fails, just return and will try other chunks.
 | ||||||
|  |         let mut internal_manager = if let ChunkType::MultiVMA(internal_manager) = &self.internal { | ||||||
|  |             internal_manager | ||||||
|  |                 .try_lock() | ||||||
|  |                 .map_err(|_| errno!(EAGAIN, "try other chunks"))? | ||||||
|  |         } else { | ||||||
|  |             unreachable!(); | ||||||
|  |         }; | ||||||
|  |         trace!("get lock, try mmap in chunk: {:?}", self); | ||||||
|  |         if internal_manager.chunk_manager().free_size() < options.size() { | ||||||
|  |             return_errno!(ENOMEM, "no enough size without trying. try other chunks"); | ||||||
|  |         } | ||||||
|  |         internal_manager.chunk_manager().mmap(options) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn is_single_vma(&self) -> bool { | ||||||
|  |         if let ChunkType::SingleVMA(_) = self.internal { | ||||||
|  |             true | ||||||
|  |         } else { | ||||||
|  |             false | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn find_mmap_region(&self, addr: usize) -> Result<VMRange> { | ||||||
|  |         let internal = &self.internal; | ||||||
|  |         match self.internal() { | ||||||
|  |             ChunkType::SingleVMA(vma) => { | ||||||
|  |                 let vma = vma.lock().unwrap(); | ||||||
|  |                 if vma.contains(addr) { | ||||||
|  |                     return Ok(vma.range().clone()); | ||||||
|  |                 } else { | ||||||
|  |                     return_errno!(ESRCH, "addr not found in this chunk") | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             ChunkType::MultiVMA(internal_manager) => { | ||||||
|  |                 return internal_manager | ||||||
|  |                     .lock() | ||||||
|  |                     .unwrap() | ||||||
|  |                     .chunk_manager | ||||||
|  |                     .find_mmap_region(addr); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(Debug)] | ||||||
|  | pub enum ChunkType { | ||||||
|  |     SingleVMA(SgxMutex<VMArea>), | ||||||
|  |     MultiVMA(SgxMutex<ChunkInternal>), | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(Debug)] | ||||||
|  | pub struct ChunkInternal { | ||||||
|  |     chunk_manager: ChunkManager, | ||||||
|  |     process_set: HashSet<pid_t>, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const PROCESS_SET_INIT_SIZE: usize = 5; | ||||||
|  | 
 | ||||||
|  | impl ChunkInternal { | ||||||
|  |     pub fn new(vm_range: VMRange) -> Result<Self> { | ||||||
|  |         let chunk_manager = ChunkManager::from(vm_range.start(), vm_range.size())?; | ||||||
|  | 
 | ||||||
|  |         let mut process_set = HashSet::with_capacity(PROCESS_SET_INIT_SIZE); | ||||||
|  |         process_set.insert(current!().process().pid()); | ||||||
|  |         Ok(Self { | ||||||
|  |             chunk_manager, | ||||||
|  |             process_set, | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn add_process(&mut self, pid: pid_t) { | ||||||
|  |         self.process_set.insert(pid); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn chunk_manager(&mut self) -> &mut ChunkManager { | ||||||
|  |         &mut self.chunk_manager | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn is_owned_by_current_process(&self) -> bool { | ||||||
|  |         let current_pid = current!().process().pid(); | ||||||
|  |         self.process_set.contains(¤t_pid) && self.process_set.len() == 1 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn free_size(&self) -> usize { | ||||||
|  |         *self.chunk_manager.free_size() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Clean vmas when munmap a MultiVMA chunk, return whether this chunk is cleaned
 | ||||||
|  |     pub fn clean_multi_vmas(&mut self) -> bool { | ||||||
|  |         let current_pid = current!().process().pid(); | ||||||
|  |         self.chunk_manager.clean_vmas_with_pid(current_pid); | ||||||
|  |         if self.chunk_manager.is_empty() { | ||||||
|  |             self.process_set.remove(¤t_pid); | ||||||
|  |             return true; | ||||||
|  |         } else { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										149
									
								
								src/libos/src/vm/free_space_manager.rs
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										149
									
								
								src/libos/src/vm/free_space_manager.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,149 @@ | |||||||
|  | // Implements free space management for memory.
 | ||||||
|  | // Currently only use simple vector as the base structure.
 | ||||||
|  | //
 | ||||||
|  | // Basically use address-ordered first fit to find free ranges.
 | ||||||
|  | 
 | ||||||
|  | use super::vm_util::VMMapAddr; | ||||||
|  | use super::*; | ||||||
|  | 
 | ||||||
|  | static INITIAL_SIZE: usize = 100; | ||||||
|  | 
 | ||||||
|  | #[derive(Debug, Default)] | ||||||
|  | pub struct VMFreeSpaceManager { | ||||||
|  |     free_manager: Vec<VMRange>, // Address-ordered first fit
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl VMFreeSpaceManager { | ||||||
|  |     pub fn new(initial_free_range: VMRange) -> Self { | ||||||
|  |         let mut free_manager = Vec::with_capacity(INITIAL_SIZE); | ||||||
|  |         free_manager.push(initial_free_range); | ||||||
|  | 
 | ||||||
|  |         VMFreeSpaceManager { | ||||||
|  |             free_manager: free_manager, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn free_size(&self) -> usize { | ||||||
|  |         self.free_manager | ||||||
|  |             .iter() | ||||||
|  |             .fold(0, |acc, free_range| acc + free_range.size()) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // TODO: respect options.align when mmap
 | ||||||
|  |     pub fn find_free_range_internal( | ||||||
|  |         &mut self, | ||||||
|  |         size: usize, | ||||||
|  |         align: usize, | ||||||
|  |         addr: VMMapAddr, | ||||||
|  |     ) -> Result<VMRange> { | ||||||
|  |         // Record the minimal free range that satisfies the contraints
 | ||||||
|  |         let mut result_free_range: Option<VMRange> = None; | ||||||
|  |         let mut result_idx: Option<usize> = None; | ||||||
|  |         let mut free_list = &mut self.free_manager; | ||||||
|  | 
 | ||||||
|  |         trace!("find free range, free list = {:?}", free_list); | ||||||
|  | 
 | ||||||
|  |         for (idx, free_range) in free_list.iter().enumerate() { | ||||||
|  |             let mut free_range = { | ||||||
|  |                 if free_range.size() < size { | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|  |                 unsafe { VMRange::from_unchecked(free_range.start(), free_range.end()) } | ||||||
|  |             }; | ||||||
|  | 
 | ||||||
|  |             match addr { | ||||||
|  |                 // Want a minimal free_range
 | ||||||
|  |                 VMMapAddr::Any => {} | ||||||
|  |                 // Prefer to have free_range.start == addr
 | ||||||
|  |                 VMMapAddr::Hint(addr) => { | ||||||
|  |                     if addr % align == 0 | ||||||
|  |                         && free_range.contains(addr) | ||||||
|  |                         && free_range.end() - addr >= size | ||||||
|  |                     { | ||||||
|  |                         free_range.start = addr; | ||||||
|  |                         free_range.end = addr + size; | ||||||
|  |                         self.free_list_update_range(idx, free_range); | ||||||
|  |                         return Ok(free_range); | ||||||
|  |                     } else { | ||||||
|  |                         // Hint failure, record the result but keep iterating.
 | ||||||
|  |                         if result_free_range == None | ||||||
|  |                             || result_free_range.as_ref().unwrap().size() > free_range.size() | ||||||
|  |                         { | ||||||
|  |                             result_free_range = Some(free_range); | ||||||
|  |                             result_idx = Some(idx); | ||||||
|  |                         } | ||||||
|  |                         continue; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 // Must have free_range.start == addr
 | ||||||
|  |                 VMMapAddr::Need(addr) | VMMapAddr::Force(addr) => { | ||||||
|  |                     if free_range.start() > addr { | ||||||
|  |                         return_errno!(ENOMEM, "not enough memory for fixed mmap"); | ||||||
|  |                     } | ||||||
|  |                     if !free_range.contains(addr) { | ||||||
|  |                         continue; | ||||||
|  |                     } | ||||||
|  |                     if free_range.end() - addr < size { | ||||||
|  |                         return_errno!(ENOMEM, "not enough memory for fixed mmap"); | ||||||
|  |                     } | ||||||
|  |                     free_range.start = addr; | ||||||
|  |                     free_range.end = addr + size; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             result_free_range = Some(free_range); | ||||||
|  |             result_idx = Some(idx); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if result_free_range.is_none() { | ||||||
|  |             return_errno!(ENOMEM, "not enough memory"); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         let index = result_idx.unwrap(); | ||||||
|  |         let result_free_range = { | ||||||
|  |             let free_range = result_free_range.unwrap(); | ||||||
|  |             let start = align_up(free_range.start(), align); | ||||||
|  |             let end = start + size; | ||||||
|  |             VMRange { start, end } | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         self.free_list_update_range(index, result_free_range); | ||||||
|  |         trace!("after find free range, free list = {:?}", self.free_manager); | ||||||
|  |         return Ok(result_free_range); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn free_list_update_range(&mut self, index: usize, range: VMRange) { | ||||||
|  |         let mut free_list = &mut self.free_manager; | ||||||
|  |         let ranges_after_subtraction = free_list[index].subtract(&range); | ||||||
|  |         debug_assert!(ranges_after_subtraction.len() <= 2); | ||||||
|  |         if ranges_after_subtraction.len() == 0 { | ||||||
|  |             free_list.remove(index); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         free_list[index] = ranges_after_subtraction[0]; | ||||||
|  |         if ranges_after_subtraction.len() == 2 { | ||||||
|  |             free_list.insert(index + 1, ranges_after_subtraction[1]); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn add_range_back_to_free_manager(&mut self, dirty_range: &VMRange) -> Result<()> { | ||||||
|  |         let mut free_list = &mut self.free_manager; | ||||||
|  |         free_list.push(*dirty_range); | ||||||
|  |         // Sort and merge small ranges
 | ||||||
|  |         free_list.sort_unstable_by(|range_a, range_b| range_a.start().cmp(&range_b.start())); | ||||||
|  |         let mut idx = 0; | ||||||
|  |         while (idx < free_list.len() - 1) { | ||||||
|  |             let right_range = free_list[idx + 1]; | ||||||
|  |             let mut left_range = &mut free_list[idx]; | ||||||
|  |             if left_range.end() == right_range.start() { | ||||||
|  |                 left_range.set_end(right_range.end()); | ||||||
|  |                 free_list.remove(idx + 1); | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  |             idx += 1; | ||||||
|  |         } | ||||||
|  |         trace!("after add range back free list = {:?}", free_list); | ||||||
|  |         return Ok(()); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -1,18 +1,79 @@ | |||||||
|  | /* | ||||||
|  | Occlum is a single-address-space library OS. Previously, userspace memory are divided for each process. | ||||||
|  | And all the memory are allocated when the process is created, which leads to a lot of wasted space and | ||||||
|  | complicated configuration. | ||||||
|  | 
 | ||||||
|  | In the current implementation, the whole userspace is managed as a memory pool that consists of chunks. There | ||||||
|  | are two kinds of chunks: | ||||||
|  | (1) Single VMA chunk: a chunk with only one VMA. Should be owned by exactly one process. | ||||||
|  | (2) Multi VMA chunk: a chunk with default chunk size and there could be a lot of VMAs in this chunk. Can be used | ||||||
|  | by different processes. | ||||||
|  | 
 | ||||||
|  | This design can help to achieve mainly two goals: | ||||||
|  | (1) Simplify the configuration: Users don't need to configure the process.default_mmap_size anymore. And multiple processes | ||||||
|  | running in the same Occlum instance can use dramatically different sizes of memory. | ||||||
|  | (2) Gain better performance: Two-level management(chunks & VMAs) reduces the time for finding, inserting, deleting, and iterating. | ||||||
|  | 
 | ||||||
|  | ***************** Chart for Occlum User Space Memory Management *************** | ||||||
|  |  User Space VM Manager | ||||||
|  | ┌──────────────────────────────────────────────────────────────┐ | ||||||
|  | │                            VMManager                         │ | ||||||
|  | │                                                              │ | ||||||
|  | │  Chunks (in use): B-Tree Set                                 │ | ||||||
|  | │  ┌────────────────────────────────────────────────────────┐  │ | ||||||
|  | │  │                      Multi VMA Chunk                   │  │ | ||||||
|  | │  │                     ┌───────────────────────────────┐  │  │ | ||||||
|  | │  │  Single VMA Chunk   │          ChunkManager         │  │  │ | ||||||
|  | │  │  ┌──────────────┐   │                               │  │  │ | ||||||
|  | │  │  │              │   │  VMAs (in use): Red Black Tree│  │  │ | ||||||
|  | │  │  │    VMArea    │   │  ┌─────────────────────────┐  │  │  │ | ||||||
|  | │  │  │              │   │  │                         │  │  │  │ | ||||||
|  | │  │  └──────────────┘   │  │  ┌──────┐ ┌────┐ ┌────┐ │  │  │  │ | ||||||
|  | │  │                     │  │  │ VMA  │ │VMA │ │VMA │ │  │  │  │ | ||||||
|  | │  │  Single VMA Chunk   │  │  └──────┘ └────┘ └────┘ │  │  │  │ | ||||||
|  | │  │  ┌──────────────┐   │  │                         │  │  │  │ | ||||||
|  | │  │  │              │   │  └─────────────────────────┘  │  │  │ | ||||||
|  | │  │  │    VMArea    │   │                               │  │  │ | ||||||
|  | │  │  │              │   │                               │  │  │ | ||||||
|  | │  │  └──────────────┘   │   Free Manager (free)         │  │  │ | ||||||
|  | │  │                     │   ┌────────────────────────┐  │  │  │ | ||||||
|  | │  │  Single VMA Chunk   │   │                        │  │  │  │ | ||||||
|  | │  │  ┌──────────────┐   │   │   VMFreeSpaceManager   │  │  │  │ | ||||||
|  | │  │  │              │   │   │                        │  │  │  │ | ||||||
|  | │  │  │    VMArea    │   │   └────────────────────────┘  │  │  │ | ||||||
|  | │  │  │              │   │                               │  │  │ | ||||||
|  | │  │  └──────────────┘   └───────────────────────────────┘  │  │ | ||||||
|  | │  │                                                        │  │ | ||||||
|  | │  └────────────────────────────────────────────────────────┘  │ | ||||||
|  | │                                                              │ | ||||||
|  | │  Free Manager (free)                                         │ | ||||||
|  | │  ┌────────────────────────────────────────────────────────┐  │ | ||||||
|  | │  │                                                        │  │ | ||||||
|  | │  │                   VMFreeSpaceManager                   │  │ | ||||||
|  | │  │                                                        │  │ | ||||||
|  | │  └────────────────────────────────────────────────────────┘  │ | ||||||
|  | │                                                              │ | ||||||
|  | └──────────────────────────────────────────────────────────────┘ | ||||||
|  | */ | ||||||
|  | 
 | ||||||
| use super::*; | use super::*; | ||||||
| use fs::{File, FileDesc, FileRef}; | use fs::{File, FileDesc, FileRef}; | ||||||
| use process::{Process, ProcessRef}; | use process::{Process, ProcessRef}; | ||||||
| use std::fmt; | use std::fmt; | ||||||
| 
 | 
 | ||||||
|  | mod chunk; | ||||||
|  | mod free_space_manager; | ||||||
| mod process_vm; | mod process_vm; | ||||||
| mod user_space_vm; | mod user_space_vm; | ||||||
| mod vm_area; | mod vm_area; | ||||||
|  | mod vm_chunk_manager; | ||||||
| mod vm_layout; | mod vm_layout; | ||||||
| mod vm_manager; | mod vm_manager; | ||||||
| mod vm_perms; | mod vm_perms; | ||||||
| mod vm_range; | mod vm_range; | ||||||
|  | mod vm_util; | ||||||
| 
 | 
 | ||||||
| use self::vm_layout::VMLayout; | use self::vm_layout::VMLayout; | ||||||
| use self::vm_manager::{VMManager, VMMapOptionsBuilder}; |  | ||||||
| 
 | 
 | ||||||
| pub use self::process_vm::{MMapFlags, MRemapFlags, MSyncFlags, ProcessVM, ProcessVMBuilder}; | pub use self::process_vm::{MMapFlags, MRemapFlags, MSyncFlags, ProcessVM, ProcessVMBuilder}; | ||||||
| pub use self::user_space_vm::USER_SPACE_VM_MANAGER; | pub use self::user_space_vm::USER_SPACE_VM_MANAGER; | ||||||
|  | |||||||
| @ -1,12 +1,12 @@ | |||||||
| use super::*; | use super::*; | ||||||
| 
 | 
 | ||||||
|  | use super::chunk::{Chunk, ChunkRef}; | ||||||
| use super::config; | use super::config; | ||||||
| use super::process::elf_file::{ElfFile, ProgramHeaderExt}; | use super::process::elf_file::{ElfFile, ProgramHeaderExt}; | ||||||
| use super::user_space_vm::{UserSpaceVMManager, UserSpaceVMRange, USER_SPACE_VM_MANAGER}; | use super::user_space_vm::USER_SPACE_VM_MANAGER; | ||||||
| use super::vm_manager::{ |  | ||||||
|     VMInitializer, VMManager, VMMapAddr, VMMapOptions, VMMapOptionsBuilder, VMRemapOptions, |  | ||||||
| }; |  | ||||||
| use super::vm_perms::VMPerms; | use super::vm_perms::VMPerms; | ||||||
|  | use super::vm_util::{VMInitializer, VMMapAddr, VMMapOptions, VMMapOptionsBuilder, VMRemapOptions}; | ||||||
|  | use std::collections::HashSet; | ||||||
| use std::sync::atomic::{AtomicUsize, Ordering}; | use std::sync::atomic::{AtomicUsize, Ordering}; | ||||||
| 
 | 
 | ||||||
| // Used for heap and stack start address randomization.
 | // Used for heap and stack start address randomization.
 | ||||||
| @ -69,9 +69,6 @@ impl<'a, 'b> ProcessVMBuilder<'a, 'b> { | |||||||
|         let stack_size = self |         let stack_size = self | ||||||
|             .stack_size |             .stack_size | ||||||
|             .unwrap_or(config::LIBOS_CONFIG.process.default_stack_size); |             .unwrap_or(config::LIBOS_CONFIG.process.default_stack_size); | ||||||
|         let mmap_size = self |  | ||||||
|             .mmap_size |  | ||||||
|             .unwrap_or(config::LIBOS_CONFIG.process.default_mmap_size); |  | ||||||
| 
 | 
 | ||||||
|         // Before allocating memory, let's first calcualte how much memory
 |         // Before allocating memory, let's first calcualte how much memory
 | ||||||
|         // we need in total by iterating the memory layouts required by
 |         // we need in total by iterating the memory layouts required by
 | ||||||
| @ -92,11 +89,10 @@ impl<'a, 'b> ProcessVMBuilder<'a, 'b> { | |||||||
|             }) |             }) | ||||||
|             .collect(); |             .collect(); | ||||||
| 
 | 
 | ||||||
|         // TODO: Make heap and stack 16-byte aligned instead of page aligned.
 |         // Make heap and stack 16-byte aligned
 | ||||||
|         let other_layouts = vec![ |         let other_layouts = vec![ | ||||||
|             VMLayout::new(heap_size, PAGE_SIZE)?, |             VMLayout::new(heap_size, 16)?, | ||||||
|             VMLayout::new(stack_size, PAGE_SIZE)?, |             VMLayout::new(stack_size, 16)?, | ||||||
|             VMLayout::new(mmap_size, PAGE_SIZE)?, |  | ||||||
|         ]; |         ]; | ||||||
|         let process_layout = elf_layouts.iter().chain(other_layouts.iter()).fold( |         let process_layout = elf_layouts.iter().chain(other_layouts.iter()).fold( | ||||||
|             VMLayout::new_empty(), |             VMLayout::new_empty(), | ||||||
| @ -108,85 +104,61 @@ impl<'a, 'b> ProcessVMBuilder<'a, 'b> { | |||||||
| 
 | 
 | ||||||
|         // Now that we end up with the memory layout required by the process,
 |         // Now that we end up with the memory layout required by the process,
 | ||||||
|         // let's allocate the memory for the process
 |         // let's allocate the memory for the process
 | ||||||
|         let process_range = { USER_SPACE_VM_MANAGER.alloc(process_layout)? }; |         let mut chunks = HashSet::new(); | ||||||
|         let process_base = process_range.range().start(); |  | ||||||
|         // Use the vm_manager to manage the whole process VM (including mmap region)
 |  | ||||||
|         let mut vm_manager = VMManager::from(process_base, process_range.range().size())?; |  | ||||||
|         // Note: we do not need to fill zeros of the mmap region.
 |  | ||||||
|         // VMManager will fill zeros (if necessary) on mmap.
 |  | ||||||
| 
 |  | ||||||
|         // Tracker to track the min_start for each part
 |  | ||||||
|         let mut min_start = |  | ||||||
|             process_base + Self::get_randomize_offset(process_range.range().size() >> 3); |  | ||||||
|         // Init the memory for ELFs in the process
 |         // Init the memory for ELFs in the process
 | ||||||
|         let mut elf_ranges = Vec::with_capacity(2); |         let mut elf_ranges = Vec::with_capacity(2); | ||||||
|         elf_layouts |         elf_layouts | ||||||
|             .iter() |             .iter() | ||||||
|             .zip(self.elfs.iter()) |             .zip(self.elfs.iter()) | ||||||
|             .map(|(elf_layout, elf_file)| { |             .map(|(elf_layout, elf_file)| { | ||||||
|                 let desired_range = VMRange::new_with_layout(elf_layout, min_start); |  | ||||||
|                 let vm_option = VMMapOptionsBuilder::default() |                 let vm_option = VMMapOptionsBuilder::default() | ||||||
|                     .size(desired_range.size()) |                     .size(elf_layout.size()) | ||||||
|                     .addr(VMMapAddr::Need(desired_range.start())) |                     .align(elf_layout.align()) | ||||||
|                     .perms(VMPerms::ALL) // set it to read | write | exec for simplicity
 |                     .perms(VMPerms::ALL) // set it to read | write | exec for simplicity
 | ||||||
|                     .initializer(VMInitializer::DoNothing()) |                     .initializer(VMInitializer::DoNothing()) | ||||||
|                     .build()?; |                     .build()?; | ||||||
|                 let elf_start = vm_manager.mmap(vm_option)?; |                 let (elf_range, chunk_ref) = USER_SPACE_VM_MANAGER.alloc(&vm_option)?; | ||||||
|                 debug_assert!(desired_range.start == elf_start); |                 debug_assert!(elf_range.start() % elf_layout.align() == 0); | ||||||
|                 debug_assert!(elf_start % elf_layout.align() == 0); |                 Self::init_elf_memory(&elf_range, elf_file)?; | ||||||
|                 debug_assert!(process_range.range().is_superset_of(&desired_range)); |                 trace!("elf range = {:?}", elf_range); | ||||||
|                 Self::init_elf_memory(&desired_range, elf_file)?; |                 elf_ranges.push(elf_range); | ||||||
|                 min_start = desired_range.end(); |                 chunks.insert(chunk_ref); | ||||||
|                 elf_ranges.push(desired_range); |  | ||||||
|                 trace!("elf range = {:?}", desired_range); |  | ||||||
|                 Ok(()) |                 Ok(()) | ||||||
|             }) |             }) | ||||||
|             .collect::<Result<()>>()?; |             .collect::<Result<()>>()?; | ||||||
| 
 | 
 | ||||||
|         // Init the heap memory in the process
 |         // Init the heap memory in the process
 | ||||||
|         let heap_layout = &other_layouts[0]; |         let heap_layout = &other_layouts[0]; | ||||||
|         let heap_min_start = min_start + Self::get_randomize_offset(RANGE_FOR_RANDOMIZATION); |  | ||||||
|         let heap_range = VMRange::new_with_layout(heap_layout, heap_min_start); |  | ||||||
|         let vm_option = VMMapOptionsBuilder::default() |         let vm_option = VMMapOptionsBuilder::default() | ||||||
|             .size(heap_range.size()) |             .size(heap_layout.size()) | ||||||
|             .addr(VMMapAddr::Need(heap_range.start())) |             .align(heap_layout.align()) | ||||||
|             .perms(VMPerms::READ | VMPerms::WRITE) |             .perms(VMPerms::READ | VMPerms::WRITE) | ||||||
|             .build()?; |             .build()?; | ||||||
|         let heap_start = vm_manager.mmap(vm_option)?; |         let (heap_range, chunk_ref) = USER_SPACE_VM_MANAGER.alloc(&vm_option)?; | ||||||
|         debug_assert!(heap_range.start == heap_start); |         debug_assert!(heap_range.start() % heap_layout.align() == 0); | ||||||
|         trace!("heap range = {:?}", heap_range); |         trace!("heap range = {:?}", heap_range); | ||||||
|         let brk = AtomicUsize::new(heap_range.start()); |         let brk = AtomicUsize::new(heap_range.start()); | ||||||
|         min_start = heap_range.end(); |         chunks.insert(chunk_ref); | ||||||
| 
 | 
 | ||||||
|         // Init the stack memory in the process
 |         // Init the stack memory in the process
 | ||||||
|         let stack_layout = &other_layouts[1]; |         let stack_layout = &other_layouts[1]; | ||||||
|         let stack_min_start = min_start + Self::get_randomize_offset(RANGE_FOR_RANDOMIZATION); |  | ||||||
|         let stack_range = VMRange::new_with_layout(stack_layout, stack_min_start); |  | ||||||
|         let vm_option = VMMapOptionsBuilder::default() |         let vm_option = VMMapOptionsBuilder::default() | ||||||
|             .size(stack_range.size()) |             .size(stack_layout.size()) | ||||||
|             .addr(VMMapAddr::Need(stack_range.start())) |             .align(heap_layout.align()) | ||||||
|             .perms(VMPerms::READ | VMPerms::WRITE) |             .perms(VMPerms::READ | VMPerms::WRITE) | ||||||
|             .build()?; |             .build()?; | ||||||
|         let stack_start = vm_manager.mmap(vm_option)?; |         let (stack_range, chunk_ref) = USER_SPACE_VM_MANAGER.alloc(&vm_option)?; | ||||||
|         debug_assert!(stack_range.start == stack_start); |         debug_assert!(stack_range.start() % stack_layout.align() == 0); | ||||||
|  |         chunks.insert(chunk_ref); | ||||||
|         trace!("stack range = {:?}", stack_range); |         trace!("stack range = {:?}", stack_range); | ||||||
|         min_start = stack_range.end(); |  | ||||||
|         // Note: we do not need to fill zeros for stack
 |  | ||||||
| 
 |  | ||||||
|         debug_assert!(process_range.range().is_superset_of(&heap_range)); |  | ||||||
|         debug_assert!(process_range.range().is_superset_of(&stack_range)); |  | ||||||
| 
 |  | ||||||
|         // Set mmap prefered start address
 |  | ||||||
|         vm_manager.set_mmap_prefered_start_addr(min_start); |  | ||||||
|         let vm_manager = SgxMutex::new(vm_manager); |  | ||||||
| 
 | 
 | ||||||
|  |         let mem_chunks = Arc::new(RwLock::new(chunks)); | ||||||
|         Ok(ProcessVM { |         Ok(ProcessVM { | ||||||
|             process_range, |  | ||||||
|             elf_ranges, |             elf_ranges, | ||||||
|             heap_range, |             heap_range, | ||||||
|             stack_range, |             stack_range, | ||||||
|             brk, |             brk, | ||||||
|             vm_manager, |             mem_chunks, | ||||||
|         }) |         }) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -255,39 +227,83 @@ impl<'a, 'b> ProcessVMBuilder<'a, 'b> { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // MemChunks is the structure to track all the chunks which are used by this process.
 | ||||||
|  | type MemChunks = Arc<RwLock<HashSet<ChunkRef>>>; | ||||||
|  | 
 | ||||||
| /// The per-process virtual memory
 | /// The per-process virtual memory
 | ||||||
| #[derive(Debug)] | #[derive(Debug)] | ||||||
| pub struct ProcessVM { | pub struct ProcessVM { | ||||||
|     vm_manager: SgxMutex<VMManager>, // manage the whole process VM
 |  | ||||||
|     elf_ranges: Vec<VMRange>, |     elf_ranges: Vec<VMRange>, | ||||||
|     heap_range: VMRange, |     heap_range: VMRange, | ||||||
|     stack_range: VMRange, |     stack_range: VMRange, | ||||||
|     brk: AtomicUsize, |     brk: AtomicUsize, | ||||||
|     // Memory safety notes: the process_range field must be the last one.
 |     // Memory safety notes: the mem_chunks field must be the last one.
 | ||||||
|     //
 |     //
 | ||||||
|     // Rust drops fields in the same order as they are declared. So by making
 |     // Rust drops fields in the same order as they are declared. So by making
 | ||||||
|     // process_range the last field, we ensure that when all other fields are
 |     // mem_chunks the last field, we ensure that when all other fields are
 | ||||||
|     // dropped, their drop methods (if provided) can still access the memory
 |     // dropped, their drop methods (if provided) can still access the memory
 | ||||||
|     // region represented by the process_range field.
 |     // region represented by the mem_chunks field.
 | ||||||
|     process_range: UserSpaceVMRange, |     mem_chunks: MemChunks, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl Default for ProcessVM { | impl Default for ProcessVM { | ||||||
|     fn default() -> ProcessVM { |     fn default() -> ProcessVM { | ||||||
|         ProcessVM { |         ProcessVM { | ||||||
|             process_range: USER_SPACE_VM_MANAGER.alloc_dummy(), |  | ||||||
|             elf_ranges: Default::default(), |             elf_ranges: Default::default(), | ||||||
|             heap_range: Default::default(), |             heap_range: Default::default(), | ||||||
|             stack_range: Default::default(), |             stack_range: Default::default(), | ||||||
|             brk: Default::default(), |             brk: Default::default(), | ||||||
|             vm_manager: Default::default(), |             mem_chunks: Arc::new(RwLock::new(HashSet::new())), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | impl Drop for ProcessVM { | ||||||
|  |     fn drop(&mut self) { | ||||||
|  |         let mut mem_chunks = self.mem_chunks.write().unwrap(); | ||||||
|  |         // There are two cases when this drop is called:
 | ||||||
|  |         // (1) Process exits normally and in the end, drop process VM
 | ||||||
|  |         // (2) During creating process stage, process VM is ready but there are some other errors when creating the process, e.g. spawn_attribute is set
 | ||||||
|  |         // to a wrong value
 | ||||||
|  |         //
 | ||||||
|  |         // For the first case, the process VM is cleaned in the exit procedure and nothing is needed. For the second cases, mem_chunks is not empty and should
 | ||||||
|  |         // be cleaned here.
 | ||||||
|  | 
 | ||||||
|  |         // In the first case, the current is reset to idle thread
 | ||||||
|  |         // In the second case, the current thread belongs to parent process
 | ||||||
|  |         let current = current!(); | ||||||
|  |         if current.tid() != 0 { | ||||||
|  |             mem_chunks | ||||||
|  |                 .drain_filter(|chunk| chunk.is_single_vma()) | ||||||
|  |                 .for_each(|chunk| USER_SPACE_VM_MANAGER.free_chunk(&chunk)) | ||||||
|  |         } | ||||||
|  |         assert!(mem_chunks.len() == 0); | ||||||
|  |         info!("Process VM dropped"); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| impl ProcessVM { | impl ProcessVM { | ||||||
|  |     pub fn mem_chunks(&self) -> &MemChunks { | ||||||
|  |         &self.mem_chunks | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn add_mem_chunk(&self, chunk: ChunkRef) { | ||||||
|  |         let mut mem_chunks = self.mem_chunks.write().unwrap(); | ||||||
|  |         mem_chunks.insert(chunk); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn remove_mem_chunk(&self, chunk: &ChunkRef) { | ||||||
|  |         let mut mem_chunks = self.mem_chunks.write().unwrap(); | ||||||
|  |         mem_chunks.remove(chunk); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn replace_mem_chunk(&self, old_chunk: &ChunkRef, new_chunk: ChunkRef) { | ||||||
|  |         self.remove_mem_chunk(old_chunk); | ||||||
|  |         self.add_mem_chunk(new_chunk) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     pub fn get_process_range(&self) -> &VMRange { |     pub fn get_process_range(&self) -> &VMRange { | ||||||
|         self.process_range.range() |         USER_SPACE_VM_MANAGER.range() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub fn get_elf_ranges(&self) -> &[VMRange] { |     pub fn get_elf_ranges(&self) -> &[VMRange] { | ||||||
| @ -335,6 +351,18 @@ impl ProcessVM { | |||||||
|         Ok(new_brk) |         Ok(new_brk) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     // Get a NON-accurate free size for current process
 | ||||||
|  |     pub fn get_free_size(&self) -> usize { | ||||||
|  |         let chunk_free_size = { | ||||||
|  |             let process_chunks = self.mem_chunks.read().unwrap(); | ||||||
|  |             process_chunks | ||||||
|  |                 .iter() | ||||||
|  |                 .fold(0, |acc, chunks| acc + chunks.free_size()) | ||||||
|  |         }; | ||||||
|  |         let free_size = chunk_free_size + USER_SPACE_VM_MANAGER.free_size(); | ||||||
|  |         free_size | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     pub fn mmap( |     pub fn mmap( | ||||||
|         &self, |         &self, | ||||||
|         addr: usize, |         addr: usize, | ||||||
| @ -346,9 +374,6 @@ impl ProcessVM { | |||||||
|     ) -> Result<usize> { |     ) -> Result<usize> { | ||||||
|         let addr_option = { |         let addr_option = { | ||||||
|             if flags.contains(MMapFlags::MAP_FIXED) { |             if flags.contains(MMapFlags::MAP_FIXED) { | ||||||
|                 if !self.process_range.range().contains(addr) { |  | ||||||
|                     return_errno!(EINVAL, "Beyond valid memory range"); |  | ||||||
|                 } |  | ||||||
|                 VMMapAddr::Force(addr) |                 VMMapAddr::Force(addr) | ||||||
|             } else { |             } else { | ||||||
|                 if addr == 0 { |                 if addr == 0 { | ||||||
| @ -360,7 +385,8 @@ impl ProcessVM { | |||||||
|         }; |         }; | ||||||
|         let initializer = { |         let initializer = { | ||||||
|             if flags.contains(MMapFlags::MAP_ANONYMOUS) { |             if flags.contains(MMapFlags::MAP_ANONYMOUS) { | ||||||
|                 VMInitializer::FillZeros() |                 // There is no need to fill zeros in mmap. Cleaning is done after munmap.
 | ||||||
|  |                 VMInitializer::DoNothing() | ||||||
|             } else { |             } else { | ||||||
|                 let file_ref = current!().file(fd)?; |                 let file_ref = current!().file(fd)?; | ||||||
|                 VMInitializer::LoadFromFile { |                 VMInitializer::LoadFromFile { | ||||||
| @ -386,7 +412,7 @@ impl ProcessVM { | |||||||
|             .initializer(initializer) |             .initializer(initializer) | ||||||
|             .writeback_file(writeback_file) |             .writeback_file(writeback_file) | ||||||
|             .build()?; |             .build()?; | ||||||
|         let mmap_addr = self.vm_manager.lock().unwrap().mmap(mmap_options)?; |         let mmap_addr = USER_SPACE_VM_MANAGER.mmap(&mmap_options)?; | ||||||
|         Ok(mmap_addr) |         Ok(mmap_addr) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -397,18 +423,12 @@ impl ProcessVM { | |||||||
|         new_size: usize, |         new_size: usize, | ||||||
|         flags: MRemapFlags, |         flags: MRemapFlags, | ||||||
|     ) -> Result<usize> { |     ) -> Result<usize> { | ||||||
|         if let Some(new_addr) = flags.new_addr() { |  | ||||||
|             if !self.process_range.range().contains(new_addr) { |  | ||||||
|                 return_errno!(EINVAL, "new_addr is beyond valid memory range"); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         let mremap_option = VMRemapOptions::new(old_addr, old_size, new_size, flags)?; |         let mremap_option = VMRemapOptions::new(old_addr, old_size, new_size, flags)?; | ||||||
|         self.vm_manager.lock().unwrap().mremap(&mremap_option) |         USER_SPACE_VM_MANAGER.mremap(&mremap_option) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub fn munmap(&self, addr: usize, size: usize) -> Result<()> { |     pub fn munmap(&self, addr: usize, size: usize) -> Result<()> { | ||||||
|         self.vm_manager.lock().unwrap().munmap(addr, size) |         USER_SPACE_VM_MANAGER.munmap(addr, size) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub fn mprotect(&self, addr: usize, size: usize, perms: VMPerms) -> Result<()> { |     pub fn mprotect(&self, addr: usize, size: usize, perms: VMPerms) -> Result<()> { | ||||||
| @ -419,38 +439,21 @@ impl ProcessVM { | |||||||
|             align_up(size, PAGE_SIZE) |             align_up(size, PAGE_SIZE) | ||||||
|         }; |         }; | ||||||
|         let protect_range = VMRange::new_with_size(addr, size)?; |         let protect_range = VMRange::new_with_size(addr, size)?; | ||||||
|         if !self.process_range.range().is_superset_of(&protect_range) { |  | ||||||
|             return_errno!(ENOMEM, "invalid range"); |  | ||||||
|         } |  | ||||||
|         let mut mmap_manager = self.vm_manager.lock().unwrap(); |  | ||||||
| 
 | 
 | ||||||
|         // TODO: support mprotect vm regions in addition to mmap
 |         return USER_SPACE_VM_MANAGER.mprotect(addr, size, perms); | ||||||
|         if !mmap_manager.range().is_superset_of(&protect_range) { |  | ||||||
|             warn!("Do not support mprotect memory outside the mmap region yet"); |  | ||||||
|             return Ok(()); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         mmap_manager.mprotect(addr, size, perms) |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub fn msync(&self, addr: usize, size: usize) -> Result<()> { |     pub fn msync(&self, addr: usize, size: usize) -> Result<()> { | ||||||
|         let sync_range = VMRange::new_with_size(addr, size)?; |         return USER_SPACE_VM_MANAGER.msync(addr, size); | ||||||
|         let mut mmap_manager = self.vm_manager.lock().unwrap(); |  | ||||||
|         mmap_manager.msync_by_range(&sync_range) |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub fn msync_by_file(&self, sync_file: &FileRef) { |     pub fn msync_by_file(&self, sync_file: &FileRef) { | ||||||
|         let mut mmap_manager = self.vm_manager.lock().unwrap(); |         return USER_SPACE_VM_MANAGER.msync_by_file(sync_file); | ||||||
|         mmap_manager.msync_by_file(sync_file); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Return: a copy of the found region
 |     // Return: a copy of the found region
 | ||||||
|     pub fn find_mmap_region(&self, addr: usize) -> Result<VMRange> { |     pub fn find_mmap_region(&self, addr: usize) -> Result<VMRange> { | ||||||
|         self.vm_manager |         USER_SPACE_VM_MANAGER.find_mmap_region(addr) | ||||||
|             .lock() |  | ||||||
|             .unwrap() |  | ||||||
|             .find_mmap_region(addr) |  | ||||||
|             .map(|range_ref| *range_ref) |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,62 +1,69 @@ | |||||||
| use super::*; | use super::*; | ||||||
|  | use crate::ctor::dtor; | ||||||
| use config::LIBOS_CONFIG; | use config::LIBOS_CONFIG; | ||||||
|  | use std::ops::{Deref, DerefMut}; | ||||||
|  | use vm_manager::VMManager; | ||||||
| 
 | 
 | ||||||
| /// The virtual memory manager for the entire user space
 | /// The virtual memory manager for the entire user space
 | ||||||
| pub struct UserSpaceVMManager { | pub struct UserSpaceVMManager(VMManager); | ||||||
|     total_size: usize, |  | ||||||
|     free_size: SgxMutex<usize>, |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| impl UserSpaceVMManager { | impl UserSpaceVMManager { | ||||||
|     fn new() -> UserSpaceVMManager { |     fn new() -> Result<UserSpaceVMManager> { | ||||||
|         let rsrv_mem_size = LIBOS_CONFIG.resource_limits.user_space_size; |         let rsrv_mem_size = LIBOS_CONFIG.resource_limits.user_space_size; | ||||||
|         UserSpaceVMManager { |  | ||||||
|             total_size: rsrv_mem_size, |  | ||||||
|             free_size: SgxMutex::new(rsrv_mem_size), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     pub fn alloc(&self, vm_layout: VMLayout) -> Result<UserSpaceVMRange> { |  | ||||||
|         let size = align_up(vm_layout.size(), vm_layout.align()); |  | ||||||
|         let vm_range = unsafe { |         let vm_range = unsafe { | ||||||
|             let ptr = sgx_alloc_rsrv_mem(size); |             // TODO: Current sgx_alloc_rsrv_mem implmentation will commit all the pages of the desired size, which will consume
 | ||||||
|  |             // a lot of time. When EDMM is supported, there is no need to commit all the pages at the initialization stage. A function
 | ||||||
|  |             // which reserves memory but not commit pages should be provided then.
 | ||||||
|  |             let ptr = sgx_alloc_rsrv_mem(rsrv_mem_size); | ||||||
|             let perm = MemPerm::READ | MemPerm::WRITE; |             let perm = MemPerm::READ | MemPerm::WRITE; | ||||||
|             if ptr.is_null() { |             if ptr.is_null() { | ||||||
|                 return_errno!(ENOMEM, "run out of reserved memory"); |                 return_errno!(ENOMEM, "run out of reserved memory"); | ||||||
|             } |             } | ||||||
|             // Change the page permission to RW (default)
 |             // Change the page permission to RW (default)
 | ||||||
|             assert!(sgx_tprotect_rsrv_mem(ptr, size, perm.bits()) == sgx_status_t::SGX_SUCCESS); |             assert!( | ||||||
|  |                 sgx_tprotect_rsrv_mem(ptr, rsrv_mem_size, perm.bits()) == sgx_status_t::SGX_SUCCESS | ||||||
|  |             ); | ||||||
| 
 | 
 | ||||||
|             let addr = ptr as usize; |             let addr = ptr as usize; | ||||||
|             debug!("allocated rsrv addr is 0x{:x}, len is 0x{:x}", addr, size); |             debug!( | ||||||
|             VMRange::from_unchecked(addr, addr + size) |                 "allocated rsrv addr is 0x{:x}, len is 0x{:x}", | ||||||
|  |                 addr, rsrv_mem_size | ||||||
|  |             ); | ||||||
|  |             VMRange::from_unchecked(addr, addr + rsrv_mem_size) | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         *self.free_size.lock().unwrap() -= size; |         let vm_manager = VMManager::init(vm_range)?; | ||||||
|         Ok(UserSpaceVMRange::new(vm_range)) |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     fn add_free_size(&self, user_space_vmrange: &UserSpaceVMRange) { |         Ok(UserSpaceVMManager(vm_manager)) | ||||||
|         *self.free_size.lock().unwrap() += user_space_vmrange.range().size(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // The empty range is not added to sub_range
 |  | ||||||
|     pub fn alloc_dummy(&self) -> UserSpaceVMRange { |  | ||||||
|         let empty_user_vm_range = unsafe { VMRange::from_unchecked(0, 0) }; |  | ||||||
|         UserSpaceVMRange::new(empty_user_vm_range) |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub fn get_total_size(&self) -> usize { |     pub fn get_total_size(&self) -> usize { | ||||||
|         self.total_size |         self.range().size() | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|     pub fn get_free_size(&self) -> usize { | // This provides module teardown function attribute similar with `__attribute__((destructor))` in C/C++ and will
 | ||||||
|         *self.free_size.lock().unwrap() | // be called after the main function. Static variables are still safe to visit at this time.
 | ||||||
|  | #[dtor] | ||||||
|  | fn free_user_space() { | ||||||
|  |     let range = USER_SPACE_VM_MANAGER.range(); | ||||||
|  |     assert!(USER_SPACE_VM_MANAGER.verified_clean_when_exit()); | ||||||
|  |     let addr = range.start() as *const c_void; | ||||||
|  |     let size = range.size(); | ||||||
|  |     info!("free user space VM: {:?}", range); | ||||||
|  |     assert!(unsafe { sgx_free_rsrv_mem(addr, size) == 0 }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Deref for UserSpaceVMManager { | ||||||
|  |     type Target = VMManager; | ||||||
|  | 
 | ||||||
|  |     fn deref(&self) -> &Self::Target { | ||||||
|  |         &self.0 | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| lazy_static! { | lazy_static! { | ||||||
|     pub static ref USER_SPACE_VM_MANAGER: UserSpaceVMManager = UserSpaceVMManager::new(); |     pub static ref USER_SPACE_VM_MANAGER: UserSpaceVMManager = UserSpaceVMManager::new().unwrap(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bitflags! { | bitflags! { | ||||||
| @ -96,32 +103,3 @@ extern "C" { | |||||||
|     //
 |     //
 | ||||||
|     fn sgx_tprotect_rsrv_mem(addr: *const c_void, length: usize, prot: i32) -> sgx_status_t; |     fn sgx_tprotect_rsrv_mem(addr: *const c_void, length: usize, prot: i32) -> sgx_status_t; | ||||||
| } | } | ||||||
| 
 |  | ||||||
| #[derive(Debug)] |  | ||||||
| pub struct UserSpaceVMRange { |  | ||||||
|     vm_range: VMRange, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl UserSpaceVMRange { |  | ||||||
|     fn new(vm_range: VMRange) -> UserSpaceVMRange { |  | ||||||
|         UserSpaceVMRange { vm_range } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     pub fn range(&self) -> &VMRange { |  | ||||||
|         &self.vm_range |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl Drop for UserSpaceVMRange { |  | ||||||
|     fn drop(&mut self) { |  | ||||||
|         let addr = self.vm_range.start() as *const c_void; |  | ||||||
|         let size = self.vm_range.size(); |  | ||||||
|         if size == 0 { |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         USER_SPACE_VM_MANAGER.add_free_size(self); |  | ||||||
|         info!("user space vm free: {:?}", self.vm_range); |  | ||||||
|         assert!(unsafe { sgx_free_rsrv_mem(addr, size) == 0 }); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | |||||||
| @ -4,25 +4,40 @@ use super::vm_perms::VMPerms; | |||||||
| use super::vm_range::VMRange; | use super::vm_range::VMRange; | ||||||
| use super::*; | use super::*; | ||||||
| 
 | 
 | ||||||
|  | use intrusive_collections::rbtree::{Link, RBTree}; | ||||||
|  | use intrusive_collections::{intrusive_adapter, KeyAdapter}; | ||||||
|  | 
 | ||||||
| #[derive(Clone, Debug, Default)] | #[derive(Clone, Debug, Default)] | ||||||
| pub struct VMArea { | pub struct VMArea { | ||||||
|     range: VMRange, |     range: VMRange, | ||||||
|     perms: VMPerms, |     perms: VMPerms, | ||||||
|     writeback_file: Option<(FileRef, usize)>, |     writeback_file: Option<(FileRef, usize)>, | ||||||
|  |     pid: pid_t, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl VMArea { | impl VMArea { | ||||||
|     pub fn new(range: VMRange, perms: VMPerms, writeback_file: Option<(FileRef, usize)>) -> Self { |     pub fn new( | ||||||
|  |         range: VMRange, | ||||||
|  |         perms: VMPerms, | ||||||
|  |         writeback_file: Option<(FileRef, usize)>, | ||||||
|  |         pid: pid_t, | ||||||
|  |     ) -> Self { | ||||||
|         Self { |         Self { | ||||||
|             range, |             range, | ||||||
|             perms, |             perms, | ||||||
|             writeback_file, |             writeback_file, | ||||||
|  |             pid, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// Create a new VMArea object that inherits the write-back file (if any), but has
 |     /// Create a new VMArea object that inherits the write-back file (if any), but has
 | ||||||
|     /// a new range and permissions.
 |     /// a new range and permissions.
 | ||||||
|     pub fn inherits_file_from(vma: &VMArea, new_range: VMRange, new_perms: VMPerms) -> Self { |     pub fn inherits_file_from( | ||||||
|  |         vma: &VMArea, | ||||||
|  |         new_range: VMRange, | ||||||
|  |         new_perms: VMPerms, | ||||||
|  |         pid: pid_t, | ||||||
|  |     ) -> Self { | ||||||
|         let new_writeback_file = vma.writeback_file.as_ref().map(|(file, file_offset)| { |         let new_writeback_file = vma.writeback_file.as_ref().map(|(file, file_offset)| { | ||||||
|             let new_file = file.clone(); |             let new_file = file.clone(); | ||||||
| 
 | 
 | ||||||
| @ -36,7 +51,7 @@ impl VMArea { | |||||||
|             }; |             }; | ||||||
|             (new_file, new_file_offset) |             (new_file, new_file_offset) | ||||||
|         }); |         }); | ||||||
|         Self::new(new_range, new_perms, new_writeback_file) |         Self::new(new_range, new_perms, new_writeback_file, pid) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub fn perms(&self) -> VMPerms { |     pub fn perms(&self) -> VMPerms { | ||||||
| @ -47,6 +62,10 @@ impl VMArea { | |||||||
|         &self.range |         &self.range | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     pub fn pid(&self) -> pid_t { | ||||||
|  |         self.pid | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     pub fn writeback_file(&self) -> &Option<(FileRef, usize)> { |     pub fn writeback_file(&self) -> &Option<(FileRef, usize)> { | ||||||
|         &self.writeback_file |         &self.writeback_file | ||||||
|     } |     } | ||||||
| @ -59,7 +78,7 @@ impl VMArea { | |||||||
|         self.deref() |         self.deref() | ||||||
|             .subtract(other) |             .subtract(other) | ||||||
|             .into_iter() |             .into_iter() | ||||||
|             .map(|range| Self::inherits_file_from(self, range, self.perms())) |             .map(|range| Self::inherits_file_from(self, range, self.perms(), self.pid())) | ||||||
|             .collect() |             .collect() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -72,7 +91,7 @@ impl VMArea { | |||||||
|             } |             } | ||||||
|             new_range.unwrap() |             new_range.unwrap() | ||||||
|         }; |         }; | ||||||
|         let new_vma = VMArea::inherits_file_from(self, new_range, self.perms()); |         let new_vma = VMArea::inherits_file_from(self, new_range, self.perms(), self.pid()); | ||||||
|         Some(new_vma) |         Some(new_vma) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -109,3 +128,56 @@ impl Deref for VMArea { | |||||||
|         &self.range |         &self.range | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | #[derive(Clone)] | ||||||
|  | pub struct VMAObj { | ||||||
|  |     link: Link, | ||||||
|  |     vma: VMArea, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl fmt::Debug for VMAObj { | ||||||
|  |     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||||||
|  |         write!(f, "{:?}", self.vma) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // key adapter for RBTree which is sorted by the start of vma ranges
 | ||||||
|  | intrusive_adapter!(pub VMAAdapter = Box<VMAObj>: VMAObj { link : Link }); | ||||||
|  | impl<'a> KeyAdapter<'a> for VMAAdapter { | ||||||
|  |     type Key = usize; | ||||||
|  |     fn get_key(&self, vma_obj: &'a VMAObj) -> usize { | ||||||
|  |         vma_obj.vma.range().start() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl VMAObj { | ||||||
|  |     pub fn new_vma_obj(vma: VMArea) -> Box<Self> { | ||||||
|  |         Box::new(Self { | ||||||
|  |             link: Link::new(), | ||||||
|  |             vma, | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn vma(&self) -> &VMArea { | ||||||
|  |         &self.vma | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl VMArea { | ||||||
|  |     pub fn new_obj( | ||||||
|  |         range: VMRange, | ||||||
|  |         perms: VMPerms, | ||||||
|  |         writeback_file: Option<(FileRef, usize)>, | ||||||
|  |         pid: pid_t, | ||||||
|  |     ) -> Box<VMAObj> { | ||||||
|  |         Box::new(VMAObj { | ||||||
|  |             link: Link::new(), | ||||||
|  |             vma: VMArea { | ||||||
|  |                 range, | ||||||
|  |                 perms, | ||||||
|  |                 writeback_file, | ||||||
|  |                 pid, | ||||||
|  |             }, | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | |||||||
							
								
								
									
										654
									
								
								src/libos/src/vm/vm_chunk_manager.rs
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										654
									
								
								src/libos/src/vm/vm_chunk_manager.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,654 @@ | |||||||
|  | use super::*; | ||||||
|  | 
 | ||||||
|  | use super::free_space_manager::VMFreeSpaceManager as FreeRangeManager; | ||||||
|  | use super::vm_area::*; | ||||||
|  | use super::vm_perms::VMPerms; | ||||||
|  | use super::vm_util::*; | ||||||
|  | use std::collections::BTreeSet; | ||||||
|  | 
 | ||||||
|  | use intrusive_collections::rbtree::{Link, RBTree}; | ||||||
|  | use intrusive_collections::Bound; | ||||||
|  | use intrusive_collections::RBTreeLink; | ||||||
|  | use intrusive_collections::{intrusive_adapter, KeyAdapter}; | ||||||
|  | 
 | ||||||
|  | /// Memory chunk manager.
 | ||||||
|  | ///
 | ||||||
|  | /// Chunk is the memory unit for Occlum. For chunks with `default` size, every chunk is managed by a ChunkManager which provides
 | ||||||
|  | /// usedful memory management APIs such as mmap, munmap, mremap, mprotect, etc.
 | ||||||
|  | /// ChunkManager is implemented basically with two data structures: a red-black tree to track vmas in use and a FreeRangeManager to track
 | ||||||
|  | /// ranges which are free.
 | ||||||
|  | /// For vmas-in-use, there are two sentry vmas with zero length at the front and end of the red-black tree.
 | ||||||
|  | #[derive(Debug, Default)] | ||||||
|  | pub struct ChunkManager { | ||||||
|  |     range: VMRange, | ||||||
|  |     free_size: usize, | ||||||
|  |     vmas: RBTree<VMAAdapter>, | ||||||
|  |     free_manager: FreeRangeManager, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl ChunkManager { | ||||||
|  |     pub fn from(addr: usize, size: usize) -> Result<Self> { | ||||||
|  |         let range = VMRange::new(addr, addr + size)?; | ||||||
|  |         let vmas = { | ||||||
|  |             let start = range.start(); | ||||||
|  |             let end = range.end(); | ||||||
|  |             let start_sentry = { | ||||||
|  |                 let range = VMRange::new_empty(start)?; | ||||||
|  |                 let perms = VMPerms::empty(); | ||||||
|  |                 // sentry vma shouldn't belong to any process
 | ||||||
|  |                 VMAObj::new_vma_obj(VMArea::new(range, perms, None, 0)) | ||||||
|  |             }; | ||||||
|  |             let end_sentry = { | ||||||
|  |                 let range = VMRange::new_empty(end)?; | ||||||
|  |                 let perms = VMPerms::empty(); | ||||||
|  |                 VMAObj::new_vma_obj(VMArea::new(range, perms, None, 0)) | ||||||
|  |             }; | ||||||
|  |             let mut new_tree = RBTree::new(VMAAdapter::new()); | ||||||
|  |             new_tree.insert(start_sentry); | ||||||
|  |             new_tree.insert(end_sentry); | ||||||
|  |             new_tree | ||||||
|  |         }; | ||||||
|  |         Ok(ChunkManager { | ||||||
|  |             range, | ||||||
|  |             free_size: range.size(), | ||||||
|  |             vmas, | ||||||
|  |             free_manager: FreeRangeManager::new(range.clone()), | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn range(&self) -> &VMRange { | ||||||
|  |         &self.range | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn vmas(&self) -> &RBTree<VMAAdapter> { | ||||||
|  |         &self.vmas | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn free_size(&self) -> &usize { | ||||||
|  |         &self.free_size | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn is_empty(&self) -> bool { | ||||||
|  |         self.vmas.iter().count() == 2 // only sentry vmas
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn clean_vmas_with_pid(&mut self, pid: pid_t) { | ||||||
|  |         let mut vmas_cursor = self.vmas.cursor_mut(); | ||||||
|  |         vmas_cursor.move_next(); // move to the first element of the tree
 | ||||||
|  |         while !vmas_cursor.is_null() { | ||||||
|  |             let vma = vmas_cursor.get().unwrap().vma(); | ||||||
|  |             if vma.pid() != pid || vma.size() == 0 { | ||||||
|  |                 // Skip vmas which doesn't belong to this process
 | ||||||
|  |                 vmas_cursor.move_next(); | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             Self::flush_file_vma(vma); | ||||||
|  | 
 | ||||||
|  |             if !vma.perms().is_default() { | ||||||
|  |                 VMPerms::apply_perms(vma, VMPerms::default()); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             unsafe { | ||||||
|  |                 let buf = vma.as_slice_mut(); | ||||||
|  |                 buf.iter_mut().for_each(|b| *b = 0) | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             self.free_manager.add_range_back_to_free_manager(vma); | ||||||
|  |             self.free_size += vma.size(); | ||||||
|  | 
 | ||||||
|  |             // Remove this vma from vmas list
 | ||||||
|  |             vmas_cursor.remove(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn mmap(&mut self, options: &VMMapOptions) -> Result<usize> { | ||||||
|  |         let addr = *options.addr(); | ||||||
|  |         let size = *options.size(); | ||||||
|  |         let align = *options.align(); | ||||||
|  | 
 | ||||||
|  |         if let VMMapAddr::Force(addr) = addr { | ||||||
|  |             self.munmap(addr, size)?; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // Find and allocate a new range for this mmap request
 | ||||||
|  |         let new_range = self | ||||||
|  |             .free_manager | ||||||
|  |             .find_free_range_internal(size, align, addr)?; | ||||||
|  |         let new_addr = new_range.start(); | ||||||
|  |         let writeback_file = options.writeback_file().clone(); | ||||||
|  |         let current_pid = current!().process().pid(); | ||||||
|  |         let new_vma = VMArea::new(new_range, *options.perms(), writeback_file, current_pid); | ||||||
|  | 
 | ||||||
|  |         // Initialize the memory of the new range
 | ||||||
|  |         unsafe { | ||||||
|  |             let buf = new_vma.as_slice_mut(); | ||||||
|  |             options.initializer().init_slice(buf)?; | ||||||
|  |         } | ||||||
|  |         // Set memory permissions
 | ||||||
|  |         if !options.perms().is_default() { | ||||||
|  |             VMPerms::apply_perms(&new_vma, new_vma.perms()); | ||||||
|  |         } | ||||||
|  |         self.free_size -= new_vma.size(); | ||||||
|  |         // After initializing, we can safely insert the new VMA
 | ||||||
|  |         self.vmas.insert(VMAObj::new_vma_obj(new_vma)); | ||||||
|  |         Ok(new_addr) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn munmap_range(&mut self, range: VMRange) -> Result<()> { | ||||||
|  |         let bound = range.start(); | ||||||
|  |         let current_pid = current!().process().pid(); | ||||||
|  | 
 | ||||||
|  |         // The cursor to iterate vmas that might intersect with munmap_range.
 | ||||||
|  |         // Upper bound returns the vma whose start address is below and nearest to the munmap range. Start from this range.
 | ||||||
|  |         let mut vmas_cursor = self.vmas.upper_bound_mut(Bound::Included(&bound)); | ||||||
|  |         while !vmas_cursor.is_null() && vmas_cursor.get().unwrap().vma().start() <= range.end() { | ||||||
|  |             let vma = &vmas_cursor.get().unwrap().vma(); | ||||||
|  |             warn!("munmap related vma = {:?}", vma); | ||||||
|  |             if vma.size() == 0 || current_pid != vma.pid() { | ||||||
|  |                 vmas_cursor.move_next(); | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  |             let intersection_vma = match vma.intersect(&range) { | ||||||
|  |                 None => { | ||||||
|  |                     vmas_cursor.move_next(); | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|  |                 Some(intersection_vma) => intersection_vma, | ||||||
|  |             }; | ||||||
|  | 
 | ||||||
|  |             // File-backed VMA needs to be flushed upon munmap
 | ||||||
|  |             Self::flush_file_vma(&intersection_vma); | ||||||
|  |             if !&intersection_vma.perms().is_default() { | ||||||
|  |                 VMPerms::apply_perms(&intersection_vma, VMPerms::default()); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if vma.range() == intersection_vma.range() { | ||||||
|  |                 // Exact match. Just remove.
 | ||||||
|  |                 vmas_cursor.remove(); | ||||||
|  |             } else { | ||||||
|  |                 // The intersection_vma is a subset of current vma
 | ||||||
|  |                 let mut remain_vmas = vma.subtract(&intersection_vma); | ||||||
|  |                 if remain_vmas.len() == 1 { | ||||||
|  |                     let new_obj = VMAObj::new_vma_obj(remain_vmas.pop().unwrap()); | ||||||
|  |                     vmas_cursor.replace_with(new_obj); | ||||||
|  |                     vmas_cursor.move_next(); | ||||||
|  |                 } else { | ||||||
|  |                     debug_assert!(remain_vmas.len() == 2); | ||||||
|  |                     let vma_left_part = VMAObj::new_vma_obj(remain_vmas.swap_remove(0)); | ||||||
|  |                     vmas_cursor.replace_with(vma_left_part); | ||||||
|  |                     let vma_right_part = VMAObj::new_vma_obj(remain_vmas.pop().unwrap()); | ||||||
|  |                     // The new element will be inserted at the correct position in the tree based on its key automatically.
 | ||||||
|  |                     vmas_cursor.insert(vma_right_part); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // Reset zero
 | ||||||
|  |             unsafe { | ||||||
|  |                 warn!("intersection vma = {:?}", intersection_vma); | ||||||
|  |                 let buf = intersection_vma.as_slice_mut(); | ||||||
|  |                 buf.iter_mut().for_each(|b| *b = 0) | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             self.free_manager | ||||||
|  |                 .add_range_back_to_free_manager(intersection_vma.range()); | ||||||
|  |             self.free_size += intersection_vma.size(); | ||||||
|  |         } | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn munmap(&mut self, addr: usize, size: usize) -> Result<()> { | ||||||
|  |         let size = { | ||||||
|  |             if size == 0 { | ||||||
|  |                 return_errno!(EINVAL, "size of munmap must not be zero"); | ||||||
|  |             } | ||||||
|  |             align_up(size, PAGE_SIZE) | ||||||
|  |         }; | ||||||
|  |         let munmap_range = { | ||||||
|  |             let munmap_range = VMRange::new(addr, addr + size)?; | ||||||
|  | 
 | ||||||
|  |             let effective_munmap_range_opt = munmap_range.intersect(&self.range); | ||||||
|  |             if effective_munmap_range_opt.is_none() { | ||||||
|  |                 return Ok(()); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             let effective_munmap_range = effective_munmap_range_opt.unwrap(); | ||||||
|  |             if effective_munmap_range.empty() { | ||||||
|  |                 return Ok(()); | ||||||
|  |             } | ||||||
|  |             effective_munmap_range | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         self.munmap_range(munmap_range) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn mremap(&mut self, options: &VMRemapOptions) -> Result<usize> { | ||||||
|  |         let old_addr = options.old_addr(); | ||||||
|  |         let old_size = options.old_size(); | ||||||
|  |         let old_range = VMRange::new_with_size(old_addr, old_size)?; | ||||||
|  |         let new_size = options.new_size(); | ||||||
|  |         let flags = options.flags(); | ||||||
|  |         let size_type = SizeType::new(&old_size, &new_size); | ||||||
|  | 
 | ||||||
|  |         return_errno!(ENOSYS, "Under development"); | ||||||
|  | 
 | ||||||
|  |         // Old dead code. Could be used for future development.
 | ||||||
|  |         #[cfg(dev)] | ||||||
|  |         { | ||||||
|  |             // The old range must be contained in one VMA
 | ||||||
|  |             let idx = self | ||||||
|  |                 .find_containing_vma_idx(&old_range) | ||||||
|  |                 .ok_or_else(|| errno!(EFAULT, "invalid range"))?; | ||||||
|  |             let containing_vma = &self.vmas[idx]; | ||||||
|  |             // Get the memory permissions of the old range
 | ||||||
|  |             let perms = containing_vma.perms(); | ||||||
|  |             // Get the write back file of the old range if there is one.
 | ||||||
|  |             let writeback_file = containing_vma.writeback_file(); | ||||||
|  | 
 | ||||||
|  |             // FIXME: Current implementation for file-backed memory mremap has limitation that if a SUBRANGE of the previous
 | ||||||
|  |             // file-backed mmap with MAP_SHARED is then mremap-ed with MREMAP_MAYMOVE, there will be two vmas that have the same backed file.
 | ||||||
|  |             // For Linux, writing to either memory vma or the file will update the other two equally. But we won't be able to support this before
 | ||||||
|  |             // we really have paging. Thus, if the old_range is not equal to a recorded vma, we will just return with error.
 | ||||||
|  |             if writeback_file.is_some() && &old_range != containing_vma.range() { | ||||||
|  |                 return_errno!(EINVAL, "Known limition") | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // Implement mremap as one optional mmap followed by one optional munmap.
 | ||||||
|  |             //
 | ||||||
|  |             // The exact arguments for the mmap and munmap are determined by the values of MRemapFlags,
 | ||||||
|  |             // SizeType and writeback_file. There is a total of 18 combinations among MRemapFlags and
 | ||||||
|  |             // SizeType and writeback_file. As some combinations result in the same mmap and munmap operations,
 | ||||||
|  |             // the following code only needs to match below patterns of (MRemapFlags, SizeType, writeback_file)
 | ||||||
|  |             // and treat each case accordingly.
 | ||||||
|  | 
 | ||||||
|  |             // Determine whether need to do mmap. And when possible, determine the returned address
 | ||||||
|  |             let (need_mmap, mut ret_addr) = match (flags, size_type, writeback_file) { | ||||||
|  |                 (MRemapFlags::None, SizeType::Growing, None) => { | ||||||
|  |                     let vm_initializer_for_new_range = VMInitializer::FillZeros(); | ||||||
|  |                     let mmap_opts = VMMapOptionsBuilder::default() | ||||||
|  |                         .size(new_size - old_size) | ||||||
|  |                         .addr(VMMapAddr::Need(old_range.end())) | ||||||
|  |                         .perms(perms) | ||||||
|  |                         .initializer(vm_initializer_for_new_range) | ||||||
|  |                         .build()?; | ||||||
|  |                     let ret_addr = Some(old_addr); | ||||||
|  |                     (Some(mmap_opts), ret_addr) | ||||||
|  |                 } | ||||||
|  |                 (MRemapFlags::None, SizeType::Growing, Some((backed_file, offset))) => { | ||||||
|  |                     // Update writeback file offset
 | ||||||
|  |                     let new_writeback_file = | ||||||
|  |                         Some((backed_file.clone(), offset + containing_vma.size())); | ||||||
|  |                     let vm_initializer_for_new_range = VMInitializer::LoadFromFile { | ||||||
|  |                         file: backed_file.clone(), | ||||||
|  |                         offset: offset + containing_vma.size(), // file-backed mremap should start from the end of previous mmap/mremap file
 | ||||||
|  |                     }; | ||||||
|  |                     let mmap_opts = VMMapOptionsBuilder::default() | ||||||
|  |                         .size(new_size - old_size) | ||||||
|  |                         .addr(VMMapAddr::Need(old_range.end())) | ||||||
|  |                         .perms(perms) | ||||||
|  |                         .initializer(vm_initializer_for_new_range) | ||||||
|  |                         .writeback_file(new_writeback_file) | ||||||
|  |                         .build()?; | ||||||
|  |                     let ret_addr = Some(old_addr); | ||||||
|  |                     (Some(mmap_opts), ret_addr) | ||||||
|  |                 } | ||||||
|  |                 (MRemapFlags::MayMove, SizeType::Growing, None) => { | ||||||
|  |                     let prefered_new_range = | ||||||
|  |                         VMRange::new_with_size(old_addr + old_size, new_size - old_size)?; | ||||||
|  |                     if self.is_free_range(&prefered_new_range) { | ||||||
|  |                         // Don't need to move the old range
 | ||||||
|  |                         let vm_initializer_for_new_range = VMInitializer::FillZeros(); | ||||||
|  |                         let mmap_ops = VMMapOptionsBuilder::default() | ||||||
|  |                             .size(prefered_new_range.size()) | ||||||
|  |                             .addr(VMMapAddr::Need(prefered_new_range.start())) | ||||||
|  |                             .perms(perms) | ||||||
|  |                             .initializer(vm_initializer_for_new_range) | ||||||
|  |                             .build()?; | ||||||
|  |                         (Some(mmap_ops), Some(old_addr)) | ||||||
|  |                     } else { | ||||||
|  |                         // Need to move old range to a new range and init the new range
 | ||||||
|  |                         let vm_initializer_for_new_range = | ||||||
|  |                             VMInitializer::CopyFrom { range: old_range }; | ||||||
|  |                         let mmap_ops = VMMapOptionsBuilder::default() | ||||||
|  |                             .size(new_size) | ||||||
|  |                             .addr(VMMapAddr::Any) | ||||||
|  |                             .perms(perms) | ||||||
|  |                             .initializer(vm_initializer_for_new_range) | ||||||
|  |                             .build()?; | ||||||
|  |                         // Cannot determine the returned address for now, which can only be obtained after calling mmap
 | ||||||
|  |                         let ret_addr = None; | ||||||
|  |                         (Some(mmap_ops), ret_addr) | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 (MRemapFlags::MayMove, SizeType::Growing, Some((backed_file, offset))) => { | ||||||
|  |                     let prefered_new_range = | ||||||
|  |                         VMRange::new_with_size(old_addr + old_size, new_size - old_size)?; | ||||||
|  |                     if self.is_free_range(&prefered_new_range) { | ||||||
|  |                         // Don't need to move the old range
 | ||||||
|  |                         let vm_initializer_for_new_range = VMInitializer::LoadFromFile { | ||||||
|  |                             file: backed_file.clone(), | ||||||
|  |                             offset: offset + containing_vma.size(), // file-backed mremap should start from the end of previous mmap/mremap file
 | ||||||
|  |                         }; | ||||||
|  |                         // Write back file should start from new offset
 | ||||||
|  |                         let new_writeback_file = | ||||||
|  |                             Some((backed_file.clone(), offset + containing_vma.size())); | ||||||
|  |                         let mmap_ops = VMMapOptionsBuilder::default() | ||||||
|  |                             .size(prefered_new_range.size()) | ||||||
|  |                             .addr(VMMapAddr::Need(prefered_new_range.start())) | ||||||
|  |                             .perms(perms) | ||||||
|  |                             .initializer(vm_initializer_for_new_range) | ||||||
|  |                             .writeback_file(new_writeback_file) | ||||||
|  |                             .build()?; | ||||||
|  |                         (Some(mmap_ops), Some(old_addr)) | ||||||
|  |                     } else { | ||||||
|  |                         // Need to move old range to a new range and init the new range
 | ||||||
|  |                         let vm_initializer_for_new_range = { | ||||||
|  |                             let copy_end = containing_vma.end(); | ||||||
|  |                             let copy_range = VMRange::new(old_range.start(), copy_end)?; | ||||||
|  |                             let reread_file_start_offset = copy_end - containing_vma.start(); | ||||||
|  |                             VMInitializer::CopyOldAndReadNew { | ||||||
|  |                                 old_range: copy_range, | ||||||
|  |                                 file: backed_file.clone(), | ||||||
|  |                                 offset: reread_file_start_offset, | ||||||
|  |                             } | ||||||
|  |                         }; | ||||||
|  |                         let new_writeback_file = Some((backed_file.clone(), *offset)); | ||||||
|  |                         let mmap_ops = VMMapOptionsBuilder::default() | ||||||
|  |                             .size(new_size) | ||||||
|  |                             .addr(VMMapAddr::Any) | ||||||
|  |                             .perms(perms) | ||||||
|  |                             .initializer(vm_initializer_for_new_range) | ||||||
|  |                             .writeback_file(new_writeback_file) | ||||||
|  |                             .build()?; | ||||||
|  |                         // Cannot determine the returned address for now, which can only be obtained after calling mmap
 | ||||||
|  |                         let ret_addr = None; | ||||||
|  |                         (Some(mmap_ops), ret_addr) | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 (MRemapFlags::FixedAddr(new_addr), _, None) => { | ||||||
|  |                     let vm_initializer_for_new_range = | ||||||
|  |                         { VMInitializer::CopyFrom { range: old_range } }; | ||||||
|  |                     let mmap_opts = VMMapOptionsBuilder::default() | ||||||
|  |                         .size(new_size) | ||||||
|  |                         .addr(VMMapAddr::Force(new_addr)) | ||||||
|  |                         .perms(perms) | ||||||
|  |                         .initializer(vm_initializer_for_new_range) | ||||||
|  |                         .build()?; | ||||||
|  |                     let ret_addr = Some(new_addr); | ||||||
|  |                     (Some(mmap_opts), ret_addr) | ||||||
|  |                 } | ||||||
|  |                 (MRemapFlags::FixedAddr(new_addr), _, Some((backed_file, offset))) => { | ||||||
|  |                     let vm_initializer_for_new_range = { | ||||||
|  |                         let copy_end = containing_vma.end(); | ||||||
|  |                         let copy_range = VMRange::new(old_range.start(), copy_end)?; | ||||||
|  |                         let reread_file_start_offset = copy_end - containing_vma.start(); | ||||||
|  |                         VMInitializer::CopyOldAndReadNew { | ||||||
|  |                             old_range: copy_range, | ||||||
|  |                             file: backed_file.clone(), | ||||||
|  |                             offset: reread_file_start_offset, | ||||||
|  |                         } | ||||||
|  |                     }; | ||||||
|  |                     let new_writeback_file = Some((backed_file.clone(), *offset)); | ||||||
|  |                     let mmap_opts = VMMapOptionsBuilder::default() | ||||||
|  |                         .size(new_size) | ||||||
|  |                         .addr(VMMapAddr::Force(new_addr)) | ||||||
|  |                         .perms(perms) | ||||||
|  |                         .initializer(vm_initializer_for_new_range) | ||||||
|  |                         .writeback_file(new_writeback_file) | ||||||
|  |                         .build()?; | ||||||
|  |                     let ret_addr = Some(new_addr); | ||||||
|  |                     (Some(mmap_opts), ret_addr) | ||||||
|  |                 } | ||||||
|  |                 _ => (None, Some(old_addr)), | ||||||
|  |             }; | ||||||
|  | 
 | ||||||
|  |             let need_munmap = match (flags, size_type) { | ||||||
|  |                 (MRemapFlags::None, SizeType::Shrinking) | ||||||
|  |                 | (MRemapFlags::MayMove, SizeType::Shrinking) => { | ||||||
|  |                     let unmap_addr = old_addr + new_size; | ||||||
|  |                     let unmap_size = old_size - new_size; | ||||||
|  |                     Some((unmap_addr, unmap_size)) | ||||||
|  |                 } | ||||||
|  |                 (MRemapFlags::MayMove, SizeType::Growing) => { | ||||||
|  |                     if ret_addr.is_none() { | ||||||
|  |                         // We must need to do mmap. Thus unmap the old range
 | ||||||
|  |                         Some((old_addr, old_size)) | ||||||
|  |                     } else { | ||||||
|  |                         // We must choose to reuse the old range. Thus, no need to unmap
 | ||||||
|  |                         None | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 (MRemapFlags::FixedAddr(new_addr), _) => { | ||||||
|  |                     let new_range = VMRange::new_with_size(new_addr, new_size)?; | ||||||
|  |                     if new_range.overlap_with(&old_range) { | ||||||
|  |                         return_errno!(EINVAL, "new range cannot overlap with the old one"); | ||||||
|  |                     } | ||||||
|  |                     Some((old_addr, old_size)) | ||||||
|  |                 } | ||||||
|  |                 _ => None, | ||||||
|  |             }; | ||||||
|  | 
 | ||||||
|  |             // Perform mmap and munmap if needed
 | ||||||
|  |             if let Some(mmap_options) = need_mmap { | ||||||
|  |                 let mmap_addr = self.mmap(&mmap_options)?; | ||||||
|  | 
 | ||||||
|  |                 if ret_addr.is_none() { | ||||||
|  |                     ret_addr = Some(mmap_addr); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             if let Some((addr, size)) = need_munmap { | ||||||
|  |                 self.munmap(addr, size).expect("never fail"); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             debug_assert!(ret_addr.is_some()); | ||||||
|  |             Ok(ret_addr.unwrap()) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn mprotect(&mut self, addr: usize, size: usize, new_perms: VMPerms) -> Result<()> { | ||||||
|  |         let protect_range = VMRange::new_with_size(addr, size)?; | ||||||
|  |         let bound = protect_range.start(); | ||||||
|  |         let mut containing_vmas = self.vmas.upper_bound_mut(Bound::Included(&bound)); | ||||||
|  |         if containing_vmas.is_null() { | ||||||
|  |             return_errno!(ENOMEM, "invalid range"); | ||||||
|  |         } | ||||||
|  |         let current_pid = current!().process().pid(); | ||||||
|  | 
 | ||||||
|  |         // If a mprotect range is not a subrange of one vma, it must be subrange of multiple connecting vmas.
 | ||||||
|  |         while !containing_vmas.is_null() | ||||||
|  |             && containing_vmas.get().unwrap().vma().start() <= protect_range.end() | ||||||
|  |         { | ||||||
|  |             let mut containing_vma = containing_vmas.get().unwrap().vma().clone(); | ||||||
|  |             if containing_vma.pid() != current_pid { | ||||||
|  |                 containing_vmas.move_next(); | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             let old_perms = containing_vma.perms(); | ||||||
|  |             if new_perms == old_perms { | ||||||
|  |                 containing_vmas.move_next(); | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             let intersection_vma = match containing_vma.intersect(&protect_range) { | ||||||
|  |                 None => { | ||||||
|  |                     containing_vmas.move_next(); | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|  |                 Some(intersection_vma) => intersection_vma, | ||||||
|  |             }; | ||||||
|  | 
 | ||||||
|  |             if intersection_vma.range() == containing_vma.range() { | ||||||
|  |                 // The whole containing_vma is mprotected
 | ||||||
|  |                 containing_vma.set_perms(new_perms); | ||||||
|  |                 VMPerms::apply_perms(&containing_vma, containing_vma.perms()); | ||||||
|  |                 warn!("containing_vma = {:?}", containing_vma); | ||||||
|  |                 containing_vmas.replace_with(VMAObj::new_vma_obj(containing_vma)); | ||||||
|  |                 containing_vmas.move_next(); | ||||||
|  |                 continue; | ||||||
|  |             } else { | ||||||
|  |                 // A subrange of containing_vma is mprotected
 | ||||||
|  |                 debug_assert!(containing_vma.is_superset_of(&intersection_vma)); | ||||||
|  |                 let mut remain_vmas = containing_vma.subtract(&intersection_vma); | ||||||
|  |                 match remain_vmas.len() { | ||||||
|  |                     2 => { | ||||||
|  |                         // The containing VMA is divided into three VMAs:
 | ||||||
|  |                         // Shrinked old VMA:    [containing_vma.start,     protect_range.start)
 | ||||||
|  |                         // New VMA:             [protect_range.start,      protect_range.end)
 | ||||||
|  |                         // Another new vma:     [protect_range.end,        containing_vma.end)
 | ||||||
|  |                         let old_end = containing_vma.end(); | ||||||
|  |                         let protect_end = protect_range.end(); | ||||||
|  | 
 | ||||||
|  |                         // Shrinked old VMA
 | ||||||
|  |                         containing_vma.set_end(protect_range.start()); | ||||||
|  | 
 | ||||||
|  |                         // New VMA
 | ||||||
|  |                         let new_vma = VMArea::inherits_file_from( | ||||||
|  |                             &containing_vma, | ||||||
|  |                             protect_range, | ||||||
|  |                             new_perms, | ||||||
|  |                             current_pid, | ||||||
|  |                         ); | ||||||
|  |                         VMPerms::apply_perms(&new_vma, new_vma.perms()); | ||||||
|  |                         let new_vma = VMAObj::new_vma_obj(new_vma); | ||||||
|  | 
 | ||||||
|  |                         // Another new VMA
 | ||||||
|  |                         let new_vma2 = { | ||||||
|  |                             let range = VMRange::new(protect_end, old_end).unwrap(); | ||||||
|  |                             let new_vma = VMArea::inherits_file_from( | ||||||
|  |                                 &containing_vma, | ||||||
|  |                                 range, | ||||||
|  |                                 old_perms, | ||||||
|  |                                 current_pid, | ||||||
|  |                             ); | ||||||
|  |                             VMAObj::new_vma_obj(new_vma) | ||||||
|  |                         }; | ||||||
|  | 
 | ||||||
|  |                         containing_vmas.replace_with(VMAObj::new_vma_obj(containing_vma)); | ||||||
|  |                         containing_vmas.insert(new_vma); | ||||||
|  |                         containing_vmas.insert(new_vma2); | ||||||
|  |                         // In this case, there is no need to check other vmas.
 | ||||||
|  |                         break; | ||||||
|  |                     } | ||||||
|  |                     1 => { | ||||||
|  |                         let remain_vma = remain_vmas.pop().unwrap(); | ||||||
|  |                         if remain_vma.start() == containing_vma.start() { | ||||||
|  |                             // mprotect right side of the vma
 | ||||||
|  |                             containing_vma.set_end(remain_vma.end()); | ||||||
|  |                         } else { | ||||||
|  |                             // mprotect left side of the vma
 | ||||||
|  |                             debug_assert!(remain_vma.end() == containing_vma.end()); | ||||||
|  |                             containing_vma.set_start(remain_vma.start()); | ||||||
|  |                         } | ||||||
|  |                         let new_vma = VMArea::inherits_file_from( | ||||||
|  |                             &containing_vma, | ||||||
|  |                             intersection_vma.range().clone(), | ||||||
|  |                             new_perms, | ||||||
|  |                             current_pid, | ||||||
|  |                         ); | ||||||
|  |                         VMPerms::apply_perms(&new_vma, new_vma.perms()); | ||||||
|  | 
 | ||||||
|  |                         containing_vmas.replace_with(VMAObj::new_vma_obj(containing_vma)); | ||||||
|  |                         containing_vmas.insert(VMAObj::new_vma_obj(new_vma)); | ||||||
|  |                         containing_vmas.move_next(); | ||||||
|  |                         continue; | ||||||
|  |                     } | ||||||
|  |                     _ => unreachable!(), | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Sync all shared, file-backed memory mappings in the given range by flushing the
 | ||||||
|  |     /// memory content to its underlying file.
 | ||||||
|  |     pub fn msync_by_range(&mut self, sync_range: &VMRange) -> Result<()> { | ||||||
|  |         if !self.range().is_superset_of(sync_range) { | ||||||
|  |             return_errno!(ENOMEM, "invalid range"); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // ?FIXME: check if sync_range covers unmapped memory
 | ||||||
|  |         for vma_obj in &self.vmas { | ||||||
|  |             let vma = match vma_obj.vma().intersect(sync_range) { | ||||||
|  |                 None => continue, | ||||||
|  |                 Some(vma) => vma, | ||||||
|  |             }; | ||||||
|  |             Self::flush_file_vma(&vma); | ||||||
|  |         } | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Sync all shared, file-backed memory mappings of the given file by flushing
 | ||||||
|  |     /// the memory content to the file.
 | ||||||
|  |     pub fn msync_by_file(&mut self, sync_file: &FileRef) { | ||||||
|  |         for vma_obj in &self.vmas { | ||||||
|  |             let is_same_file = |file: &FileRef| -> bool { Arc::ptr_eq(&file, &sync_file) }; | ||||||
|  |             Self::flush_file_vma_with_cond(&vma_obj.vma(), is_same_file); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Flush a file-backed VMA to its file. This has no effect on anonymous VMA.
 | ||||||
|  |     pub fn flush_file_vma(vma: &VMArea) { | ||||||
|  |         Self::flush_file_vma_with_cond(vma, |_| true) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Same as flush_vma, except that an extra condition on the file needs to satisfy.
 | ||||||
|  |     pub fn flush_file_vma_with_cond<F: Fn(&FileRef) -> bool>(vma: &VMArea, cond_fn: F) { | ||||||
|  |         let (file, file_offset) = match vma.writeback_file().as_ref() { | ||||||
|  |             None => return, | ||||||
|  |             Some((file_and_offset)) => file_and_offset, | ||||||
|  |         }; | ||||||
|  |         let file_writable = file | ||||||
|  |             .access_mode() | ||||||
|  |             .map(|ac| ac.writable()) | ||||||
|  |             .unwrap_or_default(); | ||||||
|  |         if !file_writable { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         if !cond_fn(file) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         file.write_at(*file_offset, unsafe { vma.as_slice() }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn find_mmap_region(&self, addr: usize) -> Result<VMRange> { | ||||||
|  |         let vma = self.vmas.upper_bound(Bound::Included(&addr)); | ||||||
|  |         if vma.is_null() { | ||||||
|  |             return_errno!(ESRCH, "no mmap regions that contains the address"); | ||||||
|  |         } | ||||||
|  |         let vma = vma.get().unwrap().vma(); | ||||||
|  |         if vma.pid() != current!().process().pid() || !vma.contains(addr) { | ||||||
|  |             return_errno!(ESRCH, "no mmap regions that contains the address"); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return Ok(vma.range().clone()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn usage_percentage(&self) -> f32 { | ||||||
|  |         let totol_size = self.range.size(); | ||||||
|  |         let mut used_size = 0; | ||||||
|  |         self.vmas | ||||||
|  |             .iter() | ||||||
|  |             .for_each(|vma_obj| used_size += vma_obj.vma().size()); | ||||||
|  | 
 | ||||||
|  |         return used_size as f32 / totol_size as f32; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Returns whether the requested range is free
 | ||||||
|  |     fn is_free_range(&self, request_range: &VMRange) -> bool { | ||||||
|  |         self.range.is_superset_of(request_range) | ||||||
|  |             && self | ||||||
|  |                 .vmas | ||||||
|  |                 .iter() | ||||||
|  |                 .any(|vma_obj| vma_obj.vma().range().is_superset_of(request_range) == true) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Drop for ChunkManager { | ||||||
|  |     fn drop(&mut self) { | ||||||
|  |         assert!(self.is_empty()); | ||||||
|  |         assert!(self.free_size == self.range.size()); | ||||||
|  |         assert!(self.free_manager.free_size() == self.range.size()); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -8,7 +8,7 @@ pub struct VMLayout { | |||||||
| 
 | 
 | ||||||
| impl VMLayout { | impl VMLayout { | ||||||
|     pub fn new(size: usize, align: usize) -> Result<VMLayout> { |     pub fn new(size: usize, align: usize) -> Result<VMLayout> { | ||||||
|         if !align.is_power_of_two() || align % PAGE_SIZE != 0 { |         if !align.is_power_of_two() { | ||||||
|             return_errno!(EINVAL, "invalid layout"); |             return_errno!(EINVAL, "invalid layout"); | ||||||
|         } |         } | ||||||
|         Ok(VMLayout { size, align }) |         Ok(VMLayout { size, align }) | ||||||
|  | |||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -31,6 +31,26 @@ impl VMPerms { | |||||||
|     pub fn is_default(&self) -> bool { |     pub fn is_default(&self) -> bool { | ||||||
|         self.bits == Self::DEFAULT.bits |         self.bits == Self::DEFAULT.bits | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     pub fn apply_perms(protect_range: &VMRange, perms: VMPerms) { | ||||||
|  |         extern "C" { | ||||||
|  |             pub fn occlum_ocall_mprotect( | ||||||
|  |                 retval: *mut i32, | ||||||
|  |                 addr: *const c_void, | ||||||
|  |                 len: usize, | ||||||
|  |                 prot: i32, | ||||||
|  |             ) -> sgx_status_t; | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         unsafe { | ||||||
|  |             let mut retval = 0; | ||||||
|  |             let addr = protect_range.start() as *const c_void; | ||||||
|  |             let len = protect_range.size(); | ||||||
|  |             let prot = perms.bits() as i32; | ||||||
|  |             let sgx_status = occlum_ocall_mprotect(&mut retval, addr, len, prot); | ||||||
|  |             assert!(sgx_status == sgx_status_t::SGX_SUCCESS && retval == 0); | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl Default for VMPerms { | impl Default for VMPerms { | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| use super::*; | use super::*; | ||||||
| 
 | 
 | ||||||
| #[derive(Clone, Copy, Default, PartialEq)] | #[derive(Clone, Copy, Default, Eq, PartialEq, Hash)] | ||||||
| pub struct VMRange { | pub struct VMRange { | ||||||
|     pub(super) start: usize, |     pub(super) start: usize, | ||||||
|     pub(super) end: usize, |     pub(super) end: usize, | ||||||
| @ -130,7 +130,7 @@ impl VMRange { | |||||||
|     pub fn intersect(&self, other: &VMRange) -> Option<VMRange> { |     pub fn intersect(&self, other: &VMRange) -> Option<VMRange> { | ||||||
|         let intersection_start = self.start().max(other.start()); |         let intersection_start = self.start().max(other.start()); | ||||||
|         let intersection_end = self.end().min(other.end()); |         let intersection_end = self.end().min(other.end()); | ||||||
|         if intersection_start > intersection_end { |         if intersection_start >= intersection_end { | ||||||
|             return None; |             return None; | ||||||
|         } |         } | ||||||
|         unsafe { |         unsafe { | ||||||
|  | |||||||
							
								
								
									
										276
									
								
								src/libos/src/vm/vm_util.rs
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										276
									
								
								src/libos/src/vm/vm_util.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,276 @@ | |||||||
|  | use super::*; | ||||||
|  | 
 | ||||||
|  | // use super::vm_area::VMArea;
 | ||||||
|  | // use super::free_space_manager::VMFreeSpaceManager;
 | ||||||
|  | use super::vm_area::*; | ||||||
|  | use super::vm_perms::VMPerms; | ||||||
|  | use std::collections::BTreeSet; | ||||||
|  | 
 | ||||||
|  | use intrusive_collections::rbtree::{Link, RBTree}; | ||||||
|  | use intrusive_collections::Bound; | ||||||
|  | use intrusive_collections::RBTreeLink; | ||||||
|  | use intrusive_collections::{intrusive_adapter, KeyAdapter}; | ||||||
|  | 
 | ||||||
|  | #[derive(Clone, Debug)] | ||||||
|  | pub enum VMInitializer { | ||||||
|  |     DoNothing(), | ||||||
|  |     FillZeros(), | ||||||
|  |     CopyFrom { | ||||||
|  |         range: VMRange, | ||||||
|  |     }, | ||||||
|  |     LoadFromFile { | ||||||
|  |         file: FileRef, | ||||||
|  |         offset: usize, | ||||||
|  |     }, | ||||||
|  |     // For file-backed mremap which may move from old range to new range and read extra bytes from file
 | ||||||
|  |     CopyOldAndReadNew { | ||||||
|  |         old_range: VMRange, | ||||||
|  |         file: FileRef, | ||||||
|  |         offset: usize, // read file from this offset
 | ||||||
|  |     }, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Default for VMInitializer { | ||||||
|  |     fn default() -> VMInitializer { | ||||||
|  |         VMInitializer::DoNothing() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl VMInitializer { | ||||||
|  |     pub fn init_slice(&self, buf: &mut [u8]) -> Result<()> { | ||||||
|  |         match self { | ||||||
|  |             VMInitializer::DoNothing() => { | ||||||
|  |                 // Do nothing
 | ||||||
|  |             } | ||||||
|  |             VMInitializer::FillZeros() => { | ||||||
|  |                 for b in buf { | ||||||
|  |                     *b = 0; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             VMInitializer::CopyFrom { range } => { | ||||||
|  |                 let src_slice = unsafe { range.as_slice() }; | ||||||
|  |                 let copy_len = min(buf.len(), src_slice.len()); | ||||||
|  |                 buf[..copy_len].copy_from_slice(&src_slice[..copy_len]); | ||||||
|  |                 for b in &mut buf[copy_len..] { | ||||||
|  |                     *b = 0; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             VMInitializer::LoadFromFile { file, offset } => { | ||||||
|  |                 // TODO: make sure that read_at does not move file cursor
 | ||||||
|  |                 let len = file | ||||||
|  |                     .read_at(*offset, buf) | ||||||
|  |                     .cause_err(|_| errno!(EIO, "failed to init memory from file"))?; | ||||||
|  |                 for b in &mut buf[len..] { | ||||||
|  |                     *b = 0; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             VMInitializer::CopyOldAndReadNew { | ||||||
|  |                 old_range, | ||||||
|  |                 file, | ||||||
|  |                 offset, | ||||||
|  |             } => { | ||||||
|  |                 // TODO: Handle old_range with non-readable subrange
 | ||||||
|  |                 let src_slice = unsafe { old_range.as_slice() }; | ||||||
|  |                 let copy_len = src_slice.len(); | ||||||
|  |                 debug_assert!(copy_len <= buf.len()); | ||||||
|  |                 let read_len = buf.len() - copy_len; | ||||||
|  |                 buf[..copy_len].copy_from_slice(&src_slice[..copy_len]); | ||||||
|  |                 let len = file | ||||||
|  |                     .read_at(*offset, &mut buf[copy_len..]) | ||||||
|  |                     .cause_err(|_| errno!(EIO, "failed to init memory from file"))?; | ||||||
|  |                 for b in &mut buf[(copy_len + len)..] { | ||||||
|  |                     *b = 0; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(Clone, Copy, Debug, PartialEq)] | ||||||
|  | pub enum VMMapAddr { | ||||||
|  |     Any,          // Free to choose any address
 | ||||||
|  |     Hint(usize),  // Prefer the address, but can use other address
 | ||||||
|  |     Need(usize),  // Need to use the address, otherwise report error
 | ||||||
|  |     Force(usize), // Force using the address by munmap first
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Default for VMMapAddr { | ||||||
|  |     fn default() -> VMMapAddr { | ||||||
|  |         VMMapAddr::Any | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(Builder, Debug)] | ||||||
|  | #[builder(pattern = "owned", build_fn(skip), no_std)] | ||||||
|  | pub struct VMMapOptions { | ||||||
|  |     size: usize, | ||||||
|  |     align: usize, | ||||||
|  |     perms: VMPerms, | ||||||
|  |     addr: VMMapAddr, | ||||||
|  |     initializer: VMInitializer, | ||||||
|  |     // The content of the VMA can be written back to a given file at a given offset
 | ||||||
|  |     writeback_file: Option<(FileRef, usize)>, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // VMMapOptionsBuilder is generated automatically, except the build function
 | ||||||
|  | impl VMMapOptionsBuilder { | ||||||
|  |     pub fn build(mut self) -> Result<VMMapOptions> { | ||||||
|  |         let size = { | ||||||
|  |             let size = self | ||||||
|  |                 .size | ||||||
|  |                 .ok_or_else(|| errno!(EINVAL, "invalid size for mmap"))?; | ||||||
|  |             if size == 0 { | ||||||
|  |                 return_errno!(EINVAL, "invalid size for mmap"); | ||||||
|  |             } | ||||||
|  |             align_up(size, PAGE_SIZE) | ||||||
|  |         }; | ||||||
|  |         let align = { | ||||||
|  |             let align = self.align.unwrap_or(PAGE_SIZE); | ||||||
|  |             if align == 0 || !align.is_power_of_two() { | ||||||
|  |                 return_errno!(EINVAL, "invalid size for mmap"); | ||||||
|  |             } | ||||||
|  |             align | ||||||
|  |         }; | ||||||
|  |         let perms = self | ||||||
|  |             .perms | ||||||
|  |             .ok_or_else(|| errno!(EINVAL, "perms must be given"))?; | ||||||
|  |         let addr = { | ||||||
|  |             let addr = self.addr.unwrap_or_default(); | ||||||
|  |             match addr { | ||||||
|  |                 // TODO: check addr + size overflow
 | ||||||
|  |                 VMMapAddr::Any => VMMapAddr::Any, | ||||||
|  |                 VMMapAddr::Hint(addr) => { | ||||||
|  |                     let addr = align_down(addr, PAGE_SIZE); | ||||||
|  |                     VMMapAddr::Hint(addr) | ||||||
|  |                 } | ||||||
|  |                 VMMapAddr::Need(addr_) | VMMapAddr::Force(addr_) => { | ||||||
|  |                     if addr_ % align != 0 { | ||||||
|  |                         return_errno!(EINVAL, "unaligned addr for fixed mmap"); | ||||||
|  |                     } | ||||||
|  |                     addr | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  |         let initializer = match self.initializer.as_ref() { | ||||||
|  |             Some(initializer) => initializer.clone(), | ||||||
|  |             None => VMInitializer::default(), | ||||||
|  |         }; | ||||||
|  |         let writeback_file = self.writeback_file.take().unwrap_or_default(); | ||||||
|  |         Ok(VMMapOptions { | ||||||
|  |             size, | ||||||
|  |             align, | ||||||
|  |             perms, | ||||||
|  |             addr, | ||||||
|  |             initializer, | ||||||
|  |             writeback_file, | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl VMMapOptions { | ||||||
|  |     pub fn size(&self) -> &usize { | ||||||
|  |         &self.size | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn addr(&self) -> &VMMapAddr { | ||||||
|  |         &self.addr | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn perms(&self) -> &VMPerms { | ||||||
|  |         &self.perms | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn align(&self) -> &usize { | ||||||
|  |         &self.align | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn initializer(&self) -> &VMInitializer { | ||||||
|  |         &self.initializer | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn writeback_file(&self) -> &Option<(FileRef, usize)> { | ||||||
|  |         &self.writeback_file | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(Clone, Copy, PartialEq)] | ||||||
|  | pub enum SizeType { | ||||||
|  |     Same, | ||||||
|  |     Shrinking, | ||||||
|  |     Growing, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl SizeType { | ||||||
|  |     pub fn new(old_size: &usize, new_size: &usize) -> Self { | ||||||
|  |         if new_size == old_size { | ||||||
|  |             SizeType::Same | ||||||
|  |         } else if new_size < old_size { | ||||||
|  |             SizeType::Shrinking | ||||||
|  |         } else { | ||||||
|  |             SizeType::Growing | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(Debug)] | ||||||
|  | pub struct VMRemapOptions { | ||||||
|  |     old_addr: usize, | ||||||
|  |     old_size: usize, | ||||||
|  |     new_size: usize, | ||||||
|  |     flags: MRemapFlags, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl VMRemapOptions { | ||||||
|  |     pub fn new( | ||||||
|  |         old_addr: usize, | ||||||
|  |         old_size: usize, | ||||||
|  |         new_size: usize, | ||||||
|  |         flags: MRemapFlags, | ||||||
|  |     ) -> Result<Self> { | ||||||
|  |         let old_addr = if old_addr % PAGE_SIZE != 0 { | ||||||
|  |             return_errno!(EINVAL, "unaligned old address"); | ||||||
|  |         } else { | ||||||
|  |             old_addr | ||||||
|  |         }; | ||||||
|  |         let old_size = if old_size == 0 { | ||||||
|  |             // TODO: support old_size is zero for shareable mapping
 | ||||||
|  |             warn!("do not support old_size is zero"); | ||||||
|  |             return_errno!(EINVAL, "invalid old size"); | ||||||
|  |         } else { | ||||||
|  |             align_up(old_size, PAGE_SIZE) | ||||||
|  |         }; | ||||||
|  |         if let Some(new_addr) = flags.new_addr() { | ||||||
|  |             if new_addr % PAGE_SIZE != 0 { | ||||||
|  |                 return_errno!(EINVAL, "unaligned new address"); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         let new_size = if new_size == 0 { | ||||||
|  |             return_errno!(EINVAL, "invalid new size"); | ||||||
|  |         } else { | ||||||
|  |             align_up(new_size, PAGE_SIZE) | ||||||
|  |         }; | ||||||
|  |         Ok(Self { | ||||||
|  |             old_addr, | ||||||
|  |             old_size, | ||||||
|  |             new_size, | ||||||
|  |             flags, | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn old_addr(&self) -> usize { | ||||||
|  |         self.old_addr | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn old_size(&self) -> usize { | ||||||
|  |         self.old_size | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn new_size(&self) -> usize { | ||||||
|  |         self.new_size | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pub fn flags(&self) -> MRemapFlags { | ||||||
|  |         self.flags | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -239,7 +239,6 @@ int occlum_pal_destroy(void) { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     int ret = 0; |     int ret = 0; | ||||||
| 
 |  | ||||||
|     if (pal_interrupt_thread_stop() < 0) { |     if (pal_interrupt_thread_stop() < 0) { | ||||||
|         ret = -1; |         ret = -1; | ||||||
|         PAL_WARN("Cannot stop the interrupt thread: %s", errno2str(errno)); |         PAL_WARN("Cannot stop the interrupt thread: %s", errno2str(errno)); | ||||||
|  | |||||||
| @ -47,6 +47,10 @@ static int get_a_valid_range_of_hints(size_t *hint_begin, size_t *hint_end) { | |||||||
|     if (big_buf == MAP_FAILED) { |     if (big_buf == MAP_FAILED) { | ||||||
|         THROW_ERROR("mmap failed"); |         THROW_ERROR("mmap failed"); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     // Check if munmap will clean the range
 | ||||||
|  |     memset(big_buf, 0xff, big_buf_len); | ||||||
|  | 
 | ||||||
|     int ret = munmap(big_buf, big_buf_len); |     int ret = munmap(big_buf, big_buf_len); | ||||||
|     if (ret < 0) { |     if (ret < 0) { | ||||||
|         THROW_ERROR("munmap failed"); |         THROW_ERROR("munmap failed"); | ||||||
| @ -1038,6 +1042,47 @@ int test_mprotect_with_non_page_aligned_size() { | |||||||
|     *(char *)buf = 1; |     *(char *)buf = 1; | ||||||
|     *(char *)(buf  + PAGE_SIZE) = 1; |     *(char *)(buf  + PAGE_SIZE) = 1; | ||||||
| 
 | 
 | ||||||
|  |     ret = munmap(buf, PAGE_SIZE * 2); | ||||||
|  |     if (ret < 0) { | ||||||
|  |         THROW_ERROR("munmap failed"); | ||||||
|  |     } | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int test_mprotect_multiple_vmas() { | ||||||
|  |     // Create multiple VMA with PROT_NONE
 | ||||||
|  |     int flags = MAP_PRIVATE | MAP_ANONYMOUS; | ||||||
|  |     void *buf_a = mmap((void *)HINT_BEGIN, PAGE_SIZE * 2, PROT_NONE, flags, -1, 0); | ||||||
|  |     if (buf_a == MAP_FAILED || buf_a != (void *)HINT_BEGIN) { | ||||||
|  |         THROW_ERROR("mmap failed"); | ||||||
|  |     } | ||||||
|  |     void *buf_b = mmap((void *)(HINT_BEGIN + 2 * PAGE_SIZE), PAGE_SIZE, PROT_NONE, flags, -1, | ||||||
|  |                        0); | ||||||
|  |     if (buf_b == MAP_FAILED || buf_b != (void *)(HINT_BEGIN + 2 * PAGE_SIZE)) { | ||||||
|  |         THROW_ERROR("mmap failed"); | ||||||
|  |     } | ||||||
|  |     void *buf_c = mmap((void *)(HINT_BEGIN + 3 * PAGE_SIZE), PAGE_SIZE * 2, PROT_NONE, flags, | ||||||
|  |                        -1, 0); | ||||||
|  |     if (buf_c == MAP_FAILED || buf_c != (void *)(HINT_BEGIN + 3 * PAGE_SIZE)) { | ||||||
|  |         THROW_ERROR("mmap failed"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Set a part of the ranges to read-write
 | ||||||
|  |     int ret = mprotect(buf_a + PAGE_SIZE, 3 * PAGE_SIZE, PROT_READ | PROT_WRITE); | ||||||
|  |     if (ret < 0) { | ||||||
|  |         THROW_ERROR("mprotect multiple vmas failed"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Check if these ranges are writable
 | ||||||
|  |     *(char *)(buf_a + PAGE_SIZE) = 1; | ||||||
|  |     *(char *)(buf_b) = 1; | ||||||
|  |     *(char *)(buf_c) = 1; | ||||||
|  | 
 | ||||||
|  |     ret = munmap(buf_a, PAGE_SIZE * 5); | ||||||
|  |     if (ret < 0) { | ||||||
|  |         THROW_ERROR("munmap multiple vmas failed"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     return 0; |     return 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -1231,11 +1276,13 @@ static test_case_t test_cases[] = { | |||||||
|     TEST_CASE(test_munmap_with_null_addr), |     TEST_CASE(test_munmap_with_null_addr), | ||||||
|     TEST_CASE(test_munmap_with_zero_len), |     TEST_CASE(test_munmap_with_zero_len), | ||||||
|     TEST_CASE(test_munmap_with_non_page_aligned_len), |     TEST_CASE(test_munmap_with_non_page_aligned_len), | ||||||
|  | #ifdef MREMAP_SUPPORTED | ||||||
|     TEST_CASE(test_mremap), |     TEST_CASE(test_mremap), | ||||||
|     TEST_CASE(test_mremap_subrange), |     TEST_CASE(test_mremap_subrange), | ||||||
|     TEST_CASE(test_mremap_with_fixed_addr), |     TEST_CASE(test_mremap_with_fixed_addr), | ||||||
|     TEST_CASE(test_file_backed_mremap), |     TEST_CASE(test_file_backed_mremap), | ||||||
|     TEST_CASE(test_file_backed_mremap_mem_may_move), |     TEST_CASE(test_file_backed_mremap_mem_may_move), | ||||||
|  | #endif | ||||||
|     TEST_CASE(test_mprotect_once), |     TEST_CASE(test_mprotect_once), | ||||||
|     TEST_CASE(test_mprotect_twice), |     TEST_CASE(test_mprotect_twice), | ||||||
|     TEST_CASE(test_mprotect_triple), |     TEST_CASE(test_mprotect_triple), | ||||||
| @ -1243,6 +1290,7 @@ static test_case_t test_cases[] = { | |||||||
|     TEST_CASE(test_mprotect_with_invalid_addr), |     TEST_CASE(test_mprotect_with_invalid_addr), | ||||||
|     TEST_CASE(test_mprotect_with_invalid_prot), |     TEST_CASE(test_mprotect_with_invalid_prot), | ||||||
|     TEST_CASE(test_mprotect_with_non_page_aligned_size), |     TEST_CASE(test_mprotect_with_non_page_aligned_size), | ||||||
|  |     TEST_CASE(test_mprotect_multiple_vmas), | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| int main() { | int main() { | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user