Skip to content

Commit

Permalink
mm: limit AddressSpace overmapping during async page loading
Browse files Browse the repository at this point in the history
On platforms that do not create page table entries in
platform.AddressSpace.MapFile(precommit=false), i.e. platforms for which
platform.Platform.MapUnit() == 0, platform.AddressSpace.MapFile() is generally
implemented as some form of host mmap(), which only synchronously creates host
kernel VMAs (virtual memory areas) and creates page table entries lazily in
response to application faults. On such platforms, MM.mapAsLocked() creates the
largest possible host VMAs since doing so reduces future sentry-handled page
faults and has effectively no additional cost. However, when async page loading
is active, this must wait for all mapped pages to be loaded, which may result
in the faulting application blocking for significantly longer than expected (in
experiments, a single page fault could result in waiting for up to 64GB of data
to be loaded). In such cases, additionally constrain mapped sizes to limit wait
times.

PiperOrigin-RevId: 680699946
  • Loading branch information
nixprime authored and gvisor-bot committed Sep 30, 2024
1 parent 9d41ac1 commit a15559c
Show file tree
Hide file tree
Showing 2 changed files with 22 additions and 6 deletions.
22 changes: 16 additions & 6 deletions pkg/sentry/mm/address_space.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/hostarch"
"gvisor.dev/gvisor/pkg/sentry/memmap"
"gvisor.dev/gvisor/pkg/sentry/pgalloc"
"gvisor.dev/gvisor/pkg/sentry/platform"
)

Expand Down Expand Up @@ -175,6 +176,14 @@ func (mm *MemoryManager) mapASLocked(pseg pmaIterator, ar hostarch.AddrRange, pl
// By default, map entire pmas at a time, under the assumption that there
// is no cost to mapping more of a pma than necessary.
mapAR := hostarch.AddrRange{0, ^hostarch.Addr(hostarch.PageSize - 1)}
setMapUnit := func(mapUnit uint64) {
mapMask := hostarch.Addr(mapUnit - 1)
mapAR.Start = ar.Start &^ mapMask
// If rounding ar.End up overflows, just keep the existing mapAR.End.
if end := (ar.End + mapMask) &^ mapMask; end >= ar.End {
mapAR.End = end
}
}
if platformEffect != memmap.PlatformEffectDefault {
// When explicitly committing, only map ar, since overmapping may incur
// unexpected resource usage. When explicitly populating, do the same
Expand All @@ -183,12 +192,13 @@ func (mm *MemoryManager) mapASLocked(pseg pmaIterator, ar hostarch.AddrRange, pl
mapAR = ar
} else if mapUnit := mm.p.MapUnit(); mapUnit != 0 {
// Limit the range we map to ar, aligned to mapUnit.
mapMask := hostarch.Addr(mapUnit - 1)
mapAR.Start = ar.Start &^ mapMask
// If rounding ar.End up overflows, just keep the existing mapAR.End.
if end := (ar.End + mapMask) &^ mapMask; end >= ar.End {
mapAR.End = end
}
setMapUnit(mapUnit)
} else if mf, ok := pseg.ValuePtr().file.(*pgalloc.MemoryFile); ok && mf.IsAsyncLoading() {
// Impose an arbitrary mapUnit in order to avoid calling
// platform.AddressSpace.MapFile() => mf.DataFD() or mf.MapInternal()
// with unnecessarily large ranges, resulting in unnecessarily long
// waits.
setMapUnit(32 << 20)
}
if checkInvariants {
if !mapAR.IsSupersetOf(ar) {
Expand Down
6 changes: 6 additions & 0 deletions pkg/sentry/pgalloc/save_restore.go
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,12 @@ var aplWaiterPool = sync.Pool{
},
}

// IsAsyncLoading returns true if async page loading is in progress or has
// failed permanently.
func (f *MemoryFile) IsAsyncLoading() bool {
return f.asyncPageLoad.Load() != nil
}

// AwaitLoadAll blocks until async page loading has completed. If async page
// loading is not in progress, AwaitLoadAll returns immediately.
func (f *MemoryFile) AwaitLoadAll() error {
Expand Down

0 comments on commit a15559c

Please sign in to comment.