Rewrite mremap system call
This rewrite serves three purposes: 1. Fix some subtle bugs in the old implementation; 2. Implement mremap using mmap and munmap so that mremap can automatically enjoy new features (e.g., mprotect and memory permissions) once mmap and munmap support the feature. 3. Write down the invariants hold by VMManager explictly so that the correctness of the new implementation can be reason more easily.
This commit is contained in:
parent
28440b0d69
commit
70d3991ff5
@ -740,8 +740,8 @@ fn do_mremap(
|
|||||||
flags: i32,
|
flags: i32,
|
||||||
new_addr: usize,
|
new_addr: usize,
|
||||||
) -> Result<isize> {
|
) -> Result<isize> {
|
||||||
let flags = MRemapFlags::from_u32(flags as u32)?;
|
let flags = MRemapFlags::from_raw(flags as u32, new_addr)?;
|
||||||
let addr = vm::do_mremap(old_addr, old_size, new_size, flags, new_addr)?;
|
let addr = vm::do_mremap(old_addr, old_size, new_size, flags)?;
|
||||||
Ok(addr as isize)
|
Ok(addr as isize)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,15 +52,14 @@ pub fn do_mremap(
|
|||||||
old_size: usize,
|
old_size: usize,
|
||||||
new_size: usize,
|
new_size: usize,
|
||||||
flags: MRemapFlags,
|
flags: MRemapFlags,
|
||||||
new_addr: usize,
|
|
||||||
) -> Result<usize> {
|
) -> Result<usize> {
|
||||||
debug!(
|
debug!(
|
||||||
"mremap: old_addr: {:#x}, old_size: {:#x}, new_size: {:#x}, flags: {:?}, new_addr: {:#x}",
|
"mremap: old_addr: {:#x}, old_size: {:#x}, new_size: {:#x}, flags: {:?}",
|
||||||
old_addr, old_size, new_size, flags, new_addr
|
old_addr, old_size, new_size, flags
|
||||||
);
|
);
|
||||||
let current = current!();
|
let current = current!();
|
||||||
let mut current_vm = current.vm().lock().unwrap();
|
let mut current_vm = current.vm().lock().unwrap();
|
||||||
current_vm.mremap(old_addr, old_size, new_size, flags, new_addr)
|
current_vm.mremap(old_addr, old_size, new_size, flags)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn do_brk(addr: usize) -> Result<usize> {
|
pub fn do_brk(addr: usize) -> Result<usize> {
|
||||||
|
@ -290,7 +290,7 @@ impl ProcessVM {
|
|||||||
if !self.process_range.range().contains(addr) {
|
if !self.process_range.range().contains(addr) {
|
||||||
return_errno!(EINVAL, "Beyond valid memory range");
|
return_errno!(EINVAL, "Beyond valid memory range");
|
||||||
}
|
}
|
||||||
VMMapAddr::Fixed(addr)
|
VMMapAddr::Force(addr)
|
||||||
} else {
|
} else {
|
||||||
if addr == 0 {
|
if addr == 0 {
|
||||||
VMMapAddr::Any
|
VMMapAddr::Any
|
||||||
@ -325,18 +325,14 @@ impl ProcessVM {
|
|||||||
old_size: usize,
|
old_size: usize,
|
||||||
new_size: usize,
|
new_size: usize,
|
||||||
flags: MRemapFlags,
|
flags: MRemapFlags,
|
||||||
new_addr: usize,
|
|
||||||
) -> Result<usize> {
|
) -> Result<usize> {
|
||||||
let new_addr_option = if flags.contains(MRemapFlags::MREMAP_FIXED) {
|
if let Some(new_addr) = flags.new_addr() {
|
||||||
if !self.process_range.range().contains(new_addr) {
|
if !self.process_range.range().contains(new_addr) {
|
||||||
return_errno!(EINVAL, "new_addr is beyond valid memory range");
|
return_errno!(EINVAL, "new_addr is beyond valid memory range");
|
||||||
}
|
}
|
||||||
Some(new_addr)
|
}
|
||||||
} else {
|
|
||||||
None
|
let mremap_option = VMRemapOptions::new(old_addr, old_size, new_size, flags)?;
|
||||||
};
|
|
||||||
let mremap_option =
|
|
||||||
VMRemapOptions::new(old_addr, old_size, new_addr_option, new_size, flags)?;
|
|
||||||
self.mmap_manager.mremap(&mremap_option)
|
self.mmap_manager.mremap(&mremap_option)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -379,23 +375,41 @@ impl MMapFlags {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bitflags! {
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
pub struct MRemapFlags : u32 {
|
pub enum MRemapFlags {
|
||||||
const MREMAP_MAYMOVE = 1;
|
None,
|
||||||
const MREMAP_FIXED = 2;
|
MayMove,
|
||||||
}
|
FixedAddr(usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MRemapFlags {
|
impl MRemapFlags {
|
||||||
pub fn from_u32(bits: u32) -> Result<MRemapFlags> {
|
pub fn from_raw(raw_flags: u32, new_addr: usize) -> Result<Self> {
|
||||||
let flags =
|
const MREMAP_NONE: u32 = 0;
|
||||||
MRemapFlags::from_bits(bits).ok_or_else(|| errno!(EINVAL, "unknown mremap flags"))?;
|
const MREMAP_MAYMOVE: u32 = 1;
|
||||||
if flags.contains(MRemapFlags::MREMAP_FIXED) && !flags.contains(MRemapFlags::MREMAP_MAYMOVE)
|
const MREMAP_FIXED: u32 = 3;
|
||||||
{
|
|
||||||
return_errno!(EINVAL, "MREMAP_FIXED was specified without MREMAP_MAYMOVE");
|
#[deny(unreachable_patterns)]
|
||||||
}
|
let flags = match raw_flags {
|
||||||
|
MREMAP_NONE => Self::None,
|
||||||
|
MREMAP_MAYMOVE => Self::MayMove,
|
||||||
|
MREMAP_FIXED => Self::FixedAddr(new_addr),
|
||||||
|
_ => return_errno!(EINVAL, "unsupported flags"),
|
||||||
|
};
|
||||||
Ok(flags)
|
Ok(flags)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn new_addr(&self) -> Option<usize> {
|
||||||
|
match self {
|
||||||
|
MRemapFlags::FixedAddr(new_addr) => Some(*new_addr),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for MRemapFlags {
|
||||||
|
fn default() -> Self {
|
||||||
|
MRemapFlags::None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
|
@ -4,6 +4,7 @@ use super::*;
|
|||||||
pub enum VMInitializer {
|
pub enum VMInitializer {
|
||||||
DoNothing(),
|
DoNothing(),
|
||||||
FillZeros(),
|
FillZeros(),
|
||||||
|
CopyFrom { range: VMRange },
|
||||||
LoadFromFile { file: FileRef, offset: usize },
|
LoadFromFile { file: FileRef, offset: usize },
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,6 +25,14 @@ impl VMInitializer {
|
|||||||
*b = 0;
|
*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 } => {
|
VMInitializer::LoadFromFile { file, offset } => {
|
||||||
// TODO: make sure that read_at does not move file cursor
|
// TODO: make sure that read_at does not move file cursor
|
||||||
let len = file
|
let len = file
|
||||||
@ -41,8 +50,9 @@ impl VMInitializer {
|
|||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum VMMapAddr {
|
pub enum VMMapAddr {
|
||||||
Any, // Free to choose any address
|
Any, // Free to choose any address
|
||||||
Hint(usize), // Prefer the given address
|
Hint(usize), // Prefer the address, but can use other address
|
||||||
Fixed(usize), // Must be the given address
|
Need(usize), // Need to use the address, otherwise report error
|
||||||
|
Force(usize), // Force using the address by munmap first
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for VMMapAddr {
|
impl Default for VMMapAddr {
|
||||||
@ -88,11 +98,11 @@ impl VMMapOptionsBuilder {
|
|||||||
let addr = align_down(addr, PAGE_SIZE);
|
let addr = align_down(addr, PAGE_SIZE);
|
||||||
VMMapAddr::Hint(addr)
|
VMMapAddr::Hint(addr)
|
||||||
}
|
}
|
||||||
VMMapAddr::Fixed(addr) => {
|
VMMapAddr::Need(addr_) | VMMapAddr::Force(addr_) => {
|
||||||
if addr % align != 0 {
|
if addr_ % align != 0 {
|
||||||
return_errno!(EINVAL, "unaligned addr for fixed mmap");
|
return_errno!(EINVAL, "unaligned addr for fixed mmap");
|
||||||
}
|
}
|
||||||
VMMapAddr::Fixed(addr)
|
addr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -127,7 +137,6 @@ impl VMMapOptions {
|
|||||||
pub struct VMRemapOptions {
|
pub struct VMRemapOptions {
|
||||||
old_addr: usize,
|
old_addr: usize,
|
||||||
old_size: usize,
|
old_size: usize,
|
||||||
new_addr: Option<usize>,
|
|
||||||
new_size: usize,
|
new_size: usize,
|
||||||
flags: MRemapFlags,
|
flags: MRemapFlags,
|
||||||
}
|
}
|
||||||
@ -136,7 +145,6 @@ impl VMRemapOptions {
|
|||||||
pub fn new(
|
pub fn new(
|
||||||
old_addr: usize,
|
old_addr: usize,
|
||||||
old_size: usize,
|
old_size: usize,
|
||||||
new_addr: Option<usize>,
|
|
||||||
new_size: usize,
|
new_size: usize,
|
||||||
flags: MRemapFlags,
|
flags: MRemapFlags,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
@ -152,14 +160,11 @@ impl VMRemapOptions {
|
|||||||
} else {
|
} else {
|
||||||
align_up(old_size, PAGE_SIZE)
|
align_up(old_size, PAGE_SIZE)
|
||||||
};
|
};
|
||||||
let new_addr = {
|
if let Some(new_addr) = flags.new_addr() {
|
||||||
if let Some(addr) = new_addr {
|
if new_addr % PAGE_SIZE != 0 {
|
||||||
if addr % PAGE_SIZE != 0 {
|
|
||||||
return_errno!(EINVAL, "unaligned new address");
|
return_errno!(EINVAL, "unaligned new address");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
new_addr
|
|
||||||
};
|
|
||||||
let new_size = if new_size == 0 {
|
let new_size = if new_size == 0 {
|
||||||
return_errno!(EINVAL, "invalid new size");
|
return_errno!(EINVAL, "invalid new size");
|
||||||
} else {
|
} else {
|
||||||
@ -168,7 +173,6 @@ impl VMRemapOptions {
|
|||||||
Ok(Self {
|
Ok(Self {
|
||||||
old_addr,
|
old_addr,
|
||||||
old_size,
|
old_size,
|
||||||
new_addr,
|
|
||||||
new_size,
|
new_size,
|
||||||
flags,
|
flags,
|
||||||
})
|
})
|
||||||
@ -186,15 +190,46 @@ impl VMRemapOptions {
|
|||||||
self.new_size
|
self.new_size
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_addr(&self) -> Option<usize> {
|
|
||||||
self.new_addr
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn flags(&self) -> MRemapFlags {
|
pub fn flags(&self) -> MRemapFlags {
|
||||||
self.flags
|
self.flags
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Memory manager.
|
||||||
|
///
|
||||||
|
/// VMManager provides useful memory management APIs such as mmap, munmap, mremap, etc.
|
||||||
|
///
|
||||||
|
/// # Invariants
|
||||||
|
///
|
||||||
|
/// Behind the scene, VMManager maintains a list of VMRange that have been allocated.
|
||||||
|
/// (denoted as `self.sub_ranges`). To reason about the correctness of VMManager, we give
|
||||||
|
/// the set of invariants hold by VMManager.
|
||||||
|
///
|
||||||
|
/// 1. The rule of sentry:
|
||||||
|
/// ```
|
||||||
|
/// self.range.begin() == self.sub_ranges[0].start() == self.sub_ranges[0].end()
|
||||||
|
/// ```
|
||||||
|
/// and
|
||||||
|
/// ```
|
||||||
|
/// self.range.end() == self.sub_ranges[N-1].start() == self.sub_ranges[N-1].end()
|
||||||
|
/// ```
|
||||||
|
/// where `N = self.sub_ranges.len()`.
|
||||||
|
///
|
||||||
|
/// 2. The rule of non-emptyness:
|
||||||
|
/// ```
|
||||||
|
/// self.sub_ranges[i].size() > 0, for 1 <= i < self.sub_ranges.len() - 1
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// 3. The rule of ordering:
|
||||||
|
/// ```
|
||||||
|
/// self.sub_ranges[i].end() <= self.sub_ranges[i+1].start() for 0 <= i < self.sub_ranges.len() - 1
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// 4. The rule of non-mergablility:
|
||||||
|
/// ```
|
||||||
|
/// self.sub_ranges[i].end() != self.sub_ranges[i+1].start() for 1 <= i < self.sub_ranges.len() - 2
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct VMManager {
|
pub struct VMManager {
|
||||||
range: VMRange,
|
range: VMRange,
|
||||||
@ -223,7 +258,7 @@ impl VMManager {
|
|||||||
let addr = *options.addr();
|
let addr = *options.addr();
|
||||||
let size = *options.size();
|
let size = *options.size();
|
||||||
|
|
||||||
if let VMMapAddr::Fixed(addr) = addr {
|
if let VMMapAddr::Force(addr) = addr {
|
||||||
self.munmap(addr, size)?;
|
self.munmap(addr, size)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -238,82 +273,12 @@ impl VMManager {
|
|||||||
options.initializer.init_slice(buf)?;
|
options.initializer.init_slice(buf)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// After initializing, we can safely add the new subrange
|
// After initializing, we can safely insert the new subrange
|
||||||
self.sub_ranges.insert(insert_idx, new_subrange);
|
self.insert_new_subrange(insert_idx, new_subrange);
|
||||||
|
|
||||||
Ok(new_subrange_addr)
|
Ok(new_subrange_addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mremap(&mut self, options: &VMRemapOptions) -> Result<usize> {
|
|
||||||
let old_addr = options.old_addr();
|
|
||||||
let old_size = options.old_size();
|
|
||||||
let new_size = options.new_size();
|
|
||||||
let (vm_subrange, idx) = {
|
|
||||||
let idx = self.find_mmap_region_idx(old_addr)?;
|
|
||||||
let vm_subrange = self.sub_ranges[idx];
|
|
||||||
if (vm_subrange.end() - old_addr < old_size) {
|
|
||||||
// Across the vm range
|
|
||||||
return_errno!(EFAULT, "can not remap across vm range");
|
|
||||||
} else if (vm_subrange.end() - old_addr == old_size) {
|
|
||||||
// Exactly the vm range
|
|
||||||
(vm_subrange, idx)
|
|
||||||
} else {
|
|
||||||
// Part of the vm range
|
|
||||||
let old_subrange = VMRange::new(old_addr, old_addr + old_size)?;
|
|
||||||
let (subranges, offset) = {
|
|
||||||
let mut subranges = vm_subrange.subtract(&old_subrange);
|
|
||||||
let idx = subranges
|
|
||||||
.iter()
|
|
||||||
.position(|subrange| old_subrange.start() < subrange.start())
|
|
||||||
.unwrap_or_else(|| subranges.len());
|
|
||||||
subranges.insert(idx, old_subrange);
|
|
||||||
(subranges, idx)
|
|
||||||
};
|
|
||||||
self.sub_ranges.splice(idx..=idx, subranges.iter().cloned());
|
|
||||||
(old_subrange, idx + offset)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// Remap with a fixed new_addr, move it to new_addr
|
|
||||||
if let Some(new_addr) = options.new_addr() {
|
|
||||||
let new_subrange = VMRange::new(new_addr, new_addr + new_size)?;
|
|
||||||
if vm_subrange.overlap_with(&new_subrange) {
|
|
||||||
return_errno!(EINVAL, "old/new vm range overlap");
|
|
||||||
}
|
|
||||||
let new_addr = VMMapAddr::Fixed(new_addr);
|
|
||||||
let (insert_idx, free_subrange) = self.find_free_subrange(new_size, new_addr)?;
|
|
||||||
let new_subrange = self.alloc_subrange_from(new_size, new_addr, &free_subrange);
|
|
||||||
return self.move_mmap_region(&vm_subrange, (insert_idx, &new_subrange));
|
|
||||||
}
|
|
||||||
// Remap without a fixed new_addr
|
|
||||||
if old_size > new_size {
|
|
||||||
// Shrink the mmap range, just unmap the useless range
|
|
||||||
self.munmap(old_addr + new_size, old_size - new_size)?;
|
|
||||||
Ok(old_addr)
|
|
||||||
} else if old_size == new_size {
|
|
||||||
// Same size, do nothing
|
|
||||||
Ok(old_addr)
|
|
||||||
} else {
|
|
||||||
// Need to expand the mmap range, check if we can expand it
|
|
||||||
if let Some(next_subrange) = self.sub_ranges.get(idx + 1) {
|
|
||||||
let expand_size = new_size - old_size;
|
|
||||||
if next_subrange.start() - vm_subrange.end() >= expand_size {
|
|
||||||
// Memory between subranges is enough, resize it
|
|
||||||
let vm_subrange = self.sub_ranges.get_mut(idx).unwrap();
|
|
||||||
vm_subrange.resize(new_size);
|
|
||||||
return Ok(vm_subrange.start());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Not enough memory to expand, must move it to a new place
|
|
||||||
if !options.flags().contains(MRemapFlags::MREMAP_MAYMOVE) {
|
|
||||||
return_errno!(ENOMEM, "not enough memory to expand");
|
|
||||||
}
|
|
||||||
let new_addr = VMMapAddr::Any;
|
|
||||||
let (insert_idx, free_subrange) = self.find_free_subrange(new_size, new_addr)?;
|
|
||||||
let new_subrange = self.alloc_subrange_from(new_size, new_addr, &free_subrange);
|
|
||||||
self.move_mmap_region(&vm_subrange, (insert_idx, &new_subrange))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn munmap(&mut self, addr: usize, size: usize) -> Result<()> {
|
pub fn munmap(&mut self, addr: usize, size: usize) -> Result<()> {
|
||||||
let size = {
|
let size = {
|
||||||
if size == 0 {
|
if size == 0 {
|
||||||
@ -340,18 +305,143 @@ impl VMManager {
|
|||||||
.sub_ranges
|
.sub_ranges
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|subrange| {
|
.flat_map(|subrange| {
|
||||||
if subrange.size() > 0 {
|
|
||||||
subrange.subtract(&munmap_range)
|
|
||||||
} else {
|
|
||||||
// Keep the two sentry subranges intact
|
// Keep the two sentry subranges intact
|
||||||
vec![*subrange]
|
if subrange.size() == 0 {
|
||||||
|
return vec![*subrange];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let unmapped_subrange = match subrange.intersect(&munmap_range) {
|
||||||
|
None => return vec![*subrange],
|
||||||
|
Some(unmapped_subrange) => unmapped_subrange,
|
||||||
|
};
|
||||||
|
|
||||||
|
subrange.subtract(&unmapped_subrange)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
self.sub_ranges = new_sub_ranges;
|
self.sub_ranges = new_sub_ranges;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
|
enum SizeType {
|
||||||
|
Same,
|
||||||
|
Shrinking,
|
||||||
|
Growing,
|
||||||
|
};
|
||||||
|
let size_type = if new_size == old_size {
|
||||||
|
SizeType::Same
|
||||||
|
} else if new_size < old_size {
|
||||||
|
SizeType::Shrinking
|
||||||
|
} else {
|
||||||
|
SizeType::Growing
|
||||||
|
};
|
||||||
|
|
||||||
|
// The old range must not span over multiple sub-ranges
|
||||||
|
self.find_containing_subrange_idx(&old_range)
|
||||||
|
.ok_or_else(|| errno!(EFAULT, "invalid range"))?;
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// and SizeType. There is a total of 9 combinations between MRemapFlags and SizeType.
|
||||||
|
// As some combinations result in the same mmap and munmap operations, the following code
|
||||||
|
// only needs to match four patterns of (MRemapFlags, SizeType) and treat each case
|
||||||
|
// accordingly.
|
||||||
|
|
||||||
|
// Determine whether need to do mmap. And when possible, determine the returned address
|
||||||
|
// TODO: should fill zeros even when extending a file-backed mapping?
|
||||||
|
let (need_mmap, mut ret_addr) = match (flags, size_type) {
|
||||||
|
(MRemapFlags::None, SizeType::Growing) => {
|
||||||
|
let mmap_opts = VMMapOptionsBuilder::default()
|
||||||
|
.size(new_size - old_size)
|
||||||
|
.addr(VMMapAddr::Need(old_range.end()))
|
||||||
|
.initializer(VMInitializer::FillZeros())
|
||||||
|
.build()?;
|
||||||
|
let ret_addr = Some(old_addr);
|
||||||
|
(Some(mmap_opts), ret_addr)
|
||||||
|
}
|
||||||
|
(MRemapFlags::MayMove, SizeType::Growing) => {
|
||||||
|
let prefered_new_range =
|
||||||
|
VMRange::new_with_size(old_addr + old_size, new_size - old_size)?;
|
||||||
|
if self.is_free_range(&prefered_new_range) {
|
||||||
|
let mmap_ops = VMMapOptionsBuilder::default()
|
||||||
|
.size(prefered_new_range.size())
|
||||||
|
.addr(VMMapAddr::Need(prefered_new_range.start()))
|
||||||
|
.initializer(VMInitializer::FillZeros())
|
||||||
|
.build()?;
|
||||||
|
(Some(mmap_ops), Some(old_addr))
|
||||||
|
} else {
|
||||||
|
let mmap_ops = VMMapOptionsBuilder::default()
|
||||||
|
.size(new_size)
|
||||||
|
.addr(VMMapAddr::Any)
|
||||||
|
.initializer(VMInitializer::CopyFrom { range: old_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::FixedAddr(new_addr), _) => {
|
||||||
|
let mmap_opts = VMMapOptionsBuilder::default()
|
||||||
|
.size(new_size)
|
||||||
|
.addr(VMMapAddr::Force(new_addr))
|
||||||
|
.initializer(VMInitializer::CopyFrom { range: old_range })
|
||||||
|
.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 find_mmap_region(&self, addr: usize) -> Result<&VMRange> {
|
pub fn find_mmap_region(&self, addr: usize) -> Result<&VMRange> {
|
||||||
self.sub_ranges
|
self.sub_ranges
|
||||||
.iter()
|
.iter()
|
||||||
@ -359,34 +449,24 @@ impl VMManager {
|
|||||||
.ok_or_else(|| errno!(ESRCH, "no mmap regions that contains the address"))
|
.ok_or_else(|| errno!(ESRCH, "no mmap regions that contains the address"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_mmap_region_idx(&self, addr: usize) -> Result<usize> {
|
// Find a subrange that contains the given range and returns the index of the subrange
|
||||||
|
fn find_containing_subrange_idx(&self, target_range: &VMRange) -> Option<usize> {
|
||||||
self.sub_ranges
|
self.sub_ranges
|
||||||
.iter()
|
.iter()
|
||||||
.position(|subrange| subrange.contains(addr))
|
.position(|subrange| subrange.is_superset_of(target_range))
|
||||||
.ok_or_else(|| errno!(ESRCH, "no mmap regions that contains the address"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn move_mmap_region(
|
// Returns whether the requested range is free
|
||||||
&mut self,
|
fn is_free_range(&self, request_range: &VMRange) -> bool {
|
||||||
src_subrange: &VMRange,
|
self.range.is_superset_of(request_range)
|
||||||
dst_idx_and_subrange: (usize, &VMRange),
|
&& self
|
||||||
) -> Result<usize> {
|
.sub_ranges
|
||||||
let dst_idx = dst_idx_and_subrange.0;
|
.iter()
|
||||||
let dst_subrange = dst_idx_and_subrange.1;
|
.all(|range| range.overlap_with(request_range) == false)
|
||||||
unsafe {
|
|
||||||
let src_buf = src_subrange.as_slice_mut();
|
|
||||||
let dst_buf = dst_subrange.as_slice_mut();
|
|
||||||
for (d, s) in dst_buf.iter_mut().zip(src_buf.iter()) {
|
|
||||||
*d = *s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.sub_ranges.insert(dst_idx, *dst_subrange);
|
|
||||||
self.munmap(src_subrange.start(), src_subrange.size())?;
|
|
||||||
Ok(dst_subrange.start())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the free subrange that satisfies the constraints of size and address
|
// Find the free subrange that satisfies the constraints of size and address
|
||||||
fn find_free_subrange(&mut self, size: usize, addr: VMMapAddr) -> Result<(usize, VMRange)> {
|
fn find_free_subrange(&self, size: usize, addr: VMMapAddr) -> Result<(usize, VMRange)> {
|
||||||
// TODO: reduce the complexity from O(N) to O(log(N)), where N is
|
// TODO: reduce the complexity from O(N) to O(log(N)), where N is
|
||||||
// the number of existing subranges.
|
// the number of existing subranges.
|
||||||
|
|
||||||
@ -426,7 +506,7 @@ impl VMManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Must have free_range.start == addr
|
// Must have free_range.start == addr
|
||||||
VMMapAddr::Fixed(addr) => {
|
VMMapAddr::Need(addr) | VMMapAddr::Force(addr) => {
|
||||||
if free_range.start() > addr {
|
if free_range.start() > addr {
|
||||||
return_errno!(ENOMEM, "not enough memory for fixed mmap");
|
return_errno!(ENOMEM, "not enough memory for fixed mmap");
|
||||||
}
|
}
|
||||||
@ -468,11 +548,59 @@ impl VMManager {
|
|||||||
debug_assert!(free_subrange.size() >= size);
|
debug_assert!(free_subrange.size() >= size);
|
||||||
|
|
||||||
let mut new_subrange = *free_subrange;
|
let mut new_subrange = *free_subrange;
|
||||||
if let VMMapAddr::Fixed(addr) = addr {
|
|
||||||
|
if let VMMapAddr::Need(addr) = addr {
|
||||||
|
debug_assert!(addr == new_subrange.start());
|
||||||
|
}
|
||||||
|
if let VMMapAddr::Force(addr) = addr {
|
||||||
debug_assert!(addr == new_subrange.start());
|
debug_assert!(addr == new_subrange.start());
|
||||||
}
|
}
|
||||||
|
|
||||||
new_subrange.resize(size);
|
new_subrange.resize(size);
|
||||||
new_subrange
|
new_subrange
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Insert the new sub-range, and when possible, merge it with its neighbors.
|
||||||
|
fn insert_new_subrange(&mut self, insert_idx: usize, new_subrange: VMRange) {
|
||||||
|
// New sub-range can only be inserted between the two sentry sub-ranges
|
||||||
|
debug_assert!(0 < insert_idx && insert_idx < self.sub_ranges.len());
|
||||||
|
|
||||||
|
let left_idx = insert_idx - 1;
|
||||||
|
let right_idx = insert_idx;
|
||||||
|
|
||||||
|
// Double check the order
|
||||||
|
debug_assert!(self.sub_ranges[left_idx].end() <= new_subrange.start());
|
||||||
|
debug_assert!(new_subrange.end() <= self.sub_ranges[right_idx].start());
|
||||||
|
|
||||||
|
let left_mergable = if left_idx > 0 {
|
||||||
|
// Mergable if there is no gap between the left neighbor and the new sub-range
|
||||||
|
self.sub_ranges[left_idx].end() == new_subrange.start()
|
||||||
|
} else {
|
||||||
|
// The left sentry sub-range is NOT mergable with any sub-range
|
||||||
|
false
|
||||||
|
};
|
||||||
|
let right_mergable = if right_idx < self.sub_ranges.len() - 1 {
|
||||||
|
// Mergable if there is no gap between the right neighbor and the new sub-range
|
||||||
|
self.sub_ranges[right_idx].start() == new_subrange.end()
|
||||||
|
} else {
|
||||||
|
// The right sentry sub-range is NOT mergable with any sub-range
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
match (left_mergable, right_mergable) {
|
||||||
|
(false, false) => {
|
||||||
|
self.sub_ranges.insert(insert_idx, new_subrange);
|
||||||
|
}
|
||||||
|
(true, false) => {
|
||||||
|
self.sub_ranges[left_idx].end = new_subrange.end;
|
||||||
|
}
|
||||||
|
(false, true) => {
|
||||||
|
self.sub_ranges[right_idx].start = new_subrange.start;
|
||||||
|
}
|
||||||
|
(true, true) => {
|
||||||
|
self.sub_ranges[left_idx].end = self.sub_ranges[right_idx].end;
|
||||||
|
self.sub_ranges.remove(right_idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,10 @@ impl VMRange {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn new_with_size(start: usize, size: usize) -> Result<VMRange> {
|
||||||
|
Self::new(start, start + size)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn new_empty(start: usize) -> Result<VMRange> {
|
pub fn new_empty(start: usize) -> Result<VMRange> {
|
||||||
if start % PAGE_SIZE != 0 {
|
if start % PAGE_SIZE != 0 {
|
||||||
return_errno!(EINVAL, "invalid start or end");
|
return_errno!(EINVAL, "invalid start or end");
|
||||||
|
@ -630,10 +630,10 @@ int test_mremap() {
|
|||||||
if (buf == MAP_FAILED) {
|
if (buf == MAP_FAILED) {
|
||||||
THROW_ERROR("mmap failed");
|
THROW_ERROR("mmap failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (check_bytes_in_buf(buf, len, 0) < 0) {
|
if (check_bytes_in_buf(buf, len, 0) < 0) {
|
||||||
THROW_ERROR("the buffer is not initialized to zeros");
|
THROW_ERROR("the buffer is not initialized to zeros");
|
||||||
}
|
}
|
||||||
|
|
||||||
void *expand_buf = mremap(buf, len, 2 * len, MREMAP_MAYMOVE);
|
void *expand_buf = mremap(buf, len, 2 * len, MREMAP_MAYMOVE);
|
||||||
if (expand_buf == MAP_FAILED) {
|
if (expand_buf == MAP_FAILED) {
|
||||||
THROW_ERROR("mremap with big size failed");
|
THROW_ERROR("mremap with big size failed");
|
||||||
@ -642,6 +642,7 @@ int test_mremap() {
|
|||||||
THROW_ERROR("the old part of expand buffer is not zero");
|
THROW_ERROR("the old part of expand buffer is not zero");
|
||||||
}
|
}
|
||||||
memset(expand_buf, 'a', len * 2);
|
memset(expand_buf, 'a', len * 2);
|
||||||
|
|
||||||
void *shrink_buf = mremap(expand_buf, 2 * len, len, 0);
|
void *shrink_buf = mremap(expand_buf, 2 * len, len, 0);
|
||||||
if (shrink_buf == MAP_FAILED) {
|
if (shrink_buf == MAP_FAILED) {
|
||||||
THROW_ERROR("mmap with small size failed");
|
THROW_ERROR("mmap with small size failed");
|
||||||
@ -649,6 +650,7 @@ int test_mremap() {
|
|||||||
if (check_bytes_in_buf(shrink_buf, len, 'a') < 0) {
|
if (check_bytes_in_buf(shrink_buf, len, 'a') < 0) {
|
||||||
THROW_ERROR("the shrink buffer is not correct");
|
THROW_ERROR("the shrink buffer is not correct");
|
||||||
}
|
}
|
||||||
|
|
||||||
int ret = munmap(shrink_buf, len);
|
int ret = munmap(shrink_buf, len);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
THROW_ERROR("munmap failed");
|
THROW_ERROR("munmap failed");
|
||||||
@ -694,6 +696,7 @@ int test_mremap_subrange() {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: may cause segfault on Linux
|
||||||
int test_mremap_with_fixed_addr() {
|
int test_mremap_with_fixed_addr() {
|
||||||
int prot = PROT_READ | PROT_WRITE;
|
int prot = PROT_READ | PROT_WRITE;
|
||||||
int flags = MAP_PRIVATE | MAP_ANONYMOUS;
|
int flags = MAP_PRIVATE | MAP_ANONYMOUS;
|
||||||
@ -703,10 +706,10 @@ int test_mremap_with_fixed_addr() {
|
|||||||
if (buf == MAP_FAILED) {
|
if (buf == MAP_FAILED) {
|
||||||
THROW_ERROR("mmap failed");
|
THROW_ERROR("mmap failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (check_bytes_in_buf(buf, len, 0) < 0) {
|
if (check_bytes_in_buf(buf, len, 0) < 0) {
|
||||||
THROW_ERROR("the buffer is not initialized to zeros");
|
THROW_ERROR("the buffer is not initialized to zeros");
|
||||||
}
|
}
|
||||||
|
|
||||||
void *new_addr = buf + len * 2;
|
void *new_addr = buf + len * 2;
|
||||||
void *new_buf = mremap(buf, len, len, MREMAP_FIXED, new_addr);
|
void *new_buf = mremap(buf, len, len, MREMAP_FIXED, new_addr);
|
||||||
if (new_buf != MAP_FAILED || errno != EINVAL) {
|
if (new_buf != MAP_FAILED || errno != EINVAL) {
|
||||||
|
Loading…
Reference in New Issue
Block a user