Files
Tianyu Zhou fc1afc9de5 platform: add SlimVM platform
SlimVM is a lightweight VMX-based platform for gVisor that accelerates
sentry system calls by eliminating unnecessary user/kernel context
switches.

In a traditional gVisor + KVM setup, every guest syscall triggers a
full VM exit to userspace, then re-enters the kernel via a regular
syscall:

  gr0 -> hr0 (vm_exit) -> hr3 (ioctl return) -> hr0 (re-invoke syscall)

SlimVM eliminates this overhead. When the guest executes a VMCALL, the
VM exits into VMX root mode where SlimVM directly invokes the host
kernel's syscall handler, then immediately resumes the guest:

  gr0 -> hr0 (vmcall + direct function call)

SlimVM requires a companion kernel module that provides the /dev/slimvm
device interface. See https://github.com/antgroup/slimvm for the
kernel module and full documentation.

A sentry->host getpid microbenchmark (Xeon 6982P-C, host kernel 5.10.134)
shows the effect:

            │  ns/op  │ vs base │
  base          82.5       1.0x
  slimvm       420.9       5.1x
  kvm         3566.1      43.2x

slimvm is ~8.5x faster than the kvm platform on this path. See the issue
for details.

Updates: https://github.com/google/gvisor/issues/13330

Co-developed-by: Chenggang <[email protected]>
Co-developed-by: Robin Luk <[email protected]>
Co-developed-by: Tiwei Bie <[email protected]>
Co-developed-by: Jianfeng Tan <[email protected]>
Co-developed-by: liushi.ls <[email protected]>
Co-developed-by: Yong He <[email protected]>
Co-developed-by: Aaron Lu <[email protected]>
Co-developed-by: chris.zn <[email protected]>
Co-developed-by: Lai Jiangshan <[email protected]>
Co-developed-by: Lingfu <[email protected]>
Co-developed-by: Min Le <[email protected]>
Co-developed-by: Quan Xu <[email protected]>
Co-developed-by: Zhang Haoyu <[email protected]>
Co-developed-by: Dawei Shen <[email protected]>
Signed-off-by: Tianyu Zhou <[email protected]>
2026-06-25 10:27:28 +08:00

209 lines
6.3 KiB
Go

// Copyright 2026 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package slimvm
import (
"fmt"
"sort"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/hostarch"
"gvisor.dev/gvisor/pkg/hostsyscall"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/ring0"
)
type region struct {
virtual uintptr
length uintptr
}
type physicalRegion struct {
region
physical uintptr
}
// physicalRegions contains a list of available physical regions.
//
// The physical value used in physicalRegions is a number indicating the
// physical offset, aligned appropriately and starting above reservedMemory.
var physicalRegions []physicalRegion
// fillAddressSpace fills the host address space with PROT_NONE mappings until
// the number of available bits until we have a host address space size that is
// equal to the physical address space.
//
// The excluded regions are returned.
func fillAddressSpace() (excludedRegions []region) {
// We can cut vSize in half, because the kernel will be using the top
// half and we ignore it while constructing mappings. It's as if we've
// already excluded half the possible addresses.
vSize := ring0.UserspaceSize
// We exclude reservedMemory below from our physical memory size, so it
// needs to be dropped here as well. Otherwise, we could end up with
// physical addresses that are beyond what is mapped.
pSize := uintptr(1) << ring0.PhysicalAddressBits
pSize -= reservedMemory
// Sanity check.
if vSize < pSize {
return excludedRegions
}
// Calculate the required space and fill it.
//
// Virtual address range and physical address range should aligned with huge page size.
requiredAddr, ok := hostarch.Addr(vSize - pSize + hostarch.HugePageSize).RoundUp()
if !ok {
panic(fmt.Sprintf(
"overflow for vSize (%x) - pSize (%x) + HugePageSize(%x)",
vSize, pSize, hostarch.HugePageSize))
}
required := uintptr(requiredAddr)
current := required // Attempted mmap size.
for filled := uintptr(0); filled < required && current > 0; {
addr, errno := hostsyscall.RawSyscall6(
unix.SYS_MMAP,
0, // Suggested address.
current,
unix.PROT_NONE,
unix.MAP_ANONYMOUS|unix.MAP_PRIVATE|unix.MAP_NORESERVE,
0, 0)
if errno != 0 {
// One page is the smallest mapping that can be allocated.
// Without this guard, hostarch.Addr(PageSize>>1).RoundUp()
// rounds back up to PageSize, leaving current stuck and the
// loop never terminating (mirrors the equivalent guard in
// the kvm platform's fillAddressSpace).
if current == hostarch.PageSize {
current = 0
continue
}
// Attempt half the size; overflow not possible.
currentAddr, _ := hostarch.Addr(current >> 1).RoundUp()
current = uintptr(currentAddr)
continue
}
// We filled a block.
filled += current
excludedRegions = append(excludedRegions, region{
virtual: addr,
length: current,
})
// See comment above.
if filled != required {
required += hostarch.HugePageSize
}
}
if current == 0 {
panic("filling address space failed")
}
sort.Slice(excludedRegions, func(i, j int) bool {
return excludedRegions[i].virtual < excludedRegions[j].virtual
})
for _, r := range excludedRegions {
log.Infof("region: virtual [%x,%x)", r.virtual, r.virtual+r.length)
}
return excludedRegions
}
// computePhysicalRegions computes physical regions.
func computePhysicalRegions(excludedRegions []region) (physicalRegions []physicalRegion) {
physical := uintptr(reservedMemory)
addValidRegion := func(virtual, length uintptr) {
if length == 0 {
return
}
if virtual == 0 {
virtual += hostarch.PageSize
length -= hostarch.PageSize
}
if end := virtual + length; end > ring0.MaximumUserAddress {
length -= (end - ring0.MaximumUserAddress)
}
if length == 0 {
return
}
// Round physical up to the same alignment as the virtual
// address (with respect to faultBlockSize).
if offset := virtual &^ hostarch.JumboPageMask; physical&^hostarch.JumboPageMask != offset {
if newPhysical := (physical & hostarch.JumboPageMask) + offset; newPhysical > physical {
physical = newPhysical // Round up by only a little bit.
} else {
physical = ((physical + hostarch.JumboPageSize) & hostarch.JumboPageMask) + offset
}
}
physicalRegions = append(physicalRegions, physicalRegion{
region: region{
virtual: virtual,
length: length,
},
physical: physical,
})
physical += length
}
lastExcludedEnd := uintptr(0)
for _, r := range excludedRegions {
addValidRegion(lastExcludedEnd, r.virtual-lastExcludedEnd)
lastExcludedEnd = r.virtual + r.length
}
addValidRegion(lastExcludedEnd, ring0.MaximumUserAddress-lastExcludedEnd)
// Dump our all physical regions.
for _, r := range physicalRegions {
log.Infof("physicalRegion: virtual [%x,%x) => physical [%x,%x)",
r.virtual, r.virtual+r.length, r.physical, r.physical+r.length)
}
return physicalRegions
}
// physicalInit initializes physical address mappings.
func physicalInit() {
physicalRegions = computePhysicalRegions(fillAddressSpace())
}
// applyPhysicalRegions applies the given function on physical regions.
//
// Iteration continues as long as true is returned. The return value is the
// return from the last call to fn, or true if there are no entries.
//
// Precondition: physicalInit must have been called.
func applyPhysicalRegions(fn func(pr physicalRegion) bool) bool {
for _, pr := range physicalRegions {
if !fn(pr) {
return false
}
}
return true
}
// translateToPhysical translates the given virtual address.
//
// Precondition: physicalInit must have been called.
//
//go:nosplit
func translateToPhysical(virtual uintptr) (physical uintptr, length uintptr, ok bool) {
for _, pr := range physicalRegions {
if pr.virtual <= virtual && virtual < pr.virtual+pr.length {
physical = pr.physical + (virtual - pr.virtual)
length = pr.length - (virtual - pr.virtual)
ok = true
return
}
}
return
}