mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
Merge pull request #16126 from kfessel/p-mix-schedrr
sys/sched_rr: Add a round robin scheduler module
This commit is contained in:
commit
08ef57fbd0
5
.gitignore
vendored
5
.gitignore
vendored
@ -9,8 +9,9 @@ doc/doxygen/man
|
|||||||
doc/doxygen/*.log
|
doc/doxygen/*.log
|
||||||
doc/doxygen/*.db
|
doc/doxygen/*.db
|
||||||
doc/doxygen/*.tmp
|
doc/doxygen/*.tmp
|
||||||
# Built binaries
|
# bin (e.g.:build directory) and .bin files
|
||||||
*bin
|
bin
|
||||||
|
*.bin
|
||||||
# Build directory
|
# Build directory
|
||||||
/build
|
/build
|
||||||
# AFL findings
|
# AFL findings
|
||||||
|
27
examples/thread_duel/Makefile
Normal file
27
examples/thread_duel/Makefile
Normal file
@ -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
|
12
examples/thread_duel/Makefile.ci
Normal file
12
examples/thread_duel/Makefile.ci
Normal file
@ -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 \
|
||||||
|
#
|
45
examples/thread_duel/README.md
Normal file
45
examples/thread_duel/README.md
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
Thread-Duel
|
||||||
|
============
|
||||||
|
|
||||||
|
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.
|
||||||
|
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`: 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) 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 threads by adding
|
||||||
|
`CFLAGS='-DTHREAD_1={<rest_strategy>,<work>}'`
|
||||||
|
|
||||||
|
e.g.:
|
||||||
|
```
|
||||||
|
CFLAGS='-DTHREAD_1={yield_wait,3} -DTHREAD_2={bad_wait,2}' RR=0 make
|
||||||
|
```
|
||||||
|
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:
|
||||||
|
- 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.
|
153
examples/thread_duel/main.c
Normal file
153
examples/thread_duel/main.c
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
/*
|
||||||
|
* 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 <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#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
|
||||||
|
#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 */
|
||||||
|
ztimer_spin(ZTIMER_USEC, us);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 */
|
||||||
|
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 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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
|
||||||
|
* 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.
|
||||||
|
* no_wait and yield_wait threads are restless an therefore never pause.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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;
|
||||||
|
/* 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 = (STEPS_PER_SET - 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;
|
||||||
|
}
|
||||||
|
do_work(work * WORK_SCALE);
|
||||||
|
w += work;
|
||||||
|
wc->waitfn(rest * WORK_SCALE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* 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 */
|
||||||
|
|
||||||
|
#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
|
||||||
|
|
||||||
|
/*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[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[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[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");
|
||||||
|
}
|
||||||
|
}
|
@ -357,6 +357,15 @@ ifneq (,$(filter schedstatistics,$(USEMODULE)))
|
|||||||
USEMODULE += sched_cb
|
USEMODULE += sched_cb
|
||||||
endif
|
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)))
|
ifneq (,$(filter saul_reg,$(USEMODULE)))
|
||||||
USEMODULE += saul
|
USEMODULE += saul
|
||||||
endif
|
endif
|
||||||
|
@ -49,6 +49,11 @@ void auto_init(void)
|
|||||||
extern void init_schedstatistics(void);
|
extern void init_schedstatistics(void);
|
||||||
init_schedstatistics();
|
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)) {
|
if (IS_USED(MODULE_DUMMY_THREAD)) {
|
||||||
extern void dummy_thread_create(void);
|
extern void dummy_thread_create(void);
|
||||||
dummy_thread_create();
|
dummy_thread_create();
|
||||||
|
87
sys/include/sched_round_robin.h
Normal file
87
sys/include/sched_round_robin.h
Normal file
@ -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 <karl.fessel@ovgu.de>
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#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 */
|
||||||
|
/** @} */
|
19
sys/sched_round_robin/Kconfig
Normal file
19
sys/sched_round_robin/Kconfig
Normal file
@ -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
|
1
sys/sched_round_robin/Makefile
Normal file
1
sys/sched_round_robin/Makefile
Normal file
@ -0,0 +1 @@
|
|||||||
|
include $(RIOTBASE)/Makefile.base
|
118
sys/sched_round_robin/sched_round_robin.c
Normal file
118
sys/sched_round_robin/sched_round_robin.c
Normal file
@ -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 <karl.fessel@ovgu.de>
|
||||||
|
*
|
||||||
|
* @}
|
||||||
|
*/
|
||||||
|
|
||||||
|
#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);
|
||||||
|
}
|
||||||
|
}
|
10
tests/sys_sched_round_robin/Makefile
Normal file
10
tests/sys_sched_round_robin/Makefile
Normal file
@ -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
|
14
tests/sys_sched_round_robin/Makefile.ci
Normal file
14
tests/sys_sched_round_robin/Makefile.ci
Normal file
@ -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 \
|
||||||
|
#
|
54
tests/sys_sched_round_robin/README.md
Normal file
54
tests/sys_sched_round_robin/README.md
Normal file
@ -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.
|
66
tests/sys_sched_round_robin/main.c
Normal file
66
tests/sys_sched_round_robin/main.c
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* 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 <karl.fessel@ovgu.de>
|
||||||
|
* @}
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "thread.h"
|
||||||
|
|
||||||
|
static kernel_pid_t main_pid;
|
||||||
|
|
||||||
|
|
||||||
|
void * thread_wakeup_main(void *d)
|
||||||
|
{
|
||||||
|
(void) d;
|
||||||
|
puts("wakup_thread yield");
|
||||||
|
thread_yield();
|
||||||
|
while (puts("wakeup main"), thread_wakeup(main_pid) == (int)STATUS_NOT_FOUND) {
|
||||||
|
thread_yield();
|
||||||
|
};
|
||||||
|
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];
|
||||||
|
|
||||||
|
/* 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");
|
||||||
|
main_pid = thread_getpid();
|
||||||
|
thread_create(stack[0], sizeof(stack[0]), shared_prio, THREAD_CREATE_STACKTEST,
|
||||||
|
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();
|
||||||
|
|
||||||
|
/* success: main got woken up again which means "TWakup" got cpu time
|
||||||
|
* even though "TBad" was trying to hog the whole CPU */
|
||||||
|
puts("[SUCCESS]");
|
||||||
|
}
|
21
tests/sys_sched_round_robin/tests/01-run.py
Executable file
21
tests/sys_sched_round_robin/tests/01-run.py
Executable file
@ -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("main is going to sleep")
|
||||||
|
child.expect_exact("wakeup main")
|
||||||
|
child.expect_exact("[SUCCESS]")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(run(testfunc))
|
Loading…
Reference in New Issue
Block a user