From ca55c18a31789b8f2541d5d3b90e2af012d3e6ef Mon Sep 17 00:00:00 2001 From: Jamie Liu Date: Thu, 28 Oct 2021 16:52:28 -0700 Subject: Use Task blocking timer for nanosleep(2). kernel/time.Timer allocation is expensive and not sync.Poolable (since time.Timer only supports notification through a channel, requiring a goroutine to receive from the channel, and sync.Pool doesn't invoke any kind of cleanup on discarded items in the pool so it would leak timer goroutines). Using the existing Task.blockingTimer for nanosleep(), and applicable cases in clock_nanosleep(), at least avoids Timer allocation in common cases. PiperOrigin-RevId: 406248394 --- pkg/sentry/syscalls/linux/sys_time.go | 139 +++++++++++++++++----------------- 1 file changed, 71 insertions(+), 68 deletions(-) diff --git a/pkg/sentry/syscalls/linux/sys_time.go b/pkg/sentry/syscalls/linux/sys_time.go index 4adc8b8a4..134b66dac 100644 --- a/pkg/sentry/syscalls/linux/sys_time.go +++ b/pkg/sentry/syscalls/linux/sys_time.go @@ -175,73 +175,6 @@ func Time(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallC return uintptr(r), nil, nil } -// clockNanosleepRestartBlock encapsulates the state required to restart -// clock_nanosleep(2) via restart_syscall(2). -// -// +stateify savable -type clockNanosleepRestartBlock struct { - c ktime.Clock - end ktime.Time - rem hostarch.Addr -} - -// Restart implements kernel.SyscallRestartBlock.Restart. -func (n *clockNanosleepRestartBlock) Restart(t *kernel.Task) (uintptr, error) { - return 0, clockNanosleepUntil(t, n.c, n.end, n.rem, true) -} - -// clockNanosleepUntil blocks until a specified time. -// -// If blocking is interrupted, the syscall is restarted with the original -// arguments. -func clockNanosleepUntil(t *kernel.Task, c ktime.Clock, end ktime.Time, rem hostarch.Addr, needRestartBlock bool) error { - notifier, tchan := ktime.NewChannelNotifier() - timer := ktime.NewTimer(c, notifier) - - // Turn on the timer. - timer.Swap(ktime.Setting{ - Period: 0, - Enabled: true, - Next: end, - }) - - err := t.BlockWithTimer(nil, tchan) - - timer.Destroy() - - switch { - case linuxerr.Equals(linuxerr.ETIMEDOUT, err): - // Slept for entire timeout. - return nil - case err == linuxerr.ErrInterrupted: - // Interrupted. - remaining := end.Sub(c.Now()) - if remaining <= 0 { - return nil - } - - // Copy out remaining time. - if rem != 0 { - timeleft := linux.NsecToTimespec(remaining.Nanoseconds()) - if err := copyTimespecOut(t, rem, &timeleft); err != nil { - return err - } - } - if needRestartBlock { - // Arrange for a restart with the remaining duration. - t.SetSyscallRestartBlock(&clockNanosleepRestartBlock{ - c: c, - end: end, - rem: rem, - }) - return linuxerr.ERESTART_RESTARTBLOCK - } - return linuxerr.ERESTARTNOHAND - default: - panic(fmt.Sprintf("Impossible BlockWithTimer error %v", err)) - } -} - // Nanosleep implements linux syscall Nanosleep(2). func Nanosleep(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { addr := args[0].Pointer() @@ -279,10 +212,12 @@ func ClockNanosleep(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kerne return 0, nil, linuxerr.EINVAL } - // Only allow clock constants also allowed by Linux. + // Only allow clock constants also allowed by Linux. (CLOCK_TAI is + // unimplemented.) if clockID > 0 { if clockID != linux.CLOCK_REALTIME && clockID != linux.CLOCK_MONOTONIC && + clockID != linux.CLOCK_BOOTTIME && clockID != linux.CLOCK_PROCESS_CPUTIME_ID { return 0, nil, linuxerr.EINVAL } @@ -301,6 +236,74 @@ func ClockNanosleep(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kerne return 0, nil, clockNanosleepUntil(t, c, c.Now().Add(dur), rem, true) } +// clockNanosleepUntil blocks until a specified time. +// +// If blocking is interrupted, the syscall is restarted with the original +// arguments. +func clockNanosleepUntil(t *kernel.Task, c ktime.Clock, end ktime.Time, rem hostarch.Addr, needRestartBlock bool) error { + var err error + if c == t.Kernel().MonotonicClock() { + err = t.BlockWithDeadline(nil, true, end) + } else { + notifier, tchan := ktime.NewChannelNotifier() + timer := ktime.NewTimer(c, notifier) + timer.Swap(ktime.Setting{ + Period: 0, + Enabled: true, + Next: end, + }) + err = t.BlockWithTimer(nil, tchan) + timer.Destroy() + } + + switch { + case linuxerr.Equals(linuxerr.ETIMEDOUT, err): + // Slept for entire timeout. + return nil + case err == linuxerr.ErrInterrupted: + // Interrupted. + remaining := end.Sub(c.Now()) + if remaining <= 0 { + return nil + } + + // Copy out remaining time. + if rem != 0 { + timeleft := linux.NsecToTimespec(remaining.Nanoseconds()) + if err := copyTimespecOut(t, rem, &timeleft); err != nil { + return err + } + } + if needRestartBlock { + // Arrange for a restart with the remaining duration. + t.SetSyscallRestartBlock(&clockNanosleepRestartBlock{ + c: c, + end: end, + rem: rem, + }) + return linuxerr.ERESTART_RESTARTBLOCK + } + return linuxerr.ERESTARTNOHAND + default: + panic(fmt.Sprintf("Impossible BlockWithTimer error %v", err)) + } +} + +// clockNanosleepRestartBlock encapsulates the state required to restart +// clock_nanosleep(2) via restart_syscall(2). +// +// +stateify savable +type clockNanosleepRestartBlock struct { + c ktime.Clock + end ktime.Time + rem hostarch.Addr +} + +// Restart implements kernel.SyscallRestartBlock.Restart. +func (n *clockNanosleepRestartBlock) Restart(t *kernel.Task) (uintptr, error) { + return 0, clockNanosleepUntil(t, n.c, n.end, n.rem, true) +} + // Gettimeofday implements linux syscall gettimeofday(2). func Gettimeofday(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { tv := args[0].Pointer() -- cgit v1.2.3