-
Notifications
You must be signed in to change notification settings - Fork 9.8k
kernel: timeout: add Pugh skip list timeout backend #117320
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,231 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||
| /* | ||||||||||||||||||||||||||||||||||||||||||||||||
| * Copyright (c) 2026 The Zephyr Project Contributors | ||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||
| * SPDX-License-Identifier: Apache-2.0 | ||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| #ifndef ZEPHYR_KERNEL_TIMEOUT_SKIPLIST_H_ | ||||||||||||||||||||||||||||||||||||||||||||||||
| #define ZEPHYR_KERNEL_TIMEOUT_SKIPLIST_H_ | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||
| * @file | ||||||||||||||||||||||||||||||||||||||||||||||||
| * @brief Skip-list timeout backend (implementation). | ||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||
| * Pending timeouts are kept in a Pugh skip list keyed on absolute expiry | ||||||||||||||||||||||||||||||||||||||||||||||||
| * tick (struct _timeout's abs_ticks). Expected insertion and arbitrary | ||||||||||||||||||||||||||||||||||||||||||||||||
| * removal are O(log n); the earliest timeout is always the level-0 successor | ||||||||||||||||||||||||||||||||||||||||||||||||
| * of the sentinel. A timeout that is not queued has height == 0. | ||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||
| * Same-tick firing order is FIFO: a new node is inserted after every node | ||||||||||||||||||||||||||||||||||||||||||||||||
| * that already has the same abs_ticks. | ||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||
| * Node height is drawn from a geometric(1/2) distribution using a private | ||||||||||||||||||||||||||||||||||||||||||||||||
| * xorshift32, so the announce/add/abort paths never call the entropy driver. | ||||||||||||||||||||||||||||||||||||||||||||||||
| * There is no capacity limit (unlike the min-heap backend). | ||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| #define SKIPLIST_LEVELS CONFIG_TIMEOUT_SKIPLIST_MAX_LEVEL | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| BUILD_ASSERT(SKIPLIST_LEVELS >= 4 && SKIPLIST_LEVELS <= 16, | ||||||||||||||||||||||||||||||||||||||||||||||||
| "CONFIG_TIMEOUT_SKIPLIST_MAX_LEVEL must be in [4, 16]"); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| /* Sentinel: never expires, participates in every level, never fired. */ | ||||||||||||||||||||||||||||||||||||||||||||||||
| static struct _timeout sl_head = { | ||||||||||||||||||||||||||||||||||||||||||||||||
| .height = SKIPLIST_LEVELS, | ||||||||||||||||||||||||||||||||||||||||||||||||
| .forward = { NULL }, | ||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| /* Highest height of any currently queued node; 0 when the list is empty. */ | ||||||||||||||||||||||||||||||||||||||||||||||||
| static uint8_t sl_top; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| /* Marsaglia xorshift32 state; period 2^32-1, must stay non-zero. */ | ||||||||||||||||||||||||||||||||||||||||||||||||
| static uint32_t sl_rng = 2463534242U; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| static uint8_t skiplist_random_height(void) | ||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||
| uint32_t r = sl_rng; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| r ^= r << 13; | ||||||||||||||||||||||||||||||||||||||||||||||||
| r ^= r >> 17; | ||||||||||||||||||||||||||||||||||||||||||||||||
| r ^= r << 5; | ||||||||||||||||||||||||||||||||||||||||||||||||
| sl_rng = r; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| /* p = 1/2: height is 1 + the run of low 1-bits, capped at SKIPLIST_LEVELS. */ | ||||||||||||||||||||||||||||||||||||||||||||||||
| return 1 + MIN(u32_count_trailing_zeros(~r), SKIPLIST_LEVELS - 1); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| static inline struct _timeout *skiplist_first(void) | ||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||
| return sl_head.forward[0]; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| static void skiplist_drop_top(void) | ||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||
| while (sl_top > 0 && sl_head.forward[sl_top - 1] == NULL) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| sl_top--; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| static void skiplist_update_init(struct _timeout **update) | ||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||
| uint8_t i; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| for (i = 0; i < SKIPLIST_LEVELS; i++) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| update[i] = &sl_head; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| /* Splice @to out at every level it occupies. */ | ||||||||||||||||||||||||||||||||||||||||||||||||
| static void skiplist_unlink(struct _timeout *to, struct _timeout **update) | ||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||
| uint8_t i; | ||||||||||||||||||||||||||||||||||||||||||||||||
| uint8_t height = to->height; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| for (i = 0; i < height; i++) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| __ASSERT_NO_MSG(update[i]->forward[i] == to); | ||||||||||||||||||||||||||||||||||||||||||||||||
| update[i]->forward[i] = to->forward[i]; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| to->height = 0; | ||||||||||||||||||||||||||||||||||||||||||||||||
| skiplist_drop_top(); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| /* Equal keys are skipped so a later insert lands after them (same-tick FIFO). */ | ||||||||||||||||||||||||||||||||||||||||||||||||
| static void skiplist_predecessors_after(int64_t abs_ticks, struct _timeout **update) | ||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||
| struct _timeout *x = &sl_head; | ||||||||||||||||||||||||||||||||||||||||||||||||
| int i; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| for (i = (int)sl_top - 1; i >= 0; i--) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| while (x->forward[i] != NULL && x->forward[i]->abs_ticks <= abs_ticks) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| x = x->forward[i]; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| update[i] = x; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| /* Walk equal-key nodes so identity, not just expiry, selects the splice points. */ | ||||||||||||||||||||||||||||||||||||||||||||||||
| static void skiplist_predecessors_of(struct _timeout *to, struct _timeout **update) | ||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||
| struct _timeout *x = &sl_head; | ||||||||||||||||||||||||||||||||||||||||||||||||
| int i; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| for (i = (int)sl_top - 1; i >= 0; i--) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| while (x->forward[i] != NULL && x->forward[i]->abs_ticks < to->abs_ticks) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| x = x->forward[i]; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| update[i] = x; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| for (i = 0; i < to->height; i++) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| while (update[i]->forward[i] != NULL && | ||||||||||||||||||||||||||||||||||||||||||||||||
| update[i]->forward[i] != to && | ||||||||||||||||||||||||||||||||||||||||||||||||
| update[i]->forward[i]->abs_ticks == to->abs_ticks) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| update[i] = update[i]->forward[i]; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| static inline bool z_timeout_q_insert(struct _timeout *to, k_ticks_t dticks) | ||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||
| struct _timeout *update[SKIPLIST_LEVELS]; | ||||||||||||||||||||||||||||||||||||||||||||||||
| uint8_t i; | ||||||||||||||||||||||||||||||||||||||||||||||||
| uint8_t height; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| to->abs_ticks = (int64_t)curr_tick + dticks; | ||||||||||||||||||||||||||||||||||||||||||||||||
| height = skiplist_random_height(); | ||||||||||||||||||||||||||||||||||||||||||||||||
| to->height = height; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| skiplist_update_init(update); | ||||||||||||||||||||||||||||||||||||||||||||||||
| skiplist_predecessors_after(to->abs_ticks, update); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| if (height > sl_top) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| sl_top = height; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| for (i = 0; i < height; i++) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| to->forward[i] = update[i]->forward[i]; | ||||||||||||||||||||||||||||||||||||||||||||||||
| update[i]->forward[i] = to; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| return skiplist_first() == to; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| static inline bool z_timeout_q_remove(struct _timeout *to) | ||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||
| struct _timeout *update[SKIPLIST_LEVELS]; | ||||||||||||||||||||||||||||||||||||||||||||||||
| bool was_first = (skiplist_first() == to); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| __ASSERT_NO_MSG(to->height > 0); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| skiplist_update_init(update); | ||||||||||||||||||||||||||||||||||||||||||||||||
| skiplist_predecessors_of(to, update); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| skiplist_unlink(to, update); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+156
to
+164
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This loop's only body is an assertion. With
Suggested change
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| return was_first; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| static inline k_ticks_t z_timeout_q_remainder(const struct _timeout *to) | ||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||
| return (k_ticks_t)(to->abs_ticks - (int64_t)curr_tick); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| static inline k_ticks_t z_timeout_q_next_expiry(void) | ||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||
| struct _timeout *t = skiplist_first(); | ||||||||||||||||||||||||||||||||||||||||||||||||
| int64_t gap; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| if (t == NULL) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| return K_TICKS_FOREVER; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| gap = t->abs_ticks - (int64_t)curr_tick; | ||||||||||||||||||||||||||||||||||||||||||||||||
| return (gap < 0) ? 0 : (k_ticks_t)gap; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| static inline int32_t z_timeout_q_next_gap(void) | ||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||
| struct _timeout *t = skiplist_first(); | ||||||||||||||||||||||||||||||||||||||||||||||||
| int64_t gap; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| if (t == NULL) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| return INT32_MAX; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| /* Overdue must be 0: announce casts dt to uint32_t. */ | ||||||||||||||||||||||||||||||||||||||||||||||||
| gap = t->abs_ticks - (int64_t)curr_tick; | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (gap <= 0) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| return 0; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| return (int32_t)MIN(gap, (int64_t)INT32_MAX); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| static inline void z_timeout_q_advance(int32_t dt) | ||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||
| ARG_UNUSED(dt); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| /* Earliest node, so it is the head's successor at every level it occupies. */ | ||||||||||||||||||||||||||||||||||||||||||||||||
| static inline struct _timeout *z_timeout_q_pop_due(void) | ||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||
| struct _timeout *t = skiplist_first(); | ||||||||||||||||||||||||||||||||||||||||||||||||
| uint8_t i; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| if ((t == NULL) || (t->abs_ticks > (int64_t)curr_tick)) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| return NULL; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| for (i = 0; i < t->height; i++) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| __ASSERT_NO_MSG(sl_head.forward[i] == t); | ||||||||||||||||||||||||||||||||||||||||||||||||
| sl_head.forward[i] = t->forward[i]; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| t->height = 0; | ||||||||||||||||||||||||||||||||||||||||||||||||
| skiplist_drop_top(); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| return t; | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| #endif /* ZEPHYR_KERNEL_TIMEOUT_SKIPLIST_H_ */ | ||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Worth quantifying, since this is the main thing an integrator weighs against the min-heap, the other O(log n) option. Measured from DWARF on qemu_x86_64:
struct _timeoutis 88 bytes here against 32 for the delta list and 24 for the min-heap, andstruct k_threadgoes from 880 to 928. The gap is large because expected height is 2 while every node statically carries all 8 levels, so most of each tower is unused.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done partially. but will it make sense to keep these sizes in the Kconfig help, or should they live somewhere that can stay accurate?
The RAM cost is the real trade-off here, so a number is useful. The current wording mixes a 64-bit _timeout comparison (88 vs 32 dlist / 24 min-heap at 8 levels) with a k_thread figure that is qemu_x86_64-specific (880 -> 928). The thread delta is not even sizeof(_timeout): FXSAVE 16-byte alignment swallows 8 bytes, so Cortex-M sees the full +56.
If we keep numbers in Kconfig, I would rather quote only struct _timeout and drop the board-specific k_thread line. A one-line comparison in the docs (or next to TIMEOUT_SKIPLIST_MAX_LEVEL) would also age better than baking 880/928 into the choice help.