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:

  • volatile prevents the compiler from optimizations/reordering

  • CPUs 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::list

  • Have 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::atomic is of no use at a higher level

Mutex Intro#
  • Introduce a std::mutex into 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 section

  • Use std::scoped_lock<>

POSIX Mutexes#
  • Modify The Program to use pthread_mutex_t

  • Write a mutex class myself

  • Write a straightforward scoped lock myself

  • Explain mutex types (Mutex Types), having fun with the PTHREAD_MUTEX_RECURSIVE shithouse 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.

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_ptr usage 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.

Living Without A Heap#

Advanced Flexibility Techniques (Design Patterns?)#

  • Focus on dependency injection, resp. strategy pattern

  • C++ “interfaces”: one use of the C++ virtual keyword

  • Strategy (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: