Spinlocks#

Producer/Consumer Interrupts#

  • Rationale: interrupt takes too long (std::println())

  • ⟶ defer that to main loop

  • Use a doubly linked list

#include <why-irq.h>
#include <why-time.h>
#include <why-sensor.h>
#include <print>
#include <list>

using sensor_data = std::pair<uint64_t/*timestamp*/, uint64_t/*value*/>;
std::list<sensor_data> the_data;

int main()
{
    Why::init();

    Why::RandomSensor sensor(0, 100);
    auto isr = [&sensor](int irqnum){
        the_data.emplace_back(                         // <-- produce
            Why::now_monotonic(), sensor.get_value());
    };

    Why::IRQ::connect(2, isr);

    while (true) {
        if (the_data.size()) {                         // <-- consume
            auto [timestamp, value] = the_data.front();
            the_data.pop_front();
            std::println("timestamp={}, value={}", timestamp, value);
        }
        else
            /*power management?*/;
    }
    return 0;
}
$ why-shell code/why-spinlocks-sensor-enqueue-list
> irq 2
timestamp=199147898365133, value=100

Problem: Interrupt Asynchronity#

  • Interrupt service routine runs highly async

  • Will interrupt main loop at inopportune times

Bombard it, and see a crash at some point:

$ while true; do echo irq 2; done | \
    why-shell code/why-spinlocks-sensor-enqueue-list
...
/usr/include/c++/15/bits/stl_list.h:1674: std::__cxx11::list<_Tp, _Allocator>::reference std::__cxx11::list<_Tp, _Allocator>::back() [with _Tp = std::pair<long unsigned int, long unsigned int>; _Alloc = std::allocator<std::pair<long unsigned int, long unsigned int> >; reference = std::pair<long unsigned int, long unsigned int>&]: Assertion '!this->empty()' failed.
child died: signal 6, core dumped: 128

Note

Try as root, to get realtime behavior and hard preemption rather than soft timeslices.

Solution: Spinlock#

  • Spinlock: safe to use in interrupt context

  • jjj blah

jjj check that

  • On a multiprocessor: spin until lock can be had

    • In schedulable context, disable interrupts on the local CPU

  • On a single processor:

    • In schedulable context: disable interrupts, be happy with the lock

    • In interrupt context: nothing to do - interrupts not disabled, that’s enough

#include <why-irq.h>
#include <why-spinlock.h>
#include <why-time.h>
#include <why-sensor.h>
#include <print>
#include <list>

using sensor_data = std::pair<uint64_t/*timestamp*/, uint64_t/*value*/>;
std::list<sensor_data> the_data;
Why::Spinlock the_lock;                                // <-- instantiate

int main()
{
    Why::init();

    Why::RandomSensor sensor(0, 100);
    auto isr = [&sensor](int irqnum){
        the_lock.lock();                               // <-- use
        the_data.emplace_back(
            Why::now_monotonic(), sensor.get_value());
        the_lock.unlock();                             // <-- use
    };

    Why::IRQ::connect(2, isr);

    while (true) {
        if (the_data.size()) {
            the_lock.lock();                           // <-- use
            auto [timestamp, value] = the_data.front();
            the_data.pop_front();
            the_lock.unlock();                         // <-- use
            std::println("timestamp={}, value={}", timestamp, value);
        }
        else
            /*power management?*/;
    }
    return 0;
}

Bombard it like earlier, all well

$ while true; do echo irq 2; done | \
    why-shell code/why-spinlocks-sensor-enqueue-list-spinlock
...