Files
lyphyserandHanzo Dev 26b6f703f1 systrap: express spin/deep-sleep timeouts as durations, fix arm64 idle CPU
The deepSleepTimeout/handshakeTimeout thresholds bound how long stub
threads and the dispatcher spin before deep-sleeping. They were hardcoded
in cputicks() units tuned for the amd64 TSC (~2GHz), e.g. 40000 ticks for
"20µs". But cputicks() advances at a platform-specific rate unrelated to
the CPU clock: on arm64 it reads CNTVCT_EL0, which runs at CNTFRQ_EL0 --
anywhere from tens of MHz up to ~1GHz depending on the core -- so a fixed
tick count maps to a different wall-clock duration there. On a tens-of-MHz
part, 40000 ticks is ~2ms (~100x the intended 20µs), so idle stub threads
and the dispatcher spin far too long before sleeping, burning CPU on
otherwise idle containers (issue #13361).

Neither RDTSC nor CNTVCT_EL0 is an instruction/cycle counter; both are
constant-rate time references at frequencies that differ per platform, so
the timeouts are fundamentally durations. Express them once as nanosecond
durations and convert to cputicks() units via a new per-arch
cputicksFreq():
  - arm64 reads CNTFRQ_EL0 (exact, no calibration).
  - amd64 returns the nominal 2GHz the constants were tuned against, so
    amd64 behavior is unchanged.

initSleepTimeouts() performs the conversion once in New(), before
stubInit() copies deepSleepTimeout into the stub.

Co-authored-by: Hanzo Dev <[email protected]>
2026-06-16 19:38:17 +00:00

60 lines
2.3 KiB
Go

// Copyright 2019 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 systrap
import (
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/sentry/arch"
)
// getCNTFRQ returns the frequency (in Hz) of the system counter read by
// cputicks(), as reported by CNTFRQ_EL0.
func getCNTFRQ() int64
// typicalCNTFRQ is a fallback system counter frequency (24MHz, a common
// reference-crystal value) used only if CNTFRQ_EL0 reads back as zero. The
// architecture requires firmware to program CNTFRQ_EL0, so real hardware always
// reports a non-zero value; this only guards against a misconfigured board.
// Actual frequencies vary widely -- from tens of MHz up to ~1GHz on newer
// cores -- so this is a best-effort guess, not a value that fits all hardware.
const typicalCNTFRQ = 24 * 1000 * 1000
// cputicksFreq returns the frequency in Hz of the counter read by cputicks().
// On arm64 cputicks() reads CNTVCT_EL0, whose frequency is reported exactly by
// CNTFRQ_EL0, so no calibration is needed. It always returns a non-zero value:
// the sleep-timeout conversion needs a frequency, and there is no sane
// cross-platform default (the counter rate is platform-specific and ranges from
// tens of MHz to ~GHz), so the fallback must be arch-specific.
func cputicksFreq() uint64 {
if freq := getCNTFRQ(); freq > 0 {
return uint64(freq)
}
return typicalCNTFRQ
}
func stackPointer(r *arch.Registers) uintptr {
return uintptr(r.Sp)
}
// configureSystrapAddressSpace overrides the default 48-bit address space
// parameters when the host uses a different VA width. On 48-bit VA hosts,
// ConfigureAddressSpace(1<<48) re-affirms the defaults.
//
// This function MUST be called during systrap initialization, before any
// Context64 is created.
func configureSystrapAddressSpace() {
arch.ConfigureAddressSpace(uintptr(linux.TaskSize))
}