Skip to content

Thread Management

oveRTOS threads are portable wrappers around the native thread primitive of each supported backend: FreeRTOS tasks, Zephyr threads, NuttX pthreads, and POSIX pthreads. The same ove/thread.h API compiles unchanged for all four backends — priority mapping, state reporting, and stack profiling are translated at compile time with no runtime indirection.

Thread States

stateDiagram-v2
    [*] --> READY: ove_thread_create() / ove_thread_init()

    READY --> RUNNING: scheduler selects thread
    RUNNING --> READY: ove_thread_yield() / preempted

    RUNNING --> BLOCKED: ove_thread_sleep_ms() / sync wait
    BLOCKED --> READY: delay expires / object signalled

    RUNNING --> SUSPENDED: ove_thread_suspend()
    READY --> SUSPENDED: ove_thread_suspend()
    SUSPENDED --> READY: ove_thread_resume()

    RUNNING --> TERMINATED: entry function returns
    TERMINATED --> [*]: ove_thread_destroy() / ove_thread_deinit()
State Meaning
READY Thread is runnable; waiting for the CPU.
RUNNING Currently executing on the CPU.
BLOCKED Sleeping or waiting on a sync object; will be unblocked automatically.
SUSPENDED Explicitly paused via ove_thread_suspend(); stays suspended until ove_thread_resume().
TERMINATED Entry function has returned. The handle is still valid until the thread is destroyed.

Priority Levels

Constant Value Description
OVE_PRIO_IDLE 0 Lowest priority; runs only when no other thread is ready. Suitable for background statistics collection.
OVE_PRIO_LOW 1 Low-priority background work (logging, cleanup).
OVE_PRIO_BELOW_NORMAL 2 Below-default priority; useful for deferring non-urgent work.
OVE_PRIO_NORMAL 3 Default application priority. Most application threads should use this level.
OVE_PRIO_ABOVE_NORMAL 4 Slightly elevated; useful for UI or moderate-latency tasks.
OVE_PRIO_HIGH 5 High priority; prefer for time-sensitive processing threads.
OVE_PRIO_REALTIME 6 Real-time priority; use with care — starvation of lower-priority threads is possible.
OVE_PRIO_CRITICAL 7 Highest priority; reserved for critical system tasks (watchdog feeds, safety monitors).

Each value maps to a backend-specific numeric priority at initialisation time. Higher values mean higher scheduling priority on all backends.

Thread Lifecycle

sequenceDiagram
    participant App as Application
    participant Sched as Scheduler
    participant T as Worker Thread

    App->>Sched: ove_thread_create(&handle, STACK_SZ, &desc)
    Note over Sched: Thread enters READY state

    Sched->>T: entry(arg) thread begins executing
    activate T

    T->>Sched: ove_thread_sleep_ms(100)
    Note over T,Sched: Thread moves to BLOCKED
    deactivate T

    Sched-->>T: delay expires, READY then RUNNING
    activate T

    T->>Sched: ove_thread_yield()
    Note over T,Sched: Voluntarily relinquishes CPU
    deactivate T

    Sched-->>T: rescheduled
    activate T

    App->>Sched: ove_thread_suspend(handle)
    Note over T,Sched: Thread moves to SUSPENDED

    App->>Sched: ove_thread_resume(handle)
    Note over T,Sched: Thread returns to READY

    T-->>Sched: entry returns, TERMINATED
    deactivate T

    App->>Sched: ove_thread_destroy(handle)
    Note over Sched: Resources freed

Allocation Strategies

Heap allocation — ove_thread_create / ove_thread_destroy

The preferred API. Works in both standard heap mode and zero-heap mode (CONFIG_OVE_ZERO_HEAP). In zero-heap mode the macro generates per-call-site static storage — stack_sz must be a compile-time constant. Do not call inside a loop to create multiple independent threads; use ove_thread_init() with separate storage instead.

#include <ove/ove.h>

static void worker_entry(void *arg)
{
    (void)arg;
    for (;;) {
        /* do work */
        ove_thread_sleep_ms(50);
    }
}

static ove_thread_t worker;

void app_start(void)
{
    struct ove_thread_desc desc = {
        .name     = "worker",
        .entry    = worker_entry,
        .arg      = NULL,
        .priority = OVE_PRIO_NORMAL,
    };

    ove_thread_create(&worker, 2048, &desc);
}

Static allocation — ove_thread_init / ove_thread_deinit

Use when the thread object lives in an array, a struct, or when the allocation site cannot be a unique call-site (e.g. inside a loop). The caller supplies both a storage object and a stack buffer.

#include <ove/ove.h>

/* Declare storage at file scope (backend-specific opaque type). */
static ove_thread_storage_t worker_storage;
static uint8_t              worker_stack[2048] __attribute__((aligned(8)));

static ove_thread_t worker;

void app_start(void)
{
    struct ove_thread_desc desc = {
        .name       = "worker",
        .entry      = worker_entry,
        .arg        = NULL,
        .priority   = OVE_PRIO_NORMAL,
        .stack_size = sizeof(worker_stack),
        .stack      = worker_stack,
    };

    ove_thread_init(&worker, &worker_storage, &desc);
}

OVE_THREAD_DEFINE_STATIC macro

Combines storage declaration, stack allocation, and initialisation into a single file-scope statement. Works in both heap and zero-heap mode.

OVE_THREAD_DEFINE_STATIC(worker, worker_entry, NULL, OVE_PRIO_NORMAL, 2048);

API Reference

Function Signature Description
ove_thread_init int (ove_thread_t *handle, ove_thread_storage_t *storage, const struct ove_thread_desc *desc) Initialise a thread from caller-supplied static storage and stack.
ove_thread_deinit int (ove_thread_t handle) Stop and release a thread created with ove_thread_init(). Static storage is not freed.
ove_thread_create int (ove_thread_t *handle, size_t stack_sz, const struct ove_thread_desc *desc) Create a thread (heap or zero-heap macro). stack_sz must be a compile-time constant in zero-heap mode.
ove_thread_destroy int (ove_thread_t handle) Stop and free a thread created with ove_thread_create().
ove_thread_get_self ove_thread_t (void) Return the handle of the currently executing thread.
ove_thread_set_priority void (ove_thread_t handle, ove_prio_t prio) Change the scheduling priority of a thread at runtime.
ove_thread_sleep_ms void (uint32_t ms) Block the calling thread for at least ms milliseconds. Passing 0 yields for one scheduler tick.
ove_thread_yield void (void) Voluntarily relinquish the CPU to another ready thread of equal or higher priority.
ove_thread_start_scheduler void (void) Start the RTOS scheduler. Usually called indirectly via ove_run(). Does not return on most platforms.
ove_thread_suspend void (ove_thread_t handle) Prevent a thread from being scheduled. May be called on the calling thread itself.
ove_thread_resume void (ove_thread_t handle) Resume a previously suspended thread.
ove_thread_get_stack_usage size_t (ove_thread_t handle) Return the historical peak stack usage in bytes (high-water mark). Returns 0 if the backend does not support stack profiling.
ove_thread_get_state ove_thread_state_t (ove_thread_t handle) Query the current execution state of a thread.
ove_thread_get_runtime_stats int (ove_thread_t handle, struct ove_thread_stats *stats) Retrieve total CPU time (runtime_us) and utilisation percentage (cpu_percent_x100) since the scheduler started. Returns OVE_ERR_NOT_SUPPORTED if the backend does not provide runtime accounting.

Runtime Statistics and Stack Profiling

graph LR
    T["Worker Thread<br/><small>ove_thread_t handle</small>"]

    subgraph Stats
        direction TB
        R["ove_thread_get_runtime_stats()"]
        S["ove_thread_get_stack_usage()"]
        ST["ove_thread_get_state()"]
    end

    T --> R
    T --> S
    T --> ST

    R --> RU["runtime_us<br/><small>total CPU time μs</small>"]
    R --> CPU["cpu_percent_x100<br/><small>e.g. 1250 = 12.50%</small>"]
    S --> HW["stack high-water mark<br/><small>bytes used at peak</small>"]
    ST --> STATE["OVE_THREAD_STATE_*<br/><small>RUNNING / READY / BLOCKED<br/>SUSPENDED / TERMINATED</small>"]

ove_thread_get_runtime_stats() returns OVE_ERR_NOT_SUPPORTED when the backend does not provide CPU accounting. ove_thread_get_stack_usage() returns 0 when stack profiling is unavailable.

struct ove_thread_stats stats;
int rc = ove_thread_get_runtime_stats(worker, &stats);
if (rc == OVE_OK) {
    /* stats.cpu_percent_x100 == 1250 means 12.50 % */
}

size_t peak = ove_thread_get_stack_usage(worker);
/* peak > 0 only when the backend supports high-water marking */

Example: Worker Thread with Sleep Loop

A typical pattern for a periodic background task — create once at startup, run forever with a fixed sleep interval, and inspect the stack peak during development.

#include <ove/ove.h>

#define WORKER_STACK_SZ  2048
#define WORKER_PERIOD_MS 100

static volatile uint32_t sensor_value;
static ove_thread_t       sensor_thread;

static void sensor_entry(void *arg)
{
    (void)arg;
    for (;;) {
        sensor_value = read_sensor();
        ove_thread_sleep_ms(WORKER_PERIOD_MS);
    }
}

void ove_main(void)
{
    struct ove_thread_desc desc = {
        .name     = "sensor",
        .entry    = sensor_entry,
        .arg      = NULL,
        .priority = OVE_PRIO_NORMAL,
    };

    ove_thread_create(&sensor_thread, WORKER_STACK_SZ, &desc);
    ove_run();  /* starts the scheduler — does not return */
}

Kconfig Options

Option Default Description
CONFIG_OVE_THREAD always enabled Thread management subsystem. Always compiled; cannot be disabled. The scheduler must be running before any thread API is called.
Header Contents
ove/thread.h Thread descriptor struct, priority enum, state enum, runtime stats struct, all 13 thread functions/macros