Agenda: (Linux/Zephyr) Systems Programming With C++#
Three days of systems programming, interwoven with select C++ topics.
Why Use An RTOS?#
Discuss +/- list
Query audence’s understanding of “realtime”
Multithreading, And All Its Consequences#
Intro: Load-Modify-Store Conflict#
Following
Race Conditions, and Mutexes,
hack a quick intro to the mother of all race conditions. “Quick”
because we use std::thread and two lambdas. One lambda to count up
a global int variable 10_000_000 times, another to count it down
10_000_000, being puzzled that they don’t meet at 0.
POSIX Threads: Creation And Lifetime#
How are threads created, and what tuning knobs exist to control their behavior - attributes. Provoke a discussion about realtime scheduling, and its hazards (priority inversion). Defer the solution (priority inheritance) until later in the threads section.
Lets largely disregard thread termination (it is unlikely that there is a usecase for it), in favor of a short discussion.
It is a complex topic, despite the simple API
The effect of cancellation might have unpleasant side effects when the thread is in the middle of critical section.
Mutual Exclusion#
First, lets refactor the outcome of Intro: Load-Modify-Store Conflict to
use <pthread.h>, making thread creation a little less easy (which
is good).
Atomics, GCC Atomic Builtins, Zephyr Atomics#
Modify load-modify-store to use C++ atomics; this is simplest.
And Volatile?#
Why volatile is of no use? There’s that same question in
Compiler Intrinsics,
elaborate on it.
Essentially:
volatileprevents the compiler from optimizations/reorderingCPUs are much more intelligent than you might think and reorder memory accesses internally
Mutexes#
Usecase For A Mutex#
Modify load-modify-store to not use atomics to protect the integer
Replace the integer with a
std::listHave two threads hammer on the poor list until crashes
⟶ Like load-modify-store, list manipulation is also a sequence of non-atomic operations (pointer bending) that may interleave and produce unwanted outcome
⟶
std::atomicis of no use at a higher level
Mutex Intro#
Introduce a
std::mutexinto the code, right next to the list, as a second global variable which is acquired/released by the user⟶ Not so pretty
Encapsulate the list and its lock into a surrounding class, and provide methods that guarantee thread safety
Scoped Locking#
Imagine premature
return, or an exception thrown in the middle of the critical sectionUse
std::scoped_lock<>
POSIX Mutexes#
Modify The Program to use
pthread_mutex_tWrite a mutex class myself
Write a straightforward scoped lock myself
Explain mutex types (Mutex Types), having fun with the
PTHREAD_MUTEX_RECURSIVEshithouse analogy
Spinlocks#
Discussion:
Share data between IRQ and thread context
IRQ must not sleep
Deadlock Scenario: Locking Mutexes In Undefined Order#
Write a braindead program: two threads, two mutexes
⟶ works most of the time
Introduce an artificial race (most races happen so infrequently that three days wont suffice to see them)
Deadlock Scenario: Priority Inversion#
Given three threads with different hard realtime priorities
Running on a single CPU, ideally (multicore is way more subtle) ⟶ for demo, use taskset
Make them lockup and explain how an unrelated medium priority thread can prevent a high priority thread from running
⟶ Inversion
Communication (POSIX Condition Variables)#
Motivation#
Queue/FIFO class with two ends
Producer and consumer threads on either end
No locking
⟶ crashes obviously
Locking#
Add a mutex to prevent races
⟶ What if queue full/empty?
Enter Condition Variable#
Discuss lost wakeup bug
Seeing queue empty, a thread has to go to sleep, in order to wait for elements to become available
⟶ An element might appear right after the thread has seen empty, and before sleep
Implement wait conditions on either side, discussing matters as we go (
pthread_cond_wait(),pthread_cond_signal())Discuss spurious wakup <https://en.wikipedia.org/wiki/Spurious_wakeup>
Optional: C++ Memory Model#
This is about multiprocessor memory visibility, and how C++ atomics (and likely any atomics) can be used to achieve lock-free synchronization/communication. A very good a-ha for those interested.
Threading Alternative: Event Driven Programming#
Following the above material (loosely), hack together something that would work on Zephyr.
A Zephyr kernel message queue to feeds data into the program
Another message queue that can trigger commit/rollback
C++#
Smart Pointers, Move Semantics, And Perfect Forwarding#
Short overview of smart pointers in C++, knowing that we’ll never allocated anything that we have to take ownership of (
std::unique_ptrusage requires a bit of move-understanding, so this is a good intro)
Switch to a straightforward String class, like in
Implementing Move Semantics (class String, Live Hacked),
and begin a deeper dive into C++’s braindead pitfalls.
The Rule Of Three/Five/Zero, including move. There is a separate slide deck on move here.
Only because it looks syntactically like move, but is a completely different thing: Perfect Forwarding
Living Without A Heap#
Advanced Flexibility Techniques (Design Patterns?)#
Focus on dependency injection, resp. strategy pattern
C++ “interfaces”: one use of the C++
virtualkeywordStrategy (Dependency Injection)
Builder pattern: given a pile of related (via interfaces, Strategy) classes, how to best build a bigger thing of it
Miscellaneous#
The rest that does not fit anywhere:
std::functionresp. inplace functions WG21-SG14/SG14Show creative
autousage: Abbreviated function templates (with concepts :-) )