Skip to content

mm: implement mremap(2) - #1432

Open
gburd wants to merge 1 commit into
cloudius-systems:masterfrom
gburd:pr/mremap
Open

mm: implement mremap(2)#1432
gburd wants to merge 1 commit into
cloudius-systems:masterfrom
gburd:pr/mremap

Conversation

@gburd

@gburd gburd commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

What

Implements mremap(2), which was missing entirely on OSv: it was not in the
syscall table and had no libc entry point, so applications and allocators that
resize an mmap region failed (glibc/musl realloc of an mmap-backed chunk calls
the internal __mremap).

How

mmu::mremap() (core/mmu.cc) is built on the existing VMA machinery and the
public, self-locking map_anon()/map_file()/munmap() primitives. It handles
a single, wholly mapped source vma (Linux likewise requires the old range to
fall within one mapping; a range spanning several vmas or a hole yields
EFAULT):

  • shrink / no-op: drop the tail in place, address unchanged;
  • grow in place: when the region immediately after the source is free;
  • grow with MREMAP_MAYMOVE: allocate a new region of the new size, migrate
    the contents (anonymous: copy; file-backed: remap the file at the same offset
    so the grown tail is served by the fault path with no copy), then unmap the old
    range.

The source vma is inspected once under vma_list_mutex to snapshot its
perm/flags/type/file/offset and whether its tail is free, so the rest of the
function can call the self-locking primitives without nesting the non-recursive
vma write lock.

MREMAP_FIXED is not supported yet and returns EINVAL rather than doing the
wrong thing; the anonymous and file-backed paths cover the callers we have.

Wired as syscall SYS_mremap (via long_mremap, mirroring long_mmap since the
return value is a pointer) with a matching tracepoint, plus the libc
mremap()/__mremap() entry points, and both symbols exported from
libc.so.6 and ld-musl.so.1.

Testing

tests/tst-mremap.cc covers grow, shrink, in-place-blocked (ENOMEM without
MREMAP_MAYMOVE), file-backed move, and the EINVAL/EFAULT error paths. It
passes on OSv under KVM and on native Linux (semantics match). tst-mmap
continues to pass, so there is no regression in the VMA path.

(Recreated from #1412, which GitHub auto-closed when its branch was rebased onto current master. Same change, rebased and build/boot-verified on master 3aba46c.)

@gburd gburd mentioned this pull request Jul 13, 2026
@gburd
gburd force-pushed the pr/mremap branch 3 times, most recently from da653f5 to 68e7e9b Compare July 15, 2026 14:37
@wkozaczuk
wkozaczuk requested a review from Copilot July 25, 2026 04:08

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds initial support for Linux mremap(2) in OSv, wiring it end-to-end (MMU implementation → syscall plumbing → libc entry points) and introducing a dedicated test to validate expected grow/shrink/move behaviors.

Changes:

  • Implement mmu::mremap() using existing VMA primitives, supporting shrink, in-place grow, and MAYMOVE-based relocation (with MREMAP_FIXED rejected).
  • Wire SYS_mremap via long_mremap, add a syscall tracepoint, and add libc mremap() / __mremap() entry points.
  • Add tst-mremap and integrate it into the tests module build.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
tests/tst-mremap.cc New functional test coverage for anon and file-backed mremap() behaviors and error paths.
syscalls/syscalls.cc.in Adds long_mremap syscall declaration to the generated syscall table.
syscalls/syscall_tracepoints.cc.in Adds a syscall tracepoint for long_mremap.
modules/tests/Makefile Registers the new tst-mremap.so test module.
linux.cc Adds a Linux-hosted long_mremap() shim for syscall emulation/testing.
libc/mman.cc Adds libc mremap() wrapper and internal __mremap() symbol.
include/osv/mmu.hh Declares the new mmu::mremap() API.
exported_symbols/osv_libc.so.6.symbols Exports mremap from OSv’s glibc-compat libc.
exported_symbols/osv_ld-musl.so.1.symbols Exports mremap from OSv’s musl loader.
core/mmu.cc Implements mmu::mremap() and supporting helpers using VMA machinery.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/tst-mremap.cc
Comment on lines +14 to +18
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
Comment thread core/mmu.cc Outdated
// mapped source vma is supported (Linux requires the old range to fall in one
// mapping too); a range spanning several vmas or a hole yields EFAULT.
//
// ponytail: anon and file-backed mappings are handled; the move path preserves
Comment thread core/mmu.cc
Comment on lines +1446 to +1451
auto* fvma = dynamic_cast<file_vma*>(&*i);
s.is_file = fvma != nullptr;
if (fvma) {
s.file = fvma->file();
s.offset = fvma->offset();
}
Comment thread core/mmu.cc
Comment on lines +1482 to +1485
if (new_size == 0 || (flags & MREMAP_FIXED)) {
errno = EINVAL;
return MAP_FAILED;
}
Comment thread core/mmu.cc
Comment on lines +1495 to +1501
if (new_size <= old_size) {
if (new_size < old_size) {
munmap(reinterpret_cast<void*>(old_start + new_size),
old_size - new_size);
}
return old_addr;
}
Comment thread core/mmu.cc
Comment on lines +1549 to +1550
if (src.is_file) {
// File-backed move: remap the file at the new address with the same
Comment on lines 557 to 560
mount
mprotect
mremap
mrand48
Comment on lines 666 to 669
mount
mprotect
mremap
mrand48
@gburd

gburd commented Jul 29, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the review (and Copilot). Addressed in the current tip (3f8152e, rebased onto master and squashed into one coherent commit):

  • "ponytail" word (comment): removed; the doc comment now reads as a plain TODO:.
  • Missing <errno.h> in the test: added.
  • old_size == 0: now rejected with EINVAL (it is only meaningful on Linux for mirroring a shareable mapping, which we do not implement; a zero old_size would otherwise "grow" from nothing or munmap a zero-length tail).
  • Shrink path ignored munmap's return: now checked. The source range was verified wholly mapped, so this cannot fail with EINVAL, but any error is now surfaced (via to_libc() + MAP_FAILED) rather than reporting a success that left the mapping unchanged.
  • File offset when old_addr starts mid-VMA: inspect_mremap_src() now records fvma->offset() + (start - vma_start), so a sub-range remap re-derives the correct file bytes instead of the VMA's base offset.
  • MAP_PRIVATE file move dropping COW-dirtied pages: rather than silently returning success with lost data, a writable MAP_PRIVATE file mapping move is now refused with EINVAL. A correct private-dirty-page copy is left as a TODO in the comment.
  • __mremap not exported: added to both osv_ld-musl.so.1.symbols and osv_libc.so.6.symbols (the __mremap entry point already existed in libc/mman.cc). Note OSv uses its own core/mempool allocator rather than musl's mallocng/oldmalloc, so nothing internal depends on __mremap, but exporting it is harmless and covers any caller that resolves the internal name.

Needs a build to confirm it compiles and the test passes on the OSv image; I have queued that.

@gburd
gburd force-pushed the pr/mremap branch 2 times, most recently from 871c20c to 89a822a Compare July 29, 2026 11:38
Add Linux mremap(2): resize (and optionally move) an existing mapping.
Wired end-to-end through mmu::mremap(), the syscall dispatch/tracepoints,
and the libc entry points (mremap and __mremap), with a dedicated test.

Only a single, wholly mapped source vma is supported (Linux likewise
requires the old range to fall in one mapping); a range spanning several
vmas or a hole yields EFAULT. Both anon and file-backed mappings are
handled, and the move path preserves the mapping type/perm/flags (and the
correct file offset when the source starts mid-vma). MREMAP_FIXED is not
supported yet and returns EINVAL.

Correctness and robustness:
 - Reject sizes that overflow when page-aligned and address ranges that
   wrap before any range arithmetic uses them; old_size == 0 and
   new_size == 0 are EINVAL.
 - The grow-in-place path re-verifies the tail range is free under the
   write lock and inserts the tail vma atomically, so a concurrent
   mmap/stack allocation cannot be clobbered (evacuated) by mmap_fixed.
 - The shrink path checks munmap()'s return and fails rather than
   reporting a success that left the mapping unchanged.
 - Moving a writable MAP_PRIVATE file mapping is refused with EINVAL
   rather than silently losing COW-dirtied private pages; a correct
   private-dirty-page copy is left as a TODO.

Export both mremap and __mremap so callers that resolve the internal
musl name link correctly.

Signed-off-by: Greg Burd <greg@burd.me>
@gburd

gburd commented Jul 29, 2026

Copy link
Copy Markdown
Contributor Author

Pushed a couple of build fixes on top (now c12b8121):

  • The shrink-path munmap error check I'd added used a struct error in a boolean context (no implicit conversion) and then an if-init-statement, which the kernel's gnu++14 -Werror rejects. Now a plain two-statement auto e = munmap(...); if (e.bad()) { ... }.
  • Added an old_size == 0 -> EINVAL assert to test_errors() so that guard is exercised, not just source-verified.

Rebuilt image=tests: core/mmu.cc compiles, __mremap links (present in both osv_ld-musl.so.1.symbols and osv_libc.so.6.symbols, linker-script sort intact), and tst-mremap passes all asserts (grow/shrink/no-maymove/file-move/errors).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants