Threads#
Threads Basics#
Primary advantage over interrupts: may block. E.g.
Zephyr
Preemptive: timesliced (much like
SCHED_RRin Linux)Cooperative: not timesliced (scheduling disabled); can still be interrupted by an ISR
One stack per thread
Priorities: when a higher priority thread becomes runnable, the currently running thread is suspended, and a context switch is made to run the higher priority thread
Main Thread#
Main thread: always there, implicitly created
Zephyr: runs at highest priority
⟶ Careful
#include <why-thread.h>
#include <print>
int main()
{
Why::init();
auto mainthread = Why::Thread::self();
std::println("priority: {}", mainthread.prioritystr());
return 0;
}
$ sudo ./why-threads-mainthread
priority: Highest
Explicitly Created Threads#
Created explicitly to handle a specific task
Priority to distinguish from other threads
Zephyr: priority is mandatory
WhyOS: priority is optional; default Linux fair scheduling
#include <why-thread.h>
#include <print>
#include <cstdlib>
int main()
{
Why::init();
auto result = Why::Thread::create(
[](){
std::println("being scheduled, priority: {}", Why::Thread::self().prioritystr());
}
);
if (!result) {
std::println("no thread created: {}", result.error().msg());
return 1;
}
return 0;
}
#include <why-thread.h>
#include <print>
int main()
{
Why::init();
auto result = Why::Thread::create(
[](){
std::println("being scheduled, priority: {}", Why::Thread::self().prioritystr());
},
Why::Thread::Priority::Medium
);
if (!result) {
std::println("no thread created: {}", result.error().msg());
return 1;
}
return 0;
}
Scheduling: Coordinating Multiple Threads#
A running thread is given only so much time (a timeslice); after that time a scheduling decision is made
Scheduling decision: choosing a runnable/ready thread to run on the CPU
Context switch: saving CPU registers of one thread, and restoring those of another
Potential reschedule points (i.e. when a scheduling decision is made)
A thread has consumed its timeslice ⟶ from potentially multiple runnable threads one must be chosen
A thread voluntarily relinquishes the CPU; for example by calling
Why::msleep()Return of an ISR
A thread becomes runnable/ready, when for example
awakes from
Why::msleep()a semaphore that it was waiting to take is given (by another thread or an interrupt)
…
In the moment a thread becomes runnable, and a lower priority thread has the CPU, a context switch is made in favor of the newcomer
Interrupts are not schedulable ⟶ are not scheduled
Attention: Blocking In Interrupt Service Routines#
Programming paradigm: waiting - voluntarily relinquishing the CPU
ISR must not wait - if they do …
⟶ all sorts of undefined behavior (for performance reasons checks are not made)
Interrupts not arriving anymore
Stalling the system
WhyOS: interrupts are queued until the ISR returns ⟶ correct, but huge latency problem
Other archs/platforms may differ
Demo: Sleeping in an ISR#
Arch dependent
WhyOS: crash if blocking in syscall
#include <why-irq.h>
#include <why-time.h>
#include <print>
int main()
{
Why::init();
Why::IRQ::connect(
5,
[](int){
std::println("seeing irq 5, nap time ...");
Why::msleep(5*1000);
std::println("... wakeup");
}
);
Why::pause();
return 0;
}
$ why-shell ./why-threads-sleep-in-isr
> irq 5
seeing irq 5, nap time ...
> irq 5
... 5 seconds silence ...
> ... wakeup
seeing irq 5, nap time ...
Demo: Scheduling In Action#
#include <why-thread.h>
#include <why-time.h>
#include <print>
int main()
{
Why::init();
auto thread1 = Why::Thread::create(
[](){
for (;;)
std::println("1");
},
Why::Thread::Priority::Medium
);
if (! thread1) {
std::println(stderr, "thread creation failed: {}", thread1.error().msg());
return 1;
}
auto thread2 = Why::Thread::create(
[](){
for (;;)
std::println("2");
},
Why::Thread::Priority::Medium
);
if (! thread2) {
std::println(stderr, "thread creation failed: {}", thread2.error().msg());
return 1;
}
Why::pause();
return 0;
}
$ sudo ./why-threads-scheduling-demo
1
2
1
1
2
...