Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sys/sched_rr: Add a round robin scheduler module #16126

Merged
merged 6 commits into from
Dec 8, 2021
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions examples/thread_duel/Makefile.ci
Original file line number Diff line number Diff line change
@@ -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 \
#
35 changes: 21 additions & 14 deletions examples/thread_duel/README.md
Original file line number Diff line number Diff line change
@@ -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={<rest_strategy>,<work>}'`

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.
80 changes: 47 additions & 33 deletions examples/thread_duel/main.c
Original file line number Diff line number Diff line change
@@ -20,49 +20,57 @@

#define PRINT_STEPS 10
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it would be better to not call this PRINT_STEPS but something like STEPS_PER_SET

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or add another define STEPS_PER_SET if you want to keep both distinct.

#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 */
};

/*
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment would maybe be better in the thread handler than on the thread config.

* 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.
* 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");
}