Aerlync Logo
calendar

23 Jun 2025

calendar

5 min read

High Level Design of Timeouts in Zephyr

-Sayooj K Karun

High Level Design of Timeouts in Zephyr

A timeout is a software event scheduled to occur after a specific number of kernel ticks. It’s used to:

  • Wake up threads after a delay (k_sleep, k_timer_start, etc.).
  • Schedule work items
  • Drive internal kernel timing (e.g., watchdogs, polling).

Core Components

ComponentDescription
timeout_listSorted linked list of all active timeouts (sooner first).
curr_tickSoftware-maintained tick counter (keep in sync with hardware timer).
timeout_lockSpinlock for protecting timeout list access in SMP.ISR-safe manner.
_timeout structKernel object representing a timeout. Often embedded inside other objects.

How Time Progresses

  1. The hardware timer fires an interrupt on each tick (e.g. every 1 ms).
  2. ISR calls z_clock_announce(ticks_elapsed) to inform kernel of how much time has passed.
  3. Kernel updates curr_tick.
  4. Expired timeouts are removed from the list and their callbacks are invoked.
  5. Kernel then sets the next timer interrupt to the soonest timeout’s expiration.

What is “announce”?

`sys_clock_announce()` or `z_clock_announce()` is the mechanism by which the hardware tells the kernel how many ticks have elapsed.

It “announces” time progression, and the kernel responds by:

  • Updating `curr_tick`
  • Expiring timeouts
  • Waking up sleeping threads
  • Running callbacks

This separation lets the kernel remain platform-independent, while the SoC-specific timer code just needs to call the `announce()` function when needed.

Timeout Lifecycle

  1. Added to timeout_list via z_add_timeout()
  2. Sorted into the list based on expiration tick
  3. Tick updated via z_clock_announce()
  4. Expired timeouts removed, their callbacks called
  5. Next timeout determines when the next interrupt should fire (z_set_timeout_expiry())

Example: k_sleep()

k_sleep(K_MSEC(100));

Internally:

  • Converts 100 ms -> N ticks.
  • Creates a _timeout object for current thread.
  • Inserts into `timeout_list`.
  • Thread is put to sleep.
  • After N ticks, `z_clock_announce()` expires the timeout.
  • Thread is woken up and continues execution.
Diagram: How time passes.

Key Timeout Functions in Zephyr

z_add_timeout()

Purpose:

Add a timeout to the kernel’s `timeout_list` in a sorted position based on when it should expire.

  • locks timeout_lock
  • Initializes the timeout structure
  1. Sets the callback.
  2. Sets the expiration time: to->dticks = ticks + curr_tick.
  • Inserts into timeout_list sorted by dticks (earliest first).
  • Calls z_set_timeout_expiry() to update the hardware timer if needed.

Why Sorted?

To ensure only the soonest timeout affects the next hardware timer interrupt, improving power efficiency.

z_abort_timeout()

Purpose:

Cancel a previously added timeout(e.g., thread woke up early or was aborted).

  • Acquires timeout_lock.
  • Removes the timeout from timeout_list.
  • Clears the callback and expiration fields.

Used when a timeout was set for a thread or timer, but it no longer needs to wait.

z_set_timeout_expiry()

Purpose:

Update the hardware timer to fire at the appropriate time (next timeout tick).

Parameters:

  • ticks: Delay in ticks until next known expiration.
  • idle: True if the system is idle - may allow longer delay if sloppy idle mode is on.

Logic:

  • If ticks == K_TICKS_FOREVER, the timer can sleep indefinitely.
  • Otherwise, the system sets the hardware timer to fire at curr_tick + ticks

z_clock_announce()

Purpose:

Called by the hardware timer ISR to tell the kernel that ticks have passed.

Logic:

  • Increments curr_tick by ticks
  • Loops over timeout_list:

For each timeout where dticks <= curr_tick, remove and call its callback.

  • Handles annouce_remaining to ensure one tick’s worth of timeouts are handled in each round.
  • runs callbacks(could wake threads)
  • Prepares system for next timer interrupt.

z_get_next_timeout_expiry()

Purpose:

Used internally by the kernel to figure out when the next timeout will expire.

Return:

  • INT_MAX if no timeouts are active.
  • Number of ticks from curr_tick to the next timeout.

Example Flow: Timeout Insert and Expire

  • z_add_timeout(thread->timeout, wakeup_func, 100) -> Scheduled for 100 ticks.
  • 100 ticks later, z_clock_announce(100) is called by timer ISR.
  • Timeout is found and removed.
  • wakeup_func() is called to make thread ready.

announce_remaining - Ensuring One Tick’s worth of Timeouts per Round

z_clock_announce(ticks) is called by the hardware to notify that N ticks have passed since the last call. But a lot could have happened during those ticks. Many timeouts may have expired.

If Zephyr were to process all expired timeouts at once, it could:

  • starve real-time tasks needing immediate execution.
  • Violate maximum interrupt latency constraints.
  • Lock the kernel for too long in a single loop function

Zephyr limits how many ticks are processed in one call to z_clock_announce(), using announce_remaining variable.

Detailed Mechanism

  1. When z_clock_announce(ticks) is called:
  • It sets announce_remaining = tick

2. Zephyr the enters a loop to:

  • increment current tick
  • check if any timeout has expired
  • Fires their callbacks
  • Decrement announce_remaining

3. After processing 1 tick worth of expiration timeouts, it exits and lets the scheduler run.

4. If announce_remaining > 0, the kernel will resume processing the remaining ticks in a cooperative context (i.e not from ISR)

Advantages:

  • Keeps the interrupt handler short.
  • Gives preemptive threads a chance to run.
  • Allows better power and scheduling behavior.

This is especially helpful when the system wakes up from deep sleep, say, 50 ticks passed. It avoids monopoly over CPU by timer right after CPU wakes up.

Sloppy Timeout

A configuration in Zephyr that allows the kernel to stop tracking ticks precisely when the system is in idle or power-saving states. (CONFIG_SYSTEM_CLOCK_SLOPPY_IDLE)

In normal operation, the kernel:

  • Tracks tick-by-tick progress
  • Maintains precise tick count in curr_tick

But when sloppy idle is enabled:

  • Instead of firing every tick, the hardware timer can sleep longer.
  • The kernel “accepts” the coarser tracking of time in exchange for lower power use.
  • Upon wake-up, z_clock_announce() is called with a bulk number of ticks passed.

Example:

If system was idle for 60 ms:

  • normal mode: 60 interrupts
  • sloppy mode: 1 timer interrupt after 60 ms, z_clock_announce(60) called.

Why is it called “Sloppy”?

Because tick precision is lost. Events that should have happened at tick 20, 40 and 60 all appears to happen at 60. This is okay for some apps, but not for precise timers.

Recommended Blogs

Exploring Zephyr RTOS: A Lightweight, Scalable Real-Time Operating System for the Modern IoT Era
calendar

14 May 2025

calendar

5 min read

Exploring Zephyr RTOS: A Lightweight, Scalable Real-Time Operating System for the Modern IoT Era

Zephyr OS Security: Architecture, Features, and the Future of IoT Security
calendar

21 May 2025

calendar

5 min read

Zephyr OS Security: Architecture, Features, and the Future of IoT Security

Edge AI: Intelligence at the Frontier of Computing
calendar

15 Oct 2025

calendar

5 min read

Edge AI: Intelligence at the Frontier of Computing

Build with the Most Trusted Engineering Partner

Aerlync Logo

Delivers cutting-edge embedded solutions, from firmware development to wireless protocols, ensuring reliability and innovation.

facebook
linkedin
twitter
insta

Privacy Policy

Terms of Service

Copyright © 2026

High Level Design of Timeouts in Zephyr | Aerlync