mirror of
https://github.com/asterinas/asterinas.git
synced 2025-06-22 08:53:29 +00:00
Support the system call mremap
This commit is contained in:
committed by
Tate, Hongliang Tian
parent
33345f184a
commit
11f9675f37
@ -15,7 +15,7 @@ support the loading of Linux kernel modules.
|
|||||||
## System Calls
|
## System Calls
|
||||||
|
|
||||||
At the time of writing,
|
At the time of writing,
|
||||||
Asterinas implements 213 out of the 336 system calls
|
Asterinas implements 214 out of the 336 system calls
|
||||||
provided by Linux on x86-64 architecture.
|
provided by Linux on x86-64 architecture.
|
||||||
|
|
||||||
| Numbers | Names | Is Implemented |
|
| Numbers | Names | Is Implemented |
|
||||||
@ -45,7 +45,7 @@ provided by Linux on x86-64 architecture.
|
|||||||
| 22 | pipe | ✅ |
|
| 22 | pipe | ✅ |
|
||||||
| 23 | select | ✅ |
|
| 23 | select | ✅ |
|
||||||
| 24 | sched_yield | ✅ |
|
| 24 | sched_yield | ✅ |
|
||||||
| 25 | mremap | ❌ |
|
| 25 | mremap | ✅ |
|
||||||
| 26 | msync | ✅ |
|
| 26 | msync | ✅ |
|
||||||
| 27 | mincore | ❌ |
|
| 27 | mincore | ❌ |
|
||||||
| 28 | madvise | ✅ |
|
| 28 | madvise | ✅ |
|
||||||
|
@ -91,7 +91,7 @@ impl Heap {
|
|||||||
let new_size = new_heap_end - self.base;
|
let new_size = new_heap_end - self.base;
|
||||||
|
|
||||||
// Expand the heap.
|
// Expand the heap.
|
||||||
root_vmar.resize_mapping(self.base, old_size, new_size)?;
|
root_vmar.resize_mapping(self.base, old_size, new_size, false)?;
|
||||||
|
|
||||||
self.current_heap_end.store(new_heap_end, Ordering::Release);
|
self.current_heap_end.store(new_heap_end, Ordering::Release);
|
||||||
Ok(new_heap_end)
|
Ok(new_heap_end)
|
||||||
|
@ -60,6 +60,7 @@ use crate::syscall::{
|
|||||||
mmap::sys_mmap,
|
mmap::sys_mmap,
|
||||||
mount::sys_mount,
|
mount::sys_mount,
|
||||||
mprotect::sys_mprotect,
|
mprotect::sys_mprotect,
|
||||||
|
mremap::sys_mremap,
|
||||||
msync::sys_msync,
|
msync::sys_msync,
|
||||||
munmap::sys_munmap,
|
munmap::sys_munmap,
|
||||||
nanosleep::{sys_clock_nanosleep, sys_nanosleep},
|
nanosleep::{sys_clock_nanosleep, sys_nanosleep},
|
||||||
@ -277,6 +278,7 @@ impl_syscall_nums_and_dispatch_fn! {
|
|||||||
SYS_RECVMSG = 212 => sys_recvmsg(args[..3]);
|
SYS_RECVMSG = 212 => sys_recvmsg(args[..3]);
|
||||||
SYS_BRK = 214 => sys_brk(args[..1]);
|
SYS_BRK = 214 => sys_brk(args[..1]);
|
||||||
SYS_MUNMAP = 215 => sys_munmap(args[..2]);
|
SYS_MUNMAP = 215 => sys_munmap(args[..2]);
|
||||||
|
SYS_MREMAP = 216 => sys_mremap(args[..5]);
|
||||||
SYS_CLONE = 220 => sys_clone(args[..5], &user_ctx);
|
SYS_CLONE = 220 => sys_clone(args[..5], &user_ctx);
|
||||||
SYS_EXECVE = 221 => sys_execve(args[..3], &mut user_ctx);
|
SYS_EXECVE = 221 => sys_execve(args[..3], &mut user_ctx);
|
||||||
SYS_MMAP = 222 => sys_mmap(args[..6]);
|
SYS_MMAP = 222 => sys_mmap(args[..6]);
|
||||||
|
@ -69,6 +69,7 @@ use crate::syscall::{
|
|||||||
mmap::sys_mmap,
|
mmap::sys_mmap,
|
||||||
mount::sys_mount,
|
mount::sys_mount,
|
||||||
mprotect::sys_mprotect,
|
mprotect::sys_mprotect,
|
||||||
|
mremap::sys_mremap,
|
||||||
msync::sys_msync,
|
msync::sys_msync,
|
||||||
munmap::sys_munmap,
|
munmap::sys_munmap,
|
||||||
nanosleep::{sys_clock_nanosleep, sys_nanosleep},
|
nanosleep::{sys_clock_nanosleep, sys_nanosleep},
|
||||||
@ -184,6 +185,7 @@ impl_syscall_nums_and_dispatch_fn! {
|
|||||||
SYS_ACCESS = 21 => sys_access(args[..2]);
|
SYS_ACCESS = 21 => sys_access(args[..2]);
|
||||||
SYS_PIPE = 22 => sys_pipe(args[..1]);
|
SYS_PIPE = 22 => sys_pipe(args[..1]);
|
||||||
SYS_SELECT = 23 => sys_select(args[..5]);
|
SYS_SELECT = 23 => sys_select(args[..5]);
|
||||||
|
SYS_MREMAP = 25 => sys_mremap(args[..5]);
|
||||||
SYS_MSYNC = 26 => sys_msync(args[..3]);
|
SYS_MSYNC = 26 => sys_msync(args[..3]);
|
||||||
SYS_SCHED_YIELD = 24 => sys_sched_yield(args[..0]);
|
SYS_SCHED_YIELD = 24 => sys_sched_yield(args[..0]);
|
||||||
SYS_MADVISE = 28 => sys_madvise(args[..3]);
|
SYS_MADVISE = 28 => sys_madvise(args[..3]);
|
||||||
|
@ -74,6 +74,7 @@ mod mknod;
|
|||||||
mod mmap;
|
mod mmap;
|
||||||
mod mount;
|
mod mount;
|
||||||
mod mprotect;
|
mod mprotect;
|
||||||
|
mod mremap;
|
||||||
mod msync;
|
mod msync;
|
||||||
mod munmap;
|
mod munmap;
|
||||||
mod nanosleep;
|
mod nanosleep;
|
||||||
|
95
kernel/src/syscall/mremap.rs
Normal file
95
kernel/src/syscall/mremap.rs
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
use align_ext::AlignExt;
|
||||||
|
|
||||||
|
use super::SyscallReturn;
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
pub fn sys_mremap(
|
||||||
|
old_addr: Vaddr,
|
||||||
|
old_size: usize,
|
||||||
|
new_size: usize,
|
||||||
|
flags: i32,
|
||||||
|
new_addr: Vaddr,
|
||||||
|
ctx: &Context,
|
||||||
|
) -> Result<SyscallReturn> {
|
||||||
|
let flags = MremapFlags::from_bits(flags).ok_or(Errno::EINVAL)?;
|
||||||
|
let new_addr = do_sys_mremap(old_addr, old_size, new_size, flags, new_addr, ctx)?;
|
||||||
|
Ok(SyscallReturn::Return(new_addr as _))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn do_sys_mremap(
|
||||||
|
old_addr: Vaddr,
|
||||||
|
old_size: usize,
|
||||||
|
new_size: usize,
|
||||||
|
flags: MremapFlags,
|
||||||
|
new_addr: Vaddr,
|
||||||
|
ctx: &Context,
|
||||||
|
) -> Result<Vaddr> {
|
||||||
|
debug!(
|
||||||
|
"mremap: old_addr = 0x{:x}, old_size = {}, new_size = {}, flags = {:?}, new_addr = 0x{:x}",
|
||||||
|
old_addr, old_size, new_size, flags, new_addr,
|
||||||
|
);
|
||||||
|
|
||||||
|
if old_addr % PAGE_SIZE != 0 {
|
||||||
|
return_errno_with_message!(Errno::EINVAL, "mremap: `old_addr` must be page-aligned");
|
||||||
|
}
|
||||||
|
if new_size == 0 {
|
||||||
|
return_errno_with_message!(Errno::EINVAL, "mremap: `new_size` cannot be zero");
|
||||||
|
}
|
||||||
|
if old_size == 0 {
|
||||||
|
return_errno_with_message!(
|
||||||
|
Errno::EINVAL,
|
||||||
|
"mremap: copying shareable mapping is not supported"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let old_size = old_size.align_up(PAGE_SIZE);
|
||||||
|
let new_size = new_size.align_up(PAGE_SIZE);
|
||||||
|
|
||||||
|
let user_space = ctx.user_space();
|
||||||
|
let root_vmar = user_space.root_vmar();
|
||||||
|
|
||||||
|
if !flags.contains(MremapFlags::MREMAP_FIXED) && new_size <= old_size {
|
||||||
|
if new_size < old_size {
|
||||||
|
// We can shrink a old range which spans multiple mappings. See
|
||||||
|
// <https://github.com/google/gvisor/blob/95d875276806484f974ce9e95556a561331f8e22/test/syscalls/linux/mremap.cc#L100-L117>.
|
||||||
|
root_vmar.resize_mapping(old_addr, old_size, new_size, false)?;
|
||||||
|
}
|
||||||
|
return Ok(old_addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if flags.contains(MremapFlags::MREMAP_MAYMOVE) {
|
||||||
|
if flags.contains(MremapFlags::MREMAP_FIXED) {
|
||||||
|
root_vmar.remap(old_addr, old_size, Some(new_addr), new_size)
|
||||||
|
} else {
|
||||||
|
root_vmar.remap(old_addr, old_size, None, new_size)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if flags.contains(MremapFlags::MREMAP_FIXED) {
|
||||||
|
return_errno_with_message!(
|
||||||
|
Errno::EINVAL,
|
||||||
|
"mremap: `MREMAP_FIXED` specified without also specifying `MREMAP_MAYMOVE`"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// We can ensure that `new_size > old_size` here. Since we are enlarging
|
||||||
|
// the old mapping, it is necessary to check whether the old range lies
|
||||||
|
// in a single mapping.
|
||||||
|
//
|
||||||
|
// FIXME: According to <https://man7.org/linux/man-pages/man2/mremap.2.html>,
|
||||||
|
// if the `MREMAP_MAYMOVE` flag is not set, and the mapping cannot
|
||||||
|
// be expanded at the current `Vaddr`, we should return an `ENOMEM`.
|
||||||
|
// However, `resize_mapping` returns a `EACCES` in this case.
|
||||||
|
root_vmar.resize_mapping(old_addr, old_size, new_size, true)?;
|
||||||
|
Ok(old_addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bitflags! {
|
||||||
|
struct MremapFlags: i32 {
|
||||||
|
const MREMAP_MAYMOVE = 1 << 0;
|
||||||
|
const MREMAP_FIXED = 1 << 1;
|
||||||
|
// TODO: Add support for this flag, which exists since Linux 5.7.
|
||||||
|
// const MREMAP_DONTUNMAP = 1 << 2;
|
||||||
|
}
|
||||||
|
}
|
@ -86,16 +86,48 @@ impl<R> Vmar<R> {
|
|||||||
/// The range of the mapping goes from `map_addr..map_addr + old_size` to
|
/// The range of the mapping goes from `map_addr..map_addr + old_size` to
|
||||||
/// `map_addr..map_addr + new_size`.
|
/// `map_addr..map_addr + new_size`.
|
||||||
///
|
///
|
||||||
/// The range of the original mapping does not have to solely map to a
|
|
||||||
/// whole [`VmMapping`], but it must ensure that all existing ranges have a
|
|
||||||
/// mapping. Otherwise, this method will return `Err`.
|
|
||||||
///
|
|
||||||
/// If the new mapping size is smaller than the original mapping size, the
|
/// If the new mapping size is smaller than the original mapping size, the
|
||||||
/// extra part will be unmapped. If the new mapping is larger than the old
|
/// extra part will be unmapped. If the new mapping is larger than the old
|
||||||
/// mapping and the extra part overlaps with existing mapping, resizing
|
/// mapping and the extra part overlaps with existing mapping, resizing
|
||||||
/// will fail and return `Err`.
|
/// will fail and return `Err`.
|
||||||
pub fn resize_mapping(&self, map_addr: Vaddr, old_size: usize, new_size: usize) -> Result<()> {
|
///
|
||||||
self.0.resize_mapping(map_addr, old_size, new_size)
|
/// - When `check_single_mapping` is `true`, this method will check whether
|
||||||
|
/// the range of the original mapping is covered by a single [`VmMapping`].
|
||||||
|
/// If not, this method will return an `Err`.
|
||||||
|
/// - When `check_single_mapping` is `false`, The range of the original
|
||||||
|
/// mapping does not have to solely map to a whole [`VmMapping`], but it
|
||||||
|
/// must ensure that all existing ranges have a mapping. Otherwise, this
|
||||||
|
/// method will return an `Err`.
|
||||||
|
pub fn resize_mapping(
|
||||||
|
&self,
|
||||||
|
map_addr: Vaddr,
|
||||||
|
old_size: usize,
|
||||||
|
new_size: usize,
|
||||||
|
check_single_mapping: bool,
|
||||||
|
) -> Result<()> {
|
||||||
|
self.0
|
||||||
|
.resize_mapping(map_addr, old_size, new_size, check_single_mapping)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remaps the original mapping to a new address and/or size.
|
||||||
|
///
|
||||||
|
/// If the new mapping size is smaller than the original mapping size, the
|
||||||
|
/// extra part will be unmapped.
|
||||||
|
///
|
||||||
|
/// - If `new_addr` is `Some(new_addr)`, this method attempts to move the
|
||||||
|
/// mapping from `old_addr..old_addr + old_size` to `new_addr..new_addr +
|
||||||
|
/// new_size`. If any existing mappings lie within the target range,
|
||||||
|
/// they will be unmapped before the move.
|
||||||
|
/// - If `new_addr` is `None`, a new range of size `new_size` will be
|
||||||
|
/// allocated, and the original mapping will be moved there.
|
||||||
|
pub fn remap(
|
||||||
|
&self,
|
||||||
|
old_addr: Vaddr,
|
||||||
|
old_size: usize,
|
||||||
|
new_addr: Option<Vaddr>,
|
||||||
|
new_size: usize,
|
||||||
|
) -> Result<Vaddr> {
|
||||||
|
self.0.remap(old_addr, old_size, new_addr, new_size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,7 +161,7 @@ impl VmarInner {
|
|||||||
|
|
||||||
/// Returns `Ok` if the calling process may expand its mapped
|
/// Returns `Ok` if the calling process may expand its mapped
|
||||||
/// memory by the passed size.
|
/// memory by the passed size.
|
||||||
fn check_expand_size(&mut self, expand_size: usize) -> Result<()> {
|
fn check_extra_size_fits_rlimit(&self, expand_size: usize) -> Result<()> {
|
||||||
let Some(process) = Process::current() else {
|
let Some(process) = Process::current() else {
|
||||||
// When building a `Process`, the kernel task needs to build
|
// When building a `Process`, the kernel task needs to build
|
||||||
// some `VmMapping`s, in which case this branch is reachable.
|
// some `VmMapping`s, in which case this branch is reachable.
|
||||||
@ -151,6 +183,23 @@ impl VmarInner {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks whether `addr..addr + size` is covered by a single `VmMapping`,
|
||||||
|
/// and returns the address of the single `VmMapping` if successful.
|
||||||
|
fn check_lies_in_single_mapping(&self, addr: Vaddr, size: usize) -> Result<Vaddr> {
|
||||||
|
if let Some(vm_mapping) = self
|
||||||
|
.vm_mappings
|
||||||
|
.find_one(&addr)
|
||||||
|
.filter(|vm_mapping| vm_mapping.map_end() - addr >= size)
|
||||||
|
{
|
||||||
|
Ok(vm_mapping.map_to_addr())
|
||||||
|
} else {
|
||||||
|
// FIXME: In Linux, two adjacent mappings created by `mmap` with
|
||||||
|
// identical properties can be `mremap`ed together. Fix this by
|
||||||
|
// adding an auto-merge mechanism for adjacent `VmMapping`s.
|
||||||
|
return_errno_with_message!(Errno::EFAULT, "The range must lie in a single mapping");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Inserts a `VmMapping` into the `Vmar`.
|
/// Inserts a `VmMapping` into the `Vmar`.
|
||||||
///
|
///
|
||||||
/// Make sure the insertion doesn't exceed address space limit.
|
/// Make sure the insertion doesn't exceed address space limit.
|
||||||
@ -277,6 +326,61 @@ impl VmarInner {
|
|||||||
|
|
||||||
return_errno_with_message!(Errno::ENOMEM, "Cannot find free region for mapping");
|
return_errno_with_message!(Errno::ENOMEM, "Cannot find free region for mapping");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Splits and unmaps the found mapping if the new size is smaller.
|
||||||
|
/// Enlarges the last mapping if the new size is larger.
|
||||||
|
fn resize_mapping(
|
||||||
|
&mut self,
|
||||||
|
vm_space: &VmSpace,
|
||||||
|
map_addr: Vaddr,
|
||||||
|
old_size: usize,
|
||||||
|
new_size: usize,
|
||||||
|
rss_delta: &mut RssDelta,
|
||||||
|
) -> Result<()> {
|
||||||
|
debug_assert_eq!(map_addr % PAGE_SIZE, 0);
|
||||||
|
debug_assert_eq!(old_size % PAGE_SIZE, 0);
|
||||||
|
debug_assert_eq!(new_size % PAGE_SIZE, 0);
|
||||||
|
|
||||||
|
// FIXME: We should check whether all existing ranges in
|
||||||
|
// `map_addr..map_addr + old_size` have a mapping. If not,
|
||||||
|
// we should return a `Err`.
|
||||||
|
|
||||||
|
if new_size == 0 {
|
||||||
|
return_errno_with_message!(Errno::EINVAL, "can not resize a mapping to 0 size");
|
||||||
|
}
|
||||||
|
|
||||||
|
if new_size == old_size {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let old_map_end = map_addr + old_size;
|
||||||
|
let new_map_end = map_addr.checked_add(new_size).ok_or(Errno::EINVAL)?;
|
||||||
|
if !is_userspace_vaddr(new_map_end) {
|
||||||
|
return_errno_with_message!(Errno::EINVAL, "resize to a invalid new size");
|
||||||
|
}
|
||||||
|
|
||||||
|
if new_size < old_size {
|
||||||
|
self.alloc_free_region_exact_truncate(
|
||||||
|
vm_space,
|
||||||
|
new_map_end,
|
||||||
|
old_map_end - new_map_end,
|
||||||
|
rss_delta,
|
||||||
|
)?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.alloc_free_region_exact(old_map_end, new_map_end - old_map_end)?;
|
||||||
|
|
||||||
|
let last_mapping = self.vm_mappings.find_one(&(old_map_end - 1)).unwrap();
|
||||||
|
let last_mapping_addr = last_mapping.map_to_addr();
|
||||||
|
debug_assert_eq!(last_mapping.map_end(), old_map_end);
|
||||||
|
|
||||||
|
self.check_extra_size_fits_rlimit(new_map_end - old_map_end)?;
|
||||||
|
let last_mapping = self.remove(&last_mapping_addr).unwrap();
|
||||||
|
let last_mapping = last_mapping.enlarge(new_map_end - old_map_end);
|
||||||
|
self.insert(last_mapping);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const ROOT_VMAR_LOWEST_ADDR: Vaddr = 0x001_0000; // 64 KiB is the Linux configurable default
|
pub const ROOT_VMAR_LOWEST_ADDR: Vaddr = 0x001_0000; // 64 KiB is the Linux configurable default
|
||||||
@ -422,43 +526,114 @@ impl Vmar_ {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Split and unmap the found mapping if resize smaller.
|
/// Splits and unmaps the found mapping if the new size is smaller.
|
||||||
// Enlarge the last mapping if resize larger.
|
/// Enlarges the last mapping if the new size is larger.
|
||||||
fn resize_mapping(&self, map_addr: Vaddr, old_size: usize, new_size: usize) -> Result<()> {
|
fn resize_mapping(
|
||||||
debug_assert!(map_addr % PAGE_SIZE == 0);
|
&self,
|
||||||
debug_assert!(old_size % PAGE_SIZE == 0);
|
map_addr: Vaddr,
|
||||||
debug_assert!(new_size % PAGE_SIZE == 0);
|
old_size: usize,
|
||||||
|
new_size: usize,
|
||||||
if new_size == 0 {
|
check_single_mapping: bool,
|
||||||
return_errno_with_message!(Errno::EINVAL, "can not resize a mapping to 0 size");
|
) -> Result<()> {
|
||||||
}
|
let mut rss_delta = RssDelta::new(self);
|
||||||
|
|
||||||
if new_size == old_size {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let old_map_end = map_addr + old_size;
|
|
||||||
let new_map_end = map_addr + new_size;
|
|
||||||
|
|
||||||
if new_size < old_size {
|
|
||||||
self.remove_mapping(new_map_end..old_map_end)?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut inner = self.inner.write();
|
let mut inner = self.inner.write();
|
||||||
let last_mapping = inner.vm_mappings.find_one(&(old_map_end - 1)).unwrap();
|
if check_single_mapping {
|
||||||
let last_mapping_addr = last_mapping.map_to_addr();
|
inner.check_lies_in_single_mapping(map_addr, old_size)?;
|
||||||
let extra_mapping_start = last_mapping.map_end();
|
}
|
||||||
|
inner.resize_mapping(&self.vm_space, map_addr, old_size, new_size, &mut rss_delta)?;
|
||||||
inner.check_expand_size(new_map_end - extra_mapping_start)?;
|
|
||||||
|
|
||||||
let last_mapping = inner.remove(&last_mapping_addr).unwrap();
|
|
||||||
inner.alloc_free_region_exact(extra_mapping_start, new_map_end - extra_mapping_start)?;
|
|
||||||
let last_mapping = last_mapping.enlarge(new_map_end - extra_mapping_start);
|
|
||||||
inner.insert(last_mapping);
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn remap(
|
||||||
|
&self,
|
||||||
|
old_addr: Vaddr,
|
||||||
|
old_size: usize,
|
||||||
|
new_addr: Option<Vaddr>,
|
||||||
|
new_size: usize,
|
||||||
|
) -> Result<Vaddr> {
|
||||||
|
let mut inner = self.inner.write();
|
||||||
|
let old_mapping_addr = inner.check_lies_in_single_mapping(old_addr, old_size)?;
|
||||||
|
|
||||||
|
let mut old_range = old_addr..old_addr + old_size;
|
||||||
|
let mut old_size = old_size;
|
||||||
|
let mut rss_delta = RssDelta::new(self);
|
||||||
|
|
||||||
|
// Allocate a new free region that does not overlap with the old range.
|
||||||
|
let new_range = if let Some(new_addr) = new_addr {
|
||||||
|
let new_range = new_addr..new_addr.checked_add(new_size).ok_or(Errno::EINVAL)?;
|
||||||
|
if new_addr % PAGE_SIZE != 0
|
||||||
|
|| !is_userspace_vaddr(new_addr)
|
||||||
|
|| !is_userspace_vaddr(new_range.end)
|
||||||
|
{
|
||||||
|
return_errno_with_message!(Errno::EINVAL, "remap: invalid fixed new addr");
|
||||||
|
}
|
||||||
|
if is_intersected(&old_range, &new_range) {
|
||||||
|
return_errno_with_message!(
|
||||||
|
Errno::EINVAL,
|
||||||
|
"remap: the new range overlaps with the old one"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
inner.alloc_free_region_exact_truncate(
|
||||||
|
&self.vm_space,
|
||||||
|
new_addr,
|
||||||
|
new_size,
|
||||||
|
&mut rss_delta,
|
||||||
|
)?
|
||||||
|
} else {
|
||||||
|
inner.alloc_free_region(new_size, PAGE_SIZE)?
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create a new `VmMapping`.
|
||||||
|
let old_mapping = {
|
||||||
|
let vm_mapping = inner.remove(&old_mapping_addr).unwrap();
|
||||||
|
let (left, old_mapping, right) = vm_mapping.split_range(&old_range)?;
|
||||||
|
if let Some(left) = left {
|
||||||
|
inner.insert(left);
|
||||||
|
}
|
||||||
|
if let Some(right) = right {
|
||||||
|
inner.insert(right);
|
||||||
|
}
|
||||||
|
if new_size < old_size {
|
||||||
|
let (old_mapping, taken) = old_mapping.split(old_range.start + new_size)?;
|
||||||
|
rss_delta.add(taken.rss_type(), -(taken.unmap(&self.vm_space)? as isize));
|
||||||
|
old_size = new_size;
|
||||||
|
old_range = old_range.start..(old_range.start + old_size);
|
||||||
|
old_mapping
|
||||||
|
} else {
|
||||||
|
old_mapping
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Now we can ensure that `new_size >= old_size`.
|
||||||
|
let new_mapping = old_mapping.clone_for_remap_at(new_range.start)?;
|
||||||
|
inner.insert(new_mapping.enlarge(new_size - old_size));
|
||||||
|
|
||||||
|
// Move the mapping.
|
||||||
|
let preempt_guard = disable_preempt();
|
||||||
|
let total_range = old_range.start.min(new_range.start)..old_range.end.max(new_range.end);
|
||||||
|
let vmspace = self.vm_space();
|
||||||
|
let mut cursor = vmspace.cursor_mut(&preempt_guard, &total_range).unwrap();
|
||||||
|
let mut current_offset = 0;
|
||||||
|
cursor.jump(old_range.start).unwrap();
|
||||||
|
while let Some(mapped_va) = cursor.find_next(old_size - current_offset) {
|
||||||
|
let (va, Some((frame, prop))) = cursor.query().unwrap() else {
|
||||||
|
panic!("Found mapped page but query failed");
|
||||||
|
};
|
||||||
|
debug_assert_eq!(mapped_va, va.start);
|
||||||
|
cursor.unmap(PAGE_SIZE);
|
||||||
|
|
||||||
|
let offset = mapped_va - old_range.start;
|
||||||
|
cursor.jump(new_range.start + offset).unwrap();
|
||||||
|
cursor.map(frame, prop);
|
||||||
|
|
||||||
|
current_offset = offset + PAGE_SIZE;
|
||||||
|
cursor.jump(old_range.start + current_offset).unwrap();
|
||||||
|
}
|
||||||
|
cursor.flusher().dispatch_tlb_flush();
|
||||||
|
cursor.flusher().sync_tlb_flush();
|
||||||
|
|
||||||
|
Ok(new_range.start)
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the attached `VmSpace`.
|
/// Returns the attached `VmSpace`.
|
||||||
fn vm_space(&self) -> &Arc<VmSpace> {
|
fn vm_space(&self) -> &Arc<VmSpace> {
|
||||||
&self.vm_space
|
&self.vm_space
|
||||||
@ -783,7 +958,7 @@ where
|
|||||||
|
|
||||||
let mut inner = parent.0.inner.write();
|
let mut inner = parent.0.inner.write();
|
||||||
|
|
||||||
inner.check_expand_size(map_size).or_else(|e| {
|
inner.check_extra_size_fits_rlimit(map_size).or_else(|e| {
|
||||||
if can_overwrite {
|
if can_overwrite {
|
||||||
let offset = offset.ok_or(Error::with_message(
|
let offset = offset.ok_or(Error::with_message(
|
||||||
Errno::EINVAL,
|
Errno::EINVAL,
|
||||||
@ -791,7 +966,7 @@ where
|
|||||||
))?;
|
))?;
|
||||||
// MAP_FIXED may remove pages overlapped with requested mapping.
|
// MAP_FIXED may remove pages overlapped with requested mapping.
|
||||||
let expand_size = map_size - inner.count_overlap_size(offset..offset + map_size);
|
let expand_size = map_size - inner.count_overlap_size(offset..offset + map_size);
|
||||||
inner.check_expand_size(expand_size)
|
inner.check_extra_size_fits_rlimit(expand_size)
|
||||||
} else {
|
} else {
|
||||||
Err(e)
|
Err(e)
|
||||||
}
|
}
|
||||||
|
@ -113,6 +113,12 @@ impl VmMapping {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn clone_for_remap_at(&self, va: Vaddr) -> Result<VmMapping> {
|
||||||
|
let mut vm_mapping = self.new_fork()?;
|
||||||
|
vm_mapping.map_to_addr = va;
|
||||||
|
Ok(vm_mapping)
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the mapping's start address.
|
/// Returns the mapping's start address.
|
||||||
pub fn map_to_addr(&self) -> Vaddr {
|
pub fn map_to_addr(&self) -> Vaddr {
|
||||||
self.map_to_addr
|
self.map_to_addr
|
||||||
@ -388,7 +394,7 @@ impl VmMapping {
|
|||||||
///
|
///
|
||||||
/// The address must be within the mapping and page-aligned. The address
|
/// The address must be within the mapping and page-aligned. The address
|
||||||
/// must not be either the start or the end of the mapping.
|
/// must not be either the start or the end of the mapping.
|
||||||
fn split(self, at: Vaddr) -> Result<(Self, Self)> {
|
pub fn split(self, at: Vaddr) -> Result<(Self, Self)> {
|
||||||
debug_assert!(self.map_to_addr < at && at < self.map_end());
|
debug_assert!(self.map_to_addr < at && at < self.map_end());
|
||||||
debug_assert!(at % PAGE_SIZE == 0);
|
debug_assert!(at % PAGE_SIZE == 0);
|
||||||
|
|
||||||
|
80
test/apps/mmap/mmap_and_mremap.c
Normal file
80
test/apps/mmap/mmap_and_mremap.c
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
#define _GNU_SOURCE
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include "../network/test.h"
|
||||||
|
|
||||||
|
#define PAGE_SIZE 4096
|
||||||
|
|
||||||
|
const char *content = "kjfkljk*wigo&h";
|
||||||
|
|
||||||
|
void *x_mmap(void *addr, size_t length, int prot, int flags, int fd,
|
||||||
|
off_t offset)
|
||||||
|
{
|
||||||
|
void *result = mmap(addr, length, prot, flags, fd, offset);
|
||||||
|
if (result == MAP_FAILED) {
|
||||||
|
perror("mmap");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
FN_TEST(mmap_and_mremap)
|
||||||
|
{
|
||||||
|
char *addr = x_mmap(NULL, 3 * PAGE_SIZE, PROT_READ | PROT_WRITE,
|
||||||
|
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||||
|
TEST_SUCC(munmap(addr, 3 * PAGE_SIZE));
|
||||||
|
|
||||||
|
addr = x_mmap(addr, PAGE_SIZE, PROT_READ | PROT_WRITE,
|
||||||
|
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
|
||||||
|
strcpy(addr, content);
|
||||||
|
|
||||||
|
char *addr2 = x_mmap(addr + 2 * PAGE_SIZE, PAGE_SIZE,
|
||||||
|
PROT_READ | PROT_WRITE,
|
||||||
|
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
|
||||||
|
|
||||||
|
char *new_addr = mremap(addr, PAGE_SIZE, 3 * PAGE_SIZE, MREMAP_MAYMOVE);
|
||||||
|
if (new_addr == MAP_FAILED) {
|
||||||
|
perror("mremap");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The following operation (if uncommented) would cause a segmentation fault.
|
||||||
|
// strcpy(addr, "Writing to old address");
|
||||||
|
|
||||||
|
TEST_RES(strcmp(new_addr, content), _ret == 0);
|
||||||
|
strcpy(new_addr + PAGE_SIZE, "Writing to page 2 (new)");
|
||||||
|
strcpy(new_addr + 2 * PAGE_SIZE, "Writing to page 3 (new)");
|
||||||
|
|
||||||
|
TEST_SUCC(munmap(new_addr, 3 * PAGE_SIZE));
|
||||||
|
TEST_SUCC(munmap(addr2, PAGE_SIZE));
|
||||||
|
}
|
||||||
|
END_TEST()
|
||||||
|
|
||||||
|
FN_TEST(mmap_and_mremap_fixed)
|
||||||
|
{
|
||||||
|
char *addr = x_mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE,
|
||||||
|
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||||
|
strcpy(addr, content);
|
||||||
|
|
||||||
|
// Map and unmap a target region to ensure we know it's free
|
||||||
|
char *fixed_addr = x_mmap(NULL, PAGE_SIZE, PROT_NONE,
|
||||||
|
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||||
|
TEST_SUCC(munmap(fixed_addr, PAGE_SIZE)); // free it for mremap
|
||||||
|
|
||||||
|
char *new_addr = mremap(addr, PAGE_SIZE, PAGE_SIZE,
|
||||||
|
MREMAP_MAYMOVE | MREMAP_FIXED, fixed_addr);
|
||||||
|
if (new_addr != fixed_addr) {
|
||||||
|
perror("mremap");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_RES(strcmp(new_addr, content), _ret == 0);
|
||||||
|
TEST_SUCC(munmap(new_addr, PAGE_SIZE));
|
||||||
|
}
|
||||||
|
END_TEST()
|
@ -28,6 +28,7 @@ hello_world/hello_world
|
|||||||
itimer/setitimer
|
itimer/setitimer
|
||||||
itimer/timer_create
|
itimer/timer_create
|
||||||
mmap/mmap_and_fork
|
mmap/mmap_and_fork
|
||||||
|
mmap/mmap_and_mremap
|
||||||
mmap/mmap_shared_filebacked
|
mmap/mmap_shared_filebacked
|
||||||
mmap/mmap_readahead
|
mmap/mmap_readahead
|
||||||
mmap/mmap_vmrss
|
mmap/mmap_vmrss
|
||||||
|
@ -25,6 +25,7 @@ TESTS ?= \
|
|||||||
mknod_test \
|
mknod_test \
|
||||||
mmap_test \
|
mmap_test \
|
||||||
mount_test \
|
mount_test \
|
||||||
|
mremap_test \
|
||||||
msync_test \
|
msync_test \
|
||||||
open_create_test \
|
open_create_test \
|
||||||
open_test \
|
open_test \
|
||||||
|
6
test/syscall_test/gvisor/blocklists/mremap_test
Normal file
6
test/syscall_test/gvisor/blocklists/mremap_test
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
MremapDeathTest.SharedAnon
|
||||||
|
MremapTest.InPlace_Copy
|
||||||
|
MremapTest.MayMove_Copy
|
||||||
|
MremapTest.MustMove_Copy
|
||||||
|
PrivateShared/MremapParamTest.InPlace_ExpansionFailure/*
|
||||||
|
PrivateShared/MremapParamTest.Fixed_ShrinkingAcrossVMAs/*
|
@ -883,11 +883,11 @@ mprotect05
|
|||||||
# mq_timedsend01
|
# mq_timedsend01
|
||||||
# mq_unlink01
|
# mq_unlink01
|
||||||
|
|
||||||
# mremap01
|
mremap01
|
||||||
# mremap02
|
mremap02
|
||||||
# mremap03
|
mremap03
|
||||||
# mremap04
|
# mremap04
|
||||||
# mremap05
|
mremap05
|
||||||
# mremap06
|
# mremap06
|
||||||
|
|
||||||
# mseal01
|
# mseal01
|
||||||
|
Reference in New Issue
Block a user