235 lines
8.1 KiB
Rust
235 lines
8.1 KiB
Rust
// 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::*;
|
|
|
|
use super::vm_util::VMMapAddr;
|
|
|
|
use std::cmp::Ordering;
|
|
|
|
const 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())
|
|
}
|
|
|
|
pub fn find_free_range_internal(
|
|
&mut self,
|
|
size: usize,
|
|
align: usize,
|
|
addr: VMMapAddr,
|
|
) -> Result<VMRange> {
|
|
// Record the minimal free range that satisfies the constraints
|
|
let mut result_free_range: Option<VMRange> = None;
|
|
let mut result_idx: Option<usize> = None;
|
|
let mut free_list = &mut self.free_manager;
|
|
|
|
debug!(
|
|
"try to find free range, current free list = {:?}",
|
|
free_list
|
|
);
|
|
|
|
match addr {
|
|
VMMapAddr::Any => {}
|
|
VMMapAddr::Hint(addr) | VMMapAddr::Need(addr) | VMMapAddr::Force(addr) => {
|
|
if addr % align != 0 {
|
|
return_errno!(EINVAL, "user-provided address is not well-aligned");
|
|
}
|
|
}
|
|
}
|
|
|
|
for (idx, free_range) in free_list.iter().enumerate() {
|
|
let mut free_range = {
|
|
if free_range.size() < size {
|
|
continue;
|
|
}
|
|
free_range.clone()
|
|
};
|
|
|
|
match addr {
|
|
// Want a minimal free_range
|
|
VMMapAddr::Any => {
|
|
let start = align_up(free_range.start(), align);
|
|
let end = start + size;
|
|
if end > free_range.end() {
|
|
continue;
|
|
}
|
|
free_range.start = start;
|
|
free_range.end = end;
|
|
}
|
|
// Prefer to have free_range.start == addr
|
|
VMMapAddr::Hint(addr) => {
|
|
if free_range.contains(addr) && free_range.end() - addr >= size {
|
|
free_range.start = addr;
|
|
free_range.end = addr + size;
|
|
} else {
|
|
// Hint failure, record the result but keep iterating.
|
|
if result_free_range == None
|
|
|| result_free_range.as_ref().unwrap().size() > free_range.size()
|
|
{
|
|
let start = align_up(free_range.start(), align);
|
|
let end = start + size;
|
|
if end > free_range.end() {
|
|
continue;
|
|
}
|
|
free_range.start = start;
|
|
free_range.end = end;
|
|
|
|
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 = result_free_range.unwrap();
|
|
|
|
self.free_list_update_range(index, result_free_range);
|
|
debug!(
|
|
"result free range = {:?}. After finding free range, free list = {:?}",
|
|
result_free_range, 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<()> {
|
|
debug!("add back dirty range: {:?}", dirty_range);
|
|
let mut free_list = &mut self.free_manager;
|
|
|
|
// If the free list is empty, insert the dirty range and it's done.
|
|
if free_list.is_empty() {
|
|
free_list.push(*dirty_range);
|
|
return Ok(());
|
|
}
|
|
|
|
let dirty_range_start = dirty_range.start();
|
|
let dirty_range_end = dirty_range.end();
|
|
|
|
// If the dirty range is before the first free range or after the last free range
|
|
let head_range = &mut free_list[0];
|
|
match dirty_range_end.cmp(&head_range.start()) {
|
|
Ordering::Equal => {
|
|
head_range.set_start(dirty_range_start);
|
|
return Ok(());
|
|
}
|
|
Ordering::Less => {
|
|
free_list.insert(0, *dirty_range);
|
|
return Ok(());
|
|
}
|
|
_ => (),
|
|
}
|
|
|
|
let tail_range = free_list.last_mut().unwrap();
|
|
match dirty_range_start.cmp(&tail_range.end()) {
|
|
Ordering::Equal => {
|
|
tail_range.set_end(dirty_range_end);
|
|
return Ok(());
|
|
}
|
|
Ordering::Greater => {
|
|
free_list.push(*dirty_range);
|
|
return Ok(());
|
|
}
|
|
_ => (),
|
|
}
|
|
|
|
// The dirty range must be between some two ranges.
|
|
debug_assert!(free_list.len() >= 2);
|
|
let mut idx = 0;
|
|
|
|
while idx < free_list.len() - 1 {
|
|
let left_range = free_list[idx];
|
|
let right_range = free_list[idx + 1];
|
|
|
|
if left_range.end() <= dirty_range_start && dirty_range_end <= right_range.start() {
|
|
match (
|
|
dirty_range.is_contiguous_with(&left_range),
|
|
dirty_range.is_contiguous_with(&right_range),
|
|
) {
|
|
(true, true) => {
|
|
let left_range = &mut free_list[idx];
|
|
left_range.set_end(right_range.end());
|
|
free_list.remove(idx + 1);
|
|
}
|
|
(true, false) => {
|
|
let left_range = &mut free_list[idx];
|
|
left_range.set_end(dirty_range_end);
|
|
}
|
|
(false, true) => {
|
|
let right_range = &mut free_list[idx + 1];
|
|
right_range.set_start(dirty_range_start);
|
|
}
|
|
(false, false) => {
|
|
free_list.insert(idx + 1, *dirty_range);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
idx += 1;
|
|
}
|
|
return Ok(());
|
|
}
|
|
|
|
pub fn is_free_range(&self, request_range: &VMRange) -> bool {
|
|
self.free_manager
|
|
.iter()
|
|
.any(|free_range| free_range.is_superset_of(request_range))
|
|
}
|
|
}
|