github.com/MerlinKodo/gvisor@v0.0.0-20231110090155-957f62ecf90e/pkg/sentry/mm/address_space.go (about)

     1  // Copyright 2018 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package mm
    16  
    17  import (
    18  	"fmt"
    19  
    20  	"github.com/MerlinKodo/gvisor/pkg/context"
    21  	"github.com/MerlinKodo/gvisor/pkg/hostarch"
    22  	"github.com/MerlinKodo/gvisor/pkg/sentry/platform"
    23  )
    24  
    25  // AddressSpace returns the platform.AddressSpace bound to mm.
    26  //
    27  // Preconditions: The caller must have called mm.Activate().
    28  func (mm *MemoryManager) AddressSpace() platform.AddressSpace {
    29  	if mm.active.Load() == 0 {
    30  		panic("trying to use inactive address space?")
    31  	}
    32  	return mm.as
    33  }
    34  
    35  // Activate ensures this MemoryManager has a platform.AddressSpace.
    36  //
    37  // The caller must not hold any locks when calling Activate.
    38  //
    39  // When this MemoryManager is no longer needed by a task, it should call
    40  // Deactivate to release the reference.
    41  func (mm *MemoryManager) Activate(ctx context.Context) error {
    42  	// Fast path: the MemoryManager already has an active
    43  	// platform.AddressSpace, and we just need to indicate that we need it too.
    44  	for {
    45  		active := mm.active.Load()
    46  		if active == 0 {
    47  			// Fall back to the slow path.
    48  			break
    49  		}
    50  		if mm.active.CompareAndSwap(active, active+1) {
    51  			return nil
    52  		}
    53  	}
    54  
    55  	for {
    56  		// Slow path: may need to synchronize with other goroutines changing
    57  		// mm.active to or from zero.
    58  		mm.activeMu.Lock()
    59  		// Inline Unlock instead of using a defer for performance since this
    60  		// method is commonly in the hot-path.
    61  
    62  		// Check if we raced with another goroutine performing activation.
    63  		if mm.active.Load() > 0 {
    64  			// This can't race; Deactivate can't decrease mm.active from 1 to 0
    65  			// without holding activeMu.
    66  			mm.active.Add(1)
    67  			mm.activeMu.Unlock()
    68  			return nil
    69  		}
    70  
    71  		// Do we have a context? If so, then we never unmapped it. This can
    72  		// only be the case if !mm.p.CooperativelySchedulesAddressSpace().
    73  		if mm.as != nil {
    74  			mm.active.Store(1)
    75  			mm.activeMu.Unlock()
    76  			return nil
    77  		}
    78  
    79  		// Get a new address space. We must force unmapping by passing nil to
    80  		// NewAddressSpace if requested. (As in the nil interface object, not a
    81  		// typed nil.)
    82  		mappingsID := (any)(mm)
    83  		if mm.unmapAllOnActivate {
    84  			mappingsID = nil
    85  		}
    86  		as, c, err := mm.p.NewAddressSpace(mappingsID)
    87  		if err != nil {
    88  			mm.activeMu.Unlock()
    89  			return err
    90  		}
    91  		if as == nil {
    92  			// AddressSpace is unavailable, we must wait.
    93  			//
    94  			// activeMu must not be held while waiting, as the user of the address
    95  			// space we are waiting on may attempt to take activeMu.
    96  			mm.activeMu.Unlock()
    97  
    98  			sleep := mm.p.CooperativelySchedulesAddressSpace() && mm.sleepForActivation
    99  			if sleep {
   100  				// Mark this task sleeping while waiting for the address space to
   101  				// prevent the watchdog from reporting it as a stuck task.
   102  				ctx.UninterruptibleSleepStart(false)
   103  			}
   104  			<-c
   105  			if sleep {
   106  				ctx.UninterruptibleSleepFinish(false)
   107  			}
   108  			continue
   109  		}
   110  
   111  		// Okay, we could restore all mappings at this point.
   112  		// But forget that. Let's just let them fault in.
   113  		mm.as = as
   114  
   115  		// Unmapping is done, if necessary.
   116  		mm.unmapAllOnActivate = false
   117  
   118  		// Now that m.as has been assigned, we can set m.active to a non-zero value
   119  		// to enable the fast path.
   120  		mm.active.Store(1)
   121  
   122  		mm.activeMu.Unlock()
   123  		return nil
   124  	}
   125  }
   126  
   127  // Deactivate releases a reference to the MemoryManager.
   128  func (mm *MemoryManager) Deactivate() {
   129  	// Fast path: this is not the last goroutine to deactivate the
   130  	// MemoryManager.
   131  	for {
   132  		active := mm.active.Load()
   133  		if active == 1 {
   134  			// Fall back to the slow path.
   135  			break
   136  		}
   137  		if mm.active.CompareAndSwap(active, active-1) {
   138  			return
   139  		}
   140  	}
   141  
   142  	mm.activeMu.Lock()
   143  	// Same as Activate.
   144  
   145  	// Still active?
   146  	if mm.active.Add(-1) > 0 {
   147  		mm.activeMu.Unlock()
   148  		return
   149  	}
   150  
   151  	// Can we hold on to the address space?
   152  	if !mm.p.CooperativelySchedulesAddressSpace() {
   153  		mm.activeMu.Unlock()
   154  		return
   155  	}
   156  
   157  	// Release the address space.
   158  	mm.as.Release()
   159  
   160  	// Lost it.
   161  	mm.as = nil
   162  	mm.activeMu.Unlock()
   163  }
   164  
   165  // mapASLocked maps addresses in ar into mm.as. If precommit is true, mappings
   166  // for all addresses in ar should be precommitted.
   167  //
   168  // Preconditions:
   169  //   - mm.activeMu must be locked.
   170  //   - mm.as != nil.
   171  //   - ar.Length() != 0.
   172  //   - ar must be page-aligned.
   173  //   - pseg == mm.pmas.LowerBoundSegment(ar.Start).
   174  func (mm *MemoryManager) mapASLocked(pseg pmaIterator, ar hostarch.AddrRange, precommit bool) error {
   175  	// By default, map entire pmas at a time, under the assumption that there
   176  	// is no cost to mapping more of a pma than necessary.
   177  	mapAR := hostarch.AddrRange{0, ^hostarch.Addr(hostarch.PageSize - 1)}
   178  	if precommit {
   179  		// When explicitly precommitting, only map ar, since overmapping may
   180  		// incur unexpected resource usage.
   181  		mapAR = ar
   182  	} else if mapUnit := mm.p.MapUnit(); mapUnit != 0 {
   183  		// Limit the range we map to ar, aligned to mapUnit.
   184  		mapMask := hostarch.Addr(mapUnit - 1)
   185  		mapAR.Start = ar.Start &^ mapMask
   186  		// If rounding ar.End up overflows, just keep the existing mapAR.End.
   187  		if end := (ar.End + mapMask) &^ mapMask; end >= ar.End {
   188  			mapAR.End = end
   189  		}
   190  	}
   191  	if checkInvariants {
   192  		if !mapAR.IsSupersetOf(ar) {
   193  			panic(fmt.Sprintf("mapAR %#v is not a superset of ar %#v", mapAR, ar))
   194  		}
   195  	}
   196  
   197  	// Since this checks ar.End and not mapAR.End, we will never map a pma that
   198  	// is not required.
   199  	for pseg.Ok() && pseg.Start() < ar.End {
   200  		pma := pseg.ValuePtr()
   201  		pmaAR := pseg.Range()
   202  		pmaMapAR := pmaAR.Intersect(mapAR)
   203  		perms := pma.effectivePerms
   204  		if pma.needCOW {
   205  			perms.Write = false
   206  		}
   207  		if perms.Any() { // MapFile precondition
   208  			if err := mm.as.MapFile(pmaMapAR.Start, pma.file, pseg.fileRangeOf(pmaMapAR), perms, precommit); err != nil {
   209  				return err
   210  			}
   211  		}
   212  		pseg = pseg.NextSegment()
   213  	}
   214  	return nil
   215  }
   216  
   217  // unmapASLocked removes all AddressSpace mappings for addresses in ar.
   218  //
   219  // Preconditions: mm.activeMu must be locked.
   220  func (mm *MemoryManager) unmapASLocked(ar hostarch.AddrRange) {
   221  	if mm.as == nil {
   222  		// No AddressSpace? Force all mappings to be unmapped on the next
   223  		// Activate.
   224  		mm.unmapAllOnActivate = true
   225  		return
   226  	}
   227  
   228  	// unmapASLocked doesn't require vmas or pmas to exist for ar, so it can be
   229  	// passed ranges that include addresses that can't be mapped by the
   230  	// application.
   231  	ar = ar.Intersect(mm.applicationAddrRange())
   232  
   233  	// Note that this AddressSpace may or may not be active. If the
   234  	// platform does not require cooperative sharing of AddressSpaces, they
   235  	// are retained between Deactivate/Activate calls. Despite not being
   236  	// active, it is still valid to perform operations on these address
   237  	// spaces.
   238  	mm.as.Unmap(ar.Start, uint64(ar.Length()))
   239  }