Tasks? Processes? Threads?¶
Tasks¶
In userspace, there is no such thing as a task
Only processes and threads
Kernel/scheduler is different
Process is-a task
Thread is-a task
Tasks are scheduled
A Typical Bare Metal Application¶
Back in Time, Back in Technology
One timer interrupt with two responsibilities
Update status
Show status
// g++ -o /tmp/embedded-app-1-irq embedded-app-1-irq.cpp -lrt
#include <signal.h>
#include <time.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
enum State
{
INITIAL,
RISING,
FALLING
};
struct app_status {
int state;
int tick_counter;
char display[12];
};
struct app_status status;
static void update_status(int)
{
switch (status.state) {
case INITIAL:
if (status.tick_counter == 9)
status.state = RISING;
break;
case RISING:
status.display[status.tick_counter] = '/';
if (status.tick_counter == 9)
status.state = FALLING;
break;
case FALLING:
status.display[status.tick_counter] = '\\';
if (status.tick_counter == 9)
status.state = RISING;
break;
}
if (status.tick_counter < 9)
status.tick_counter++;
else
status.tick_counter = 0;
write(STDOUT_FILENO, status.display, sizeof(status.display));
}
int main()
{
// initialize application
{
status.state = INITIAL;
status.tick_counter = 0;
strcpy(status.display, "----------\n");
}
// establish timer handler
{
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = update_status;
int error = sigaction(SIGRTMIN, &sa, NULL);
if (error) {
perror("sigaction");
return 1;
}
}
// create and start timer
{
struct sigevent sev;
memset(&sev, 0, sizeof(sev));
sev.sigev_notify = SIGEV_SIGNAL;
sev.sigev_signo = SIGRTMIN;
timer_t timer;
int error = timer_create(CLOCK_MONOTONIC, &sev, &timer);
if (error) {
perror("timer_create");
return 1;
}
itimerspec tspec = {
/*interval*/
{
/*sec*/ 0,
/*nsec*/ 1000*1000*1000 / 2 // half a second
},
/*initial expiration in a second*/
{
/*sec*/ 1,
/*nsec*/ 0
}
};
error = timer_settime(timer, 0, &tspec, NULL);
if (error) {
perror("timer_settime");
return 1;
}
}
while (true)
pause(); // or do power management, on a real bare metal
// platform
return 0;
}
$ g++ -o /tmp/embedded-app-1-irq embedded-app-1-irq.cpp -lrt
$ /tmp/embedded-app-1-irq
----------
----------
----------
----------
----------
----------
----------
----------
----------
----------
/--------- <---- rising
//--------
///-------
////------
/////-----
//////----
^C
Hm. Need Another Timer Interrupt!¶
Update rates and display rates are the same currently
Want them to diverge for whatever reason
⟶ “update” timer (0.5s, and a separate “show” timer (1s)
// g++ -o /tmp/embedded-app-2-irq embedded-app-2-irq.cpp -lrt
#include <signal.h>
#include <time.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
enum State
{
INITIAL,
RISING,
FALLING
};
struct app_status {
int state;
int tick_counter;
char display[12];
};
struct app_status status;
static void update_status(int)
{
switch (status.state) {
case INITIAL:
if (status.tick_counter == 9)
status.state = RISING;
break;
case RISING:
status.display[status.tick_counter] = '/';
if (status.tick_counter == 9)
status.state = FALLING;
break;
case FALLING:
status.display[status.tick_counter] = '\\';
if (status.tick_counter == 9)
status.state = RISING;
break;
}
if (status.tick_counter < 9)
status.tick_counter++;
else
status.tick_counter = 0;
}
static void show_status(int)
{
write(STDOUT_FILENO, status.display, sizeof(status.display));
}
int main()
{
// initialize application
{
status.state = INITIAL;
status.tick_counter = 0;
strcpy(status.display, "----------\n");
}
// establish update timer handler
{
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = update_status;
int error = sigaction(SIGRTMIN, &sa, NULL);
if (error) {
perror("sigaction");
return 1;
}
}
// create and start update timer
{
struct sigevent sev;
memset(&sev, 0, sizeof(sev));
sev.sigev_notify = SIGEV_SIGNAL;
sev.sigev_signo = SIGRTMIN;
timer_t timer;
int error = timer_create(CLOCK_MONOTONIC, &sev, &timer);
if (error) {
perror("timer_create");
return 1;
}
itimerspec tspec = {
/*interval*/
{
/*sec*/ 0,
/*nsec*/ 1000*1000*1000 / 2 // half a second
},
/*initial expiration in a second*/
{
/*sec*/ 1,
/*nsec*/ 0
}
};
error = timer_settime(timer, 0, &tspec, NULL);
if (error) {
perror("timer_settime");
return 1;
}
}
// establish show timer handler
{
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = show_status;
int error = sigaction(SIGRTMIN+1, &sa, NULL);
if (error) {
perror("sigaction");
return 1;
}
}
// create and start show timer
{
struct sigevent sev;
memset(&sev, 0, sizeof(sev));
sev.sigev_notify = SIGEV_SIGNAL;
sev.sigev_signo = SIGRTMIN+1;
timer_t timer;
int error = timer_create(CLOCK_MONOTONIC, &sev, &timer);
if (error) {
perror("timer_create");
return 1;
}
itimerspec tspec = {
/*interval*/
{
/*sec*/ 1,
/*nsec*/ 0
},
/*initial expiration in a second*/
{
/*sec*/ 1,
/*nsec*/ 0
}
};
error = timer_settime(timer, 0, &tspec, NULL);
if (error) {
perror("timer_settime");
return 1;
}
}
while (true)
pause(); // or do power management, on a real bare metal
// platform
return 0;
}
$ g++ -o /tmp/embedded-app-1-irq embedded-app-2-irq.cpp -lrt
$ /tmp/embedded-app-2-irq
----------
----------
----------
----------
----------
----------
//--------
////------
//////----
////////--
//////////
\\////////
^C
(Missing every second status update, roughly)
Away From Interrupts: Use An Operating System¶
I want to do periodic actions
Number of actions will soon exceed number of timer chips
⟶ need something more capable
⟶ an Embedded OS
FreeRTOS is a popular choice these days
Embedded OSen typically come with
Tasks and a task scheduler
Syncronous ways of sleeping
Arbitrary number of virtual timers, multiplexed on top of physical timer chips
⟶ replace timer interrupts with synchronous loops in tasks
// g++ -o /tmp/embedded-app-tasks embedded-app-tasks.cpp -lrt -lpthread
#include <signal.h>
#include <time.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <pthread.h>
enum State
{
INITIAL,
RISING,
FALLING
};
struct app_status {
int state;
int tick_counter;
char display[12];
};
struct app_status status;
static void* update_status_func(void*)
{
// initial expiration in a second
struct timespec initial_ts = {
/*sec*/ 1,
/*nsec*/ 0
};
nanosleep(&initial_ts, NULL);
while (true) {
switch (status.state) {
case INITIAL:
if (status.tick_counter == 9)
status.state = RISING;
break;
case RISING:
status.display[status.tick_counter] = '/';
if (status.tick_counter == 9)
status.state = FALLING;
break;
case FALLING:
status.display[status.tick_counter] = '\\';
if (status.tick_counter == 9)
status.state = RISING;
break;
}
if (status.tick_counter < 9)
status.tick_counter++;
else
status.tick_counter = 0;
// interval
struct timespec interval_ts = {
/*sec*/ 0,
/*nsec*/ 1000*1000*1000 / 2 // half a second
};
nanosleep(&interval_ts, NULL);
}
return NULL;
}
static void* show_status_func(void*)
{
// initial expiration in a second
struct timespec initial_ts = {
/*sec*/ 1,
/*nsec*/ 0
};
nanosleep(&initial_ts, NULL);
while (true) {
write(STDOUT_FILENO, status.display, sizeof(status.display));
struct timespec interval_ts = {
/*sec*/ 1,
/*nsec*/ 0
};
nanosleep(&interval_ts, NULL);
}
return NULL;
}
int main()
{
// initialize application
{
status.state = INITIAL;
status.tick_counter = 0;
strcpy(status.display, "----------\n");
}
// start update task
{
pthread_t update_task;
int error = pthread_create(&update_task, NULL, update_status_func, NULL);
if (error) {
fprintf(stderr, "pthread_create: %s\n", strerror(error));
return 1;
}
}
// start show task
{
pthread_t show_task;
int error = pthread_create(&show_task, NULL, show_status_func, NULL);
if (error) {
fprintf(stderr, "pthread_create: %s\n", strerror(error));
return 1;
}
}
while (true)
pause(); // or do power management, on a real bare metal
// platform
return 0;
}
$ g++ -o /tmp/embedded-app-tasks embedded-app-tasks.cpp -lrt -lpthread
$ /tmp/embedded-app-tasks
...
Tasks?¶
Great win: one process with two tasks (threads)
Lets do some introspection
$ ps -efl|grep embedded-app-tasks
0 S jfasch 231765 225819 0 80 0 - 5635 - 17:47 pts/4 00:00:00 /tmp/embedded-app-tasks
$ ps -L 231765
PID LWP TTY STAT TIME COMMAND
231765 231765 pts/4 Sl+ 0:00 /tmp/embedded-app-tasks
231765 231766 pts/4 Sl+ 0:00 /tmp/embedded-app-tasks
231765 231767 pts/4 Sl+ 0:00 /tmp/embedded-app-tasks
strace
: system call tracer
$ strace -p 231765
strace: Process 231765 attached
pause(
$ strace -p 231766
strace: Process 231766 attached
restart_syscall(<... resuming interrupted read ...>) = 0
clock_nanosleep(CLOCK_REALTIME, 0, {tv_sec=0, tv_nsec=500000000}, NULL) = 0
...
$ strace -p 231767
strace: Process 231767 attached
restart_syscall(<... resuming interrupted read ...>) = 0
write(1, "\\\\\\\\\\\\////\n\0", 12) = 12
...
Threads Are Great: More Functionality¶
Not being interrupt driven anymore
⟶ good
⟶ flexible
⟶ lets add more functionality!
Nonsense: switch terminal between reverse and normal
Reverse:
"\033[7m"
Normal:
"\033[0m"
// g++ -o /tmp/embedded-app-more-tasks embedded-app-more-tasks.cpp -lrt -lpthread
#include <signal.h>
#include <time.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <pthread.h>
enum State
{
INITIAL,
RISING,
FALLING
};
struct app_status {
int state;
int tick_counter;
char display[12];
};
struct app_status status;
static void* update_status_func(void*)
{
// initial expiration in a second
struct timespec initial_ts = {
/*sec*/ 1,
/*nsec*/ 0
};
nanosleep(&initial_ts, NULL);
while (true) {
switch (status.state) {
case INITIAL:
if (status.tick_counter == 9)
status.state = RISING;
break;
case RISING:
status.display[status.tick_counter] = '/';
if (status.tick_counter == 9)
status.state = FALLING;
break;
case FALLING:
status.display[status.tick_counter] = '\\';
if (status.tick_counter == 9)
status.state = RISING;
break;
}
if (status.tick_counter < 9)
status.tick_counter++;
else
status.tick_counter = 0;
// interval
struct timespec interval_ts = {
/*sec*/ 0,
/*nsec*/ 1000*1000*1000 / 2 // half a second
};
nanosleep(&interval_ts, NULL);
}
return NULL;
}
static void* show_status_func(void*)
{
// initial expiration in a second
struct timespec initial_ts = {
/*sec*/ 1,
/*nsec*/ 0
};
nanosleep(&initial_ts, NULL);
while (true) {
write(STDOUT_FILENO, status.display, sizeof(status.display));
struct timespec interval_ts = {
/*sec*/ 1,
/*nsec*/ 0
};
nanosleep(&interval_ts, NULL);
}
return NULL;
}
static void* flash_func(void*)
{
bool is_reverse = false;
static char reverse[] = "\033[7m";
static char normal[] = "\033[0m";
while (true) {
if (is_reverse)
write(STDOUT_FILENO, reverse, sizeof(reverse));
else
write(STDOUT_FILENO, normal, sizeof(normal));
is_reverse = !is_reverse;
struct timespec interval_ts = {
/*sec*/ 2,
/*nsec*/ 0
};
nanosleep(&interval_ts, NULL);
}
}
int main()
{
// initialize application
{
status.state = INITIAL;
status.tick_counter = 0;
strcpy(status.display, "----------\n");
}
// start update task
{
pthread_t update_task;
int error = pthread_create(&update_task, NULL, update_status_func, NULL);
if (error) {
fprintf(stderr, "pthread_create: %s\n", strerror(error));
return 1;
}
}
// start show task
{
pthread_t show_task;
int error = pthread_create(&show_task, NULL, show_status_func, NULL);
if (error) {
fprintf(stderr, "pthread_create: %s\n", strerror(error));
return 1;
}
}
// start reverse/normal task
{
pthread_t flash_task;
int error = pthread_create(&flash_task, NULL, flash_func, NULL);
if (error) {
fprintf(stderr, "pthread_create: %s\n", strerror(error));
return 1;
}
}
while (true)
pause(); // or do power management, on a real bare metal
// platform
return 0;
}
$ g++ -o /tmp/embedded-app-more-tasks embedded-app-more-tasks.cpp -lrt -lpthread
$ /tmp/embedded-app-more-tasks
...
Are Threads Great?¶
Cramming tons of functionality into a single program requires some programming skills
Program = Microcontroller
Our program approaches 200 lines which is still manageable (functionality is trivial though)
Need internet?
Need filesystem?
Need <insert favorite feature>?
Worst case: one thread causes a bug … (hint: it’s the unrelated terminal flasher)
// g++ -o /tmp/embedded-app-more-tasks-buggy embedded-app-more-tasks-buggy.cpp -lrt
#include <signal.h>
#include <time.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <pthread.h>
enum State
{
INITIAL,
RISING,
FALLING
};
struct app_status {
int state;
int tick_counter;
char display[12];
};
struct app_status status;
static void* update_status_func(void*)
{
// initial expiration in a second
struct timespec initial_ts = {
/*sec*/ 1,
/*nsec*/ 0
};
nanosleep(&initial_ts, NULL);
while (true) {
switch (status.state) {
case INITIAL:
if (status.tick_counter == 9)
status.state = RISING;
break;
case RISING:
status.display[status.tick_counter] = '/';
if (status.tick_counter == 9)
status.state = FALLING;
break;
case FALLING:
status.display[status.tick_counter] = '\\';
if (status.tick_counter == 9)
status.state = RISING;
break;
}
if (status.tick_counter < 9)
status.tick_counter++;
else
status.tick_counter = 0;
// interval
struct timespec interval_ts = {
/*sec*/ 0,
/*nsec*/ 1000*1000*1000 / 2 // half a second
};
nanosleep(&interval_ts, NULL);
}
return NULL;
}
static void* show_status_func(void*)
{
// initial expiration in a second
struct timespec initial_ts = {
/*sec*/ 1,
/*nsec*/ 0
};
nanosleep(&initial_ts, NULL);
while (true) {
write(STDOUT_FILENO, status.display, sizeof(status.display));
struct timespec interval_ts = {
/*sec*/ 1,
/*nsec*/ 0
};
nanosleep(&interval_ts, NULL);
}
return NULL;
}
static void* flash_func(void*)
{
bool is_reverse = false;
static char reverse[] = "\033[7m";
static char normal[] = "\033[0m";
// nasty!
int i = 0;
while (true) {
if (is_reverse)
write(STDOUT_FILENO, reverse, sizeof(reverse));
else
write(STDOUT_FILENO, normal, sizeof(normal));
is_reverse = !is_reverse;
struct timespec interval_ts = {
/*sec*/ 2,
/*nsec*/ 0
};
nanosleep(&interval_ts, NULL);
// nasty!
if (++i == 7) {
status.display[0] = '@';
status.display[1] = '!';
status.display[2] = '%';
status.display[3] = '$';
status.display[4] = '#';
i = 0;
}
}
}
int main()
{
// initialize application
{
status.state = INITIAL;
status.tick_counter = 0;
strcpy(status.display, "----------\n");
}
// start update task
{
pthread_t update_task;
int error = pthread_create(&update_task, NULL, update_status_func, NULL);
if (error) {
fprintf(stderr, "pthread_create: %s\n", strerror(error));
return 1;
}
}
// start show task
{
pthread_t show_task;
int error = pthread_create(&show_task, NULL, show_status_func, NULL);
if (error) {
fprintf(stderr, "pthread_create: %s\n", strerror(error));
return 1;
}
}
// start reverse/normal task
{
pthread_t flash_task;
int error = pthread_create(&flash_task, NULL, flash_func, NULL);
if (error) {
fprintf(stderr, "pthread_create: %s\n", strerror(error));
return 1;
}
}
while (true)
pause(); // or do power management, on a real bare metal
// platform
return 0;
}
$ g++ -o /tmp/embedded-app-more-tasks-buggy embedded-app-more-tasks-buggy.cpp -lrt -lpthread
$ /tmp/embedded-app-more-tasks-buggy
...
Stability Considerations¶
Three tasks (indepenently running entities)
Update status
Show status
Toggle terminal fore/background
Stability considerations
The first two are related (communicate)
The last one is complete unrelated - it even causes a bug
Probably the latter requires a bit more isolation!
Processes, Address Spaces¶
Maps virtual addresses to physical addresses
Adds memory protections (on a per-page granularity, usually 4K)
Read-only
Executable (contains CPU instructions)
…
⟶ Memory access exceptions, leading to program crash
Enter address spaces
A process has its own address space
Must not access memory that it does not own; Segmentation fault (in Unix terminology), or Memory access violation
Stabilizing¶
Lets rip the offender out; “decouple the rest from it”
Make a single threaded program that we start separately. Respectively,
Revert back to
code/embedded-app-tasks.cpp
Flash terminal in separate program
// g++ -o /tmp/flash-terminal flash-terminal.cpp
#include <unistd.h>
#include <time.h>
int main()
{
bool is_reverse = false;
static char reverse[] = "\033[7m";
static char normal[] = "\033[0m";
while (true) {
if (is_reverse)
write(STDOUT_FILENO, reverse, sizeof(reverse));
else
write(STDOUT_FILENO, normal, sizeof(normal));
is_reverse = !is_reverse;
struct timespec interval_ts = {
/*sec*/ 2,
/*nsec*/ 0
};
nanosleep(&interval_ts, NULL);
}
return 0;
}
$ g++ -o /tmp/flash-terminal flash-terminal.cpp
$ /tmp/flash-terminal & # <--- BACKGROUND, ON SAME TERMINAL!
$ /tmp/embedded-app-tasks
... stable terminal flashing here ...