Skip to content

File buffer.h

FileList > buffer > buffer.h

Go to the source code of this file

High-performance x86-64 Circular Buffer for RF Streaming. More...

  • #include <fcntl.h>
  • #include <stdio.h>
  • #include <sys/mman.h>
  • #include <sys/stat.h>
  • #include <unistd.h>
  • #include "doppler/dp_interrupt.h"
  • #include <stdbool.h>
  • #include <stddef.h>
  • #include <stdint.h>
  • #include "doppler/clib_common.h"
  • #include "doppler/dp_state.h"
  • #include "doppler/jm_perf.h"
  • #include "doppler/util/util_core.h"
  • #include <stdlib.h>
  • #include <string.h>

Public Types

Type Name
enum dp_wait_status_t
Why a ring's wait can or cannot be satisfied right now.

Public Static Functions

Type Name
void * dp__buf_alloc (size_t bytes, void ** handle_out)
Allocates a double-mapped ring-buffer region of bytes .
void * dp__buf_alloc_file (size_t bytes, void ** handle_out, const char * path, int * existed)
As dp__buf_alloc(), but the pages are backed by a FILE.
void dp__buf_free (void * addr, size_t bytes, void * handle)
Releases a double-mapped region created by dp__buf_alloc().
void dp__buf_sync (void * addr, size_t bytes)
Flush a file-backed region to disk.
size_t dp__page_size (void)
Returns the granularity the double-mapped views must align to.

Macros

Type Name
define DECLARE_DP_BUFFER (name, type)
Generates a type-specific circular buffer implementation.
define DECLARE_DP_BUFFER_FRAMES (name, type, elem)
Declares the framed face of the name ring: any chunk in, fixed (and optionally overlapping) frames out.
define DECLARE_DP_BUFFER_VIEW (name, type, elem) /* multi line expression */
Declares the element-typed face of the name ring.
define DP_ALIGN (n) \_\_attribute\_\_ ((aligned (n)))
define DP_ASSERT_2X (tag, elem, type) typedef char dp\_assert\_2x\_##tag[sizeof (elem) == 2 \* sizeof (type) ? 1 : -1]
define DP_ASSERT_PWR2 (n) typedef char dp\_assert\_pwr2\_##n[((n) & ((n) - 1)) == 0 ? 1 : -1]
define DP_BUFFER_UNUSED
define DP_CACHELINE 64
Standard x86-64 cache-line size (64 bytes).
define DP_FRAMER_STATE_MAGIC [**DP\_FOURCC**](dp__state_8h.md#define-dp_fourcc) ('F', 'R', 'M', 'R')
FourCC of a framer snapshot (see dp_<name>_framer_state_bytes).
define DP_FRAMER_STATE_WRITTEN_OFFSET (sample_t, frame_n) /* multi line expression */
Byte offset of the written counter in a framer snapshot.
define DP_SPIN_HINT () ((void)0)

Detailed Description

Virtual Memory Buffers

doppler uses virtual memory mirroring to eliminate the "wrap-around" problem in circular buffers. This allows for zero-copy, branchless access to contiguous blocks of data across the buffer boundary.

Virtual Memory Mirroring

By mapping the same physical memory to two adjacent virtual addresses (A and A + N), we exploit the CPU's MMU to handle circular indexing at the hardware level.

of Two Masking

We use & mask instead of % capacity. On x86-64, bitwise AND is a single-cycle instruction, whereas integer modulo can take 20-80 cycles.

Sharing

The head and tail pointers are separated by 64 bytes to prevent the "Ping-Pong" effect where two CPU cores constantly invalidate each other's cache lines when updating indices.

Optimization

DP_SPIN_HINT() is used in the consumer loop to reduce power consumption and prevent the CPU from mispredicting the "loop end" during high-frequency polling.

Public Types Documentation

enum dp_wait_status_t

Why a ring's wait can or cannot be satisfied right now.

enum dp_wait_status_t {
    DP_WAIT_OK = 0,
    DP_WAIT_PENDING = 1,
    DP_WAIT_TOO_LARGE = 2,
    DP_WAIT_CLOSED = 3,
    DP_WAIT_INTERRUPTED = 4
};

dp_*_wait() and dp_*_peek() return NULL for more than one reason, and the reasons call for different responses: end of stream is normal and a consumer loop catches it, an interrupt means stop, too-large is a caller bug, and "not yet" is no failure at all. dp_*_wait_status() owns the PRECEDENCE between them, so a binding or a consumer asks one question instead of re-deriving the order from three which is what the Python binding did, in three hand-written copies.


Public Static Functions Documentation

function dp__buf_alloc

Allocates a double-mapped ring-buffer region of bytes .

static inline void * dp__buf_alloc (
    size_t bytes,
    void ** handle_out
) 

The returned address addr satisfies: * addr(0..bytes-1) ← first view (writable) * addr(bytes..2*bytes-1) ← second view (same physical pages)

On Windows, a HANDLE to the file-mapping object is written to handle_out and must be passed to dp__buf_free(). On POSIX, handle_out is set to NULL.

Returns:

Base address of the double-mapped region, or NULL on failure.


function dp__buf_alloc_file

As dp__buf_alloc(), but the pages are backed by a FILE.

static inline void * dp__buf_alloc_file (
    size_t bytes,
    void ** handle_out,
    const char * path,
    int * existed
) 

The mirror trick is indifferent to where the fd came from, so a persistent ring is the same double mapping over an open()ed path instead of an anonymous one. Because the mapping is MAP_SHARED, the ring's samples ARE the file's contents: there is no separate write path to disk, no copy, and no way for the two to disagree. The kernel writes the pages back on its own schedule; dp__buf_sync() forces the point.

The file is created if absent and truncated to bytes. An EXISTING file of the right size is mapped as it stands, which is what lets a ring survive the process that filled it: the caller restores the head/tail positions and the samples are simply there.

Parameters:

  • bytes Size of ONE mapping (the mirror unit is 2x this).
  • handle_out Set to NULL on POSIX (as dp__buf_alloc).
  • path File to back the ring with.
  • existed If non-NULL, set to 1 when the file was already the right size (so its contents are the ring's), 0 when it was created or resized.

Returns:

Base address of the double-mapped region, or NULL on failure.

Note:

On Windows the file is a CreateFileMapping over CreateFileA, mirrored by the same dp__win_map_twice() as the anonymous ring, and dp__buf_sync() flushes the view but does not wait for the disk.


function dp__buf_free

Releases a double-mapped region created by dp__buf_alloc().

static inline void dp__buf_free (
    void * addr,
    size_t bytes,
    void * handle
) 

Parameters:

  • addr Base address returned by dp__buf_alloc().
  • bytes Size of ONE mapping (same value passed to dp__buf_alloc).
  • handle Platform handle returned via handle_out (Win32: HANDLE, else NULL).

function dp__buf_sync

Flush a file-backed region to disk.

static inline void dp__buf_sync (
    void * addr,
    size_t bytes
) 

A no-op for an anonymous ring, and harmless there. Call it where a checkpoint is TAKEN: the samples are in the page cache until the kernel decides otherwise, so a blob written without this names a history that a crash can still lose.

Parameters:

  • addr Base address (the first view).
  • bytes Size of ONE mapping.

function dp__page_size

Returns the granularity the double-mapped views must align to.

static inline size_t dp__page_size (
    void
) 

This is the unit the ring-buffer mirror is rounded up to. On POSIX that is the page size. On Windows it is the allocation granularity (64 KiB), which is ≥ dwPageSize: MapViewOfFileEx requires each view's base address to be a multiple of the allocation granularity, so the second (mirror) view at base + bytes is only placeable when bytes is a whole multiple of it. Using dwPageSize (4 KiB) here would let a sub-64-KiB buffer pass the size check and then fail to map.


Macro Definition Documentation

define DECLARE_DP_BUFFER

Generates a type-specific circular buffer implementation.

#define DECLARE_DP_BUFFER (
    name,
    type
) 

Parameters:

  • name Suffix for generated names (e.g., f32, i16).
  • type Underlying primitive type for complex I/Q samples.

define DECLARE_DP_BUFFER_FRAMES

Declares the framed face of the name ring: any chunk in, fixed (and optionally overlapping) frames out.

#define DECLARE_DP_BUFFER_FRAMES (
    name,
    type,
    elem
) 

The ring is a transport; this is what a block processor wants on top of it. A consumer of N-sample frames at hop H used to write the same loop write what fits, peek N, process, consume H and some wrote it against the ring's head, tail and mask. The framer is that loop, once: framer_feed and framer_next are the write and the drain, framer_flush ends a stream, and the snapshot triplet makes the carry resumable. The output is a function of the INPUT STREAM and not of how it was split into chunks.

It is a sibling of DECLARE_DP_BUFFER_VIEW rather than part of DECLARE_DP_BUFFER, so a ring that does not frame (telemetry's) is unchanged. Single-threaded: the framer owns its ring exclusively, on both sides.

Parameters:

  • name Ring instance suffix, as passed to DECLARE_DP_BUFFER.
  • type Stored scalar type.
  • elem Element type spanning exactly two scalars.
DECLARE_DP_BUFFER_FRAMES (f32, float, float _Complex)

int main (void)
{
  dp_f32_t *ring = dp_f32_create (4 * 8);
  dp_f32_framer_t fr;
  if (!ring || dp_f32_framer_init (&fr, ring, 8, 4) != DP_OK)
    return 1;                                  // 8-sample frames, hop 4
  float _Complex x[12] = { 0 };
  size_t used = dp_f32_framer_feed_view (&fr, x, 12, 2);
  int rows = 0;
  while (dp_f32_framer_next_view (&fr))
    rows++;                                    // 12 samples: frames at 0, 4
  dp_f32_destroy (ring);
  return used > 0 && rows == 2 ? 0 : 1;
}

define DECLARE_DP_BUFFER_VIEW

Declares the element-typed face of the name ring.

#define DECLARE_DP_BUFFER_VIEW (
    name,
    type,
    elem
) `/* multi line expression */`

The ring stores SCALARS (type *data, two per complex sample); a caller that thinks in samples the Python binding above all wants one ELEMENT per sample. dp_<name>_wait_view(), _peek_view(), _write_view() and _write_some_view() are the same four calls addressed that way. Each is a cast and nothing else: every count in this header is already in samples, so no length arithmetic is introduced that could disagree with the scalar face, and the view stays zero-copy.

Siblings rather than a change to the scalar face, because the scalar face is what every C consumer addresses. One macro rather than three pairs, because a cast written three times is three places for the element type to drift which is what doppler#1346 was.

It is instantiated by the header that owns the element type (f32_buffer/f32_buffer_core.h and its two siblings), not here: the complex element is spelled through the portability header those include, and this file stays free of it.

Parameters:

  • name Ring instance suffix, as passed to DECLARE_DP_BUFFER.
  • type Stored scalar type (float, int16_t, ...).
  • elem Element type spanning exactly two scalars.
dp_f32_t *ab = dp_f32_create (1024);
float _Complex x[4] = { 1, 2, 3, 4 };
dp_f32_write_some_view (ab, x, 4);              // 4 samples
float _Complex *v = dp_f32_peek_view (ab, 4);   // 4 samples, not 8 floats
dp_f32_consume (ab, 4);
dp_f32_destroy (ab);

define DP_ALIGN

#define DP_ALIGN (
    n
) `__attribute__ ((aligned (n)))`

define DP_ASSERT_2X

#define DP_ASSERT_2X (
    tag,
    elem,
    type
) `typedef char dp_assert_2x_##tag[sizeof (elem) == 2 * sizeof (type) ? 1 : -1]`

define DP_ASSERT_PWR2

#define DP_ASSERT_PWR2 (
    n
) `typedef char dp_assert_pwr2_##n[((n) & ((n) - 1)) == 0 ? 1 : -1]`

define DP_BUFFER_UNUSED

#define DP_BUFFER_UNUSED 

define DP_CACHELINE

Standard x86-64 cache-line size (64 bytes).

#define DP_CACHELINE `64`


define DP_FRAMER_STATE_MAGIC

FourCC of a framer snapshot (see dp_<name>_framer_state_bytes).

#define DP_FRAMER_STATE_MAGIC `DP_FOURCC ('F', 'R', 'M', 'R')`


define DP_FRAMER_STATE_WRITTEN_OFFSET

Byte offset of the written counter in a framer snapshot.

#define DP_FRAMER_STATE_WRITTEN_OFFSET (
    sample_t,
    frame_n
) `/* multi line expression */`

The layout is [hdr][live][sizeof sample][frame_n - 1 samples] then the counters written, frames, hop (see dp_<name>_framer_state_bytes). A test that corrupts a counter on purpose names the offset here rather than restating the arithmetic, so the layout has one declaration.

Parameters:

  • sample_t Scalar sample type (float, double, int16_t).
  • frame_n Samples per frame.

define DP_SPIN_HINT

#define DP_SPIN_HINT (

) `((void)0)`


The documentation for this class was generated from the following file native/inc/doppler/buffer/buffer.h