in src/enclave_proc/resource_manager.rs [351:449]
fn allocate(&mut self) -> NitroCliResult<&Vec<MemoryRegion>> {
let mut allocated_pages = BTreeMap::<u64, u32>::new();
let mut needed_mem = self.requested_mem as i64;
let mut split_index = 0;
info!(
"Allocating memory regions to hold {} bytes.",
self.requested_mem
);
// Always allocate larger pages first, to reduce fragmentation and page count.
// Once an allocation of a given page size fails, proceed to the next smaller
// page size and retry.
for page_info in HUGE_PAGE_MAP.iter() {
while needed_mem >= page_info.1 as i64 {
match MemoryRegion::new(page_info.0) {
Ok(value) => {
needed_mem -= value.mem_size as i64;
self.mem_regions.push(value);
}
Err(_) => break,
}
}
}
// If the user requested exactly the amount of memory that was reserved earlier,
// we should be left with no more memory that needs allocation. But if the user
// requests a smaller amount, we must then aim to reduce waster memory from
// larger-page allocations (Ex: if we have 1 x 1 GB page and 1 x 2 MB page, but
// we want to allocate only 512 MB, the above algorithm will have allocated only
// the 2 MB page, since the 1 GB page was too large for what was needed; we now
// need to allocate in increasing order of page size in order to reduce wastage).
if needed_mem > 0 {
for page_info in HUGE_PAGE_MAP.iter().rev() {
while needed_mem > 0 {
match MemoryRegion::new(page_info.0) {
Ok(value) => {
needed_mem -= value.mem_size as i64;
self.mem_regions.push(value);
}
Err(_) => break,
}
}
}
}
// If we still have memory to allocate, it means we have insufficient resources.
if needed_mem > 0 {
return Err(new_nitro_cli_failure!(
&format!(
"Failed to allocate entire memory ({} MB remained)",
needed_mem >> 20
),
NitroCliErrorEnum::InsufficientMemoryAvailable
)
.add_info(vec!["memory", &(self.requested_mem >> 20).to_string()]));
}
// At this point, we may have allocated more than we need, so we release all
// regions we no longer need, starting with the smallest ones.
self.mem_regions
.sort_by(|reg1, reg2| reg2.mem_size.cmp(®1.mem_size));
needed_mem = self.requested_mem as i64;
for region in self.mem_regions.iter() {
if needed_mem <= 0 {
break;
}
needed_mem -= region.mem_size as i64;
split_index += 1
}
// The regions that we no longer need are freed automatically on draining, since
// MemRegion implements Drop.
self.mem_regions.drain(split_index..);
// Generate a summary of the allocated memory.
for region in self.mem_regions.iter() {
if let Some(page_count) = allocated_pages.get_mut(®ion.mem_size) {
*page_count += 1;
} else {
allocated_pages.insert(region.mem_size, 1);
}
}
info!(
"Allocated {} region(s): {}",
self.mem_regions.len(),
allocated_pages
.iter()
.map(|(size, count)| format!("{} page(s) of {} MB", count, size >> 20))
.collect::<Vec<String>>()
.join(", ")
);
Ok(&self.mem_regions)
}