Uthread API

The uthread framework is a basic task scheduler that allows to run functions "in parallel" on a single CPU core. The scheduling is cooperative, not preemptive – meaning that context switches from one task to another task is voluntary, via a call to uthread_schedule(). This characteristic makes thread synchronization much easier, because a thread cannot be interrupted in the middle of a critical section (reading from or writing to shared state, for instance).

CONFIG_UTHREAD in lib/Kconfig enables the uthread framework. When disabled, the uthread_create() and uthread_schedule() functions may still be used so that code differences between uthreads enabled and disabled can be reduced to a minimum.

structuthread

a thread object

Definition

struct uthread {
 void (*fn)(void *arg);
 void *arg;
 jmp_buf ctx;
 void *stack;
 bool done;
 unsigned int grp_id;
 struct list_head list;
};

Members

fn

thread entry point

arg

argument passed to the entry point when the thread is started

ctx

context to resume execution of this thread (via longjmp())

stack

initial stack pointer for the thread

done

true once fn has returned, false otherwise

grp_id

user-supplied identifier for this thread and possibly others. A thread can belong to zero or one group (not more), and a group may contain any number of threads.

list

link in the global scheduler list

enumuthread_mutex_state

internal state of a struct uthread_mutex

Constants

UTHREAD_MUTEX_UNLOCKED

mutex has no owner

UTHREAD_MUTEX_LOCKED

mutex has one owner

structuthread_mutex

a mutex object

Definition

struct uthread_mutex {
 enum uthread_mutex_state state;
};

Members

state

the internal state of the mutex

intuthread_create(structuthread *uthr, void(*fn)(void*), void*arg, size_tstack_sz, unsignedintgrp_id)

Create a uthread object and make it ready for execution

Parameters

struct uthread *uthr

a pointer to a user-allocated uthread structure to store information about the new thread, or NULL to let the framework allocate and manage its own structure.

void (*fn)(void *)

the thread’s entry point

void *arg

argument passed to the thread’s entry point

size_t stack_sz

stack size for the new thread (in bytes). The stack is allocated on the heap.

unsigned int grp_id

an optional thread group ID that the new thread should belong to (zero for no group)

Description

Threads are automatically deleted when they return from their entry point.

booluthread_schedule(void)

yield the CPU to the next runnable thread

Parameters

void

no arguments

Description

This function is called either by the main thread or any secondary thread (that is, any thread created via uthread_create()) to switch execution to the next runnable thread.

Return

true if a thread was scheduled, false if no runnable thread was found

unsignedintuthread_grp_new_id(void)

return a new ID for a thread group

Parameters

void

no arguments

Return

the new thread group ID

booluthread_grp_done(unsignedintgrp_id)

test if all threads in a group are done

Parameters

unsigned int grp_id

the ID of the thread group that should be considered

Return

false if the group contains at least one runnable thread (i.e., one thread which entry point has not returned yet), true otherwise

intuthread_mutex_lock(structuthread_mutex *mutex)

lock a mutex

Parameters

struct uthread_mutex *mutex

pointer to the mutex to lock

Description

If the cwmutexlock is available (i.e., not owned by any other thread), then it is locked for use by the current thread. Otherwise the current thread blocks: it enters a wait loop by scheduling other threads until the mutex becomes unlocked.

Return

0 on success, in which case the lock is owned by the calling thread. != 0 otherwise (the lock is not owned by the calling thread).

intuthread_mutex_trylock(structuthread_mutex *mutex)

lock a mutex if not currently locked

Parameters

struct uthread_mutex *mutex

pointer to the mutex to lock

Description

Similar to uthread_mutex_lock() except return immediately if the mutex is locked already.

Return

0 on success, in which case the lock is owned by the calling thread. EBUSY if the mutex is already locked by another thread. Any other non-zero value on error.

intuthread_mutex_unlock(structuthread_mutex *mutex)

unlock a mutex

Parameters

struct uthread_mutex *mutex

pointer to the mutex to unlock

Description

The mutex is assumed to be owned by the calling thread on entry. On exit, it is unlocked.

Return

0 on success, != 0 on error

Example

Here is an example of how to use this API:

 1// SPDX-License-Identifier: GPL-2.0+
 2/*
 3 * Copyright 2025 Linaro Limited
 4 *
 5 * Unit test for uthread
 6 */
 7
 8#include<stdbool.h>
 9#include<test/lib.h>
 10#include<test/ut.h>
 11#include<uthread.h>
 12
 13staticintcount;
 14
 15/* A thread entry point */
 16staticvoidworker(void*arg)
 17{
 18intloops=(int)(unsignedlong)arg;
 19inti;
 20
 21for(i=0;i<loops;i++){
 22count++;
 23uthread_schedule();
 24}
 25}
 26
 27/*
 28 * uthread() - testing the uthread API
 29 *
 30 * This function creates two threads with the same entry point. The first one
 31 * receives 5 as an argument, the second one receives 10. The number indicates
 32 * the number of time the worker thread should loop on uthread_schedule()
 33 * before returning. The workers increment a global counter each time they loop.
 34 * As a result the main thread knows how many times it should call
 35 * uthread_schedule() to let the two threads proceed, and it also knows which
 36 * value the counter should have at any moment.
 37 */
 38staticintuthread(structunit_test_state*uts)
 39{
 40inti;
 41intid1,id2;
 42
 43count=0;
 44id1=uthread_grp_new_id();
 45ut_assert(id1!=0);
 46id2=uthread_grp_new_id();
 47ut_assert(id2!=0);
 48ut_assert(id1!=id2);
 49ut_assertok(uthread_create(NULL,worker,(void*)5,0,id1));
 50ut_assertok(uthread_create(NULL,worker,(void*)10,0,0));
 51/*
 52	 * The first call is expected to schedule the first worker, which will
 53	 * schedule the second one, which will schedule back to the main thread
 54	 * (here). Therefore count should be 2.
 55	 */
 56ut_assert(uthread_schedule());
 57ut_asserteq(2,count);
 58ut_assert(!uthread_grp_done(id1));
 59/* Four more calls should bring the count to 10 */
 60for(i=0;i<4;i++){
 61ut_assert(!uthread_grp_done(id1));
 62ut_assert(uthread_schedule());
 63}
 64ut_asserteq(10,count);
 65/* This one allows the first worker to exit */
 66ut_assert(uthread_schedule());
 67/* At this point there should be no runnable thread in group 'id1' */
 68ut_assert(uthread_grp_done(id1));
 69/* Five more calls for the second worker to finish incrementing */
 70for(i=0;i<5;i++)
 71ut_assert(uthread_schedule());
 72ut_asserteq(15,count);
 73/* Plus one call to let the second worker return from its entry point */
 74ut_assert(uthread_schedule());
 75/* Now both tasks should be done, schedule should return false */
 76ut_assert(!uthread_schedule());
 77
 78return0;
 79}
 80LIB_TEST(uthread,0);
 81
 82structmw_args{
 83structunit_test_state*uts;
 84structuthread_mutex*m;
 85intflag;
 86};
 87
 88staticintmutex_worker_ret;
 89
 90staticint_mutex_worker(structmw_args*args)
 91{
 92structunit_test_state*uts=args->uts;
 93
 94ut_asserteq(-EBUSY,uthread_mutex_trylock(args->m));
 95ut_assertok(uthread_mutex_lock(args->m));
 96args->flag=1;
 97ut_assertok(uthread_mutex_unlock(args->m));
 98
 99return0;
100}
101
102staticvoidmutex_worker(void*arg)
103{
104mutex_worker_ret=_mutex_worker((structmw_args*)arg);
105}
106
107/*
108 * thread_mutex() - testing uthread mutex operations
109 *
110 */
111staticintuthread_mutex(structunit_test_state*uts)
112{
113structuthread_mutexm=UTHREAD_MUTEX_INITIALIZER;
114structmw_argsargs={.uts=uts,.m=&m,.flag=0};
115intid;
116inti;
117
118id=uthread_grp_new_id();
119ut_assert(id!=0);
120/* Take the mutex */
121ut_assertok(uthread_mutex_lock(&m));
122/* Start a thread */
123ut_assertok(uthread_create(NULL,mutex_worker,(void*)&args,0,
124id));
125/* Let the thread run for a bit */
126for(i=0;i<100;i++)
127ut_assert(uthread_schedule());
128/* Thread should not have set the flag due to the mutex */
129ut_asserteq(0,args.flag);
130/* Release the mutex */
131ut_assertok(uthread_mutex_unlock(&m));
132/* Schedule the thread until it is done */
133while(uthread_schedule())
134;
135/* Now the flag should be set */
136ut_asserteq(1,args.flag);
137/* And the mutex should be available */
138ut_assertok(uthread_mutex_trylock(&m));
139ut_assertok(uthread_mutex_unlock(&m));
140
141/* Of course no error are expected from the thread routine */
142ut_assertok(mutex_worker_ret);
143
144return0;
145}
146LIB_TEST(uthread_mutex,0);