From b12dd0881f3fe995516811b775f41de470c1b251 Mon Sep 17 00:00:00 2001 From: Luis Capelo Date: Wed, 5 Aug 2026 16:08:59 +0000 Subject: [PATCH] nvproxy: expose CLOCK_MONOTONIC_RAW as a distinct clock for capProfiling GPU profilers (Nsight Systems/CUPTI) build their trace timeline from CLOCK_MONOTONIC_RAW. gVisor serves CLOCK_MONOTONIC_RAW as an alias of CLOCK_MONOTONIC, this will drift from CLOCK_MONOTONIC_RAW breaking profiling. When nvproxy is enabled with `capProfiling`, this change exposes CLOCK_MONOTONIC_RAW as a clock distinct from CLOCK_MONOTONIC that tracks the host's CLOCK_MONOTONIC_RAW (absolute, unadjusted). CLOCK_MONOTONIC is unchanged. When profiling is disabled, CLOCK_MONOTONIC_RAW continues to alias CLOCK_MONOTONIC as before. --- pkg/sentry/fsimpl/testutil/kernel.go | 2 +- pkg/sentry/kernel/kernel.go | 9 ++++ pkg/sentry/kernel/timekeeper.go | 49 ++++++++++++++---- pkg/sentry/kernel/timekeeper_test.go | 58 ++++++++++++++++++++-- pkg/sentry/kernel/vdso.go | 10 ++++ pkg/sentry/syscalls/linux/sys_time.go | 5 +- pkg/sentry/time/calibrated_clock.go | 35 ++++++++++--- pkg/sentry/time/clock_id.go | 7 ++- pkg/sentry/time/clocks.go | 17 ++++++- runsc/boot/loader.go | 20 +++++++- runsc/boot/restore.go | 9 ++-- vdso/vdso.cc | 6 ++- vdso/vdso_time.cc | 71 ++++++++++++++++++++++++--- vdso/vdso_time.h | 1 + 14 files changed, 258 insertions(+), 41 deletions(-) diff --git a/pkg/sentry/fsimpl/testutil/kernel.go b/pkg/sentry/fsimpl/testutil/kernel.go index 57473375411..73c03c8e929 100644 --- a/pkg/sentry/fsimpl/testutil/kernel.go +++ b/pkg/sentry/fsimpl/testutil/kernel.go @@ -93,7 +93,7 @@ func Boot() (*kernel.Kernel, error) { // Create timekeeper. tk := kernel.NewTimekeeper() params := kernel.NewVDSOParamPage(k.MemoryFile(), vdso.ParamPage.FileRange()) - tk.SetClocks(time.NewCalibratedClocks(), params) + tk.SetClocks(time.NewCalibratedClocks(false), params) creds := auth.NewRootCredentials(auth.NewRootUserNamespace()) diff --git a/pkg/sentry/kernel/kernel.go b/pkg/sentry/kernel/kernel.go index f963b99dbcf..8e5b4ca50b5 100644 --- a/pkg/sentry/kernel/kernel.go +++ b/pkg/sentry/kernel/kernel.go @@ -1906,6 +1906,15 @@ func (k *Kernel) MonotonicClock() ktime.SampledClock { return k.timekeeper.monotonicClock } +// MonotonicRawClock returns the system CLOCK_MONOTONIC_RAW clock. When it is +// not enabled as a distinct clock this is the same as MonotonicClock. +func (k *Kernel) MonotonicRawClock() ktime.SampledClock { + if k.timekeeper.monotonicRawClock != nil { + return k.timekeeper.monotonicRawClock + } + return k.timekeeper.monotonicClock +} + // Syslog returns the syslog. func (k *Kernel) Syslog() *syslog { return &k.syslog diff --git a/pkg/sentry/kernel/timekeeper.go b/pkg/sentry/kernel/timekeeper.go index 19fd3036a49..f78c000df5c 100644 --- a/pkg/sentry/kernel/timekeeper.go +++ b/pkg/sentry/kernel/timekeeper.go @@ -59,6 +59,12 @@ type Timekeeper struct { // monotonicClock is a ktime.Clock based on timekeeper's Monotonic. monotonicClock *timekeeperClock + // monotonicRawClock is a ktime.Clock based on timekeeper's MonotonicRaw. + // It is non-nil only when the clock source tracks a distinct + // CLOCK_MONOTONIC_RAW; otherwise CLOCK_MONOTONIC_RAW aliases + // CLOCK_MONOTONIC. It is derived by SetClocks, anew on restore. + monotonicRawClock *timekeeperClock `state:"nosave"` + // bootTime is the realtime when the system "booted". i.e., when // SetClocks was called in the initial (not restored) run. bootTime ktime.Time @@ -158,6 +164,14 @@ func (t *Timekeeper) SetClocks(c sentrytime.Clocks, params *VDSOParamPage) { t.clocks = c + // Serve CLOCK_MONOTONIC_RAW as a distinct clock iff the clock source + // tracks it (see NewCalibratedClocks). Deriving this from the source keeps + // boot and restore coherent: a restored sandbox follows its current + // configuration, not the checkpointed one. + if _, err := c.GetTime(sentrytime.MonotonicRaw); err == nil { + t.monotonicRawClock = &timekeeperClock{tk: t, c: sentrytime.MonotonicRaw} + } + // Compute the offset of the monotonic clock from the base Clocks. // // In a fresh (not restored) sentry, monotonic time starts at zero. @@ -213,20 +227,31 @@ func (t *Timekeeper) update(parked bool) { // Call Update within a Write block to prevent the VDSO from using the old // params between Update and Write. if err := t.params.Write(func() vdsoParams { - monotonicParams, monotonicOk, realtimeParams, realtimeOk := t.clocks.Update(parked) + res := t.clocks.Update(parked) var p vdsoParams - if monotonicOk { + if res.MonotonicOk { p.monotonicReady = 1 - p.monotonicBaseCycles = int64(monotonicParams.BaseCycles) - p.monotonicBaseRef = int64(monotonicParams.BaseRef) + t.monotonicOffset - p.monotonicFrequency = monotonicParams.Frequency + p.monotonicBaseCycles = int64(res.Monotonic.BaseCycles) + p.monotonicBaseRef = int64(res.Monotonic.BaseRef) + t.monotonicOffset + p.monotonicFrequency = res.Monotonic.Frequency } - if realtimeOk { + if res.RealtimeOk { p.realtimeReady = 1 - p.realtimeBaseCycles = int64(realtimeParams.BaseCycles) - p.realtimeBaseRef = int64(realtimeParams.BaseRef) - p.realtimeFrequency = realtimeParams.Frequency + p.realtimeBaseCycles = int64(res.Realtime.BaseCycles) + p.realtimeBaseRef = int64(res.Realtime.BaseRef) + p.realtimeFrequency = res.Realtime.Frequency + } + if t.monotonicRawClock != nil { + // Raw tracks absolute host CLOCK_MONOTONIC_RAW, so unlike + // monotonic its base ref is published without monotonicOffset. + p.monotonicRawEnabled = 1 + if res.MonotonicRawOk { + p.monotonicRawReady = 1 + p.monotonicRawBaseCycles = int64(res.MonotonicRaw.BaseCycles) + p.monotonicRawBaseRef = int64(res.MonotonicRaw.BaseRef) + p.monotonicRawFrequency = res.MonotonicRaw.Frequency + } } return p }); err != nil { @@ -397,6 +422,12 @@ func (t *Timekeeper) GetTime(c sentrytime.ClockID) (int64, error) { } <-t.restored } + if c == sentrytime.MonotonicRaw && t.monotonicRawClock == nil { + // Alias of CLOCK_MONOTONIC when the clock source does not track a + // distinct CLOCK_MONOTONIC_RAW. This also covers raw clock users + // restored from a checkpoint whose source tracked it. + c = sentrytime.Monotonic + } // update the calibration if needed and keep the timekeeper calibrated // during the read diff --git a/pkg/sentry/kernel/timekeeper_test.go b/pkg/sentry/kernel/timekeeper_test.go index 2014067e2ac..1f67253c9fc 100644 --- a/pkg/sentry/kernel/timekeeper_test.go +++ b/pkg/sentry/kernel/timekeeper_test.go @@ -28,13 +28,15 @@ import ( // mockClocks is a sentrytime.Clocks that simply returns the times in the // struct. type mockClocks struct { - monotonic int64 - realtime int64 + monotonic int64 + realtime int64 + monotonicRaw int64 + monotonicRawEnabled bool } // Update implements sentrytime.Clocks.Update. It does nothing. -func (*mockClocks) Update(parked bool) (monotonicParams sentrytime.Parameters, monotonicOk bool, realtimeParam sentrytime.Parameters, realtimeOk bool) { - return +func (*mockClocks) Update(parked bool) sentrytime.UpdateResult { + return sentrytime.UpdateResult{} } // GetTime implements sentrytime.Clocks.GetTime. @@ -44,6 +46,11 @@ func (c *mockClocks) GetTime(id sentrytime.ClockID) (int64, error) { return c.monotonic, nil case sentrytime.Realtime: return c.realtime, nil + case sentrytime.MonotonicRaw: + if c.monotonicRawEnabled { + return c.monotonicRaw, nil + } + return 0, linuxerr.EINVAL default: return 0, linuxerr.EINVAL } @@ -64,7 +71,7 @@ func stateTestClocklessTimekeeper(tb testing.TB) (*Timekeeper, *VDSOParamPage) { func stateTestTimekeeper(tb testing.TB) *Timekeeper { t, params := stateTestClocklessTimekeeper(tb) - t.SetClocks(sentrytime.NewCalibratedClocks(), params) + t.SetClocks(sentrytime.NewCalibratedClocks(false), params) return t } @@ -153,3 +160,44 @@ func TestTimekeeperMonotonicJumpBackwards(t *testing.T) { t.Errorf("GetTime got %d want 100000", now) } } + +// TestTimekeeperMonotonicRawEnabled tests that when the clock source tracks a +// distinct CLOCK_MONOTONIC_RAW, GetTime exposes it directly (absolute, not +// starting at zero), while CLOCK_MONOTONIC is unaffected. +func TestTimekeeperMonotonicRawEnabled(t *testing.T) { + c := &mockClocks{ + monotonic: 100000, + monotonicRaw: 999999, + monotonicRawEnabled: true, + } + + tk, params := stateTestClocklessTimekeeper(t) + tk.SetClocks(c, params) + defer tk.Destroy() + + // Monotonic is unaffected: still starts at zero. + if now, err := tk.GetTime(sentrytime.Monotonic); err != nil || now != 0 { + t.Errorf("GetTime(Monotonic) got (%d, %v) want (0, nil)", now, err) + } + // MonotonicRaw exposes the raw source's absolute value. + if now, err := tk.GetTime(sentrytime.MonotonicRaw); err != nil || now != 999999 { + t.Errorf("GetTime(MonotonicRaw) got (%d, %v) want (999999, nil)", now, err) + } +} + +// TestTimekeeperMonotonicRawDisabled tests that when the clock source does not +// track CLOCK_MONOTONIC_RAW, it aliases CLOCK_MONOTONIC. +func TestTimekeeperMonotonicRawDisabled(t *testing.T) { + c := &mockClocks{ + monotonic: 100000, + } + + tk, params := stateTestClocklessTimekeeper(t) + tk.SetClocks(c, params) + defer tk.Destroy() + + // MonotonicRaw aliases Monotonic: both start at zero. + if now, err := tk.GetTime(sentrytime.MonotonicRaw); err != nil || now != 0 { + t.Errorf("GetTime(MonotonicRaw) got (%d, %v) want (0, nil)", now, err) + } +} diff --git a/pkg/sentry/kernel/vdso.go b/pkg/sentry/kernel/vdso.go index 0e2f5fd385d..fcd453528fa 100644 --- a/pkg/sentry/kernel/vdso.go +++ b/pkg/sentry/kernel/vdso.go @@ -40,6 +40,16 @@ type vdsoParams struct { realtimeBaseCycles int64 realtimeBaseRef int64 realtimeFrequency uint64 + + // monotonicRawEnabled, when non-zero, tells the VDSO that + // CLOCK_MONOTONIC_RAW is a distinct clock (described by the fields below) + // rather than an alias of CLOCK_MONOTONIC. monotonicRawReady is set only + // once it has been calibrated; until then the VDSO falls back to a syscall. + monotonicRawEnabled uint64 + monotonicRawReady uint64 + monotonicRawBaseCycles int64 + monotonicRawBaseRef int64 + monotonicRawFrequency uint64 } // VDSOParamPage manages a VDSO parameter page. diff --git a/pkg/sentry/syscalls/linux/sys_time.go b/pkg/sentry/syscalls/linux/sys_time.go index 385e09bc233..969f2346203 100644 --- a/pkg/sentry/syscalls/linux/sys_time.go +++ b/pkg/sentry/syscalls/linux/sys_time.go @@ -123,9 +123,10 @@ func getClock(t *kernel.Task, clockID int32) (ktime.Clock, error) { switch clockID { case linux.CLOCK_REALTIME, linux.CLOCK_REALTIME_COARSE: return t.Kernel().RealtimeClock(), nil + case linux.CLOCK_MONOTONIC_RAW: + return t.Kernel().MonotonicRawClock(), nil case linux.CLOCK_MONOTONIC, linux.CLOCK_MONOTONIC_COARSE, - linux.CLOCK_MONOTONIC_RAW, linux.CLOCK_BOOTTIME: - // CLOCK_MONOTONIC approximates CLOCK_MONOTONIC_RAW. + linux.CLOCK_BOOTTIME: // CLOCK_BOOTTIME is internally mapped to CLOCK_MONOTONIC, as: // - CLOCK_BOOTTIME should behave as CLOCK_MONOTONIC while also // including suspend time. diff --git a/pkg/sentry/time/calibrated_clock.go b/pkg/sentry/time/calibrated_clock.go index a73e1b8f405..8d411183cc5 100644 --- a/pkg/sentry/time/calibrated_clock.go +++ b/pkg/sentry/time/calibrated_clock.go @@ -234,22 +234,38 @@ type CalibratedClocks struct { // realtime is the realtime equivalent of monotonic. realtime *CalibratedClock + + // monotonicRaw tracks the host CLOCK_MONOTONIC_RAW clock. It is nil unless + // enabled via NewCalibratedClocks. + monotonicRaw *CalibratedClock } -// NewCalibratedClocks creates a CalibratedClocks. -func NewCalibratedClocks() *CalibratedClocks { - return &CalibratedClocks{ +// NewCalibratedClocks creates a CalibratedClocks. If monotonicRawEnabled is +// true, host CLOCK_MONOTONIC_RAW is additionally tracked as a distinct clock +// (addressable as MonotonicRaw). +func NewCalibratedClocks(monotonicRawEnabled bool) *CalibratedClocks { + c := &CalibratedClocks{ monotonic: NewCalibratedClock(Monotonic), realtime: NewCalibratedClock(Realtime), } + if monotonicRawEnabled { + c.monotonicRaw = NewCalibratedClock(MonotonicRaw) + } + return c } // Update implements Clocks.Update. -func (c *CalibratedClocks) Update(parked bool) (Parameters, bool, Parameters, bool) { - monotonicParams, monotonicOk := c.monotonic.Update(parked) - realtimeParams, realtimeOk := c.realtime.Update(parked) +func (c *CalibratedClocks) Update(parked bool) UpdateResult { + var res UpdateResult + res.Monotonic, res.MonotonicOk = c.monotonic.Update(parked) + res.Realtime, res.RealtimeOk = c.realtime.Update(parked) + if c.monotonicRaw != nil { + // Keep the raw clock calibrated and publish its parameters so the VDSO + // can serve CLOCK_MONOTONIC_RAW without a syscall. + res.MonotonicRaw, res.MonotonicRawOk = c.monotonicRaw.Update(parked) + } - return monotonicParams, monotonicOk, realtimeParams, realtimeOk + return res } // GetTime implements Clocks.GetTime. @@ -259,6 +275,11 @@ func (c *CalibratedClocks) GetTime(id ClockID) (int64, error) { return c.monotonic.GetTime() case Realtime: return c.realtime.GetTime() + case MonotonicRaw: + if c.monotonicRaw != nil { + return c.monotonicRaw.GetTime() + } + return 0, linuxerr.EINVAL default: return 0, linuxerr.EINVAL } diff --git a/pkg/sentry/time/clock_id.go b/pkg/sentry/time/clock_id.go index 724f59dd939..e79601b0880 100644 --- a/pkg/sentry/time/clock_id.go +++ b/pkg/sentry/time/clock_id.go @@ -23,8 +23,9 @@ type ClockID int32 // These are the supported Linux clock identifiers. const ( - Realtime ClockID = iota - Monotonic + Realtime ClockID = 0 + Monotonic ClockID = 1 + MonotonicRaw ClockID = 4 ) // String implements fmt.Stringer.String. @@ -34,6 +35,8 @@ func (c ClockID) String() string { return "Realtime" case Monotonic: return "Monotonic" + case MonotonicRaw: + return "MonotonicRaw" default: return strconv.Itoa(int(c)) } diff --git a/pkg/sentry/time/clocks.go b/pkg/sentry/time/clocks.go index ec69cd52a73..b4cb85a1cec 100644 --- a/pkg/sentry/time/clocks.go +++ b/pkg/sentry/time/clocks.go @@ -14,6 +14,21 @@ package time +// UpdateResult holds the timekeeping parameters produced by Clocks.Update. +// +// Each *Ok field reports whether the corresponding *Params were successfully +// calibrated and may be published to the VDSO. MonotonicRaw is only populated +// by clock sources that track a distinct CLOCK_MONOTONIC_RAW; otherwise +// MonotonicRawOk is false. +type UpdateResult struct { + Monotonic Parameters + MonotonicOk bool + Realtime Parameters + RealtimeOk bool + MonotonicRaw Parameters + MonotonicRawOk bool +} + // Clocks represents a clock source that contains both a monotonic and realtime // clock. type Clocks interface { @@ -23,7 +38,7 @@ type Clocks interface { // Update should be called at approximately ApproxUpdateInterval. // // parked indicates that the clock was not read for at least ApproxUpdateInterval - Update(parked bool) (monotonicParams Parameters, monotonicOk bool, realtimeParam Parameters, realtimeOk bool) + Update(parked bool) UpdateResult // GetTime returns the current time in nanoseconds for the given clock. // diff --git a/runsc/boot/loader.go b/runsc/boot/loader.go index c2f2575e078..724622bfca8 100644 --- a/runsc/boot/loader.go +++ b/runsc/boot/loader.go @@ -514,6 +514,24 @@ func getRootCredentials(spec *specs.Spec, conf *config.Config, userNs *auth.User return creds } +// shouldEnableClockMonotonicRaw reports whether CLOCK_MONOTONIC_RAW should be +// exposed as a distinct clock tracking the host's CLOCK_MONOTONIC_RAW, rather +// than aliasing CLOCK_MONOTONIC as it does by default. +// +// This exists for GPU profiling: profilers such as Nsight Systems/CUPTI anchor +// the GPU timeline in the host's CLOCK_MONOTONIC_RAW domain, which drifts from +// CLOCK_MONOTONIC by NTP frequency adjustment. When nvproxy grants +// CapProfiling, the sandbox must therefore serve a CLOCK_MONOTONIC_RAW in that +// same (absolute, unadjusted) domain. It is enabled only in that case; every +// other clock comment in this feature refers back here for the rationale. +func shouldEnableClockMonotonicRaw(spec *specs.Spec, conf *config.Config) bool { + if !specutils.NVProxyEnabled(spec, conf) { + return false + } + caps, err := specutils.NVProxyDriverCapsAllowed(conf) + return err == nil && caps&nvconf.CapProfiling != 0 +} + // New initializes a new kernel loader configured by spec. // New also handles setting up a kernel for restoring a container. func New(args Args) (*Loader, error) { @@ -675,7 +693,7 @@ func New(args Args) (*Loader, error) { // Create timekeeper. tk := kernel.NewTimekeeper() params := kernel.NewVDSOParamPage(l.k.MemoryFile(), vdso.ParamPage.FileRange()) - tk.SetClocks(time.NewCalibratedClocks(), params) + tk.SetClocks(time.NewCalibratedClocks(shouldEnableClockMonotonicRaw(args.Spec, args.Conf)), params) if err := enableStrace(args.Conf); err != nil { return nil, fmt.Errorf("enabling strace: %w", err) diff --git a/runsc/boot/restore.go b/runsc/boot/restore.go index d0c7d8e0c10..7d9ddb04736 100644 --- a/runsc/boot/restore.go +++ b/runsc/boot/restore.go @@ -488,16 +488,19 @@ func (r *restorer) restore(l *Loader) error { return err } - // Load the state. + // Load the state. The Timekeeper serves a distinct CLOCK_MONOTONIC_RAW + // iff the clock source tracks it (see shouldEnableClockMonotonicRaw), so a + // restored sandbox follows its current configuration. + clocks := time.NewCalibratedClocks(shouldEnableClockMonotonicRaw(l.root.spec, l.root.conf)) r.timer.Reached("loading kernel") if r.extractRootFsMode { - if err := l.k.ExtractRootfsUpperLayer(ctx, r.stateFile, r.asyncMFLoader, nil, time.NewCalibratedClocks(), r.rootFsOutputTar); err != nil { + if err := l.k.ExtractRootfsUpperLayer(ctx, r.stateFile, r.asyncMFLoader, nil, clocks, r.rootFsOutputTar); err != nil { return fmt.Errorf("failed to extract rootfs upper layer: %w", err) } r.timer.Reached("rootfs upper layer extracted") return nil } - if err := l.k.LoadFrom(ctx, r.stateFile, r.asyncMFLoader, nil, l, time.NewCalibratedClocks(), &vfs.CompleteRestoreOptions{}, r.timer.Fork("kernel load")); err != nil { + if err := l.k.LoadFrom(ctx, r.stateFile, r.asyncMFLoader, nil, l, clocks, &vfs.CompleteRestoreOptions{}, r.timer.Fork("kernel load")); err != nil { return fmt.Errorf("failed to load kernel: %w", err) } r.timer.Reached("kernel loaded") diff --git a/vdso/vdso.cc b/vdso/vdso.cc index 524c3298945..c9b9dcf9cc1 100644 --- a/vdso/vdso.cc +++ b/vdso/vdso.cc @@ -35,10 +35,12 @@ int __common_clock_gettime(clockid_t clock, struct timespec* ts) { ret = ClockRealtime(ts); break; + case CLOCK_MONOTONIC_RAW: + ret = ClockMonotonicRaw(ts); + break; + case CLOCK_BOOTTIME: // Fallthrough, CLOCK_BOOTTIME is an alias for CLOCK_MONOTONIC - case CLOCK_MONOTONIC_RAW: - // Fallthrough, CLOCK_MONOTONIC_RAW is an alias for CLOCK_MONOTONIC case CLOCK_MONOTONIC_COARSE: // Fallthrough, CLOCK_MONOTONIC_COARSE is an alias for CLOCK_MONOTONIC case CLOCK_MONOTONIC: diff --git a/vdso/vdso_time.cc b/vdso/vdso_time.cc index 1bb4bb86b63..f2f805b385c 100644 --- a/vdso/vdso_time.cc +++ b/vdso/vdso_time.cc @@ -41,6 +41,16 @@ struct params { int64_t realtime_base_cycles; int64_t realtime_base_ref; uint64_t realtime_frequency; + + // monotonic_raw_enabled, when non-zero, means CLOCK_MONOTONIC_RAW is a + // distinct clock (described by the fields below) rather than an alias of + // CLOCK_MONOTONIC. monotonic_raw_ready is set only once that clock has been + // calibrated; until then we fall back to a syscall. See ClockMonotonicRaw. + uint64_t monotonic_raw_enabled; + uint64_t monotonic_raw_ready; + int64_t monotonic_raw_base_cycles; + int64_t monotonic_raw_base_ref; + uint64_t monotonic_raw_frequency; }; // Returns a pointer to the global parameter page. @@ -91,6 +101,14 @@ inline uint64_t cycles_to_ns(uint64_t frequency, uint64_t cycles) { return ((unsigned __int128)cycles * mult) >> 32; } +// compute_time projects a calibrated clock's base to now_cycles. +inline int64_t compute_time(int64_t base_ref, int64_t base_cycles, + uint64_t frequency, int64_t now_cycles) { + int64_t delta_cycles = + (now_cycles < base_cycles) ? 0 : now_cycles - base_cycles; + return base_ref + cycles_to_ns(frequency, delta_cycles); +} + // ClockRealtime() is the VDSO implementation of clock_gettime(CLOCK_REALTIME). int ClockRealtime(struct timespec* ts) { struct params* params = get_params(); @@ -116,10 +134,8 @@ int ClockRealtime(struct timespec* ts) { return sys_clock_gettime(CLOCK_REALTIME, ts); } - int64_t delta_cycles = - (now_cycles < base_cycles) ? 0 : now_cycles - base_cycles; - int64_t now_ns = base_ref + cycles_to_ns(frequency, delta_cycles); - *ts = ns_to_timespec(now_ns); + *ts = ns_to_timespec(compute_time(base_ref, base_cycles, frequency, + now_cycles)); return 0; } @@ -149,10 +165,49 @@ int ClockMonotonic(struct timespec* ts) { return sys_clock_gettime(CLOCK_MONOTONIC, ts); } - int64_t delta_cycles = - (now_cycles < base_cycles) ? 0 : now_cycles - base_cycles; - int64_t now_ns = base_ref + cycles_to_ns(frequency, delta_cycles); - *ts = ns_to_timespec(now_ns); + *ts = ns_to_timespec(compute_time(base_ref, base_cycles, frequency, + now_cycles)); + return 0; +} + +// ClockMonotonicRaw() is the VDSO implementation of +// clock_gettime(CLOCK_MONOTONIC_RAW). +// +// When not enabled, it aliases CLOCK_MONOTONIC in the VDSO. When enabled but +// not yet calibrated, it falls back to a syscall for the brief startup/restore +// window; otherwise it is computed in the VDSO from the raw params. +int ClockMonotonicRaw(struct timespec* ts) { + struct params* params = get_params(); + uint64_t seq; + uint64_t enabled; + uint64_t ready; + int64_t base_ref; + int64_t base_cycles; + uint64_t frequency; + int64_t now_cycles; + + do { + seq = read_seqcount_begin(¶ms->seq_count); + enabled = params->monotonic_raw_enabled; + ready = params->monotonic_raw_ready; + base_ref = params->monotonic_raw_base_ref; + base_cycles = params->monotonic_raw_base_cycles; + frequency = params->monotonic_raw_frequency; + now_cycles = cycle_clock(); + } while (read_seqcount_retry(¶ms->seq_count, seq)); + + if (!enabled) { + // CLOCK_MONOTONIC_RAW aliases CLOCK_MONOTONIC, served in the VDSO. + return ClockMonotonic(ts); + } + if (!ready) { + // The sandbox kernel ensures that we won't compute a time later than this + // once the params are ready. + return sys_clock_gettime(CLOCK_MONOTONIC_RAW, ts); + } + + *ts = ns_to_timespec(compute_time(base_ref, base_cycles, frequency, + now_cycles)); return 0; } diff --git a/vdso/vdso_time.h b/vdso/vdso_time.h index 70d079efc9b..178f0ea9dab 100644 --- a/vdso/vdso_time.h +++ b/vdso/vdso_time.h @@ -21,6 +21,7 @@ namespace vdso { int ClockRealtime(struct timespec* ts); int ClockMonotonic(struct timespec* ts); +int ClockMonotonicRaw(struct timespec* ts); } // namespace vdso