L4Re Operating System Framework
Interface and Usage Documentation
Loading...
Searching...
No Matches
ring_buffer
Go to the documentation of this file.
1// vim:set ft=cpp: -*- Mode: C++ -*-
2/*
3 * Copyright (C) 2026 Kernkonzept GmbH.
4 * Author(s): Martin Decky <martin.decky@kernkonzept.com>
5 *
6 * License: see LICENSE.spdx (in this directory or the directories above)
7 */
8
28
29#pragma once
30
31#include <cstddef>
32#include <stdexcept>
33#include <atomic>
34#include <bit>
35#include <mutex>
36#include <functional>
37
38namespace utrace {
39
41#if defined(ARCH_arm64)
42 static constexpr size_t stable_cache_alignment = 256U;
43#elif defined(ARCH_ppc32)
44 static constexpr size_t stable_cache_alignment = 128U;
45#else
46 static constexpr size_t stable_cache_alignment = 64U;
47#endif
48
49#ifdef __GCC_DESTRUCTIVE_SIZE
50static_assert(stable_cache_alignment >= __GCC_DESTRUCTIVE_SIZE,
51 "Stable cache alignment not sufficient");
52#endif // __GCC_DESTRUCTIVE_SIZE
53
62template<typename SEQUENCE_TYPE>
64{
65public:
66 using Sequence = SEQUENCE_TYPE;
67 using Generation = std::atomic<Sequence>;
68
70 static constexpr Sequence const nil = 0U;
71
83 static void check_items(size_t const items)
84 {
85 if (items == 0)
86 throw std::invalid_argument("Zero items not supported.");
87
88 if (items != std::bit_ceil(items))
89 throw std::invalid_argument("Number of items must be power of two.");
90 }
91
103 static void check_mask(size_t const mask)
104 {
105 auto items = mask + 1;
106 if (items != std::bit_ceil(items))
107 throw std::length_error("Invalid mask.");
108 }
109};
110
124template<typename SEQUENCE_TYPE>
126{
127public:
128 using Sequence = SEQUENCE_TYPE;
129 using Generation = typename Ring_buffer<Sequence>::Generation;
130
131 template<typename S, typename T, auto G> friend class Ring_buffer_producer;
132 template<typename S, typename T, auto G>
133 friend class Ring_buffer_consumer_raw;
134
142 unsigned version() const
143 { return _version; }
144
152 size_t alignment() const
153 { return _alignment; }
154
156 size_t mask() const
157 { return _mask; }
158
160 size_t items() const
161 { return _mask + 1; }
162
163private:
165 unsigned _version;
166
168 size_t _alignment;
169
171 alignas(stable_cache_alignment) size_t _mask;
172
174 alignas(stable_cache_alignment) Generation _tail;
175};
176
186template<typename ITEM_TYPE>
187static constexpr size_t ring_slot_alignment = std::bit_ceil(sizeof(ITEM_TYPE));
188
203template<typename ITEM_TYPE, auto GENERATION_PTR>
204class alignas(ring_slot_alignment<ITEM_TYPE>) Ring_slot
205{
206public:
207 using This = Ring_slot;
208 using Item = ITEM_TYPE;
209
210 template<typename S, typename T, auto G> friend class Ring_buffer_producer;
211 template<typename S, typename T, auto G>
212 friend class Ring_buffer_consumer_raw;
213
221 static size_t size(size_t const items)
222 { return items * sizeof(This); }
223
224private:
227 auto generation() const noexcept
228 { return std::atomic_ref(_data.*GENERATION_PTR); }
229
231 Item _data;
232};
233
244template<typename SEQUENCE_TYPE, typename ITEM_TYPE, auto GENERATION_PTR>
245class Ring_buffer_producer : public Ring_buffer<SEQUENCE_TYPE>
246{
247public:
248 using This = Ring_buffer_producer;
249 using Super = Ring_buffer<SEQUENCE_TYPE>;
250
251 using Item = ITEM_TYPE;
252
253 using Status = Ring_status<SEQUENCE_TYPE>;
255
271 Ring_buffer_producer(Status &status, Slot &slots, unsigned const version,
272 size_t const items) : _status(status), _slots(slots)
273 {
275
276 _status._version = version;
277 _status._alignment = stable_cache_alignment;
278 _status._mask = items - 1;
279 _status._tail = Super::nil;
280
281 for (size_t i = 0; i < items; ++i)
282 _slots[i].generation() = Super::nil;
283 }
284
286 size_t items() const
287 { return _status.items(); }
288
297 void enqueue(Item const &item) const
298 {
299 auto next = ++_status._tail;
300 auto index = next & _status._mask;
301
302 auto &generation = _slots[index].generation();
303 auto &slot = _slots[index]._data;
304
305 generation.store(Super::nil, std::memory_order_release);
306 slot = item;
307 generation.store(next, std::memory_order_release);
308 }
309
310private:
311 Ring_buffer_producer() = delete;
312
313 Status &_status;
314 Slot &_slots;
315};
316
330template<typename SEQUENCE_TYPE, typename ITEM_TYPE, auto GENERATION_PTR>
331class Ring_buffer_consumer_raw : public Ring_buffer<SEQUENCE_TYPE>
332{
333public:
334 using This = Ring_buffer_consumer_raw;
335 using Super = Ring_buffer<SEQUENCE_TYPE>;
336
337 using Sequence = SEQUENCE_TYPE;
338 using Item = ITEM_TYPE;
339
340 using Status = Ring_status<Sequence>;
342
350 enum class Drop_policy
351 {
362
372 };
373
375 enum class State
376 {
377 Idle = 0, //< No item dequeued.
378 Ready, //< An item has been dequeued.
379 Dropped //< Some items have been dropped.
380 };
381
391 Ring_buffer_consumer_raw(Status const &status, Slot const *slots)
392 : _status(status), _slots(slots)
393 { This::check_mask(_status._mask); }
394
396 size_t items() const
397 { return _status.items(); }
398
432 State peek(Item &item, Drop_policy policy, Sequence &current,
433 Sequence *drops = nullptr) const
434 {
435 if (drops)
436 *drops = 0;
437
438 // First access to the ring buffer.
439 if (current == Super::nil)
440 current = 1;
441
442 auto index = current & _status._mask;
443
444 auto const &generation = _slots[index].generation();
445 auto const &slot = _slots[index]._data;
446
447 auto seen = generation.load();
448 if (seen < current)
449 {
450 // Expected item not produced yet.
451 return State::Idle;
452 }
453
454 if (seen > current)
455 goto drop;
456
457 item = slot;
458 seen = generation.load();
459
460 // Make sure the item has not been overwritten while we have been copying
461 // the data.
462 if (seen == current)
463 {
464 // Consume item.
465 ++current;
466 return State::Ready;
467 }
468
469 drop:
470 // Seen > current -> drop detected.
471 Sequence head = _status._tail;
472
473 // Conservative drop policy continues at the tail.
474 // Minimal drop policy continues at the head.
475 if (policy == Drop_policy::Minimal)
476 head -= _status._mask;
477
478 // How many items have been dropped.
479 if (drops)
480 *drops = head - current;
481
482 current = head;
483 return State::Dropped;
484 }
485
486private:
487 Ring_buffer_consumer_raw() = delete;
488
489 Status const &_status;
490 Slot const *_slots;
491};
492
506template<typename SEQUENCE_TYPE, typename ITEM_TYPE, auto GENERATION_PTR>
508 : public Ring_buffer_consumer_raw<SEQUENCE_TYPE, ITEM_TYPE, GENERATION_PTR>
509{
510public:
511 using Sequence = SEQUENCE_TYPE;
512 using Item = ITEM_TYPE;
513
515
516 using Drop_policy = Super::Drop_policy;
517 using State = Super::State;
518 using Status = Ring_status<Sequence>;
520
521 using Yield = std::function<bool (Sequence)>;
522
532 Ring_buffer_consumer(Status const &status, Slot const *slots)
533 : Super(status, slots)
534 {}
535
557 State peek(Item &item, Drop_policy policy, Sequence *drops = nullptr)
558 {
559 const std::lock_guard guard(_lock);
560 return Super::peek(item, policy, _current, drops);
561 }
562
592 size_t dequeue(Item items[], size_t capacity, size_t burst,
593 Drop_policy policy, Yield const &yield,
594 Sequence *drops = nullptr)
595 {
596 if (drops)
597 *drops = 0;
598
599 size_t count = 0;
600 size_t idle_cycles = 0;
601
602 for (;;)
603 {
604 Sequence current_drops;
605 auto status = peek(items[count], policy, &current_drops);
606
607 if (status == State::Dropped)
608 {
609 // Some items have been dropped. We need to notify the caller
610 // about it. Thus return with the items already dequeued.
611
612 if (drops)
613 *drops = current_drops;
614
615 break;
616 }
617
618 if (status == State::Ready)
619 {
620 // Next item dequeued. Return if there is no space for more items
621 // in the array.
622
623 ++count;
624 if (count == capacity)
625 break;
626
627 continue;
628 }
629
630 if (status == State::Idle)
631 {
632 // No next item has been produced yet. Return if we have already
633 // dequeued enough items. Otherwise just spin, but allow the yield
634 // function to implement passive waiting.
635
636 if (count >= burst)
637 break;
638
639 ++idle_cycles;
640 if (yield(idle_cycles))
641 idle_cycles = 0;
642 }
643 }
644
645 return count;
646 }
647
648private:
649 Ring_buffer_consumer() = delete;
650 State peek(Item &, Drop_policy, Sequence &, Sequence *) const = delete;
651
653 std::mutex _lock;
654
656 Sequence _current = 0;
657};
658
659} // namespace utrace
State
Status of the dequeue operation.
Definition ring_buffer:376
Ring_buffer_consumer_raw(Status const &status, Slot const *slots)
Construct ring buffer consumer.
Definition ring_buffer:391
size_t items() const
Get the number of items.
Definition ring_buffer:396
State peek(Item &item, Drop_policy policy, Sequence &current, Sequence *drops=nullptr) const
Poll and possibly dequeue an item from the ring buffer.
Definition ring_buffer:432
@ Conservative
Conservative item drop policy.
Definition ring_buffer:361
size_t dequeue(Item items[], size_t capacity, size_t burst, Drop_policy policy, Yield const &yield, Sequence *drops=nullptr)
Dequeue items from the ring buffer.
Definition ring_buffer:592
State peek(Item &item, Drop_policy policy, Sequence *drops=nullptr)
Poll and possibly dequeue an item from the ring buffer.
Definition ring_buffer:557
Ring_buffer_consumer(Status const &status, Slot const *slots)
Construct ring buffer consumer.
Definition ring_buffer:532
Ring_buffer_producer(Status &status, Slot &slots, unsigned const version, size_t const items)
Construct ring buffer producer.
Definition ring_buffer:271
void enqueue(Item const &item) const
Enqueue an item in the ring buffer.
Definition ring_buffer:297
size_t items() const
Get the number of items.
Definition ring_buffer:286
Ring buffer.
Definition ring_buffer:64
static void check_mask(size_t const mask)
Check that the item mask is a power of two minus one.
Definition ring_buffer:103
static constexpr Sequence const nil
Invalid (non-committed) items set their sequence counter to 0.
Definition ring_buffer:70
static void check_items(size_t const items)
Check that the number of items is a power of two.
Definition ring_buffer:83
Ring buffer slot.
Definition ring_buffer:205
static size_t size(size_t const items)
Get the size (in bytes) of the given count of items.
Definition ring_buffer:221
Ring buffer status area.
Definition ring_buffer:126
size_t mask() const
Get the item mask.
Definition ring_buffer:156
size_t items() const
Get the number of items.
Definition ring_buffer:160
size_t alignment() const
Get the stable alignment of the ring buffer status members.
Definition ring_buffer:152
unsigned version() const
Get the version of the ring buffer items.
Definition ring_buffer:142