Do not use ktime.Timer for CPU clock ticks.

PiperOrigin-RevId: 480424573
This commit is contained in:
Jamie Liu
2022-10-11 12:30:18 -07:00
committed by gVisor bot
parent 0e9ef844eb
commit 2e844f74fc
4 changed files with 222 additions and 275 deletions
+8
View File
@@ -4,6 +4,13 @@ load("//pkg/sync/locking:locking.bzl", "declare_mutex", "declare_rwmutex")
package(licenses = ["notice"])
declare_mutex(
name = "cpu_clock_mutex",
out = "cpu_clock_mutex.go",
package = "kernel",
prefix = "cpuClock",
)
declare_mutex(
name = "user_counters_mutex",
out = "user_counters_mutex.go",
@@ -201,6 +208,7 @@ go_library(
"cgroup.go",
"cgroup_mutex.go",
"context.go",
"cpu_clock_mutex.go",
"fd_table.go",
"fd_table_mutex.go",
"fd_table_refs.go",
+59 -106
View File
@@ -20,7 +20,7 @@
//
// Kernel.extMu
// ThreadGroup.timerMu
// ktime.Timer.mu (for kernelCPUClockTicker and IntervalTimer)
// ktime.Timer.mu (for IntervalTimer) and Kernel.cpuClockMu
// TaskSet.mu
// SignalHandlers.mu
// Task.mu
@@ -183,12 +183,6 @@ type Kernel struct {
// syslog is the kernel log.
syslog syslog
// runningTasksMu synchronizes disable/enable of cpuClockTicker when
// the kernel is idle (runningTasks == 0).
//
// runningTasksMu is used to exclude critical sections when the timer
// disables itself and when the first active task enables the timer,
// ensuring that tasks always see a valid cpuClock value.
runningTasksMu runningTasksMutex `state:"nosave"`
// runningTasks is the total count of tasks currently in
@@ -199,36 +193,46 @@ type Kernel struct {
// further protected by runningTasksMu (see incRunningTasks).
runningTasks atomicbitops.Int64
// cpuClock is incremented every linux.ClockTick. cpuClock is used to
// measure task CPU usage, since sampling monotonicClock twice on every
// syscall turns out to be unreasonably expensive. This is similar to how
// Linux does task CPU accounting on x86 (CONFIG_IRQ_TIME_ACCOUNTING),
// although Linux also uses scheduler timing information to improve
// resolution (kernel/sched/cputime.c:cputime_adjust()), which we can't do
// since "preeemptive" scheduling is managed by the Go runtime, which
// doesn't provide this information.
// runningTasksCond is signaled when runningTasks is incremented from 0 to 1.
//
// Invariant: runningTasksCond.L == &runningTasksMu.
runningTasksCond sync.Cond `state:"nosave"`
// cpuClock is incremented every linux.ClockTick by a goroutine running
// kernel.runCPUClockTicker() while runningTasks != 0.
//
// cpuClock is used to measure task CPU usage, since sampling monotonicClock
// twice on every syscall turns out to be unreasonably expensive. This is
// similar to how Linux does task CPU accounting on x86
// (CONFIG_IRQ_TIME_ACCOUNTING), although Linux also uses scheduler timing
// information to improve resolution
// (kernel/sched/cputime.c:cputime_adjust()), which we can't do since
// "preeemptive" scheduling is managed by the Go runtime, which doesn't
// provide this information.
//
// cpuClock is mutable, and is accessed using atomic memory operations.
cpuClock atomicbitops.Uint64
// cpuClockTicker increments cpuClock.
cpuClockTicker *ktime.Timer `state:"nosave"`
// cpuClockMu is used to make increments of cpuClock, and updates of timers
// based on cpuClock, atomic.
cpuClockMu cpuClockMutex `state:"nosave"`
// cpuClockTickerDisabled indicates that cpuClockTicker has been
// disabled because no tasks are running.
// cpuClockTickerRunning is true if the goroutine that increments cpuClock is
// running and false if it is blocked in runningTasksCond.Wait() or if it
// never started.
//
// cpuClockTickerDisabled is protected by runningTasksMu.
cpuClockTickerDisabled bool
// cpuClockTickerRunning is protected by runningTasksMu.
cpuClockTickerRunning bool
// cpuClockTickerSetting is the ktime.Setting of cpuClockTicker at the
// point it was disabled. It is cached here to avoid a lock ordering
// violation with cpuClockTicker.mu when runningTaskMu is held.
// cpuClockTickerWakeCh is sent to to wake the goroutine that increments
// cpuClock if it's sleeping between ticks.
cpuClockTickerWakeCh chan struct{} `state:"nosave"`
// cpuClockTickerStopCond is broadcast when cpuClockTickerRunning transitions
// from true to false.
//
// cpuClockTickerSetting is only valid when cpuClockTickerDisabled is
// true.
//
// cpuClockTickerSetting is protected by runningTasksMu.
cpuClockTickerSetting ktime.Setting
// Invariant: cpuClockTickerStopCond.L == &runningTasksMu.
cpuClockTickerStopCond sync.Cond `state:"nosave"`
// uniqueID is used to generate unique identifiers.
//
@@ -411,6 +415,9 @@ func (k *Kernel) Init(args InitKernelArgs) error {
if k.rootNetworkNamespace == nil {
k.rootNetworkNamespace = inet.NewRootNamespace(nil, nil)
}
k.runningTasksCond.L = &k.runningTasksMu
k.cpuClockTickerWakeCh = make(chan struct{}, 1)
k.cpuClockTickerStopCond.L = &k.runningTasksMu
k.applicationCores = args.ApplicationCores
if args.UseHostCores {
k.useHostCores = true
@@ -680,6 +687,10 @@ func (k *Kernel) invalidateUnsavableMappings(ctx context.Context) error {
func (k *Kernel) LoadFrom(ctx context.Context, r wire.Reader, timeReady chan struct{}, net inet.Stack, clocks sentrytime.Clocks, vfsOpts *vfs.CompleteRestoreOptions) error {
loadStart := time.Now()
k.runningTasksCond.L = &k.runningTasksMu
k.cpuClockTickerWakeCh = make(chan struct{}, 1)
k.cpuClockTickerStopCond.L = &k.runningTasksMu
initAppCores := k.applicationCores
// Load the pre-saved CPUID FeatureSet.
@@ -1119,11 +1130,10 @@ func (k *Kernel) Start() error {
}
k.started = true
k.cpuClockTicker = ktime.NewTimer(k.timekeeper.monotonicClock, newKernelCPUClockTicker(k))
k.cpuClockTicker.Swap(ktime.Setting{
Enabled: true,
Period: linux.ClockTick,
})
k.runningTasksMu.Lock()
k.cpuClockTickerRunning = true
k.runningTasksMu.Unlock()
go k.runCPUClockTicker()
// If k was created by LoadKernelFrom, timers were stopped during
// Kernel.SaveTo and need to be resumed. If k was created by NewKernel,
// this is a no-op.
@@ -1150,11 +1160,18 @@ func (k *Kernel) Start() error {
// - Any task goroutines running in k must be stopped.
// - k.extMu must be locked.
func (k *Kernel) pauseTimeLocked(ctx context.Context) {
// k.cpuClockTicker may be nil since Kernel.SaveTo() may be called before
// Kernel.Start().
if k.cpuClockTicker != nil {
k.cpuClockTicker.Pause()
// Since all task goroutines have been stopped by precondition, the CPU clock
// ticker should stop on its own; wait for it to do so, waking it up from
// sleeping betwen ticks if necessary.
k.runningTasksMu.Lock()
for k.cpuClockTickerRunning {
select {
case k.cpuClockTickerWakeCh <- struct{}{}:
default:
}
k.cpuClockTickerStopCond.Wait()
}
k.runningTasksMu.Unlock()
// By precondition, nothing else can be interacting with PIDNamespace.tids
// or FDTable.files, so we can iterate them without synchronization. (We
@@ -1195,9 +1212,8 @@ func (k *Kernel) pauseTimeLocked(ctx context.Context) {
// - Any task goroutines running in k must be stopped.
// - k.extMu must be locked.
func (k *Kernel) resumeTimeLocked(ctx context.Context) {
if k.cpuClockTicker != nil {
k.cpuClockTicker.Resume()
}
// The CPU clock ticker will automatically resume as task goroutines resume
// execution.
k.timekeeper.ResumeUpdates()
for t := range k.tasks.Root.tids {
@@ -1236,73 +1252,10 @@ func (k *Kernel) incRunningTasks() {
// Transition from 0 -> 1. Synchronize with other transitions and timer.
k.runningTasksMu.Lock()
tasks = k.runningTasks.Load()
if tasks != 0 {
// We're no longer the first task, no need to
// re-enable.
k.runningTasks.Add(1)
k.runningTasksMu.Unlock()
return
if k.runningTasks.Add(1) == 1 {
k.runningTasksCond.Signal()
}
if !k.cpuClockTickerDisabled {
// Timer was never disabled.
k.runningTasks.Store(1)
k.runningTasksMu.Unlock()
return
}
// We need to update cpuClock for all of the ticks missed while we
// slept, and then re-enable the timer.
//
// The Notify in Swap isn't sufficient. kernelCPUClockTicker.Notify
// always increments cpuClock by 1 regardless of the number of
// expirations as a heuristic to avoid over-accounting in cases of CPU
// throttling.
//
// We want to cover the normal case, when all time should be accounted,
// so we increment for all expirations. Throttling is less concerning
// here because the ticker is only disabled from Notify. This means
// that Notify must schedule and compensate for the throttled period
// before the timer is disabled. Throttling while the timer is disabled
// doesn't matter, as nothing is running or reading cpuClock anyways.
//
// S/R also adds complication, as there are two cases. Recall that
// monotonicClock will jump forward on restore.
//
// 1. If the ticker is enabled during save, then on Restore Notify is
// called with many expirations, covering the time jump, but cpuClock
// is only incremented by 1.
//
// 2. If the ticker is disabled during save, then after Restore the
// first wakeup will call this function and cpuClock will be
// incremented by the number of expirations across the S/R.
//
// These cause very different value of cpuClock. But again, since
// nothing was running while the ticker was disabled, those differences
// don't matter.
setting, exp := k.cpuClockTickerSetting.At(k.timekeeper.monotonicClock.Now())
if exp > 0 {
k.cpuClock.Add(exp)
}
// Now that cpuClock is updated it is safe to allow other tasks to
// transition to running.
k.runningTasks.Store(1)
// N.B. we must unlock before calling Swap to maintain lock ordering.
//
// cpuClockTickerDisabled need not wait until after Swap to become
// true. It is sufficient that the timer *will* be enabled.
k.cpuClockTickerDisabled = false
k.runningTasksMu.Unlock()
// This won't call Notify (unless it's been ClockTick since setting.At
// above). This means we skip the thread group work in Notify. However,
// since nothing was running while we were disabled, none of the timers
// could have expired.
k.cpuClockTicker.Swap(setting)
return
}
}
+18 -28
View File
@@ -68,42 +68,32 @@ func (t *Task) Setitimer(id int32, newitv linux.ItimerVal) (linux.ItimerVal, err
tm, olds = t.tg.itimerRealTimer.Swap(news)
case linux.ITIMER_VIRTUAL:
c := t.tg.UserCPUClock()
var err error
t.k.cpuClockTicker.Atomically(func() {
tm = c.Now()
var news ktime.Setting
news, err = ktime.SettingFromSpecAt(newitv.Value.ToDuration(), newitv.Interval.ToDuration(), tm)
if err != nil {
return
}
t.tg.signalHandlers.mu.Lock()
olds = t.tg.itimerVirtSetting
t.tg.itimerVirtSetting = news
t.tg.updateCPUTimersEnabledLocked()
t.tg.signalHandlers.mu.Unlock()
})
t.k.cpuClockMu.Lock()
defer t.k.cpuClockMu.Unlock()
tm = c.Now()
news, err := ktime.SettingFromSpecAt(newitv.Value.ToDuration(), newitv.Interval.ToDuration(), tm)
if err != nil {
return linux.ItimerVal{}, err
}
t.tg.signalHandlers.mu.Lock()
olds = t.tg.itimerVirtSetting
t.tg.itimerVirtSetting = news
t.tg.updateCPUTimersEnabledLocked()
t.tg.signalHandlers.mu.Unlock()
case linux.ITIMER_PROF:
c := t.tg.CPUClock()
var err error
t.k.cpuClockTicker.Atomically(func() {
tm = c.Now()
var news ktime.Setting
news, err = ktime.SettingFromSpecAt(newitv.Value.ToDuration(), newitv.Interval.ToDuration(), tm)
if err != nil {
return
}
t.tg.signalHandlers.mu.Lock()
olds = t.tg.itimerProfSetting
t.tg.itimerProfSetting = news
t.tg.updateCPUTimersEnabledLocked()
t.tg.signalHandlers.mu.Unlock()
})
t.k.cpuClockMu.Lock()
defer t.k.cpuClockMu.Unlock()
tm = c.Now()
news, err := ktime.SettingFromSpecAt(newitv.Value.ToDuration(), newitv.Interval.ToDuration(), tm)
if err != nil {
return linux.ItimerVal{}, err
}
t.tg.signalHandlers.mu.Lock()
olds = t.tg.itimerProfSetting
t.tg.itimerProfSetting = news
t.tg.updateCPUTimersEnabledLocked()
t.tg.signalHandlers.mu.Unlock()
default:
return linux.ItimerVal{}, linuxerr.EINVAL
}
+137 -141
View File
@@ -335,140 +335,136 @@ func (tg *ThreadGroup) CPUClock() ktime.Clock {
return &tgClock{tg: tg, includeSys: true}
}
type kernelCPUClockTicker struct {
k *Kernel
func (k *Kernel) runCPUClockTicker() {
tickTimer := time.NewTimer(linux.ClockTick)
rng := rand.New(rand.NewSource(rand.Int63()))
var tgs []*ThreadGroup
// These are essentially kernelCPUClockTicker.Notify local variables that
// are cached between calls to reduce allocations.
rng *rand.Rand
tgs []*ThreadGroup
}
for {
// Wait for the next CPU clock tick.
wokenEarly := false
select {
case <-tickTimer.C:
tickTimer.Reset(linux.ClockTick)
case <-k.cpuClockTickerWakeCh:
// Wake up to check if we need to stop with cpuClockTickerRunning =
// false, but then continue waiting for the next CPU clock tick.
wokenEarly = true
}
func newKernelCPUClockTicker(k *Kernel) *kernelCPUClockTicker {
return &kernelCPUClockTicker{
k: k,
rng: rand.New(rand.NewSource(rand.Int63())),
}
}
// Stop the CPU clock while nothing is running.
if k.runningTasks.Load() == 0 {
k.runningTasksMu.Lock()
if k.runningTasks.Load() == 0 {
k.cpuClockTickerRunning = false
k.cpuClockTickerStopCond.Broadcast()
for k.runningTasks.Load() == 0 {
k.runningTasksCond.Wait()
}
k.cpuClockTickerRunning = true
}
k.runningTasksMu.Unlock()
}
// NotifyTimer implements ktime.TimerListener.NotifyTimer.
func (ticker *kernelCPUClockTicker) NotifyTimer(exp uint64, setting ktime.Setting) (ktime.Setting, bool) {
// Only increment cpuClock by 1 regardless of the number of expirations.
// This approximately compensates for cases where thread throttling or bad
// Go runtime scheduling prevents the kernelCPUClockTicker goroutine, and
// presumably task goroutines as well, from executing for a long period of
// time. It's also necessary to prevent CPU clocks from seeing large
// discontinuous jumps.
now := ticker.k.cpuClock.Add(1)
// Check thread group CPU timers.
tgs := ticker.k.tasks.Root.ThreadGroupsAppend(ticker.tgs)
for _, tg := range tgs {
if tg.cpuTimersEnabled.Load() == 0 {
if wokenEarly {
continue
}
ticker.k.tasks.mu.RLock()
if tg.leader == nil {
// No tasks have ever run in this thread group.
ticker.k.tasks.mu.RUnlock()
continue
}
// Accumulate thread group CPU stats, and randomly select running tasks
// using reservoir sampling to receive CPU timer signals.
var virtReceiver *Task
nrVirtCandidates := 0
var profReceiver *Task
nrProfCandidates := 0
tgUserTime := tg.exitedCPUStats.UserTime
tgSysTime := tg.exitedCPUStats.SysTime
for t := tg.tasks.Front(); t != nil; t = t.Next() {
tsched := t.TaskGoroutineSchedInfo()
tgUserTime += time.Duration(tsched.userTicksAt(now) * uint64(linux.ClockTick))
tgSysTime += time.Duration(tsched.sysTicksAt(now) * uint64(linux.ClockTick))
switch tsched.State {
case TaskGoroutineRunningApp:
// Considered by ITIMER_VIRT, ITIMER_PROF, and RLIMIT_CPU
// timers.
nrVirtCandidates++
if int(randInt31n(ticker.rng, int32(nrVirtCandidates))) == 0 {
virtReceiver = t
}
fallthrough
case TaskGoroutineRunningSys:
// Considered by ITIMER_PROF and RLIMIT_CPU timers.
nrProfCandidates++
if int(randInt31n(ticker.rng, int32(nrProfCandidates))) == 0 {
profReceiver = t
// Advance the CPU clock, and timers based on the CPU clock, atomically
// under cpuClockMu.
k.cpuClockMu.Lock()
now := k.cpuClock.Add(1)
// Check thread group CPU timers.
tgs = k.tasks.Root.ThreadGroupsAppend(tgs)
for _, tg := range tgs {
if tg.cpuTimersEnabled.Load() == 0 {
continue
}
k.tasks.mu.RLock()
if tg.leader == nil {
// No tasks have ever run in this thread group.
k.tasks.mu.RUnlock()
continue
}
// Accumulate thread group CPU stats, and randomly select running tasks
// using reservoir sampling to receive CPU timer signals.
var virtReceiver *Task
nrVirtCandidates := 0
var profReceiver *Task
nrProfCandidates := 0
tgUserTime := tg.exitedCPUStats.UserTime
tgSysTime := tg.exitedCPUStats.SysTime
for t := tg.tasks.Front(); t != nil; t = t.Next() {
tsched := t.TaskGoroutineSchedInfo()
tgUserTime += time.Duration(tsched.userTicksAt(now) * uint64(linux.ClockTick))
tgSysTime += time.Duration(tsched.sysTicksAt(now) * uint64(linux.ClockTick))
switch tsched.State {
case TaskGoroutineRunningApp:
// Considered by ITIMER_VIRT, ITIMER_PROF, and RLIMIT_CPU
// timers.
nrVirtCandidates++
if int(randInt31n(rng, int32(nrVirtCandidates))) == 0 {
virtReceiver = t
}
fallthrough
case TaskGoroutineRunningSys:
// Considered by ITIMER_PROF and RLIMIT_CPU timers.
nrProfCandidates++
if int(randInt31n(rng, int32(nrProfCandidates))) == 0 {
profReceiver = t
}
}
}
}
tgVirtNow := ktime.FromNanoseconds(tgUserTime.Nanoseconds())
tgProfNow := ktime.FromNanoseconds((tgUserTime + tgSysTime).Nanoseconds())
tgVirtNow := ktime.FromNanoseconds(tgUserTime.Nanoseconds())
tgProfNow := ktime.FromNanoseconds((tgUserTime + tgSysTime).Nanoseconds())
// All of the following are standard (not real-time) signals, which are
// automatically deduplicated, so we ignore the number of expirations.
tg.signalHandlers.mu.Lock()
// It should only be possible for these timers to advance if we found
// at least one running task.
if virtReceiver != nil {
// ITIMER_VIRTUAL
newItimerVirtSetting, exp := tg.itimerVirtSetting.At(tgVirtNow)
tg.itimerVirtSetting = newItimerVirtSetting
if exp != 0 {
virtReceiver.sendSignalLocked(SignalInfoPriv(linux.SIGVTALRM), true)
// All of the following are standard (not real-time) signals, which are
// automatically deduplicated, so we ignore the number of expirations.
tg.signalHandlers.mu.Lock()
// It should only be possible for these timers to advance if we found
// at least one running task.
if virtReceiver != nil {
// ITIMER_VIRTUAL
newItimerVirtSetting, exp := tg.itimerVirtSetting.At(tgVirtNow)
tg.itimerVirtSetting = newItimerVirtSetting
if exp != 0 {
virtReceiver.sendSignalLocked(SignalInfoPriv(linux.SIGVTALRM), true)
}
}
}
if profReceiver != nil {
// ITIMER_PROF
newItimerProfSetting, exp := tg.itimerProfSetting.At(tgProfNow)
tg.itimerProfSetting = newItimerProfSetting
if exp != 0 {
profReceiver.sendSignalLocked(SignalInfoPriv(linux.SIGPROF), true)
if profReceiver != nil {
// ITIMER_PROF
newItimerProfSetting, exp := tg.itimerProfSetting.At(tgProfNow)
tg.itimerProfSetting = newItimerProfSetting
if exp != 0 {
profReceiver.sendSignalLocked(SignalInfoPriv(linux.SIGPROF), true)
}
// RLIMIT_CPU soft limit
newRlimitCPUSoftSetting, exp := tg.rlimitCPUSoftSetting.At(tgProfNow)
tg.rlimitCPUSoftSetting = newRlimitCPUSoftSetting
if exp != 0 {
profReceiver.sendSignalLocked(SignalInfoPriv(linux.SIGXCPU), true)
}
// RLIMIT_CPU hard limit
rlimitCPUMax := tg.limits.Get(limits.CPU).Max
if rlimitCPUMax != limits.Infinity && !tgProfNow.Before(ktime.FromSeconds(int64(rlimitCPUMax))) {
profReceiver.sendSignalLocked(SignalInfoPriv(linux.SIGKILL), true)
}
}
// RLIMIT_CPU soft limit
newRlimitCPUSoftSetting, exp := tg.rlimitCPUSoftSetting.At(tgProfNow)
tg.rlimitCPUSoftSetting = newRlimitCPUSoftSetting
if exp != 0 {
profReceiver.sendSignalLocked(SignalInfoPriv(linux.SIGXCPU), true)
}
// RLIMIT_CPU hard limit
rlimitCPUMax := tg.limits.Get(limits.CPU).Max
if rlimitCPUMax != limits.Infinity && !tgProfNow.Before(ktime.FromSeconds(int64(rlimitCPUMax))) {
profReceiver.sendSignalLocked(SignalInfoPriv(linux.SIGKILL), true)
}
}
tg.signalHandlers.mu.Unlock()
tg.signalHandlers.mu.Unlock()
ticker.k.tasks.mu.RUnlock()
k.tasks.mu.RUnlock()
}
k.cpuClockMu.Unlock()
// Retain tgs between calls to Notify to reduce allocations.
for i := range tgs {
tgs[i] = nil
}
tgs = tgs[:0]
}
// Retain tgs between calls to Notify to reduce allocations.
for i := range tgs {
tgs[i] = nil
}
ticker.tgs = tgs[:0]
// If nothing is running, we can disable the timer.
tasks := ticker.k.runningTasks.Load()
if tasks == 0 {
ticker.k.runningTasksMu.Lock()
defer ticker.k.runningTasksMu.Unlock()
tasks := ticker.k.runningTasks.Load()
if tasks != 0 {
// Raced with a 0 -> 1 transition.
return setting, false
}
// Stop the timer. We must cache the current setting so the
// kernel can access it without violating the lock order.
ticker.k.cpuClockTickerSetting = setting
ticker.k.cpuClockTickerDisabled = true
setting.Enabled = false
return setting, true
}
return setting, false
}
// randInt31n returns a random integer in [0, n).
@@ -494,27 +490,27 @@ func randInt31n(rng *rand.Rand, n int32) int32 {
//
// Preconditions: The caller must be running on the task goroutine.
func (t *Task) NotifyRlimitCPUUpdated() {
t.k.cpuClockTicker.Atomically(func() {
t.tg.pidns.owner.mu.RLock()
defer t.tg.pidns.owner.mu.RUnlock()
t.tg.signalHandlers.mu.Lock()
defer t.tg.signalHandlers.mu.Unlock()
rlimitCPU := t.tg.limits.Get(limits.CPU)
t.tg.rlimitCPUSoftSetting = ktime.Setting{
Enabled: rlimitCPU.Cur != limits.Infinity,
Next: ktime.FromNanoseconds((time.Duration(rlimitCPU.Cur) * time.Second).Nanoseconds()),
Period: time.Second,
t.k.cpuClockMu.Lock()
defer t.k.cpuClockMu.Unlock()
t.tg.pidns.owner.mu.RLock()
defer t.tg.pidns.owner.mu.RUnlock()
t.tg.signalHandlers.mu.Lock()
defer t.tg.signalHandlers.mu.Unlock()
rlimitCPU := t.tg.limits.Get(limits.CPU)
t.tg.rlimitCPUSoftSetting = ktime.Setting{
Enabled: rlimitCPU.Cur != limits.Infinity,
Next: ktime.FromNanoseconds((time.Duration(rlimitCPU.Cur) * time.Second).Nanoseconds()),
Period: time.Second,
}
if rlimitCPU.Max != limits.Infinity {
// Check if tg is already over the hard limit.
tgcpu := t.tg.cpuStatsAtLocked(t.k.CPUClockNow())
tgProfNow := ktime.FromNanoseconds((tgcpu.UserTime + tgcpu.SysTime).Nanoseconds())
if !tgProfNow.Before(ktime.FromSeconds(int64(rlimitCPU.Max))) {
t.sendSignalLocked(SignalInfoPriv(linux.SIGKILL), true)
}
if rlimitCPU.Max != limits.Infinity {
// Check if tg is already over the hard limit.
tgcpu := t.tg.cpuStatsAtLocked(t.k.CPUClockNow())
tgProfNow := ktime.FromNanoseconds((tgcpu.UserTime + tgcpu.SysTime).Nanoseconds())
if !tgProfNow.Before(ktime.FromSeconds(int64(rlimitCPU.Max))) {
t.sendSignalLocked(SignalInfoPriv(linux.SIGKILL), true)
}
}
t.tg.updateCPUTimersEnabledLocked()
})
}
t.tg.updateCPUTimersEnabledLocked()
}
// Preconditions: The signal mutex must be locked.