kvm: map entrie sentry address space if the kernel is 6.9 or higher

Before th 6.9 kernel we couldn't map the entire sentry address space into VM,
because there were a two-byte overhead per page in the kernel. This issue was
fixed by a364c014a2c1 ("kvm/x86: allocate the write-tracking metadata
on-demand").

KVM benchmark results:
cpu: 11th Gen Intel(R) Core(TM) i7-1185G7 @ 3.00GHz
                             │    before    │                after                │
                             │    sec/op    │    sec/op     vs base               │
ApplicationSyscall-4           336.5n ± ∞ ¹   337.9n ± ∞ ¹        ~ (p=0.786 n=5)
KernelSyscall-4                3.400µ ± ∞ ¹   3.185µ ± ∞ ¹   -6.32% (p=0.008 n=5)
SentrySyscall-4                324.4n ± ∞ ¹   206.1n ± ∞ ¹  -36.47% (p=0.008 n=5)
HostMMap-4                     6.703µ ± ∞ ¹   2.765µ ± ∞ ¹  -58.75% (p=0.008 n=5)
KernelVDSO-4                   34.59n ± ∞ ¹   33.61n ± ∞ ¹        ~ (p=0.056 n=5)
WorldSwitchToUserRoundtrip-4   5.093µ ± ∞ ¹   3.565µ ± ∞ ¹  -30.00% (p=0.008 n=5)
geomean                        871.5n         647.2n        -25.74%
¹ need >= 6 samples for confidence interval at level 0.95
PiperOrigin-RevId: 667725254
This commit is contained in:
Andrei Vagin
2024-08-26 14:56:00 -07:00
committed by gVisor bot
parent e29e270730
commit ccc5642b1d
5 changed files with 60 additions and 9 deletions
+1
View File
@@ -67,6 +67,7 @@ go_library(
"//pkg/cpuid",
"//pkg/fd",
"//pkg/hostarch",
"//pkg/hostos",
"//pkg/hosttid",
"//pkg/log",
"//pkg/metric",
+2 -2
View File
@@ -21,14 +21,14 @@ import (
"gvisor.dev/gvisor/pkg/hostarch"
)
const (
var (
// faultBlockSize is the size used for servicing memory faults.
//
// This should be large enough to avoid frequent faults and avoid using
// all available KVM slots (~512), but small enough that KVM does not
// complain about slot sizes (~4GB). See handleBluepillFault for how
// this block is used.
faultBlockSize = 2 << 30
faultBlockSize = uintptr(2 << 30)
// faultBlockMask is the mask for the fault blocks.
//
+3 -3
View File
@@ -59,7 +59,7 @@ func testSafecopy(t *testing.T, mapSize uintptr, fileSize uintptr, testFunc func
uintptr(memfile.Fd()),
0)
if errno != 0 {
t.Errorf("error mapping file: %v", errno)
t.Fatalf("error mapping file: %v", errno)
}
mappings[i] = addr
testFunc(t, c, addr)
@@ -68,8 +68,9 @@ func testSafecopy(t *testing.T, mapSize uintptr, fileSize uintptr, testFunc func
})
}
var mapSize = faultBlockSize
func TestSafecopySigbus(t *testing.T) {
mapSize := uintptr(faultBlockSize)
fileSize := mapSize - hostarch.PageSize
buf := make([]byte, hostarch.PageSize)
testSafecopy(t, mapSize, fileSize, func(t *testing.T, c *vCPU, addr uintptr) {
@@ -83,7 +84,6 @@ func TestSafecopySigbus(t *testing.T) {
}
func TestSafecopy(t *testing.T) {
mapSize := uintptr(faultBlockSize)
fileSize := mapSize
testSafecopy(t, mapSize, fileSize, func(t *testing.T, c *vCPU, addr uintptr) {
want := uint32(0x12345678)
+28
View File
@@ -517,6 +517,34 @@ func BenchmarkKernelSyscall(b *testing.B) {
})
}
func BenchmarkSentrySyscall(b *testing.B) {
// Note that the target passed here is irrelevant, we never execute SwitchToUser.
applicationTest(b, true, testutil.AddrOfGetpid(), func(c *vCPU, regs *arch.Registers, pt *pagetables.PageTables) bool {
// iteration does not include machine.Get() / machine.Put().
for i := 0; i < b.N; i++ {
testutil.Getpid()
}
return false
})
}
func BenchmarkHostMMap(b *testing.B) {
kvmTest(b, nil, func(c *vCPU) bool {
// iteration does not include machine.Get() / machine.Put().
for i := 0; i < b.N; i++ {
addr, _, errno := unix.Syscall6(unix.SYS_MMAP, 0, hostarch.PageSize, unix.PROT_READ|unix.PROT_WRITE, unix.MAP_ANONYMOUS|unix.MAP_PRIVATE, 0, 0)
if errno != 0 {
b.Fatalf("mmap failed: %s", errno)
}
_, _, errno = unix.Syscall(unix.SYS_MUNMAP, addr, hostarch.PageSize, 0)
if errno != 0 {
b.Fatalf("munmap failed: %s", errno)
}
}
return false
})
}
func BenchmarkKernelVDSO(b *testing.B) {
// Note that the target passed here is irrelevant, we never execute SwitchToUser.
applicationTest(b, true, testutil.AddrOfGetpid(), func(c *vCPU, regs *arch.Registers, pt *pagetables.PageTables) bool {
+26 -4
View File
@@ -25,6 +25,7 @@ import (
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/atomicbitops"
"gvisor.dev/gvisor/pkg/hostarch"
"gvisor.dev/gvisor/pkg/hostos"
"gvisor.dev/gvisor/pkg/hosttid"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/metric"
@@ -296,9 +297,25 @@ func newMachine(vm int) (*machine, error) {
m.upperSharedPageTables.MarkReadOnlyShared()
m.kernel.PageTables = pagetables.NewWithUpper(newAllocator(), m.upperSharedPageTables, ring0.KernelStartAddress)
// Install seccomp rules to trap runtime mmap system calls. They will
// be handled by seccompMmapHandler.
seccompMmapRules(m)
// Before the 6.9 kernel we couldn't map the entire sentry
// address space into VM, because there were a two-byte overhead
// per page in the kernel. This issue was fixed by a364c014a2c1
// ("kvm/x86: allocate the write-tracking metadata on-demand").
kernelVersion, err := hostos.KernelVersion()
if err != nil {
return nil, err
}
mapEntireAddressSpace := runtime.GOARCH != "amd64" || kernelVersion.AtLeast(6, 9)
if mapEntireAddressSpace {
// Increase faultBlockSize to be sure that we will not reach the limit.
// faultBlockSize has to equal or less than KVM_MEM_MAX_NR_PAGES.
faultBlockSize = uintptr(1) << 42
faultBlockMask = ^uintptr(faultBlockSize - 1)
} else {
// Install seccomp rules to trap runtime mmap system calls. They will
// be handled by seccompMmapHandler.
seccompMmapRules(m)
}
// Apply the physical mappings. Note that these mappings may point to
// guest physical addresses that are not actually available. These
@@ -352,6 +369,7 @@ func newMachine(vm int) (*machine, error) {
// seccompMmapHandler, so here we have to guarantee that mmap is not
// called while we hold the slot spinlock.
disableAsyncPreemption()
applyVirtualRegions(func(vr virtualRegion) {
if excludeVirtualRegion(vr) {
return // skip region.
@@ -365,8 +383,12 @@ func newMachine(vm int) (*machine, error) {
mapRegion(vr, 0)
})
if mapEntireAddressSpace {
for _, r := range physicalRegions {
m.mapPhysical(r.physical, r.length, physicalRegions)
}
}
enableAsyncPreemption()
// Initialize architecture state.
if err := m.initArchState(); err != nil {
m.Destroy()