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
fnthread entry point
argargument passed to the entry point when the thread is started
ctxcontext to resume execution of this thread (via longjmp())
stackinitial stack pointer for the thread
donetrue once fn has returned, false otherwise
grp_iduser-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.
listlink in the global scheduler list
-
enumuthread_mutex_state
internal state of a struct uthread_mutex
Constants
UTHREAD_MUTEX_UNLOCKEDmutex has no owner
UTHREAD_MUTEX_LOCKEDmutex has one owner
-
structuthread_mutex
a mutex object
Definition
struct uthread_mutex {
enum uthread_mutex_state state;
};
Members
statethe 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 *uthra 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 *argargument passed to the thread’s entry point
size_t stack_szstack size for the new thread (in bytes). The stack is allocated on the heap.
unsigned int grp_idan 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
voidno 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
voidno 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_idthe 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 *mutexpointer 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 *mutexpointer 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 *mutexpointer 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);