mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2025-12-16 10:03:50 +01:00
Merge pull request #21254 from derMihai/mir/tflags_group
core/thread_flags: add thread flags group
This commit is contained in:
commit
d732bd053d
@ -1,5 +1,5 @@
|
|||||||
# exclude submodule sources from *.c wildcard source selection
|
# exclude submodule sources from *.c wildcard source selection
|
||||||
SRC := $(filter-out mbox.c msg.c msg_bus.c thread.c thread_flags.c,$(wildcard *.c))
|
SRC := $(filter-out mbox.c msg.c msg_bus.c thread.c thread_flags.c thread_flags_group.c,$(wildcard *.c))
|
||||||
|
|
||||||
# enable submodules
|
# enable submodules
|
||||||
SUBMODULES := 1
|
SUBMODULES := 1
|
||||||
|
|||||||
133
core/include/thread_flags_group.h
Normal file
133
core/include/thread_flags_group.h
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2025 ML!PA Consulting GmbH
|
||||||
|
*
|
||||||
|
* This file is subject to the terms and conditions of the GNU Lesser
|
||||||
|
* General Public License v2.1. See the file LICENSE in the top level
|
||||||
|
* directory for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @defgroup core_thread_flags_group Thread Flags Group
|
||||||
|
* @ingroup core
|
||||||
|
* @brief Waiter groups for thread flags
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* This API is optional and must be enabled by adding "core_thread_flags_group"
|
||||||
|
* to USEMODULE.
|
||||||
|
*
|
||||||
|
* A thread flags group allows setting thread flags for an arbitrary number of
|
||||||
|
* threads (called waiters) at the same time. The waiters can join and leave
|
||||||
|
* the group at any time. An additional advantage is that the signaler (the
|
||||||
|
* "flags setter") is de-coupled from the list of waiters, i.e. it does not
|
||||||
|
* need to know which specific thread must be signaled.
|
||||||
|
*
|
||||||
|
* Example (waiter):
|
||||||
|
*
|
||||||
|
* static thread_flags_group_t group = THREAD_FLAGS_GROUP_INIT;
|
||||||
|
*
|
||||||
|
* ...
|
||||||
|
*
|
||||||
|
* thread_flags_group_join(&group);
|
||||||
|
*
|
||||||
|
* while (!some_condition_is_met()) {
|
||||||
|
* thread_flags_wait_any(SOME_FLAG);
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* thread_flags_group_leave(&group);
|
||||||
|
*
|
||||||
|
* Example (signaler):
|
||||||
|
*
|
||||||
|
* fulfill_some_condition();
|
||||||
|
* thread_flags_group_set(&group, SOME_FLAG);
|
||||||
|
*
|
||||||
|
* @{
|
||||||
|
*
|
||||||
|
* @file
|
||||||
|
* @brief Thread Flags Group API
|
||||||
|
*
|
||||||
|
* @author Mihai Renea <mihai.renea@ml-pa.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <limits.h>
|
||||||
|
|
||||||
|
#include "atomic_utils.h"
|
||||||
|
#include "thread.h"
|
||||||
|
#include "thread_flags.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* UINT_WIDTH is only provided starting with -std=c23 or newer. Until RIOT
|
||||||
|
* requires C23 as C version, we need provide it by hand when missing. */
|
||||||
|
#ifndef UINT_WIDTH
|
||||||
|
/**
|
||||||
|
* @brief Number of bits in unsigned int
|
||||||
|
*/
|
||||||
|
# define UINT_WIDTH (sizeof(unsigned) * 8)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Thread flags group.
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
/**
|
||||||
|
* Members bitfield.
|
||||||
|
*/
|
||||||
|
unsigned members[(MAXTHREADS / UINT_WIDTH) + !!(MAXTHREADS % UINT_WIDTH)];
|
||||||
|
} thread_flags_group_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialize a thread flags group.
|
||||||
|
*/
|
||||||
|
#define THREAD_FLAGS_GROUP_INIT { .members = { 0 } }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Join a thread flags group.
|
||||||
|
*
|
||||||
|
* After joining the group, the thread may call any thread_flags_wait_*()
|
||||||
|
* method as usual. The thread will remain in the group until @ref
|
||||||
|
* thread_flags_group_leave() is called.
|
||||||
|
*
|
||||||
|
* @param[out] group The thread flags group to join.
|
||||||
|
*/
|
||||||
|
static inline void thread_flags_group_join(thread_flags_group_t *group)
|
||||||
|
{
|
||||||
|
kernel_pid_t pid = thread_getpid();
|
||||||
|
/* this also optimizes away the arithmetic below if MAXTHREADS <= UINT_WIDTH */
|
||||||
|
assume(pid < MAXTHREADS);
|
||||||
|
atomic_set_bit_unsigned(atomic_bit_unsigned(&group->members[pid / UINT_WIDTH],
|
||||||
|
pid % UINT_WIDTH));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Leave a thread flags group.
|
||||||
|
*
|
||||||
|
* After leaving the group, the thread will no longer be signaled by @ref
|
||||||
|
* thread_flags_group_set().
|
||||||
|
*
|
||||||
|
* @param[out] group The thread flags group to leave.
|
||||||
|
*/
|
||||||
|
static inline void thread_flags_group_leave(thread_flags_group_t *group)
|
||||||
|
{
|
||||||
|
kernel_pid_t pid = thread_getpid();
|
||||||
|
/* this also optimizes away the arithmetic below if MAXTHREADS <= UINT_WIDTH */
|
||||||
|
assume(pid < MAXTHREADS);
|
||||||
|
atomic_clear_bit_unsigned(atomic_bit_unsigned(&group->members[pid / UINT_WIDTH],
|
||||||
|
pid % UINT_WIDTH));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set thread flags for all threads in a group.
|
||||||
|
*
|
||||||
|
* @param[in] group The thread flags group to set flags to.
|
||||||
|
* @param[in] mask The flags to set.
|
||||||
|
*/
|
||||||
|
void thread_flags_group_set(thread_flags_group_t *group, thread_flags_t mask);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
/** @} */
|
||||||
44
core/thread_flags_group.c
Normal file
44
core/thread_flags_group.c
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2025 ML!PA Consulting GmbH
|
||||||
|
*
|
||||||
|
* This file is subject to the terms and conditions of the GNU Lesser
|
||||||
|
* General Public License v2.1. See the file LICENSE in the top level
|
||||||
|
* directory for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ingroup core
|
||||||
|
* @{
|
||||||
|
*
|
||||||
|
* @file
|
||||||
|
* @brief thread flags group implementation
|
||||||
|
*
|
||||||
|
* @author Mihai Renea <mihai.renea@ml-pa.com>
|
||||||
|
*
|
||||||
|
* @}
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "bitarithm.h"
|
||||||
|
#include "irq.h"
|
||||||
|
#include "thread.h"
|
||||||
|
#include "thread_flags_group.h"
|
||||||
|
|
||||||
|
void thread_flags_group_set(thread_flags_group_t *group, thread_flags_t mask)
|
||||||
|
{
|
||||||
|
/* Interrupts must be disabled because the threads are not ordered by
|
||||||
|
* priority. */
|
||||||
|
unsigned irq_state = irq_disable();
|
||||||
|
|
||||||
|
for (kernel_pid_t i = 0; i < (kernel_pid_t)ARRAY_SIZE(group->members); i++) {
|
||||||
|
unsigned pid_block = group->members[i];
|
||||||
|
kernel_pid_t const pid_base = i * UINT_WIDTH;
|
||||||
|
uint8_t pid_offs = 0;
|
||||||
|
|
||||||
|
while (pid_block) {
|
||||||
|
pid_block = bitarithm_test_and_clear(pid_block, &pid_offs);
|
||||||
|
thread_flags_set(thread_get(pid_base + pid_offs), mask);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
irq_restore(irq_state);
|
||||||
|
}
|
||||||
12
tests/core/thread_flags_group/Makefile
Normal file
12
tests/core/thread_flags_group/Makefile
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
include ../Makefile.core_common
|
||||||
|
|
||||||
|
USEMODULE += core_thread_flags
|
||||||
|
USEMODULE += core_thread_flags_group
|
||||||
|
|
||||||
|
ifneq (,$(filter native32 native64,$(BOARD)))
|
||||||
|
# test non-trivial membership array on native
|
||||||
|
CFLAGS += -DMAXTHREADS=64
|
||||||
|
CFLAGS += -DWAITER_THREADS_CNT=40
|
||||||
|
endif
|
||||||
|
|
||||||
|
include $(RIOTBASE)/Makefile.include
|
||||||
3
tests/core/thread_flags_group/Makefile.ci
Normal file
3
tests/core/thread_flags_group/Makefile.ci
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
BOARD_INSUFFICIENT_MEMORY := \
|
||||||
|
nucleo-l011k4 \
|
||||||
|
#
|
||||||
112
tests/core/thread_flags_group/main.c
Normal file
112
tests/core/thread_flags_group/main.c
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2025 ML!PA Consulting GmbH
|
||||||
|
*
|
||||||
|
* This file is subject to the terms and conditions of the GNU Lesser
|
||||||
|
* General Public License v2.1. See the file LICENSE in the top level
|
||||||
|
* directory for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ingroup tests
|
||||||
|
* @{
|
||||||
|
*
|
||||||
|
* @file
|
||||||
|
* @brief thread flags group test application
|
||||||
|
*
|
||||||
|
* @author Mihai Renea <mihai.renea@ml-pa.com>
|
||||||
|
*
|
||||||
|
* @}
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include "atomic_utils.h"
|
||||||
|
#include "test_utils/expect.h"
|
||||||
|
#include "thread.h"
|
||||||
|
#include "thread_flags_group.h"
|
||||||
|
|
||||||
|
#ifndef WAITER_THREADS_CNT
|
||||||
|
# define WAITER_THREADS_CNT 3
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef CPU_NATIVE
|
||||||
|
# define WAITER_STACKSIZE THREAD_STACKSIZE_MAIN
|
||||||
|
#else
|
||||||
|
# define WAITER_STACKSIZE THREAD_STACKSIZE_SMALL
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static char stacks[WAITER_THREADS_CNT][WAITER_STACKSIZE];
|
||||||
|
|
||||||
|
#define GOOD_FLAG 0x2
|
||||||
|
#define BAD_FLAG 0x4
|
||||||
|
|
||||||
|
static uint8_t woken_up = 0;
|
||||||
|
static uint8_t last_prio = 0;
|
||||||
|
static thread_flags_group_t group = THREAD_FLAGS_GROUP_INIT;
|
||||||
|
|
||||||
|
static void _print_waiting(char const *what_who, kernel_pid_t pid)
|
||||||
|
{
|
||||||
|
#ifdef CPU_NATIVE
|
||||||
|
printf("%s %d\n", what_who, pid);
|
||||||
|
#else
|
||||||
|
puts(what_who);
|
||||||
|
(void)pid;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *forever_waiter(void *arg)
|
||||||
|
{
|
||||||
|
_print_waiting("waiting forever-waiter", (kernel_pid_t)(uintptr_t)arg);
|
||||||
|
thread_flags_wait_any(GOOD_FLAG | BAD_FLAG);
|
||||||
|
expect(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *waiter(void *arg)
|
||||||
|
{
|
||||||
|
thread_flags_group_join(&group);
|
||||||
|
|
||||||
|
_print_waiting("waiting waiter", (kernel_pid_t)(uintptr_t)arg);
|
||||||
|
thread_flags_wait_any(GOOD_FLAG);
|
||||||
|
|
||||||
|
_print_waiting("woken up waiter", (kernel_pid_t)(uintptr_t)arg);
|
||||||
|
|
||||||
|
expect(atomic_load_u8(&last_prio) <= thread_get_active()->priority);
|
||||||
|
|
||||||
|
atomic_store_u8(&last_prio, thread_get_active()->priority);
|
||||||
|
atomic_fetch_add_u8(&woken_up, 1);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
puts("START");
|
||||||
|
unsigned waiters_cnt = 0;
|
||||||
|
for (unsigned i = 0; i < WAITER_THREADS_CNT; i++) {
|
||||||
|
int prio = (int)THREAD_PRIORITY_MAIN - i - 1;
|
||||||
|
if (prio < 0) {
|
||||||
|
prio = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
thread_task_func_t handler = i % 3 ? (waiters_cnt++, waiter) : forever_waiter;
|
||||||
|
int res = thread_create(stacks[i], sizeof(stacks[0]),
|
||||||
|
prio, THREAD_CREATE_STACKTEST, handler,
|
||||||
|
(void *)(uintptr_t)i, "waiter");
|
||||||
|
expect(res >= 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* this shouldn't wake up */
|
||||||
|
thread_flags_group_set(&group, BAD_FLAG);
|
||||||
|
expect(atomic_load_u8(&woken_up) == 0);
|
||||||
|
|
||||||
|
puts("waking up!");
|
||||||
|
|
||||||
|
thread_flags_group_set(&group, GOOD_FLAG);
|
||||||
|
|
||||||
|
/* waiters have higher prio, so they must have finished */
|
||||||
|
expect(atomic_load_u8(&woken_up) == waiters_cnt);
|
||||||
|
|
||||||
|
puts("SUCCESS");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
13
tests/core/thread_flags_group/tests/01-run.py
Executable file
13
tests/core/thread_flags_group/tests/01-run.py
Executable file
@ -0,0 +1,13 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from testrunner import run
|
||||||
|
|
||||||
|
|
||||||
|
def testfunc(child):
|
||||||
|
child.expect("START")
|
||||||
|
child.expect("SUCCESS")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(run(testfunc))
|
||||||
Loading…
x
Reference in New Issue
Block a user