CHERIoT RTOS
A compartmentalised RTOS for CHERIoT hardware
Toggle main menu visibility
Loading...
Searching...
No Matches
sdk
include
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
*/
25
template
<
typename
Message,
26
size_t
BufferSize,
27
typename
PushLock =
NoLock
,
28
typename
PopLock =
NoLock
>
29
class
RingBuffer
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.
132
static_assert
(
sizeof
(
RingBuffer<uint32_t, 2>
) == 16);
LockGuard
A simple RAII type that owns a lock.
Definition
locks.hh:298
NoLock
Class that implements the locking concept but does not perform locking.
Definition
locks.hh:245
RingBuffer
A simple single-producer, single-consumer ring buffer that can be made multi-producer and / or multi-...
Definition
ring_buffer.hh:30
RingBuffer::pop
Message pop()
Pop the next message from the queue.
Definition
ring_buffer.hh:107
RingBuffer::push
void push(Message &&message)
Push an element into the ring.
Definition
ring_buffer.hh:83
locks.hh
C++ wrappers for synchronisation primitives.
thread.h
Functions and types used for thread management.
utils.hh
Miscellaneous utility functions and classes.
Generated by
1.18.0