Skip to content

Commit 2d971c7

Browse files
committed
kernel: timeout: avoid premature adjustment of timeout
The existing loop over timeouts in z_clock_announce adjusts the delay of the first timeout that has not been expired by the number of ticks remaining. This adjustment is (by some interpretations) incorrect when the timeout was added while processing a previous timeout and the adjustment is not zero, which can happen if multiple ticks are announced in one call. Resolve this by changing how the tick clock is managed: * curr_ticks/k_uptime_get() is adjusted as soon as the z_clock_announce() is invoked. This means that timer callbacks are able to observe the best available estimate of the current time, rather than the time at which each callback was supposed to be invoked. The new policy also ensures that k_uptime_get() is consistent with k_cycle_get(). * Prior to invoking any callbacks the sequence of expired timeouts is removed from the timeout queue, ensuring new timeouts are scheduled without the risk of inadvertently reducing their delay by unprocessed ticks. * Prior to a callback the timeout dticks field is updated to be relative to the current time, i.e. a non-positive value corresponding to how late the timeout was invoked. * The tick offset used when scheduling a recurring timeout is adjusted by the lateness of the invocation, ensuring that the expected callback times are at a fixed interval even if individual callbacks are late. * The dticks field is zeroed when a timer is initialized, aborted, started, or stopped, ensuring that non-recurring timeouts respect the offset passed as the initial duration. The new policy also enables a future enhancement where each k_timer can be configured to select between policies where repeating timeouts should be relative to the scheduled time of the last callback (historical behavior) or the actual time of the last callback (useful for certain cases). Closes #12332. Signed-off-by: Peter A. Bigot <[email protected]>
1 parent 9416324 commit 2d971c7

File tree

3 files changed

+52
-40
lines changed

3 files changed

+52
-40
lines changed

kernel/include/timeout_q.h

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ extern "C" {
2323
static inline void _init_timeout(struct _timeout *t, _timeout_func_t fn)
2424
{
2525
sys_dnode_init(&t->node);
26+
t->dticks = 0;
2627
}
2728

2829
void _add_timeout(struct _timeout *to, _timeout_func_t fn, s32_t ticks);

kernel/timeout.c

+50-39
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,6 @@ static struct k_spinlock timeout_lock;
2323

2424
static bool can_wait_forever;
2525

26-
/* Cycles left to process in the currently-executing z_clock_announce() */
27-
static int announce_remaining;
28-
2926
#if defined(CONFIG_TIMER_READS_ITS_FREQUENCY_AT_RUNTIME)
3027
int z_clock_hw_cycles_per_sec = CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC;
3128
#endif
@@ -44,33 +41,25 @@ static struct _timeout *next(struct _timeout *t)
4441
return n == NULL ? NULL : CONTAINER_OF(n, struct _timeout, node);
4542
}
4643

47-
static void remove_timeout(struct _timeout *t)
48-
{
49-
if (next(t) != NULL) {
50-
next(t)->dticks += t->dticks;
51-
}
52-
53-
sys_dlist_remove(&t->node);
54-
}
55-
56-
static s32_t elapsed(void)
57-
{
58-
return announce_remaining == 0 ? z_clock_elapsed() : 0;
59-
}
60-
6144
void _add_timeout(struct _timeout *to, _timeout_func_t fn, s32_t ticks)
6245
{
6346
__ASSERT(!sys_dnode_is_linked(&to->node), "");
6447
to->fn = fn;
48+
49+
/* @todo This really ought to be removed to allow scheduling
50+
* with negative delays, since the floor operation fails to
51+
* maintain correct periodicity for timers that are so late
52+
* they missed more than an interval. But without it
53+
* kernel/timer/timer_api:test_timer_periodicity fails. Is
54+
* the test making incorrect assumptions about how to trick
55+
* the system? */
6556
ticks = max(1, ticks);
6657

6758
LOCKED(&timeout_lock) {
6859
struct _timeout *t;
6960

70-
to->dticks = ticks + elapsed();
61+
to->dticks = ticks + z_clock_elapsed();
7162
for (t = first(); t != NULL; t = next(t)) {
72-
__ASSERT(t->dticks >= 0, "");
73-
7463
if (t->dticks > to->dticks) {
7564
t->dticks -= to->dticks;
7665
sys_dlist_insert_before(&timeout_list,
@@ -96,10 +85,14 @@ int _abort_timeout(struct _timeout *to)
9685

9786
LOCKED(&timeout_lock) {
9887
if (sys_dnode_is_linked(&to->node)) {
99-
remove_timeout(to);
88+
if (next(to) != NULL) {
89+
next(to)->dticks += to->dticks;
90+
}
91+
sys_dlist_remove(&to->node);
10092
ret = 0;
10193
}
10294
}
95+
to->dticks = 0;
10396

10497
return ret;
10598
}
@@ -132,7 +125,7 @@ s32_t _get_next_timeout_expiry(void)
132125
LOCKED(&timeout_lock) {
133126
struct _timeout *to = first();
134127

135-
ret = to == NULL ? maxw : max(0, to->dticks - elapsed());
128+
ret = to == NULL ? maxw : max(0, to->dticks - z_clock_elapsed());
136129
}
137130

138131
#ifdef CONFIG_TIMESLICING
@@ -168,33 +161,51 @@ void z_clock_announce(s32_t ticks)
168161
z_time_slice(ticks);
169162
#endif
170163

164+
sys_dlist_t ready;
165+
sys_dnode_t *node;
166+
s32_t remaining_ticks = ticks;
171167
k_spinlock_key_t key = k_spin_lock(&timeout_lock);
168+
struct _timeout *t = first();
172169

173-
announce_remaining = ticks;
174-
175-
while (first() != NULL && first()->dticks <= announce_remaining) {
176-
struct _timeout *t = first();
177-
int dt = t->dticks;
170+
curr_tick += ticks;
178171

179-
curr_tick += dt;
180-
announce_remaining -= dt;
181-
t->dticks = 0;
182-
remove_timeout(t);
172+
if (!t) {
173+
/* Fast exit, no timeouts */
174+
goto out;
175+
}
183176

184-
k_spin_unlock(&timeout_lock, key);
185-
t->fn(t);
186-
key = k_spin_lock(&timeout_lock);
177+
/* Find the first timeout that isn't at/past its deadline */
178+
while ((t != NULL) && (t->dticks <= remaining_ticks)) {
179+
remaining_ticks -= t->dticks;
180+
t = next(t);
187181
}
188182

189-
if (first() != NULL) {
190-
first()->dticks -= announce_remaining;
183+
sys_dlist_init(&ready);
184+
if (t == NULL) {
185+
sys_dlist_join(&ready, &timeout_list);
186+
} else {
187+
sys_dlist_split(&ready, &timeout_list, &t->node);
188+
t->dticks -= remaining_ticks;
191189
}
192190

193-
curr_tick += announce_remaining;
194-
announce_remaining = 0;
191+
/* Invoke the callback of each expired timeout */
192+
node = sys_dlist_peek_head(&ready);
193+
if (node) {
194+
k_spin_unlock(&timeout_lock, key);
195+
do {
196+
sys_dlist_remove(node);
197+
t = CONTAINER_OF(node, struct _timeout, node);
198+
t->dticks -= ticks;
195199

196-
z_clock_set_timeout(_get_next_timeout_expiry(), false);
200+
t->fn(t);
197201

202+
node = sys_dlist_peek_head(&ready);
203+
} while (node != NULL);
204+
key = k_spin_lock(&timeout_lock);
205+
}
206+
207+
out:
208+
z_clock_set_timeout(_get_next_timeout_expiry(), false);
198209
k_spin_unlock(&timeout_lock, key);
199210
}
200211

kernel/timer.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ void _timer_expiration_handler(struct _timeout *t)
5757
if (timer->period > 0) {
5858
key = irq_lock();
5959
_add_timeout(&timer->timeout, _timer_expiration_handler,
60-
timer->period);
60+
timer->timeout.dticks + timer->period);
6161
irq_unlock(key);
6262
}
6363

0 commit comments

Comments
 (0)