mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
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:
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user