From f54325bb99bf2ad05abfe0b8e6d6a569f23bba2b Mon Sep 17 00:00:00 2001 From: Karl Fessel Date: Tue, 2 Mar 2021 16:36:59 +0100 Subject: [PATCH 1/6] gitingnore: *bin -> bin and *.bin to not ignore round_robin --- .gitignore | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index a049643aa1..a734b7e246 100644 --- a/.gitignore +++ b/.gitignore @@ -9,8 +9,9 @@ doc/doxygen/man doc/doxygen/*.log doc/doxygen/*.db doc/doxygen/*.tmp -# Built binaries -*bin +# bin (e.g.:build directory) and .bin files +bin +*.bin # Build directory /build # AFL findings From 8c3aad1c255bd9d3139ca11982ac98d7dae59757 Mon Sep 17 00:00:00 2001 From: Karl Fessel Date: Thu, 15 Apr 2021 14:57:49 +0200 Subject: [PATCH 2/6] sys/sched_round_robin: Add a round robin scheduler module --- sys/Makefile.dep | 9 ++ sys/auto_init/auto_init.c | 5 + sys/include/sched_round_robin.h | 87 ++++++++++++++++ sys/sched_round_robin/Kconfig | 19 ++++ sys/sched_round_robin/Makefile | 1 + sys/sched_round_robin/sched_round_robin.c | 118 ++++++++++++++++++++++ 6 files changed, 239 insertions(+) create mode 100644 sys/include/sched_round_robin.h create mode 100644 sys/sched_round_robin/Kconfig create mode 100644 sys/sched_round_robin/Makefile create mode 100644 sys/sched_round_robin/sched_round_robin.c diff --git a/sys/Makefile.dep b/sys/Makefile.dep index 50112e29f3..b5852f5811 100644 --- a/sys/Makefile.dep +++ b/sys/Makefile.dep @@ -356,6 +356,15 @@ ifneq (,$(filter schedstatistics,$(USEMODULE))) USEMODULE += sched_cb endif +ifneq (,$(filter sched_round_robin,$(USEMODULE))) +# this depends on either ztimer_usec or ztimer_msec if neither is used +# prior to this msec is preferred + ifeq (,$(filter ztimer_usec,$(USEMODULE))$(filter ztimer_msec,$(USEMODULE))) + USEMODULE += ztimer_msec + endif + USEMODULE += sched_runq_callback +endif + ifneq (,$(filter saul_reg,$(USEMODULE))) USEMODULE += saul endif diff --git a/sys/auto_init/auto_init.c b/sys/auto_init/auto_init.c index d28eccde1f..062c9a3fe8 100644 --- a/sys/auto_init/auto_init.c +++ b/sys/auto_init/auto_init.c @@ -49,6 +49,11 @@ void auto_init(void) extern void init_schedstatistics(void); init_schedstatistics(); } + if (IS_USED(MODULE_SCHED_ROUND_ROBIN)) { + LOG_DEBUG("Auto init sched_round_robin.\n"); + extern void sched_round_robin_init(void); + sched_round_robin_init(); + } if (IS_USED(MODULE_DUMMY_THREAD)) { extern void dummy_thread_create(void); dummy_thread_create(); diff --git a/sys/include/sched_round_robin.h b/sys/include/sched_round_robin.h new file mode 100644 index 0000000000..4e0e2381e7 --- /dev/null +++ b/sys/include/sched_round_robin.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2020 TUBA Freiberg + * + * 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. + */ + +/** + * @defgroup sched_round_robin Round Robin Scheduler + * @ingroup sys + * @brief This module module provides round robin scheduling for all + * runable threads within each not masked priority. + * Priority 0 is masked by default. + * This implementation tries to find a balance between + * low resources (static memory: a timer and an uint8), + * fairness in terms of CPU time share and simplicity. + * But it does round robin the runqueue when the timer ticks + * even if the thread just got the CPU. + * + * This module might be used if threads are not divisible + * into priorities and cooperation can not be ensured. + * + * @{ + * + * @file + * @brief Round Robin Scheduler + * + * @author Karl Fessel + * + */ +#ifndef SCHED_ROUND_ROBIN_H +#define SCHED_ROUND_ROBIN_H + +#ifdef __cplusplus +extern "C" { +#endif + +#if !defined(SCHED_RR_TIMEOUT) || defined(DOXYGEN) +/** + * @brief Time between round robin calls in Units of SCHED_RR_TIMERBASE + * + * @details Defaults to 10ms + */ +#if MODULE_ZTIMER_MSEC +#define SCHED_RR_TIMEOUT 10 +#else +#define SCHED_RR_TIMEOUT 10000 +#endif +#endif + +#if !defined(SCHED_RR_TIMERBASE) || defined(DOXYGEN) +/** + * @brief ztimer to use for the round robin scheduler + * + * @details Defaults to ZTIMER_MSEC if available else it uses ZTIMER_USEC + */ +#if MODULE_ZTIMER_MSEC +#define SCHED_RR_TIMERBASE ZTIMER_MSEC +#else +#define SCHED_RR_TIMERBASE ZTIMER_USEC +#endif +#endif + +#if !defined(SCHED_RR_MASK) || defined(DOXYGEN) +/** + * @brief Masks off priorities that should not be scheduled default: 0 is masked + * + * @details Priority 0 (highest) should always be masked. + * Threads with that priority may not be programmed + * with the possibility of being scheduled in mind. + * Parts of this scheduler assume 0 current_rr_priority is uninitialised. + */ +#define SCHED_RR_MASK (1 << 0) +#endif + +/** + * @brief Initialises the Round Robin Scheduler + */ +void sched_round_robin_init(void); + +#ifdef __cplusplus +} +#endif + +#endif /* SCHED_ROUND_ROBIN_H */ +/** @} */ diff --git a/sys/sched_round_robin/Kconfig b/sys/sched_round_robin/Kconfig new file mode 100644 index 0000000000..dd109caa2e --- /dev/null +++ b/sys/sched_round_robin/Kconfig @@ -0,0 +1,19 @@ +# Copyright (c) 2021 TUBA Freiberg +# +# 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. +# + +config MODULE_SCHED_ROUND_ROBIN + bool "round robin scheduling support" + depends on MODULE_ZTIMER_MSEC || MODULE_ZTIMER_USEC + depends on TEST_KCONFIG + select MODULE_SCHED_RUNQUEUE_API + +if MODULE_SCHED_ROUND_ROBIN +config SCHED_RR_TIMEOUT + int "timeout for round robin scheduling" + default 10000 + +endif diff --git a/sys/sched_round_robin/Makefile b/sys/sched_round_robin/Makefile new file mode 100644 index 0000000000..48422e909a --- /dev/null +++ b/sys/sched_round_robin/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/sys/sched_round_robin/sched_round_robin.c b/sys/sched_round_robin/sched_round_robin.c new file mode 100644 index 0000000000..4cfed38592 --- /dev/null +++ b/sys/sched_round_robin/sched_round_robin.c @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2021 TUBA Freiberg + * + * 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 sys + * @{ + * + * @file + * @brief Round Robin Scheduler implementation + * + * @author Karl Fessel + * + * @} + */ + +#include "sched.h" +#include "thread.h" +#include "ztimer.h" +#include "sched_round_robin.h" + +#define ENABLE_DEBUG 0 +#include "debug.h" + +static void _sched_round_robin_cb(void *d); + +static ztimer_t _rr_timer = { .callback = _sched_round_robin_cb }; + +/* + * Assuming simple reads from and writes to a byte to be atomic on every board + * Value 0 is assumed to show this system is uninitialised. + * The timer will not be started for prio = 0; + */ +static uint8_t _current_rr_priority = 0; + +void sched_runq_callback(uint8_t prio); + +void _sched_round_robin_cb(void *d) +{ + (void)d; + /* + * reorder current Round Robin priority + * (put the current thread at the end of the run queue of its priority) + * and setup the scheduler to schedule when returning from the IRQ + */ + uint8_t prio = _current_rr_priority; + if (prio != 0xff) { + DEBUG_PUTS("Round_Robin"); + sched_runq_advance(prio); + _current_rr_priority = 0xff; + } + thread_t *active_thread = thread_get_active(); + if (active_thread) { + uint8_t active_priority = active_thread->priority; + if (active_priority == prio) { + thread_yield_higher(); + /* thread change will call the runqueue_change_cb */ + } + else { + sched_runq_callback(active_priority); + } + } +} + +static inline void _sched_round_robin_remove(void) +{ + _current_rr_priority = 0xff; + ztimer_remove(SCHED_RR_TIMERBASE, &_rr_timer); +} + +static inline void _sched_round_robin_set(uint8_t prio) +{ + if (prio == 0) { + return; + } + _current_rr_priority = prio; + ztimer_set(SCHED_RR_TIMERBASE, &_rr_timer, SCHED_RR_TIMEOUT); +} + +void sched_runq_callback(uint8_t prio) +{ + if (SCHED_RR_MASK & (1 << prio) || prio == 0) { + return; + } + + if (_current_rr_priority == prio) { + if (sched_runq_is_empty(prio)) { + _sched_round_robin_remove(); + thread_t *active_thread = thread_get_active(); + if (active_thread) { + prio = active_thread->priority; + } + else { + return; + } + } + } + + if (_current_rr_priority == 0xff && + !(SCHED_RR_MASK & (1 << prio)) && + sched_runq_more_than_one(prio)) { + _sched_round_robin_set(prio); + } +} + +void sched_round_robin_init(void) +{ + /* init _current_rr_priority */ + _current_rr_priority = 0xff; + /* check if applicable to active priority */ + thread_t *active_thread = thread_get_active(); + if (active_thread) { + sched_runq_callback(active_thread->priority); + } +} From be2aa39ea2ccf032a6900259c321888e958d7334 Mon Sep 17 00:00:00 2001 From: Karl Fessel Date: Thu, 15 Apr 2021 14:59:50 +0200 Subject: [PATCH 3/6] examples/thread-duel: add a duelling threads example --- examples/thread_duel/Makefile | 27 +++++++ examples/thread_duel/README.md | 38 +++++++++ examples/thread_duel/main.c | 139 +++++++++++++++++++++++++++++++++ 3 files changed, 204 insertions(+) create mode 100644 examples/thread_duel/Makefile create mode 100644 examples/thread_duel/README.md create mode 100644 examples/thread_duel/main.c diff --git a/examples/thread_duel/Makefile b/examples/thread_duel/Makefile new file mode 100644 index 0000000000..c9d82ad41e --- /dev/null +++ b/examples/thread_duel/Makefile @@ -0,0 +1,27 @@ +# name of your application +APPLICATION = thread_duel + +# If no BOARD is found in the environment, use this default: +BOARD ?= native + +# This has to be the absolute path to the RIOT base directory: +RIOTBASE ?= $(CURDIR)/../.. + +# This defaults to build with round_robin using ztimer +RR ?= 1 + +USEMODULE += ztimer_usec + +ifeq (1,$(RR)) + USEMODULE += sched_round_robin +endif + +# Comment this out to disable code in RIOT that does safety checking +# which is not needed in a production environment but helps in the +# development process: +DEVELHELP ?= 1 + +# Change this to 0 show compiler invocation lines by default: +QUIET ?= 1 + +include $(RIOTBASE)/Makefile.include diff --git a/examples/thread_duel/README.md b/examples/thread_duel/README.md new file mode 100644 index 0000000000..0592330104 --- /dev/null +++ b/examples/thread_duel/README.md @@ -0,0 +1,38 @@ +Thread-Duel +============ + +This is a thread duel application to show RIOTs multi-threading abilities + +Every thread will do some work (busy waiting) after the work is done +it counts up by the amount of work it did and then it rests. +There are different resting strategies and these have a huge +influence on thread fairness and scheduling. + +resting strategies for the threads of this example are: +- `nice_wait`: does nice breaks giving other threads time to use the CPU +- `bad_wait`: take breaks by busy waiting and therefore hogging the CPU +- `yield_wait`: take no explicit breaks but yields (to higher or equal priority threads) +- `no_wait`: never take a break + +After completing a batch of work (and rest) a thread will print information on the work done. +(Printing is done in steps to avoid flooding) + +If one thread (all are same priority) does rests `bad_wait`ing or `no_wait`ing at all, +scheduling without round robin will see all CPU time be hogged by that one thread +(or the first one to get it). + +In this example Round Robin scheduling is enabled by default, +to disable compile with `RR=0` + +change the behaviour of the different thread by adding `CFLAGS='-DTHREAD_1={no_wait,5}'` + +e.g.: +``` +CFLAGS='-DTHREAD_1={yield_wait,3} -DTHREAD_2={bad_wait,2}' RR=0 make +``` +will set thread 1 to be yield_waiting and to 3 works in one batch after that it will yield. +thread 2 will do 2 work bad_wait (hog the cpu while waiting). +the Round Robin scheduling is not activated. +the result will be a CPU hogged by thread 2 which will only do work for 20% of the time (really bad) +(thread 1 will have done 3 work but will never even get the chance to print that) +thread 3 will never have done work. diff --git a/examples/thread_duel/main.c b/examples/thread_duel/main.c new file mode 100644 index 0000000000..7f0407fe0b --- /dev/null +++ b/examples/thread_duel/main.c @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2020 TUBA Freiberg + * + * 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. + */ + +#include +#include +#include + +#include "thread.h" +#include "sched.h" + +#include "ztimer.h" +#include "timex.h" + +#include "sched_round_robin.h" + +#define PRINT_STEPS 10 +#define WORK_SCALE 1000 + +static void bad_wait(uint32_t us) +{ + /*keep the CPU busy waiting for some time to pass simulate working*/ + ztimer_spin(ZTIMER_USEC, us); +}__attribute__((unused)) + +static void nice_wait(uint32_t us) +{ + /*be nice give the CPU some time to do other things or rest*/ + ztimer_sleep(ZTIMER_USEC, us); +}__attribute__((unused)) + +static void yield_wait(uint32_t unused) +{ + (void) unused; + /* do not wait just yield */ + thread_yield(); +}__attribute__((unused)) + +static void no_wait(uint32_t unused) +{ + (void) unused; + /* do not wait */ +}__attribute__((unused)) + +/* + * the following are threads that count and wait with different strategies and + * print their current count in steps. + * the ration of active (doing hard work like checking the timer) + * to passive (wait to be informed when a certain time is there) waiting + * is determined by there value given to the thread. + * the restless threads do never pause. + */ + +struct worker_config { + void (*waitfn) (unsigned); + uint32_t work; +}; + +void * thread_worker(void * d) +{ + nice_wait(200 * US_PER_MS); /*always be nice at start*/ +#ifdef DEVELHELP + const char *name = thread_get_active()->name; +#else + int16_t pid = thread_getpid(); +#endif + + uint32_t w = 0; + struct worker_config *wc = d; + /* this is doing 10 Steps which divided into work (busy waiting) + * and not work of these 10 steps up to 10 might be work but not more + * if the given value is out of range work ratio is set to 5 */ + uint32_t work = wc->work; + if (work > 10) { + work = 5; + } + uint32_t rest = (10 - work); + uint32_t step = 0; + + /*work some time and rest*/ + for (;;) { + if (w - step >= PRINT_STEPS) { +#ifdef DEVELHELP + printf("%s: %" PRIu32 ", %" PRIu32 "\n", name, w, work); +#else + printf("T-Pid %i:%" PRIu32 ", %" PRIu32 "\n", pid, w, work); +#endif + step = w; + } + bad_wait(work * WORK_SCALE); + w += work; + wc->waitfn(rest * WORK_SCALE); + } +} + +/* no_wait -> a restless thread always working until it is suspended + * yield_wait -> a restless thread that yields before continuing with the next work package + * bad_wait -> a thread that does pause very intensely + * nice_wait -> a thread does nice breaks giving other threads time to do something + */ +/* yield_wait and nice_wait threads are able to work in "parallel" without sched_round_robin*/ + +#ifndef THREAD_1 +#define THREAD_1 {no_wait, 5} +#endif + +#ifndef THREAD_2 +#define THREAD_2 {no_wait, 5} +#endif + +#ifndef THREAD_3 +#define THREAD_3 {no_wait, 5} +#endif + +int main(void) +{ + { + static char stack[THREAD_STACKSIZE_DEFAULT]; + static struct worker_config wc = THREAD_1; /* 0-10 workness*/ + thread_create(stack, sizeof(stack), 7, THREAD_CREATE_STACKTEST, + thread_worker, &wc, "T1"); + } + { + static char stack[THREAD_STACKSIZE_DEFAULT]; + static struct worker_config wc = THREAD_2; /* 0-10 workness*/ + thread_create(stack, sizeof(stack), 7, THREAD_CREATE_STACKTEST, + thread_worker, &wc, "T2"); + } + { + static char stack[THREAD_STACKSIZE_DEFAULT]; + static struct worker_config wc = THREAD_3; /* 0-10 workness*/ + thread_create(stack, sizeof(stack), 7, THREAD_CREATE_STACKTEST, + thread_worker, &wc, "T3"); + } +} From fc3f5f562b23161fcbbfdb8e94297d51dde29dec Mon Sep 17 00:00:00 2001 From: Karl Fessel Date: Thu, 15 Apr 2021 15:02:41 +0200 Subject: [PATCH 4/6] test/sys_sched_round_robin: add test for sys_sched_round_robin with README.md --- tests/sys_sched_round_robin/Makefile | 10 +++ tests/sys_sched_round_robin/Makefile.ci | 14 +++++ tests/sys_sched_round_robin/README.md | 54 ++++++++++++++++ tests/sys_sched_round_robin/main.c | 70 +++++++++++++++++++++ tests/sys_sched_round_robin/tests/01-run.py | 21 +++++++ 5 files changed, 169 insertions(+) create mode 100644 tests/sys_sched_round_robin/Makefile create mode 100644 tests/sys_sched_round_robin/Makefile.ci create mode 100644 tests/sys_sched_round_robin/README.md create mode 100644 tests/sys_sched_round_robin/main.c create mode 100755 tests/sys_sched_round_robin/tests/01-run.py diff --git a/tests/sys_sched_round_robin/Makefile b/tests/sys_sched_round_robin/Makefile new file mode 100644 index 0000000000..26e633d071 --- /dev/null +++ b/tests/sys_sched_round_robin/Makefile @@ -0,0 +1,10 @@ +include ../Makefile.tests_common + +# Set to 1 to disable the round-robin scheduling module +NORR ?= 0 + +ifneq (1,$(NORR)) + USEMODULE += sched_round_robin +endif + +include $(RIOTBASE)/Makefile.include diff --git a/tests/sys_sched_round_robin/Makefile.ci b/tests/sys_sched_round_robin/Makefile.ci new file mode 100644 index 0000000000..a387bf522a --- /dev/null +++ b/tests/sys_sched_round_robin/Makefile.ci @@ -0,0 +1,14 @@ +BOARD_INSUFFICIENT_MEMORY := \ + arduino-duemilanove \ + arduino-leonardo \ + arduino-nano \ + arduino-uno \ + atmega328p \ + atmega328p-xplained-mini\ + nucleo-f031k6 \ + nucleo-f042k6 \ + nucleo-l011k4 \ + samd10-xmini \ + stk3200 \ + stm32f030f4-demo \ + # diff --git a/tests/sys_sched_round_robin/README.md b/tests/sys_sched_round_robin/README.md new file mode 100644 index 0000000000..fca8b35058 --- /dev/null +++ b/tests/sys_sched_round_robin/README.md @@ -0,0 +1,54 @@ +Round Robing Scheduling Test +======================== + +This application is a simple test case for round-robin scheduling. +Two threads are started with the same priority. +The first thread is a busy loop and is started first. +The second thread unlocks a mutex allowing the main thread to continue and exit. + +Without Round Robin scheduling the busy loop thread would run indefinitely, +with round-robin in eventually getting de-scheduled allowing the main thread to run and exit. + +Usage +===== + +By default `sched_round_robin` is included: + +`make tests/sys_sched_round_robin flash term` + +``` +...Board Initialisation... + +Help: Press s to start test, r to print it is ready +s +START +main(): This is RIOT! (Version: ...) +starting threads +double locking mutex +mutex_thread yield +bad thread looping +unlock mutex +[SUCCESS] + +``` + +It can be excluded from the build by setting the command-line argument `NORR=1`: + + +`NORR=1 make tests/sys_sched_round_robin flash term` + +``` +...Board Initialisation... + +Help: Press s to start test, r to print it is ready +s +START +main(): This is RIOT! (Version: ...) +starting threads +double locking mutex +mutex_thread yield +bad thread looping +``` + +This will loop endlessly as the bad thread does not release the CPU, +`make test` will timeout in that case. diff --git a/tests/sys_sched_round_robin/main.c b/tests/sys_sched_round_robin/main.c new file mode 100644 index 0000000000..61b30d2272 --- /dev/null +++ b/tests/sys_sched_round_robin/main.c @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2020 TUBA Freiberg + * + * 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 Test sys/sched_round_robin + * @author Karl Fessel + * @} + */ + +#include +#include + +#include "thread.h" +#include "mutex.h" + +static mutex_t _shared_mutex; + +void * thread_mutex_unlock(void *d) +{ + (void) d; + puts("mutex_thread yield"); + thread_yield(); + puts("unlock mutex"); + mutex_unlock(&_shared_mutex); + return NULL; +} + +void * thread_bad(void *d) +{ + (void) d; + puts("bad thread looping"); + for (;;) { + /* I'm a bad thread I do nothing and I do that all the time */ + } +} + +/* each thread gets a stack */ +static char stack[2][THREAD_STACKSIZE_DEFAULT]; + +/* with priority inversion this should be set to THREAD_PRIORITY_MAIN + * until then a lower priority (higher number) is the better choice */ +const uint8_t shared_prio = THREAD_PRIORITY_MAIN + 1; + +int main(void) +{ + puts("starting threads"); + + mutex_init(&_shared_mutex); + thread_create(stack[0], sizeof(stack[0]), shared_prio, THREAD_CREATE_STACKTEST, + thread_mutex_unlock, NULL, "TMutex"); + thread_create(stack[1], sizeof(stack[1]), shared_prio, THREAD_CREATE_STACKTEST, + thread_bad, NULL, "TBad"); + + puts("double locking mutex"); + + mutex_lock(&_shared_mutex); + mutex_lock(&_shared_mutex); + + /* success: mutex got unlocked, which means thread "TMutex" got cpu time + * even though "TBad" was trying to hog the whole CPU */ + puts("[SUCCESS]"); +} diff --git a/tests/sys_sched_round_robin/tests/01-run.py b/tests/sys_sched_round_robin/tests/01-run.py new file mode 100755 index 0000000000..7a9289e5c9 --- /dev/null +++ b/tests/sys_sched_round_robin/tests/01-run.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2021 TUBA Freiberg +# +# 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. + +import sys +from testrunner import run + + +def testfunc(child): + child.expect_exact("starting threads") + child.expect_exact("double locking mutex") + child.expect_exact("unlock mutex") + child.expect_exact("[SUCCESS]") + + +if __name__ == "__main__": + sys.exit(run(testfunc)) From 64b783b9fa63df793efbb651cc70e9efe7dd5bdb Mon Sep 17 00:00:00 2001 From: Karl Fessel Date: Wed, 27 Oct 2021 14:10:19 +0200 Subject: [PATCH 5/6] examples/thread-duel: improve duelling threads example --- examples/thread_duel/Makefile.ci | 12 +++++ examples/thread_duel/README.md | 35 ++++++++------ examples/thread_duel/main.c | 80 +++++++++++++++++++------------- 3 files changed, 80 insertions(+), 47 deletions(-) create mode 100644 examples/thread_duel/Makefile.ci diff --git a/examples/thread_duel/Makefile.ci b/examples/thread_duel/Makefile.ci new file mode 100644 index 0000000000..d5368f025e --- /dev/null +++ b/examples/thread_duel/Makefile.ci @@ -0,0 +1,12 @@ +BOARD_INSUFFICIENT_MEMORY := \ + arduino-duemilanove \ + arduino-leonardo \ + arduino-nano \ + arduino-uno \ + atmega328p \ + atmega328p-xplained-mini \ + nucleo-f031k6 \ + nucleo-l011k4 \ + samd10-xmini \ + stm32f030f4-demo \ + # diff --git a/examples/thread_duel/README.md b/examples/thread_duel/README.md index 0592330104..7eea1edc00 100644 --- a/examples/thread_duel/README.md +++ b/examples/thread_duel/README.md @@ -1,38 +1,45 @@ Thread-Duel ============ -This is a thread duel application to show RIOTs multi-threading abilities +This is a thread duel application to show RIOTs abilities to run multiple-threads +concurrently, even if they are neither cooperative nor dividable into different scheduler priorities, +by using the optional round-robin scheduler module. -Every thread will do some work (busy waiting) after the work is done -it counts up by the amount of work it did and then it rests. +Every thread will do some work (busy waiting). +After the work is done it counts up by the amount of work it did and then it rests. There are different resting strategies and these have a huge influence on thread fairness and scheduling. -resting strategies for the threads of this example are: +Resting strategies for the threads of this example are: - `nice_wait`: does nice breaks giving other threads time to use the CPU -- `bad_wait`: take breaks by busy waiting and therefore hogging the CPU -- `yield_wait`: take no explicit breaks but yields (to higher or equal priority threads) -- `no_wait`: never take a break +- `bad_wait`: takes breaks by busy waiting and therefore hogging the CPU +- `yield_wait`: takes no explicit breaks but yields (to higher or equal priority threads) +- `no_wait`: never takes a break After completing a batch of work (and rest) a thread will print information on the work done. (Printing is done in steps to avoid flooding) -If one thread (all are same priority) does rests `bad_wait`ing or `no_wait`ing at all, +If one thread (all are same priority) follows `bad_wait` or `no_wait` strategy, scheduling without round robin will see all CPU time be hogged by that one thread (or the first one to get it). In this example Round Robin scheduling is enabled by default, to disable compile with `RR=0` -change the behaviour of the different thread by adding `CFLAGS='-DTHREAD_1={no_wait,5}'` +Change the behaviour of the different threads by adding +`CFLAGS='-DTHREAD_1={,}'` e.g.: ``` CFLAGS='-DTHREAD_1={yield_wait,3} -DTHREAD_2={bad_wait,2}' RR=0 make ``` -will set thread 1 to be yield_waiting and to 3 works in one batch after that it will yield. -thread 2 will do 2 work bad_wait (hog the cpu while waiting). +Will set: +- thread 1 to follow `yield_waiting` strategy and +to complete 3 works in one batch after that, it will yield. +- thread 2 will do 2 work and follow `bad_wait` (hog the cpu while waiting). the Round Robin scheduling is not activated. -the result will be a CPU hogged by thread 2 which will only do work for 20% of the time (really bad) -(thread 1 will have done 3 work but will never even get the chance to print that) -thread 3 will never have done work. + +The result will be: +- CPU hogged by thread 2, which will only do work for 20% of the time (really bad) +- thread 1 will have done 3 work but will never even get the chance to print that +- thread 3 will never have done any work. diff --git a/examples/thread_duel/main.c b/examples/thread_duel/main.c index 7f0407fe0b..350fd97f3e 100644 --- a/examples/thread_duel/main.c +++ b/examples/thread_duel/main.c @@ -20,31 +20,44 @@ #define PRINT_STEPS 10 #define WORK_SCALE 1000 +#define STEPS_PER_SET 10 +__attribute__((unused)) static void bad_wait(uint32_t us) { - /*keep the CPU busy waiting for some time to pass simulate working*/ + /* keep the CPU busy waiting for some time to pass simulate working */ ztimer_spin(ZTIMER_USEC, us); -}__attribute__((unused)) +} +static void (* const do_work)(uint32_t us) = bad_wait; + +__attribute__((unused)) static void nice_wait(uint32_t us) { - /*be nice give the CPU some time to do other things or rest*/ + /* be nice give the CPU some time to do other things or rest */ ztimer_sleep(ZTIMER_USEC, us); -}__attribute__((unused)) +} +__attribute__((unused)) static void yield_wait(uint32_t unused) { (void) unused; /* do not wait just yield */ thread_yield(); -}__attribute__((unused)) +} +__attribute__((unused)) static void no_wait(uint32_t unused) { (void) unused; /* do not wait */ -}__attribute__((unused)) +} + +/* worker_config is a small configuration structure for the thread_worker */ +struct worker_config { + void (*waitfn)(uint32_t); /**< the resting strategy */ + uint32_t workload; /**< the amount of work to do per set */ +}; /* * the following are threads that count and wait with different strategies and @@ -52,17 +65,12 @@ static void no_wait(uint32_t unused) * the ration of active (doing hard work like checking the timer) * to passive (wait to be informed when a certain time is there) waiting * is determined by there value given to the thread. - * the restless threads do never pause. + * no_wait and yield_wait threads are restless an therefore never pause. */ -struct worker_config { - void (*waitfn) (unsigned); - uint32_t work; -}; - void * thread_worker(void * d) { - nice_wait(200 * US_PER_MS); /*always be nice at start*/ + nice_wait(200 * US_PER_MS); /* always be nice at start */ #ifdef DEVELHELP const char *name = thread_get_active()->name; #else @@ -71,17 +79,18 @@ void * thread_worker(void * d) uint32_t w = 0; struct worker_config *wc = d; - /* this is doing 10 Steps which divided into work (busy waiting) - * and not work of these 10 steps up to 10 might be work but not more - * if the given value is out of range work ratio is set to 5 */ - uint32_t work = wc->work; - if (work > 10) { - work = 5; + /* Each set consists of STEPS_PER_SET steps which are divided into work (busy waiting) + * and resting. + * E.g. if there are 10 steps per set, the maximum workload is 10, which means no rest. + * If the given value is out of range work ratio is set to half of STEPS_PER_SET */ + uint32_t work = wc->workload; + if (work > STEPS_PER_SET) { + work = STEPS_PER_SET / 2; } - uint32_t rest = (10 - work); + uint32_t rest = (STEPS_PER_SET - work); uint32_t step = 0; - /*work some time and rest*/ + /* work some time and rest */ for (;;) { if (w - step >= PRINT_STEPS) { #ifdef DEVELHELP @@ -91,18 +100,18 @@ void * thread_worker(void * d) #endif step = w; } - bad_wait(work * WORK_SCALE); + do_work(work * WORK_SCALE); w += work; wc->waitfn(rest * WORK_SCALE); } } - -/* no_wait -> a restless thread always working until it is suspended - * yield_wait -> a restless thread that yields before continuing with the next work package - * bad_wait -> a thread that does pause very intensely +/* * nice_wait -> a thread does nice breaks giving other threads time to do something + * bad_wait -> a thread that waits by spinning (intensely looking at the clock) + * yield_wait -> a restless thread that yields before continuing with the next work package + * no_wait -> a restless thread always working until it is preempted */ -/* yield_wait and nice_wait threads are able to work in "parallel" without sched_round_robin*/ +/* yield_wait and nice_wait threads are able to work in "parallel" without sched_round_robin */ #ifndef THREAD_1 #define THREAD_1 {no_wait, 5} @@ -116,23 +125,28 @@ void * thread_worker(void * d) #define THREAD_3 {no_wait, 5} #endif +/*a TINY Stack should be enough*/ +#ifndef WORKER_STACKSIZE +#define WORKER_STACKSIZE (THREAD_STACKSIZE_TINY+THREAD_EXTRA_STACKSIZE_PRINTF) +#endif + int main(void) { { - static char stack[THREAD_STACKSIZE_DEFAULT]; - static struct worker_config wc = THREAD_1; /* 0-10 workness*/ + static char stack[WORKER_STACKSIZE]; + static struct worker_config wc = THREAD_1; /* 0-10 workness */ thread_create(stack, sizeof(stack), 7, THREAD_CREATE_STACKTEST, thread_worker, &wc, "T1"); } { - static char stack[THREAD_STACKSIZE_DEFAULT]; - static struct worker_config wc = THREAD_2; /* 0-10 workness*/ + static char stack[WORKER_STACKSIZE]; + static struct worker_config wc = THREAD_2; /* 0-10 workness */ thread_create(stack, sizeof(stack), 7, THREAD_CREATE_STACKTEST, thread_worker, &wc, "T2"); } { - static char stack[THREAD_STACKSIZE_DEFAULT]; - static struct worker_config wc = THREAD_3; /* 0-10 workness*/ + static char stack[WORKER_STACKSIZE]; + static struct worker_config wc = THREAD_3; /* 0-10 workness */ thread_create(stack, sizeof(stack), 7, THREAD_CREATE_STACKTEST, thread_worker, &wc, "T3"); } From 2594032163443d0a5235c0685a30eee269c43814 Mon Sep 17 00:00:00 2001 From: Karl Fessel Date: Mon, 1 Nov 2021 21:22:06 +0100 Subject: [PATCH 6/6] test/sys_sched_round_robin: use sleep instead of mutex to avoid priority-inversion screwing up the test --- tests/sys_sched_round_robin/main.c | 32 +++++++++------------ tests/sys_sched_round_robin/tests/01-run.py | 4 +-- 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/tests/sys_sched_round_robin/main.c b/tests/sys_sched_round_robin/main.c index 61b30d2272..4626da8390 100644 --- a/tests/sys_sched_round_robin/main.c +++ b/tests/sys_sched_round_robin/main.c @@ -19,17 +19,18 @@ #include #include "thread.h" -#include "mutex.h" -static mutex_t _shared_mutex; +static kernel_pid_t main_pid; -void * thread_mutex_unlock(void *d) + +void * thread_wakeup_main(void *d) { (void) d; - puts("mutex_thread yield"); + puts("wakup_thread yield"); thread_yield(); - puts("unlock mutex"); - mutex_unlock(&_shared_mutex); + while (puts("wakeup main"), thread_wakeup(main_pid) == (int)STATUS_NOT_FOUND) { + thread_yield(); + }; return NULL; } @@ -45,26 +46,21 @@ void * thread_bad(void *d) /* each thread gets a stack */ static char stack[2][THREAD_STACKSIZE_DEFAULT]; -/* with priority inversion this should be set to THREAD_PRIORITY_MAIN - * until then a lower priority (higher number) is the better choice */ -const uint8_t shared_prio = THREAD_PRIORITY_MAIN + 1; +/* shared priority of the threads - lower than main waiting for it to sleep */ +static const uint8_t shared_prio = THREAD_PRIORITY_MAIN + 1; int main(void) { puts("starting threads"); - - mutex_init(&_shared_mutex); + main_pid = thread_getpid(); thread_create(stack[0], sizeof(stack[0]), shared_prio, THREAD_CREATE_STACKTEST, - thread_mutex_unlock, NULL, "TMutex"); + thread_wakeup_main, NULL, "TWakeup"); thread_create(stack[1], sizeof(stack[1]), shared_prio, THREAD_CREATE_STACKTEST, thread_bad, NULL, "TBad"); + puts("main is going to sleep"); + thread_sleep(); - puts("double locking mutex"); - - mutex_lock(&_shared_mutex); - mutex_lock(&_shared_mutex); - - /* success: mutex got unlocked, which means thread "TMutex" got cpu time + /* success: main got woken up again which means "TWakup" got cpu time * even though "TBad" was trying to hog the whole CPU */ puts("[SUCCESS]"); } diff --git a/tests/sys_sched_round_robin/tests/01-run.py b/tests/sys_sched_round_robin/tests/01-run.py index 7a9289e5c9..bd32f6c0d4 100755 --- a/tests/sys_sched_round_robin/tests/01-run.py +++ b/tests/sys_sched_round_robin/tests/01-run.py @@ -12,8 +12,8 @@ from testrunner import run def testfunc(child): child.expect_exact("starting threads") - child.expect_exact("double locking mutex") - child.expect_exact("unlock mutex") + child.expect_exact("main is going to sleep") + child.expect_exact("wakeup main") child.expect_exact("[SUCCESS]")