Skip to content

Commit

Permalink
ktime: support varying Timer implementations
Browse files Browse the repository at this point in the history
- Rename Timer to SampledTimer.

- Move all Clock methods except Now to new interface SampledClock.

- Move SampledTimer's exported methods (except SetClock) to new interface
  Timer. Combine Swap and SwapAnd into Set to reduce the number of redundant
  methods that must be implemented.

- Add interface method Clock.NewTimer.

This is in preparation for cl/693856539, which adds a second Timer
implementation.

PiperOrigin-RevId: 694299679
  • Loading branch information
nixprime authored and gvisor-bot committed Nov 8, 2024
1 parent 155cdcb commit 2e6cfa7
Show file tree
Hide file tree
Showing 15 changed files with 514 additions and 414 deletions.
12 changes: 11 additions & 1 deletion pkg/sentry/contexttest/contexttest.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ func (*globalUniqueIDProvider) UniqueID() uint64 {
// inotify cookies.
var lastInotifyCookie atomicbitops.Uint32

// hostClock implements ktime.Clock.
// hostClock implements ktime.SampledClock.
type hostClock struct {
ktime.WallRateClock
ktime.NoClockEvents
Expand All @@ -103,6 +103,16 @@ func (*hostClock) Now() ktime.Time {
return ktime.FromNanoseconds(time.Now().UnixNano())
}

// SupportsTimers implements ktime.Clock.Now.
func (*hostClock) SupportsTimers() bool {
return true
}

// NewTimer implements ktime.Clock.NewTimer.
func (c *hostClock) NewTimer(l ktime.Listener) ktime.Timer {
return ktime.NewSampledTimer(c, l)
}

// RegisterValue registers additional values with this test context. Useful for
// providing values from external packages that contexttest can't depend on.
func (t *TestContext) RegisterValue(key, value any) {
Expand Down
6 changes: 3 additions & 3 deletions pkg/sentry/fsimpl/timerfd/timerfd.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ type TimerFileDescription struct {
vfs.NoLockFD

events waiter.Queue
timer *ktime.Timer
timer ktime.Timer

// val is the number of timer expirations since the last successful
// call to PRead, or SetTime. val must be accessed using atomic memory
Expand All @@ -53,7 +53,7 @@ func New(ctx context.Context, vfsObj *vfs.VirtualFilesystem, clock ktime.Clock,
vd := vfsObj.NewAnonVirtualDentry("[timerfd]")
defer vd.DecRef(ctx)
tfd := &TimerFileDescription{}
tfd.timer = ktime.NewTimer(clock, tfd)
tfd.timer = clock.NewTimer(tfd)
if err := tfd.vfsfd.Init(tfd, flags, vd.Mount(), vd.Dentry(), &vfs.FileDescriptionOptions{
UseDentryMetadata: true,
DenyPRead: true,
Expand Down Expand Up @@ -98,7 +98,7 @@ func (tfd *TimerFileDescription) GetTime() (ktime.Time, ktime.Setting) {
// of expirations to 0, and returns the previous setting and the time at which
// it was observed.
func (tfd *TimerFileDescription) SetTime(s ktime.Setting) (ktime.Time, ktime.Setting) {
return tfd.timer.SwapAnd(s, func() { tfd.val.Store(0) })
return tfd.timer.Set(s, func() { tfd.val.Store(0) })
}

// Readiness implements waiter.Waitable.Readiness.
Expand Down
4 changes: 2 additions & 2 deletions pkg/sentry/kernel/kernel.go
Original file line number Diff line number Diff line change
Expand Up @@ -1634,12 +1634,12 @@ func (k *Kernel) ApplicationCores() uint {
}

// RealtimeClock returns the application CLOCK_REALTIME clock.
func (k *Kernel) RealtimeClock() ktime.Clock {
func (k *Kernel) RealtimeClock() ktime.SampledClock {
return k.timekeeper.realtimeClock
}

// MonotonicClock returns the application CLOCK_MONOTONIC clock.
func (k *Kernel) MonotonicClock() ktime.Clock {
func (k *Kernel) MonotonicClock() ktime.SampledClock {
return k.timekeeper.monotonicClock
}

Expand Down
6 changes: 3 additions & 3 deletions pkg/sentry/kernel/posixtimer.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import (
//
// +stateify savable
type IntervalTimer struct {
timer *ktime.Timer
timer ktime.Timer

// If target is not nil, it receives signo from timer expirations. If group
// is true, these signals are thread-group-directed. These fields are
Expand Down Expand Up @@ -215,7 +215,7 @@ func (t *Task) IntervalTimerCreate(c ktime.Clock, sigev *linux.Sigevent) (linux.
return 0, linuxerr.EINVAL
}
}
it.timer = ktime.NewTimer(c, it)
it.timer = c.NewTimer(it)

t.tg.timers[id] = it
return id, nil
Expand Down Expand Up @@ -247,7 +247,7 @@ func (t *Task) IntervalTimerSettime(id linux.TimerID, its linux.Itimerspec, abs
if err != nil {
return linux.Itimerspec{}, err
}
tm, oldS := it.timer.SwapAnd(newS, it.timerSettingChanged)
tm, oldS := it.timer.Set(newS, it.timerSettingChanged)
its = ktime.ItimerspecFromSetting(tm, oldS)
return its, nil
}
Expand Down
10 changes: 6 additions & 4 deletions pkg/sentry/kernel/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -560,12 +560,14 @@ type Task struct {
// copyScratchBuffer is exclusive to the task goroutine.
copyScratchBuffer [copyScratchBufferLen]byte `state:"nosave"`

// blockingTimer is used for blocking timeouts. blockingTimerChan is the
// channel that is sent to when blockingTimer fires.
// blockingTimer is used for blocking timeouts from ktime.SampledClocks.
// blockingTimerListener sends to blockingTimerChan when blockingTimer
// expires.
//
// blockingTimer is exclusive to the task goroutine.
blockingTimer *ktime.Timer `state:"nosave"`
blockingTimerChan <-chan struct{} `state:"nosave"`
blockingTimer *ktime.SampledTimer `state:"nosave"`
blockingTimerListener ktime.Listener `state:"nosave"`
blockingTimerChan <-chan struct{} `state:"nosave"`

// futexWaiter is used for futex(FUTEX_WAIT) syscalls.
//
Expand Down
2 changes: 1 addition & 1 deletion pkg/sentry/kernel/task_acct.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func (t *Task) Setitimer(id int32, newitv linux.ItimerVal) (linux.ItimerVal, err
if err != nil {
return linux.ItimerVal{}, err
}
tm, olds = t.tg.itimerRealTimer.Swap(news)
tm, olds = t.tg.itimerRealTimer.Set(news, nil)
case linux.ITIMER_VIRTUAL:
c := t.tg.UserCPUClock()
t.k.cpuClockMu.Lock()
Expand Down
46 changes: 39 additions & 7 deletions pkg/sentry/kernel/task_block.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func (t *Task) BlockWithTimeout(C chan struct{}, haveTimeout bool, timeout time.
clock := t.Kernel().MonotonicClock()
start := clock.Now()
deadline := start.Add(timeout)
err := t.BlockWithDeadlineFrom(C, clock, true, deadline)
err := t.blockWithDeadlineFromSampledClock(C, clock, deadline)

// Timeout, explicitly return a remaining duration of 0.
if linuxerr.Equals(linuxerr.ETIMEDOUT, err) {
Expand Down Expand Up @@ -81,7 +81,10 @@ func (t *Task) BlockWithTimeoutOn(w waiter.Waitable, mask waiter.EventMask, time
//
// Preconditions: The caller must be running on the task goroutine.
func (t *Task) BlockWithDeadline(C <-chan struct{}, haveDeadline bool, deadline ktime.Time) error {
return t.BlockWithDeadlineFrom(C, t.Kernel().MonotonicClock(), haveDeadline, deadline)
if !haveDeadline {
return t.block(C, nil)
}
return t.blockWithDeadlineFromSampledClock(C, t.Kernel().MonotonicClock(), deadline)
}

// BlockWithDeadlineFrom is similar to BlockWithDeadline, except it uses the
Expand All @@ -95,6 +98,33 @@ func (t *Task) BlockWithDeadlineFrom(C <-chan struct{}, clock ktime.Clock, haveD
return t.block(C, nil)
}

if c, ok := clock.(ktime.SampledClock); ok {
return t.blockWithDeadlineFromSampledClock(C, c, deadline)
}

// Start the timeout timer.
timer := clock.NewTimer(t.blockingTimerListener)
defer timer.Destroy()
timer.Set(ktime.Setting{
Enabled: true,
Next: deadline,
}, nil)

err := t.block(C, t.blockingTimerChan)

// Stop the timeout timer and drain the channel. If s.Enabled is true, the
// timer didn't fire yet, so t.blockingTimerChan must be empty.
if _, s := timer.Set(ktime.Setting{}, nil); !s.Enabled {
select {
case <-t.blockingTimerChan:
default:
}
}

return err
}

func (t *Task) blockWithDeadlineFromSampledClock(C <-chan struct{}, clock ktime.SampledClock, deadline ktime.Time) error {
// Start the timeout timer.
t.blockingTimer.SetClock(clock, ktime.Setting{
Enabled: true,
Expand All @@ -103,11 +133,13 @@ func (t *Task) BlockWithDeadlineFrom(C <-chan struct{}, clock ktime.Clock, haveD

err := t.block(C, t.blockingTimerChan)

// Stop the timeout timer and drain the channel.
t.blockingTimer.Swap(ktime.Setting{})
select {
case <-t.blockingTimerChan:
default:
// Stop the timeout timer and drain the channel. If s.Enabled is true, the
// timer didn't fire yet, so t.blockingTimerChan must be empty.
if _, s := t.blockingTimer.Set(ktime.Setting{}, nil); !s.Enabled {
select {
case <-t.blockingTimerChan:
default:
}
}

return err
Expand Down
5 changes: 2 additions & 3 deletions pkg/sentry/kernel/task_run.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,9 @@ func (t *Task) run(threadID uintptr) {
// Construct t.blockingTimer here. We do this here because we can't
// reconstruct t.blockingTimer during restore in Task.afterLoad(), because
// kernel.timekeeper.SetClocks() hasn't been called yet.
blockingTimerNotifier, blockingTimerChan := ktime.NewChannelNotifier()
t.blockingTimer = ktime.NewTimer(t.k.MonotonicClock(), blockingTimerNotifier)
t.blockingTimerListener, t.blockingTimerChan = ktime.NewChannelNotifier()
t.blockingTimer = ktime.NewSampledTimer(t.k.MonotonicClock(), t.blockingTimerListener)
defer t.blockingTimer.Destroy()
t.blockingTimerChan = blockingTimerChan

// Activate our address space.
t.Activate()
Expand Down
10 changes: 10 additions & 0 deletions pkg/sentry/kernel/task_sched.go
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,11 @@ func (tc *taskClock) Now() ktime.Time {
return ktime.FromNanoseconds(stats.UserTime.Nanoseconds())
}

// NewTimer implements ktime.Clock.NewTimer.
func (tc *taskClock) NewTimer(l ktime.Listener) ktime.Timer {
return ktime.NewSampledTimer(tc, l)
}

// tgClock is a ktime.Clock that measures the time a thread group has spent
// executing. tgClock is primarily used to implement CLOCK_PROCESS_CPUTIME_ID.
//
Expand All @@ -296,6 +301,11 @@ func (tgc *tgClock) Now() ktime.Time {
return ktime.FromNanoseconds(stats.UserTime.Nanoseconds())
}

// NewTimer implements ktime.Clock.NewTimer.
func (tgc *tgClock) NewTimer(l ktime.Listener) ktime.Timer {
return ktime.NewSampledTimer(tgc, l)
}

// WallTimeUntil implements ktime.Clock.WallTimeUntil.
func (tgc *tgClock) WallTimeUntil(t, now ktime.Time) time.Duration {
// Thread group CPU time should not exceed wall time * live tasks, since
Expand Down
4 changes: 2 additions & 2 deletions pkg/sentry/kernel/thread_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ type ThreadGroup struct {
timerMu threadGroupTimerMutex `state:"nosave"`

// itimerRealTimer implements ITIMER_REAL for the thread group.
itimerRealTimer *ktime.Timer
itimerRealTimer *ktime.SampledTimer

// itimerVirtSetting is the ITIMER_VIRTUAL setting for the thread group.
//
Expand Down Expand Up @@ -295,7 +295,7 @@ func (k *Kernel) NewThreadGroup(pidns *PIDNamespace, sh *SignalHandlers, termina
ioUsage: &usage.IO{},
limits: limits,
}
tg.itimerRealTimer = ktime.NewTimer(k.timekeeper.monotonicClock, &itimerRealListener{tg: tg})
tg.itimerRealTimer = ktime.NewSampledTimer(k.timekeeper.monotonicClock, &itimerRealListener{tg: tg})
tg.timers = make(map[linux.TimerID]*IntervalTimer)
tg.oldRSeqCritical.Store(&OldRSeqCriticalRegion{})
return tg
Expand Down
9 changes: 7 additions & 2 deletions pkg/sentry/kernel/timekeeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -325,15 +325,15 @@ func (t *Timekeeper) BootTime() ktime.Time {
return t.bootTime
}

// timekeeperClock is a ktime.Clock that reads time from a
// timekeeperClock is a ktime.SampledClock that reads time from a
// kernel.Timekeeper-managed clock.
//
// +stateify savable
type timekeeperClock struct {
tk *Timekeeper
c sentrytime.ClockID

// Implements ktime.Clock.WallTimeUntil.
// Implements ktime.SampledClock.WallTimeUntil.
ktime.WallRateClock `state:"nosave"`

// Implements waiter.Waitable. (We have no ability to detect
Expand All @@ -349,3 +349,8 @@ func (tc *timekeeperClock) Now() ktime.Time {
}
return ktime.FromNanoseconds(now)
}

// NewTimer implements ktime.Clock.NewTimer.
func (tc *timekeeperClock) NewTimer(l ktime.Listener) ktime.Timer {
return ktime.NewSampledTimer(tc, l)
}
11 changes: 6 additions & 5 deletions pkg/sentry/ktime/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ package(
)

go_template_instance(
name = "seqatomic_clock",
out = "seqatomic_clock_unsafe.go",
name = "seqatomic_sampled_clock",
out = "seqatomic_sampled_clock_unsafe.go",
package = "ktime",
suffix = "Clock",
suffix = "SampledClock",
template = "//pkg/sync/seqatomic:generic_seqatomic",
types = {
"Value": "Clock",
"Value": "SampledClock",
},
)

Expand All @@ -22,7 +22,8 @@ go_library(
srcs = [
"context.go",
"ktime.go",
"seqatomic_clock_unsafe.go",
"sampled_timer.go",
"seqatomic_sampled_clock_unsafe.go",
"util.go",
],
visibility = ["//pkg/sentry:internal"],
Expand Down
Loading

0 comments on commit 2e6cfa7

Please sign in to comment.