diff options
author | Joel Fernandes (Google) <joel@joelfernandes.org> | 2024-02-27 13:09:20 -0500 |
---|---|---|
committer | Joel Fernandes (Google) <joel@joelfernandes.org> | 2024-02-27 13:25:35 -0500 |
commit | ea66469f96e554d83c2e8653992ebc3e22fcc7a7 (patch) | |
tree | a882dc0cf82a410d14b599369c1a686fae57e595 | |
parent | 02cfd31343e84db5178f2c2ed78ed2d74dc56c11 (diff) | |
download | linux-tick/keep-tick-on-hrt.tar.gz |
fixup! tick-sched: Set last_tick correctly so that timer interrupts happen lesstick/keep-tick-on-hrt
Signed-off-by: Joel Fernandes (Google) <joel@joelfernandes.org>
-rw-r--r-- | kernel/time/tick-sched.c | 74 |
1 files changed, 68 insertions, 6 deletions
diff --git a/kernel/time/tick-sched.c b/kernel/time/tick-sched.c index d55e133cbd8de..42bd227eabfce 100644 --- a/kernel/time/tick-sched.c +++ b/kernel/time/tick-sched.c @@ -804,21 +804,71 @@ static inline bool local_timer_softirq_pending(void) return local_softirq_pending() & BIT(TIMER_SOFTIRQ); } +/* + * Based on the last known programmed value of the tick-sched timer (which is + * saved in ->last_tick by either the tick-sched handler, or by + * tick_nohz_stop_tick(), calculate what the time will be at the next tick + * period, considering that the tick is (re)started or left turned on. + * + * NOTE 1: We avoid ktime_get() for getting 'now' and instead rely on the + * last_jiffies_update passed into this function. This is OK because the last + * jiffies update would have happened at most TICK_NSEC ago. Thus using it as a + * reference for the next tick period is OK. (TODO: Add tracing to confirm now and last_jiffies is atmost TICK_NSEC apart) + * + * NOTE 2: In order to determine the next period, using ts->last_tick as a + * starting point for reference is way better than purely depending on the + * last_jiffies_update. In the worst case, if last_jiffies_update is not updated + * for a long time say due to issues on the do_timer CPU, at least we are going + * to consider the next->period to be at ts->last_tick at a minimum (ts->last_tick + * could have been updated by the local timer event). + */ +static ktime_t tick_nohz_get_next_period(struct tick_sched *ts, ktime_t now) +{ + ktime_t next_tick, delta; + u64 orun; + + if (ts->last_tick > now) + return ts->last_tick; // TODO, does ->last_tick boot as 0 ? Can this func be called before + + delta = ktime_sub(now, ts->last_tick); + if (delta < TICK_NSEC) + return ktime_add_ns(ts->last_tick, TICK_NSEC); + + orun = ktime_divns(delta, TICK_NSEC); + next_tick = ktime_add_ns(ts->last_tick, TICK_NSEC * orun); + if (next_tick > now) + return next_tick; + return ktime_add_ns(next_tick, TICK_NSEC); +} + static ktime_t tick_nohz_next_event(struct tick_sched *ts, int cpu) { u64 basemono, next_tick, delta, delta_hr, expires, next_hr_wo; + u64 next_period, last_jiffies_update_read; unsigned long basejiff; unsigned int seq; /* Read jiffies and the time when jiffies were updated last */ do { seq = read_seqcount_begin(&jiffies_seq); - basemono = last_jiffies_update; + last_jiffies_update_read = last_jiffies_update; basejiff = jiffies; } while (read_seqcount_retry(&jiffies_seq, seq)); ts->last_jiffies = basejiff; + + /* + * Find the next time the tick-sched timer will fire if the tick is + * (re)started or left running. + */ + next_period = tick_nohz_get_next_period(ts, last_jiffies_update_read); + + /* The basemono is set to the last time a tick event has or would have fired. */ + basemono = next_period - TICK_NSEC; ts->timer_expires_base = basemono; + // TODO: Delete if not possible. Or convert to if() and correct next_period. + WARN_ON_ONCE(last_jiffies_update_read + TICK_NSEC > next_period); + /* * Keep the periodic tick, when RCU, architecture or irq_work * requests it. @@ -831,7 +881,7 @@ static ktime_t tick_nohz_next_event(struct tick_sched *ts, int cpu) */ if (rcu_needs_cpu() || arch_needs_cpu() || irq_work_needs_cpu() || local_timer_softirq_pending()) { - next_tick = basemono + TICK_NSEC; + next_tick = next_period; } else { /* * Get the next pending timer. If high resolution @@ -1439,17 +1489,29 @@ static void tick_nohz_lowres_handler(struct clock_event_device *dev) tick_sched_do_timer(ts, now); tick_sched_handle(ts, regs); - ts->last_tick = now; - /* * In dynticks mode, tick reprogram is deferred: * - to the idle task if in dynticks-idle * - to IRQ exit if in full-dynticks. */ if (likely(!ts->tick_stopped)) { - hrtimer_set_expires(&ts->sched_timer, ts->last_tick); + /* + * In lowres mode, program the tick to fire at TICK_NSEC. + * This effectively coalesces timer expiries. + */ + hrtimer_set_expires(&ts->sched_timer, now); hrtimer_forward(&ts->sched_timer, now, TICK_NSEC); - tick_program_event(hrtimer_get_expires(&ts->sched_timer), 1); + + /* + * The tick itself might have caused a wake up from nohz and + * will cause a tick restart shortly. Make sure + * tick_nohz_restart() sees the latest value of the timer + * expiry. In case an IRQ other than the tick caused a wake up, + * tick_nohz_restart() will read the ->last_tick that + * tick_nohz_stop_tick() set instead. + */ + ts->last_tick = hrtimer_get_expires(&ts->sched_timer); + tick_program_event(ts->last_tick, 1); } } |