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.
-
struct uthread
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
-
enum uthread_mutex_state
internal state of a struct uthread_mutex
Constants
UTHREAD_MUTEX_UNLOCKED
mutex has no owner
UTHREAD_MUTEX_LOCKED
mutex has one owner
-
struct uthread_mutex
a mutex object
Definition
struct uthread_mutex {
enum uthread_mutex_state state;
};
Members
state
the internal state of the mutex
-
int uthread_create(struct uthread *uthr, void (*fn)(void*), void *arg, size_t stack_sz, unsigned int grp_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.
-
bool uthread_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
-
unsigned int uthread_grp_new_id(void)
return a new ID for a thread group
Parameters
void
no arguments
Return
the new thread group ID
-
bool uthread_grp_done(unsigned int grp_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
-
int uthread_mutex_lock(struct uthread_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).
-
int uthread_mutex_trylock(struct uthread_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.
-
int uthread_mutex_unlock(struct uthread_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
13static int count;
14
15/* A thread entry point */
16static void worker(void *arg)
17{
18 int loops = (int)(unsigned long)arg;
19 int i;
20
21 for (i = 0; i < loops; i++) {
22 count++;
23 uthread_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 */
38static int uthread(struct unit_test_state *uts)
39{
40 int i;
41 int id1, id2;
42
43 count = 0;
44 id1 = uthread_grp_new_id();
45 ut_assert(id1 != 0);
46 id2 = uthread_grp_new_id();
47 ut_assert(id2 != 0);
48 ut_assert(id1 != id2);
49 ut_assertok(uthread_create(NULL, worker, (void *)5, 0, id1));
50 ut_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 */
56 ut_assert(uthread_schedule());
57 ut_asserteq(2, count);
58 ut_assert(!uthread_grp_done(id1));
59 /* Four more calls should bring the count to 10 */
60 for (i = 0; i < 4; i++) {
61 ut_assert(!uthread_grp_done(id1));
62 ut_assert(uthread_schedule());
63 }
64 ut_asserteq(10, count);
65 /* This one allows the first worker to exit */
66 ut_assert(uthread_schedule());
67 /* At this point there should be no runnable thread in group 'id1' */
68 ut_assert(uthread_grp_done(id1));
69 /* Five more calls for the second worker to finish incrementing */
70 for (i = 0; i < 5; i++)
71 ut_assert(uthread_schedule());
72 ut_asserteq(15, count);
73 /* Plus one call to let the second worker return from its entry point */
74 ut_assert(uthread_schedule());
75 /* Now both tasks should be done, schedule should return false */
76 ut_assert(!uthread_schedule());
77
78 return 0;
79}
80LIB_TEST(uthread, 0);
81
82struct mw_args {
83 struct unit_test_state *uts;
84 struct uthread_mutex *m;
85 int flag;
86};
87
88static int mutex_worker_ret;
89
90static int _mutex_worker(struct mw_args *args)
91{
92 struct unit_test_state *uts = args->uts;
93
94 ut_asserteq(-EBUSY, uthread_mutex_trylock(args->m));
95 ut_assertok(uthread_mutex_lock(args->m));
96 args->flag = 1;
97 ut_assertok(uthread_mutex_unlock(args->m));
98
99 return 0;
100}
101
102static void mutex_worker(void *arg)
103{
104 mutex_worker_ret = _mutex_worker((struct mw_args *)arg);
105}
106
107/*
108 * thread_mutex() - testing uthread mutex operations
109 *
110 */
111static int uthread_mutex(struct unit_test_state *uts)
112{
113 struct uthread_mutex m = UTHREAD_MUTEX_INITIALIZER;
114 struct mw_args args = { .uts = uts, .m = &m, .flag = 0 };
115 int id;
116 int i;
117
118 id = uthread_grp_new_id();
119 ut_assert(id != 0);
120 /* Take the mutex */
121 ut_assertok(uthread_mutex_lock(&m));
122 /* Start a thread */
123 ut_assertok(uthread_create(NULL, mutex_worker, (void *)&args, 0,
124 id));
125 /* Let the thread run for a bit */
126 for (i = 0; i < 100; i++)
127 ut_assert(uthread_schedule());
128 /* Thread should not have set the flag due to the mutex */
129 ut_asserteq(0, args.flag);
130 /* Release the mutex */
131 ut_assertok(uthread_mutex_unlock(&m));
132 /* Schedule the thread until it is done */
133 while (uthread_schedule())
134 ;
135 /* Now the flag should be set */
136 ut_asserteq(1, args.flag);
137 /* And the mutex should be available */
138 ut_assertok(uthread_mutex_trylock(&m));
139 ut_assertok(uthread_mutex_unlock(&m));
140
141 /* Of course no error are expected from the thread routine */
142 ut_assertok(mutex_worker_ret);
143
144 return 0;
145}
146LIB_TEST(uthread_mutex, 0);