mm: limit AddressSpace overmapping during async page loading

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
This commit is contained in:
Jamie Liu
2024-09-30 13:39:11 -07:00
committed by gVisor bot
parent 9d41ac1ff0
commit a15559c56c
2 changed files with 22 additions and 6 deletions
+16 -6
View File
@@ -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"
)
@@ -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
@@ -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) {
+6
View File
@@ -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 {