CHERIoT RTOS
A compartmentalised RTOS for CHERIoT hardware
Loading...
Searching...
No Matches
ring_buffer.hh
1
2#pragma once
3#include <limits>
4#include <locks.hh>
5#include <thread.h>
6#include <utils.hh>
7
8/**
9 * A simple single-producer, single-consumer ring buffer that can be made
10 * multi-producer and / or multi-consumer by adding locking. The first two
11 * template parameters are the type of the message and the size of the buffer
12 * (in messages). The next template arguments are the locks to use for
13 * protecting the producer and consumer ends.
14 *
15 * In the uncontended (or single-producer, single-consumer) case where the
16 * producer and consumer are balanced and the ring is neither full nor empty,
17 * this will not use any cross-compartment calls. When the queue is full,
18 * producers will block on a futex. When the queue is empty, consumers will
19 * block on a futex. On transitions to non-empty and non-full, the queue will
20 * issue a `futex_wake` cross-compartment call. If locks are used, they may
21 * introduce additional cross-compartment calls.
22 *
23 * Note: The size must be a power of two.
24 */
25template<typename Message,
26 size_t BufferSize,
27 typename PushLock = NoLock,
28 typename PopLock = NoLock>
30{
31 /// The ring buffer.
32 std::array<Message, BufferSize> ring;
33 /// The lock protecting the push end, if one exists.
34 [[no_unique_address]] PushLock pushLock;
35 /// The lock protecting the pop end, if one exists.
36 [[no_unique_address]] PopLock popLock;
37 /// Free-running producer counter.
38 cheriot::atomic<uint32_t> producer;
39 /// Free-running consumer counter.
40 cheriot::atomic<uint32_t> consumer;
41
42 static_assert(BufferSize < std::numeric_limits<uint32_t>::max() / 2,
43 "The buffer size cannot be more than half the range of the "
44 "counter types or overflow will give incorrect values");
45 static_assert((1 << utils::log2<BufferSize>()) == BufferSize,
46 "Buffer size must be a power of two");
47
48 /**
49 * Helper to check if the ring is full. The counters are free running and
50 * so the displacement between them (wrapping for overflow) will be the
51 * number of elements in the buffer if the ring is full.
52 */
53 bool is_full()
54 {
55 return producer - consumer == BufferSize;
56 }
57
58 /**
59 * Check if the queue is empty. If the consumer counter has caught up with
60 * the producer then this ring is empty.
61 */
62 bool is_empty()
63 {
64 return producer == consumer;
65 }
66
67 /**
68 * Helper to convert the counter value to an array index. The counters are
69 * never reset and so this is simple modulo arithmetic. Power of two sizes
70 * are more efficient because they become bit masks.
71 */
72 size_t counter_to_index(size_t counter)
73 {
74 return counter % BufferSize;
75 }
76
77 public:
78 /**
79 * Push an element into the ring. The caller is responsible for ensuring
80 * that, if this contains pointers, they have the global permission and so
81 * the move operation will not fault.
82 */
83 void push(Message &&message)
84 {
85 bool wasEmpty;
86 {
87 LockGuard g{pushLock};
88 // Wait for the queue to not be full
89 while (is_full())
90 {
91 consumer.wait(producer - BufferSize);
92 }
93 auto i = counter_to_index(producer);
94 ring[i] = std::move(message);
95 wasEmpty = is_empty();
96 producer++;
97 }
98 if (wasEmpty)
99 {
100 producer.notify_all();
101 }
102 }
103
104 /**
105 * Pop the next message from the queue.
106 */
107 Message pop()
108 {
109 Message result;
110 bool wasFull;
111 {
112 LockGuard g{popLock};
113 // Wait for the queue to not be empty
114 while (is_empty())
115 {
116 producer.wait(consumer);
117 }
118 auto i = counter_to_index(consumer);
119 result = std::move(ring[i]);
120 wasFull = is_full();
121 consumer++;
122 }
123 if (wasFull)
124 {
125 consumer.notify_all();
126 }
127 return result;
128 }
129};
130
131// Make sure that locks consume no space if not used.
132static_assert(sizeof(RingBuffer<uint32_t, 2>) == 16);
A simple RAII type that owns a lock.
Definition locks.hh:298
Class that implements the locking concept but does not perform locking.
Definition locks.hh:245
A simple single-producer, single-consumer ring buffer that can be made multi-producer and / or multi-...
Message pop()
Pop the next message from the queue.
void push(Message &&message)
Push an element into the ring.
C++ wrappers for synchronisation primitives.
Functions and types used for thread management.
Miscellaneous utility functions and classes.