Spinlocks#
Deferring Work To Main Loop#
Interrupt still produces samples
Consumption in main loop
Communication from producer (ISR) to consumer (main loop): doubly linked list
Code: Deferring Work To Main Loop (Naively)#
#include <why-irq.h>
#include <why-time.h>
#include <why-sensor.h>
#include <print>
#include <list>
using sensor_data = std::pair<Why::TimeSpec/*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(7, isr);
while (true) {
if (the_data.size()) { // <-- consume
auto [timestamp, value] = the_data.front();
the_data.pop_front();
std::println("timestamp={}, value={}", timestamp.to_seconds(), value);
}
else
/*power management?*/;
}
return 0;
}
$ why-shell code/why-interrupt-sensor-enqueue-list
> irq 7
timestamp=199147898365133, value=100
Problem: Critical Section#
A
std::listis not at all safeNon-atomic manipulation of multiple pointers
Main loop dequeues samples
Possibly interleaved by ISR which enqueues a sample
Bombard it, and see a crash at some point:
$ while true; do echo irq 7; done | \
why-shell code/why-interupt-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.
Spinlock: Interrupt Mutual Exclusion#
Attention
Although spinlocks are easy to use and wrap around one’s own data structures, it is probably wiser to rely on OS capabilities and use message queues (or comparable devices) and scheduling.
Need something that tests and sets a flags atomically
Safe to use in ISR (should be used, actually)
Also usable between threads (see later)
On a multiprocessor: spin until lock can be had
In schedulable context, disable interrupts on the local CPU
Actual implementation/definition is much more complicated
⟶ Do not try to write one yourself
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<Why::TimeSpec/*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(7, 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.to_seconds(), value);
}
else
/*power management?*/;
}
return 0;
}
Bombard it like earlier, all well
$ while true; do echo irq 7; done | \
why-shell code/why-interrupt-sensor-enqueue-list-spinlock
...