The FreeRADIUS server $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
Loading...
Searching...
No Matches
ring_buffer.c
Go to the documentation of this file.
1/*
2 * This program is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or
5 * (at your option) any later version.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15 */
16
17/**
18 * $Id: e815bdb7970539234acbea03a59528c831cfa377 $
19 *
20 * @brief Simple ring buffers for packet contents
21 * @file io/ring_buffer.c
22 *
23 * @copyright 2016 Alan DeKok (aland@freeradius.org)
24 */
25RCSID("$Id: e815bdb7970539234acbea03a59528c831cfa377 $")
26
27#include <freeradius-devel/io/ring_buffer.h>
28#include <freeradius-devel/util/strerror.h>
29#include <freeradius-devel/util/debug.h>
30
31/*
32 * Ring buffers are allocated in a block.
33 */
35 uint8_t *buffer; //!< actual start of the ring buffer
36 size_t size; //!< Size of this ring buffer
37
38 size_t data_start; //!< start of used portion of the buffer
39 size_t data_end; //!< end of used portion of the buffer
40
41 size_t write_offset; //!< where writes are done
42 size_t reserved; //!< amount of reserved data at write_offset
43
44 bool closed; //!< whether allocations are closed
45};
46
47/** Create a ring buffer.
48 *
49 * The size provided will be rounded up to the next highest power of
50 * 2, if it's not already a power of 2.
51 *
52 * The ring buffer manages how much room is reserved (i.e. available
53 * to write to), and used. The application is responsible for
54 * tracking the start of the reservation, *and* it's write offset
55 * within that reservation.
56 *
57 * @param[in] ctx a talloc context
58 * @param[in] size of the raw ring buffer array to allocate.
59 * @return
60 * - A new ring buffer on success.
61 * - NULL on failure.
62 */
63fr_ring_buffer_t *fr_ring_buffer_create(TALLOC_CTX *ctx, size_t size)
64{
66
67 rb = talloc_zero(ctx, fr_ring_buffer_t);
68 if (!rb) {
69 fail:
70 fr_strerror_const("Failed allocating memory.");
71 return NULL;
72 }
73
74 if (size < 1024) size = 1024;
75
76 if (size > (1 << 30)) {
77 fr_strerror_const("Ring buffer size must be no more than (1 << 30)");
78 talloc_free(rb);
79 return NULL;
80 }
81
82 /*
83 * Round up to the nearest power of 2.
84 */
85 size--;
86 size |= size >> 1;
87 size |= size >> 2;
88 size |= size >> 4;
89 size |= size >> 8;
90 size |= size >> 16;
91 size++;
92
93 rb->buffer = talloc_array(rb, uint8_t, size);
94 if (!rb->buffer) {
95 talloc_free(rb);
96 goto fail;
97 }
98 rb->size = size;
99
100 return rb;
101}
102
103
104/** Reserve room in the ring buffer.
105 *
106 * The size does not need to be a power of two. The application is
107 * responsible for doing cache alignment, if required.
108 *
109 * If the reservation fails, the application should create a new ring
110 * buffer, and start reserving data there.
111 *
112 * @param[in] rb a ring buffer
113 * @param[in] size to see if we can reserve
114 * @return
115 * - NULL on error. Which can only be "ring buffer is full".
116 * - pointer to data on success
117 */
119{
120 (void) talloc_get_type_abort(rb, fr_ring_buffer_t);
121
122 if (rb->closed) {
123 fr_strerror_const("Allocation request after ring buffer is closed");
124 return NULL;
125 }
126
127 /*
128 * We're writing to the start of the buffer, and there is
129 * already data in it. See if the data fits.
130 *
131 * |***W....S****E....|
132 */
133 if (rb->write_offset < rb->data_start) {
134 if ((rb->write_offset + size) < rb->data_start) {
135 rb->reserved = size;
136 return rb->buffer + rb->write_offset;
137 }
138
139 fr_strerror_const("No memory available in ring buffer");
140 return NULL;
141 }
142
143 fr_assert(rb->write_offset == rb->data_end);
144
145 /*
146 * Data fits at the end of the ring buffer.
147 *
148 * |....S****WE....|
149 */
150 if ((rb->write_offset + size) <= rb->size) {
151 rb->reserved = size;
152 return rb->buffer + rb->write_offset;
153 }
154
155 /*
156 * Data fits at the start of the ring buffer, ensure that
157 * we write it there. This also catches the case where
158 * data_start==0.
159 *
160 * |W....S****E....|
161 */
162 if (size < rb->data_start) {
163 rb->write_offset = 0;
164 rb->reserved = size;
165 return rb->buffer;
166 }
167
168 /*
169 * Not enough room for the new data, fail the allocation.
170 *
171 * |....S****WE....|
172 */
173 fr_strerror_const("No memory available in ring buffer");
174 return NULL;
175}
176
177
178/** Mark data as allocated.
179 *
180 * The size does not need to be a power of two. The application is
181 * responsible for doing cache-line alignment, if required.
182 *
183 * The application does NOT need to call fr_ring_buffer_reserve() before
184 * calling this function.
185 *
186 * If the allocation fails, the application should create a new ring
187 * buffer, and start allocating data there.
188 *
189 * @param[in] rb a ring buffer
190 * @param[in] size to mark as "used" at the tail end of the buffer.
191 * @return
192 * - NULL on error. Which can only be "ring buffer is full".
193 * - pointer to data on success
194 */
196{
197 uint8_t *p;
198
199 (void) talloc_get_type_abort(rb, fr_ring_buffer_t);
200
201 if (rb->closed) {
202#ifndef NDEBUG
203 fr_strerror_const("Allocation request after ring buffer is closed");
204#endif
205 return NULL;
206 }
207
208 /*
209 * Shrink the "reserved" portion of the buffer by the
210 * allocated size.
211 */
212 if (rb->reserved >= size) {
213 rb->reserved -= size;
214 } else {
215 rb->reserved = 0;
216 }
217
218 /*
219 * We're writing to the start of the buffer, and there is
220 * already data in it. See if the data fits.
221 *
222 * |***W....S****E....|
223 */
224 if (rb->write_offset < rb->data_start) {
225 if ((rb->write_offset + size) < rb->data_start) {
226 p = rb->buffer + rb->write_offset;
227 rb->write_offset += size;
228 return p;
229 }
230
231#ifndef NDEBUG
232 fr_strerror_const("No memory available in ring buffer");
233#endif
234 return NULL;
235 }
236
237 fr_assert(rb->write_offset == rb->data_end);
238
239 /*
240 * Data fits at the end of the ring buffer.
241 *
242 * |....S****WE....|
243 */
244 if ((rb->write_offset + size) <= rb->size) {
245 p = rb->buffer + rb->write_offset;
246
247 rb->write_offset += size;
248 rb->data_end = rb->write_offset;
249
250 /*
251 * Don't update write_offset if it's fallen off
252 * of the end of the buffer. The data_start may
253 * be zero, and we don't want to over-write
254 * that.
255 */
256 return p;
257 }
258
259 /*
260 * Data fits at the start of the ring buffer, ensure that
261 * we write it there. This also catches the case where
262 * data_start==0.
263 *
264 * |W....S****E....|
265 */
266 if (size < rb->data_start) {
267 rb->write_offset = size;
268
269 /*
270 * Don't update data_end. It points to the tail
271 * end of the ring buffer.
272 */
273 return rb->buffer;
274 }
275
276 /*
277 * Not enough room for the new data, fail the allocation.
278 *
279 * |....S****WE....|
280 */
281#ifndef NDEBUG
282 fr_strerror_const("No memory available in ring buffer");
283#endif
284 return NULL;
285}
286
287/** Mark data as free,
288 *
289 * The size does not need to be a power of two. The application is
290 * responsible for doing cache alignment, if required. The
291 * application is responsible for tracking sizes of packets in the
292 * ring buffer.
293 *
294 * If "unused" bytes are more than what's in the buffer, the used
295 * bytes are reset to zero.
296 *
297 * @param[in] rb a ring buffer
298 * @param[in] size_to_free bytes to mark as "unused" in the buffer.
299 * @return
300 * - <0 on error. Which can only be "ring buffer is full".
301 * - 0 on success
302 */
303int fr_ring_buffer_free(fr_ring_buffer_t *rb, size_t size_to_free)
304{
305 size_t block_size;
306
307 (void) talloc_get_type_abort(rb, fr_ring_buffer_t);
308
309 /*
310 * Nothing to free, do nothing.
311 */
312 if (!size_to_free) return 0;
313
314 /*
315 * Freeing data from the middle of the buffer.
316 *
317 * |***W....S****E....|
318 */
319 if (rb->write_offset < rb->data_start) {
320 block_size = rb->data_end - rb->data_start;
321
322 /*
323 * |***W....S****E....|, free 3
324 *
325 * |***W.......S*E....|
326 */
327 if (size_to_free < block_size) {
328 rb->data_start += size_to_free;
329 return 0;
330 }
331
332 /*
333 * Free all (or more than) the block.
334 */
335 rb->data_start = 0;
336 rb->data_end = rb->write_offset;
337 size_to_free -= block_size;
338
339 /*
340 * Free everything left: empty the buffer
341 * entirely. This also handles the case of
342 * size_to_free==0 and write_offset==0.
343 */
344 if (size_to_free == rb->write_offset) {
345 goto empty_buffer;
346 }
347
348 /*
349 * The buffer has data but we're not freeing
350 * any more of it, return.
351 */
352 if (!size_to_free) return 0;
353 }
354
355 fr_assert(rb->write_offset == rb->data_end);
356
357 block_size = rb->data_end - rb->data_start;
358
359 /*
360 * Freeing too much, return an error.
361 */
362 if (size_to_free > block_size) {
363 fr_strerror_const("Cannot free more memory than exists.");
364 return -1;
365 }
366
367 /*
368 * Free some data from the start.
369 */
370 if (size_to_free < block_size) {
371 rb->data_start += size_to_free;
372 return 0;
373 }
374
375 /*
376 * Free all data in the buffer.
377 */
378empty_buffer:
379 rb->data_start = 0;
380 rb->data_end = 0;
381 rb->write_offset = 0;
382
383 /*
384 * If the ring buffer is closed to all allocations, and
385 * it's now empty, we automatically free it.
386 */
387 if (rb->closed) talloc_free(rb);
388
389 return 0;
390}
391
392/** Close a ring buffer so that no further allocations can take place.
393 *
394 * Once the ring buffer is empty, it will be automatically freed.
395 * It's called "close" and not "delete", because the ring buffer will
396 * still be active until all data has been removed.
397 *
398 * If you don't care about the data in the ring buffer, you can just
399 * call talloc_free() on it.
400 *
401 * @param[in] rb a ring buffer
402 * @return
403 * - <0 on error.
404 * - 0 on success
405 */
407{
408 (void) talloc_get_type_abort(rb, fr_ring_buffer_t);
409
410 rb->closed = true;
411 return 0;
412}
413
414
415/** Get the size of the ring buffer
416 *
417 * @param[in] rb a ring buffer
418 * @return size of the ring buffer.
419 * - <0 on error.
420 * - 0 on success
421 */
423{
424 (void) talloc_get_type_abort(rb, fr_ring_buffer_t);
425
426 return rb->size;
427}
428
429/** Get the amount of data used in a ring buffer.
430 *
431 * @param[in] rb a ring buffer
432 * @return size of the used data in the ring buffer.
433 * - <0 on error.
434 * - 0 on success
435 */
437{
438 size_t size;
439
440 (void) talloc_get_type_abort(rb, fr_ring_buffer_t);
441
442 if (rb->write_offset < rb->data_start) {
443 size = rb->write_offset;
444 } else {
445 fr_assert(rb->write_offset == rb->data_end);
446 size = 0;
447 }
448
449 size += (rb->data_end - rb->data_start);
450
451 return size;
452}
453
454/** Get a pointer to the data at the start of the ring buffer.
455 *
456 * @param[in] rb a ring buffer
457 * @param[out] p_start pointer to data at the start of the ring buffer
458 * @param[in] p_size size of the allocated block at the start of the ring buffer.
459 * @return size of the used data in the ring buffer.
460 * - <0 on error.
461 * - 0 on success
462 */
463int fr_ring_buffer_start(fr_ring_buffer_t *rb, uint8_t **p_start, size_t *p_size)
464{
465 (void) talloc_get_type_abort(rb, fr_ring_buffer_t);
466
467 *p_start = rb->buffer + rb->data_start;
468
469 if (rb->write_offset < rb->data_start) {
470 *p_size = rb->data_end - rb->data_start;
471 return 0;
472 }
473
474 *p_size = (rb->data_end - rb->data_start);
475
476 return 0;
477}
478
479/** Print debug information about the ring buffer
480 *
481 * @param[in] rb the ring buffer
482 * @param[in] fp the FILE where the messages are printed.
483 */
485{
486 fprintf(fp, "Buffer %p, write_offset %zu, data_start %zu, data_end %zu\n",
487 rb->buffer, rb->write_offset, rb->data_start, rb->data_end);
488}
#define RCSID(id)
Definition build.h:506
talloc_free(hp)
unsigned char uint8_t
#define fr_assert(_expr)
Definition rad_assert.h:37
size_t data_start
start of used portion of the buffer
Definition ring_buffer.c:38
fr_ring_buffer_t * fr_ring_buffer_create(TALLOC_CTX *ctx, size_t size)
Create a ring buffer.
Definition ring_buffer.c:63
uint8_t * fr_ring_buffer_alloc(fr_ring_buffer_t *rb, size_t size)
Mark data as allocated.
size_t reserved
amount of reserved data at write_offset
Definition ring_buffer.c:42
uint8_t * fr_ring_buffer_reserve(fr_ring_buffer_t *rb, size_t size)
Reserve room in the ring buffer.
size_t size
Size of this ring buffer.
Definition ring_buffer.c:36
int fr_ring_buffer_free(fr_ring_buffer_t *rb, size_t size_to_free)
Mark data as free,.
size_t write_offset
where writes are done
Definition ring_buffer.c:41
bool closed
whether allocations are closed
Definition ring_buffer.c:44
size_t data_end
end of used portion of the buffer
Definition ring_buffer.c:39
int fr_ring_buffer_close(fr_ring_buffer_t *rb)
Close a ring buffer so that no further allocations can take place.
size_t fr_ring_buffer_used(fr_ring_buffer_t *rb)
Get the amount of data used in a ring buffer.
void fr_ring_buffer_debug(FILE *fp, fr_ring_buffer_t *rb)
Print debug information about the ring buffer.
size_t fr_ring_buffer_size(fr_ring_buffer_t *rb)
Get the size of the ring buffer.
int fr_ring_buffer_start(fr_ring_buffer_t *rb, uint8_t **p_start, size_t *p_size)
Get a pointer to the data at the start of the ring buffer.
uint8_t * buffer
actual start of the ring buffer
Definition ring_buffer.c:35
#define fr_strerror_const(_msg)
Definition strerror.h:223