Skip to content

Commit 5611e92

Browse files
wentongwuandrewboie
authored andcommitted
kernel: add futex support
A k_futex is a lightweight mutual exclusion primitive designed to minimize kernel involvement. Uncontended operation relies only on atomic access to shared memory. k_futex structure lives in application memory. And when using futexes, the majority of the synchronization operations are performed in user mode. A user-mode thread employs the futex wait system call only when it is likely that the program has to block for a longer time until the condition becomes true. When the condition comes true, futex wake operation will be used to wake up one or more threads waiting on that futex. This patch implements two futex operations: k_futex_wait and k_futex_wake. For k_futex_wait, the comparison with the expected value, and starting to sleep are performed atomically to prevent lost wake-ups. If different context changed futex's value after the calling use-mode thread decided to block himself based on the old value, the comparison will help observing the value change and will not start to sleep. And for k_futex_wake, it will wake at most num_waiters of the waiters that are sleeping on that futex. But no guarantees are made on which threads are woken, that means scheduling priority is not taken into consideration. Fixes: #14493. Signed-off-by: Wentong Wu <[email protected]>
1 parent 4e6e2e3 commit 5611e92

File tree

5 files changed

+225
-1
lines changed

5 files changed

+225
-1
lines changed

include/kernel.h

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ struct k_poll_event;
129129
struct k_poll_signal;
130130
struct k_mem_domain;
131131
struct k_mem_partition;
132+
struct k_futex;
132133

133134
/* This enumeration needs to be kept in sync with the lists of kernel objects
134135
* and subsystems in scripts/gen_kobject_list.py, as well as the otype_to_str()
@@ -2088,6 +2089,94 @@ static inline void *z_impl_k_queue_peek_tail(struct k_queue *queue)
20882089

20892090
/** @} */
20902091

2092+
#ifdef CONFIG_USERSPACE
2093+
/**
2094+
* @brief futex structure
2095+
*
2096+
* A k_futex is a lightweight mutual exclusion primitive designed
2097+
* to minimize kernel involvement. Uncontended operation relies
2098+
* only on atomic access to shared memory. k_futex are tracked as
2099+
* kernel objects and can live in user memory so any access bypass
2100+
* the kernel object permission management mechanism.
2101+
*/
2102+
struct k_futex {
2103+
atomic_t val;
2104+
};
2105+
2106+
/**
2107+
* @brief futex kernel data structure
2108+
*
2109+
* z_futex_data are the helper data structure for k_futex to complete
2110+
* futex contended operation on kernel side, structure z_futex_data
2111+
* of every futex object is invisible in user mode.
2112+
*/
2113+
struct z_futex_data {
2114+
_wait_q_t wait_q;
2115+
struct k_spinlock lock;
2116+
};
2117+
2118+
#define Z_FUTEX_DATA_INITIALIZER(obj) \
2119+
{ \
2120+
.wait_q = Z_WAIT_Q_INIT(&obj.wait_q) \
2121+
}
2122+
2123+
/**
2124+
* @defgroup futex_apis FUTEX APIs
2125+
* @ingroup kernel_apis
2126+
* @{
2127+
*/
2128+
2129+
/**
2130+
* @brief Initialize a futex.
2131+
*
2132+
* This routine initializes a futex object, prior to its first use.
2133+
*
2134+
* @param futex Address of the k_futex.
2135+
*
2136+
* @return N/A
2137+
*/
2138+
__syscall void k_futex_init(struct k_futex *futex);
2139+
2140+
/**
2141+
* @brief Pend the current thread on a futex
2142+
*
2143+
* Tests that the supplied futex contains the expected value, and if so,
2144+
* goes to sleep until some other thread calls k_futex_wake() on it.
2145+
*
2146+
* @param futex Address of the futex.
2147+
* @param expected Expected value of the futex, if it is different the caller
2148+
* will not wait on it.
2149+
* @param timeout Waiting period on the futex, in milliseconds, or one of the
2150+
* special values K_NO_WAIT or K_FOREVER.
2151+
* @retval -EACCES Caller does not have read access to futex address.
2152+
* @retval -EAGAIN If the futex value did not match the expected parameter.
2153+
* @retval -EINVAL Futex parameter address not recognized by the kernel.
2154+
* @retval -ETIMEDOUT Thread woke up due to timeout and not a futex wakeup.
2155+
* @retval 0 if the caller went to sleep and was woken up. The caller
2156+
* should check the futex's value on wakeup to determine if it needs
2157+
* to block again.
2158+
*/
2159+
__syscall int k_futex_wait(struct k_futex *futex, int expected, s32_t timeout);
2160+
2161+
/**
2162+
* @brief Wake one/all threads pending on a futex
2163+
*
2164+
* Wake up the highest priority thread pending on the supplied futex, or
2165+
* wakeup all the threads pending on the supplied futex, and the behavior
2166+
* depends on wake_all.
2167+
*
2168+
* @param futex Futex to wake up pending threads.
2169+
* @param wake_all If true, wake up all pending threads; If false,
2170+
* wakeup the highest priority thread.
2171+
* @retval -EACCES Caller does not have access to the futex address.
2172+
* @retval -EINVAL Futex parameter address not recognized by the kernel.
2173+
* @retval Number of threads that were woken up.
2174+
*/
2175+
__syscall int k_futex_wake(struct k_futex *futex, bool wake_all);
2176+
2177+
/** @} */
2178+
#endif
2179+
20912180
struct k_fifo {
20922181
struct k_queue _queue;
20932182
};

kernel/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ target_sources_if_kconfig( kernel PRIVATE poll.c)
4646
target_sources_ifdef(
4747
CONFIG_USERSPACE
4848
kernel PRIVATE
49+
futex.c
4950
mem_domain.c
5051
userspace_handler.c
5152
userspace.c

kernel/futex.c

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/*
2+
* Copyright (c) 2019 Intel corporation
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <kernel.h>
8+
#include <kernel_structs.h>
9+
#include <spinlock.h>
10+
#include <kswap.h>
11+
#include <syscall_handler.h>
12+
#include <init.h>
13+
#include <ksched.h>
14+
15+
static struct z_futex_data *k_futex_find_data(struct k_futex *futex)
16+
{
17+
struct _k_object *obj;
18+
19+
obj = z_object_find(futex);
20+
if (obj == NULL || obj->type != K_OBJ_FUTEX) {
21+
return NULL;
22+
}
23+
24+
return (struct z_futex_data *)obj->data;
25+
}
26+
27+
void z_impl_k_futex_init(struct k_futex *futex)
28+
{
29+
futex->val = 0U;
30+
z_object_init(futex);
31+
}
32+
33+
Z_SYSCALL_HANDLER(k_futex_init, futex)
34+
{
35+
if (Z_SYSCALL_MEMORY_WRITE(futex, sizeof(struct k_futex)) != 0) {
36+
return -EACCES;
37+
}
38+
39+
z_impl_k_futex_init((struct k_futex *)futex);
40+
41+
return 0;
42+
}
43+
44+
int z_impl_k_futex_wake(struct k_futex *futex, bool wake_all)
45+
{
46+
k_spinlock_key_t key;
47+
unsigned int woken = 0;
48+
struct k_thread *thread;
49+
struct z_futex_data *futex_data;
50+
51+
futex_data = k_futex_find_data(futex);
52+
if (futex_data == NULL) {
53+
return -EINVAL;
54+
}
55+
56+
key = k_spin_lock(&futex_data->lock);
57+
58+
do {
59+
thread = z_unpend_first_thread(&futex_data->wait_q);
60+
if (thread) {
61+
z_ready_thread(thread);
62+
z_set_thread_return_value(thread, 0);
63+
woken++;
64+
}
65+
} while (thread && wake_all);
66+
67+
z_reschedule(&futex_data->lock, key);
68+
69+
return woken;
70+
}
71+
72+
Z_SYSCALL_HANDLER(k_futex_wake, futex, wake_all)
73+
{
74+
if (Z_SYSCALL_MEMORY_WRITE(futex, sizeof(struct k_futex)) != 0) {
75+
return -EACCES;
76+
}
77+
78+
return z_impl_k_futex_wake((struct k_futex *)futex, (bool)wake_all);
79+
}
80+
81+
int z_impl_k_futex_wait(struct k_futex *futex, int expected, s32_t timeout)
82+
{
83+
int ret;
84+
k_spinlock_key_t key;
85+
struct z_futex_data *futex_data;
86+
87+
futex_data = k_futex_find_data(futex);
88+
if (futex_data == NULL) {
89+
return -EINVAL;
90+
}
91+
92+
key = k_spin_lock(&futex_data->lock);
93+
94+
if (atomic_get(&futex->val) != (atomic_val_t)expected) {
95+
k_spin_unlock(&futex_data->lock, key);
96+
return -EAGAIN;
97+
}
98+
99+
ret = z_pend_curr(&futex_data->lock,
100+
key, &futex_data->wait_q, timeout);
101+
if (ret == -EAGAIN) {
102+
ret = -ETIMEDOUT;
103+
}
104+
105+
return ret;
106+
}
107+
108+
Z_SYSCALL_HANDLER(k_futex_wait, futex, expected, timeout)
109+
{
110+
if (Z_SYSCALL_MEMORY_WRITE(futex, sizeof(struct k_futex)) != 0) {
111+
return -EACCES;
112+
}
113+
114+
return z_impl_k_futex_wait((struct k_futex *)futex,
115+
expected, (s32_t)timeout);
116+
}

scripts/elf_helper.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ def kobject_to_enum(kobj):
3838
STACK_TYPE = "_k_thread_stack_element"
3939
thread_counter = 0
4040
sys_mutex_counter = 0
41+
futex_counter = 0
4142

4243
# Global type environment. Populated by pass 1.
4344
type_env = {}
@@ -56,6 +57,7 @@ class KobjectInstance:
5657
def __init__(self, type_obj, addr):
5758
global thread_counter
5859
global sys_mutex_counter
60+
global futex_counter
5961

6062
self.addr = addr
6163
self.type_obj = type_obj
@@ -72,6 +74,9 @@ def __init__(self, type_obj, addr):
7274
elif self.type_obj.name == "sys_mutex":
7375
self.data = "(u32_t)(&kernel_mutexes[%d])" % sys_mutex_counter
7476
sys_mutex_counter += 1
77+
elif self.type_obj.name == "k_futex":
78+
self.data = "(u32_t)(&futex_data[%d])" % futex_counter
79+
futex_counter += 1
7580
else:
7681
self.data = 0
7782

@@ -566,3 +571,6 @@ def get_thread_counter(self):
566571

567572
def get_sys_mutex_counter(self):
568573
return sys_mutex_counter
574+
575+
def get_futex_counter(self):
576+
return futex_counter

scripts/gen_kobject_list.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,8 @@
8686
("k_timer", (None, False)),
8787
("_k_thread_stack_element", (None, False)),
8888
("device", (None, False)),
89-
("sys_mutex", (None, True))
89+
("sys_mutex", (None, True)),
90+
("k_futex", (None, True))
9091
])
9192

9293

@@ -170,6 +171,15 @@ def write_gperf_table(fp, eh, objs, static_begin, static_end):
170171
fp.write(", ")
171172
fp.write("};\n")
172173

174+
num_futex = eh.get_futex_counter()
175+
if (num_futex != 0):
176+
fp.write("static struct z_futex_data futex_data[%d] = {\n" % num_futex)
177+
for i in range(num_futex):
178+
fp.write("Z_FUTEX_DATA_INITIALIZER(futex_data[%d])" % i)
179+
if (i != num_futex - 1):
180+
fp.write(", ")
181+
fp.write("};\n")
182+
173183
fp.write("%%\n")
174184
# Setup variables for mapping thread indexes
175185
syms = eh.get_symbols()

0 commit comments

Comments
 (0)