mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Do not use ktime.Timer for CPU clock ticks.
PiperOrigin-RevId: 480424573
This commit is contained in:
@@ -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
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user