Skip to content

Commit 4d31f1d

Browse files
author
Michael Agun
committed
Implement libbpf ringbuf synchronous API.
1 parent bc31c8b commit 4d31f1d

File tree

10 files changed

+1317
-95
lines changed

10 files changed

+1317
-95
lines changed

docs/RingBuffer.md

Lines changed: 114 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,15 @@ This proposal adds support for synchronous callbacks (like libbpf on linux) and
1818

1919
Asynchronous callback consumer:
2020

21-
1. Call `ring_buffer__new` to set up callback with RINGBUF_FLAG_AUTO_CALLBACK specified.
22-
- On Linux synchronous callbacks are always used, so the new AUTO_CALLBACK flags are Windows-specific.
23-
- Note: automatic callbacks are the current default behavior, but eventually
24-
this will change with [#4142](https://github.com/microsoft/ebpf-for-windows/issues/4142) to match the linux behavior so should always be specified.
21+
1. Call `ebpf_ring_buffer__new` to set up callback with `EBPF_RINGBUF_FLAG_AUTO_CALLBACK` specified.
22+
- On Linux synchronous callbacks are always used, so the `EBPF_RINGBUF_FLAG_AUTO_CALLBACK` flag is Windows-specific.
23+
- Note: automatic callbacks were the original default behavior, but the default has been changed to be source-compatible with Linux.
2524
2. The callback will be invoked for each record written to the ring buffer.
2625

2726
Synchronous callback consumer:
2827

29-
1. Call `ring_buffer__new` to set up callback with RINGBUF_FLAG_NO_AUTO_CALLBACK specified.
28+
1. Call `ring_buffer__new` to set up callback (uses synchronous mode by default to match Linux).
29+
- Or call `ebpf_ring_buffer__new` without `EBPF_RINGBUF_FLAG_AUTO_CALLBACK` set in flags.
3030
2. Call `ring_buffer__poll()` to wait for data if needed and invoke the callback on all available records.
3131

3232
Mapped memory consumer:
@@ -45,35 +45,28 @@ On linux `ring_buffer__poll()` and `ring_buffer__consume()` are used to invoke t
4545
`poll()` waits for available data (or until timeout), then consume all available records.
4646
`consume()` consumes all available records (without waiting).
4747

48-
Windows will initially only support `ring_buffer__poll()`, which can be called with a timeout of zero
49-
to get the same behaviour as `ring_buffer__consume()`.
48+
Windows now supports both `ring_buffer__poll()` and `ring_buffer__consume()`, with Linux-compatible behavior.
49+
`ring_buffer__consume()` is equivalent to calling `ring_buffer__poll()` with a timeout of zero.
5050

5151
#### Asynchronous callbacks
5252

53-
On Linux ring buffers currently support only synchronous callbacks (using poll/consume).
54-
In contrast, Windows eBPF currently supports only asynchronous ring buffer callbacks,
55-
where the callback is automatically invoked when data is available.
53+
On Linux ring buffers support only synchronous callbacks (using poll/consume).
54+
Windows eBPF now supports both synchronous callbacks (default, matching Linux) and asynchronous ring buffer callbacks.
5655

57-
This proposal adds support for synchronous consumers by setting the `RINGBUF_FLAG_NO_AUTO_CALLBACK` flag.
58-
With the flag set, callbacks will not automatically be called.
59-
To invoke the callback and `ring_buffer__poll()`
60-
should be called to poll for available data and invoke the callback.
61-
On Windows a timeout of zero can be passed to `ring_buffer__poll()` to get the same behaviour as `ring_buffer__consume()` (consume available records without waiting).
62-
63-
When #4142 is resolved the default behaviour will be changed from asynchronous (automatic) to synchronous callbacks,
64-
so `RINGBUF_FLAG_AUTO_CALLBACK` should always be specified for asynchronous callbacks for forward-compatibility.
56+
For synchronous callbacks (Linux-compatible), use the default behavior with `ring_buffer__new()`.
57+
For asynchronous callbacks (Windows-specific), use `ebpf_ring_buffer__new()` with the `EBPF_RINGBUF_FLAG_AUTO_CALLBACK` flag.
6558

6659
#### Memory mapped consumers
6760

6861
As an alternative to callbacks, Linux ring buffer consumers can directly access the
6962
ring buffer data by calling `mmap()` on a ring_buffer map fd to map the data into user space.
7063
`ring_buffer__epoll_fd()` is used on Linux to get an fd to use with epoll to wait for data.
7164

72-
Windows doesn't have directly compatible APIs to Linux mmap and epoll, so instead we will perfom the mapping
65+
Windows doesn't have directly compatible APIs to Linux mmap and epoll, so instead we perform the mapping
7366
in the eBPF core and use a KEVENT to signal for new data.
7467

75-
For direct memory mapped consumers on Windows, use `ebpf_ring_buffer_get_buffer` to get pointers to the producer and consumer
76-
pages mapped into user space, and `ebpf_ring_buffer_get_wait_handle()` to get the SynchronizationEvent (auto-reset) KEVENT
68+
For direct memory mapped consumers on Windows, use `ebpf_ring_buffer_map_map_buffer` to get pointers to the producer and consumer
69+
pages mapped into user space, and `ebpf_map_set_wait_handle()` to set a HANDLE
7770
to use with `WaitForSingleObject`/`WaitForMultipleObject`.
7871

7972
Similar to the linux memory layout, the first pages of the shared ring buffer memory are the "producer page" and "consumer page",
@@ -101,16 +94,20 @@ ebpf_result_t
10194
ebpf_ring_buffer_output(_Inout_ ebpf_ring_buffer_t* ring, _In_reads_bytes_(length) uint8_t* data, size_t length, size_t flags)
10295
```
10396
104-
_Note:_ The currently internal `ebpf_ring_buffer_record.h` with helpers for working with raw records will also be made public.
97+
**Note:** The currently internal `ebpf_ring_buffer_record.h` with helpers for working with raw records will also be made public.
10598
10699
#### Updated libbpf API for callback consumer
107100
108-
The default behaviour of these functions will be unchanged for now.
101+
The default behaviour of these functions has been updated to use synchronous callbacks to match Linux libbpf behavior.
102+
103+
Use `ring_buffer__new()` (defaults to synchronous mode) or `ebpf_ring_buffer__new()` with `EBPF_RINGBUF_FLAG_AUTO_CALLBACK` to set up automatic callbacks for each record.
104+
Use `ring_buffer__new()` (default behavior) or `ebpf_ring_buffer__new()` without `EBPF_RINGBUF_FLAG_AUTO_CALLBACK` to set up synchronous callbacks that are invoked via `ring_buffer__poll()` or `ring_buffer__consume()`.
109105
110-
Use the existing `ring_buffer__new()` to set up automatic callbacks for each record.
111-
Call `ebpf_ring_buffer_get_buffer()` ([New eBPF APIs](#new-ebpf-apis-for-mapped-memory-consumer))
106+
Call `ebpf_ring_buffer_map_map_buffer()` ([New eBPF APIs](#new-ebpf-apis-for-mapped-memory-consumer))
112107
to get direct access to the mapped ring buffer memory.
113108
109+
For Windows-specific functionality, use the `ebpf_ring_buffer__*` variants which accept `ebpf_ring_buffer_opts` with flags.
110+
114111
```c
115112
struct ring_buffer;
116113
@@ -121,7 +118,26 @@ struct ring_buffer_opts {
121118
};
122119
123120
/**
124-
* @brief Creates a new ring buffer manager.
121+
* @brief Creates a new ring buffer manager (Linux-compatible).
122+
*
123+
* Uses synchronous callbacks by default (matching Linux libbpf behavior).
124+
* Only one consumer can be attached at a time, so it should not be called multiple times on an fd.
125+
*
126+
* If the return value is NULL the error will be returned in errno.
127+
*
128+
* @param[in] map_fd File descriptor to ring buffer map.
129+
* @param[in] sample_cb Pointer to ring buffer notification callback function (if used).
130+
* @param[in] ctx Pointer to sample_cb callback function context.
131+
* @param[in] opts Ring buffer options (currently unused, should be NULL).
132+
*
133+
* @returns Pointer to ring buffer manager.
134+
*/
135+
struct ring_buffer *
136+
ring_buffer__new(int map_fd, ring_buffer_sample_fn sample_cb, _Inout_ void *ctx,
137+
_In_opt_ const struct ring_buffer_opts *opts);
138+
139+
/**
140+
* @brief Creates a new ring buffer manager (Windows-specific with flags).
125141
*
126142
* @note This currently returns NULL because the synchronous API is not implemented yet.
127143
*
@@ -132,13 +148,26 @@ struct ring_buffer_opts {
132148
* @param[in] map_fd File descriptor to ring buffer map.
133149
* @param[in] sample_cb Pointer to ring buffer notification callback function (if used).
134150
* @param[in] ctx Pointer to sample_cb callback function context.
135-
* @param[in] opts Ring buffer options.
151+
* @param[in] opts Ring buffer options with Windows-specific flags.
136152
*
137153
* @returns Pointer to ring buffer manager.
138154
*/
139155
struct ring_buffer *
140-
ring_buffer__new(int map_fd, ring_buffer_sample_fn sample_cb, _Inout_ void *ctx,
141-
_In_ const struct ring_buffer_opts *opts);
156+
ebpf_ring_buffer__new(int map_fd, ring_buffer_sample_fn sample_cb, _Inout_ void *ctx,
157+
_In_opt_ const struct ebpf_ring_buffer_opts *opts);
158+
159+
/**
160+
* @brief Add another ring buffer map to the ring buffer manager.
161+
*
162+
* @param[in] rb Ring buffer manager.
163+
* @param[in] map_fd File descriptor to ring buffer map.
164+
* @param[in] sample_cb Pointer to ring buffer notification callback function.
165+
* @param[in] ctx Pointer to sample_cb callback function context.
166+
*
167+
* @retval 0 Success.
168+
* @retval <0 Error.
169+
*/
170+
int ring_buffer__add(struct ring_buffer *rb, int map_fd, ring_buffer_sample_fn sample_cb, void *ctx);
142171
143172
/**
144173
* @brief poll ringbuf for new data
@@ -147,15 +176,26 @@ ring_buffer__new(int map_fd, ring_buffer_sample_fn sample_cb, _Inout_ void *ctx,
147176
* If timeout_ms is zero, poll will not wait but only invoke the callback on records that are ready.
148177
* If timeout_ms is -1, poll will wait until data is ready (no timeout).
149178
*
150-
* This function is only supported when automatic callbacks are disabled (see RINGBUF_FLAG_NO_AUTO_CALLBACK).
179+
* This function is only supported when automatic callbacks are disabled.
151180
*
152181
* @param[in] rb Pointer to ring buffer manager.
153182
* @param[in] timeout_ms Maximum time to wait for (in milliseconds).
154183
*
155-
* @returns Number of records consumed, INT_MAX, or a negative number on error
184+
* @returns Number of records consumed, or a negative number on error
156185
*/
157186
int ring_buffer__poll(_In_ struct ring_buffer *rb, int timeout_ms);
158187
188+
/**
189+
* @brief consume available records without waiting
190+
*
191+
* Equivalent to ring_buffer__poll() with timeout_ms=0.
192+
*
193+
* @param[in] rb Pointer to ring buffer manager.
194+
*
195+
* @returns Number of records consumed, or a negative number on error
196+
*/
197+
int ring_buffer__consume(_In_ struct ring_buffer *rb);
198+
159199
/**
160200
* @brief Frees a ring buffer manager.
161201
*
@@ -169,18 +209,20 @@ void ring_buffer__free(_Frees_ptr_opt_ struct ring_buffer *rb);
169209
//
170210
171211
/**
172-
* @brief Windows-specific ring buffer options structure.
212+
* @brief Ring buffer options structure.
173213
*
174-
* This structure extends ring_buffer_opts with Windows-specific fields.
214+
* This structure extends ring_buffer_opts with ebpf-for-windows-specific fields.
175215
* The first field(s) must match ring_buffer_opts exactly for compatibility.
176216
*/
177217
struct ebpf_ring_buffer_opts
178218
{
179219
size_t sz; /* Size of this struct, for forward/backward compatibility (must match ring_buffer_opts). */
180-
uint64_t flags; /* Windows-specific ring buffer option flags. */
220+
uint64_t flags; /* Ring buffer option flags. */
181221
};
182222
183-
/* Ring buffer option flags. */
223+
/**
224+
* @brief Ring buffer option flags.
225+
*/
184226
enum ebpf_ring_buffer_flags
185227
{
186228
EBPF_RINGBUF_FLAG_AUTO_CALLBACK = (uint64_t)1 << 0, /* Automatically invoke callback for each record. */
@@ -254,6 +296,7 @@ typedef struct _ebpf_ring_buffer_producer_page
254296
* Multiple calls will return the same pointers, as the ring buffer manager only maps the ring once.
255297
*
256298
* @param[in] rb Pointer to ring buffer manager.
299+
* @param[in] index Index of the map in the ring buffer manager (0-based).
257300
* @param[out] producer_page Pointer to start of read-only mapped producer page.
258301
* @param[out] consumer_page Pointer to start of read-write mapped consumer page.
259302
* @param[out] data Pointer to start of read-only double-mapped data pages.
@@ -264,6 +307,7 @@ typedef struct _ebpf_ring_buffer_producer_page
264307
*/
265308
ebpf_result_t ebpf_ring_buffer_get_buffer(
266309
_In_ struct ring_buffer *rb,
310+
_In_ uint32_t index,
267311
_Out_ ebpf_ring_buffer_consumer_page_t **consumer_page,
268312
_Out_ const ebpf_ring_buffer_producer_page_t **producer_page,
269313
_Outptr_result_buffer_(*data_size) const uint8_t **data,
@@ -434,23 +478,20 @@ For(;;) {
434478
Exit:
435479
```
436480

437-
#### Polling ring buffer consumer (using ringbuf manager)
481+
#### Polling ring buffer consumer (using ringbuf manager, matches Linux code)
438482

439483
```c
440484
// sample callback
441485
int ring_buffer_sample_fn(void *ctx, void *data, size_t size) {
442486
// … business logic to handle record …
487+
return 0;
443488
}
444489

445490
// consumer code
446-
struct ring_buffer_opts opts;
447-
opts.sz = sizeof(opts);
448-
opts.flags = RINGBUF_FLAG_NO_AUTO_CALLBACK; //no automatic callbacks
449-
450491
fd_t map_fd = bpf_obj_get(rb_map_name.c_str());
451492
if (map_fd == ebpf_fd_invalid) return 1;
452493

453-
struct ring_buffer *rb = ring_buffer__new(map_fd, ring_buffer_sample_fn sample_cb, &opts);
494+
struct ring_buffer *rb = ring_buffer__new(map_fd, ring_buffer_sample_fn, nullptr, nullptr);
454495
if (rb == NULL) return 1;
455496

456497
// now loop as long as there isn't an error
@@ -461,10 +502,40 @@ while(ring_buffer__poll(rb, -1) >= 0) {
461502
ring_buffer__free(rb);
462503
```
463504
505+
#### Asynchronous ring buffer consumer (Windows-specific)
464506
465-
### Linux consumer examples (for comparison)
507+
```c
508+
// sample callback - this will be called automatically for each record
509+
int ring_buffer_sample_fn(void *ctx, void *data, size_t size) {
510+
// … business logic to handle record …
511+
return 0;
512+
}
513+
514+
// consumer code
515+
fd_t map_fd = bpf_obj_get(rb_map_name.c_str());
516+
if (map_fd == ebpf_fd_invalid) return 1;
466517
467-
#### Linux direct mmap consumer
518+
// Set up Windows-specific ring buffer options for automatic callbacks
519+
struct ebpf_ring_buffer_opts opts = {};
520+
opts.sz = sizeof(opts);
521+
opts.flags = EBPF_RINGBUF_FLAG_AUTO_CALLBACK; // Enable automatic callbacks
522+
523+
struct ring_buffer *rb = ebpf_ring_buffer__new(map_fd, ring_buffer_sample_fn, nullptr, &opts);
524+
if (rb == NULL) return 1;
525+
526+
// With automatic callbacks, the callback function is invoked immediately
527+
// when each record is written to the ring buffer. No polling is needed.
528+
// The ring buffer manager handles the processing automatically.
529+
530+
// Keep the application running while callbacks are processed
531+
// (actual application logic would determine when to exit)
532+
Sleep(60000); // Sleep for 60 seconds or until application should exit
533+
534+
ring_buffer__free(rb);
535+
```
536+
537+
538+
### Linux direct mmap consumer example (for comparison)
468539

469540
```c
470541
size_t page_size = 4096;
@@ -573,28 +644,6 @@ munmap(producer, mmap_sz);
573644
close(map_fd);
574645
```
575646
576-
#### Linux ring buffer manager consumer
577-
578-
```c
579-
// sample callback
580-
int ring_buffer_sample_fn(void *ctx, void *data, size_t size) {
581-
// … business logic to handle record …
582-
}
583-
584-
fd_t map_fd = bpf_obj_get(rb_map_name.c_str());
585-
if (map_fd == ebpf_fd_invalid) return 1;
586-
587-
struct ring_buffer *rb = ring_buffer__new(map_fd, ring_buffer_sample_fn sample_cb, NULL);
588-
if (rb == NULL) return 1;
589-
590-
// now loop as long as there isn't an error
591-
while(ring_buffer__poll(rb, -1) >= 0) {
592-
// data processed by event callback
593-
}
594-
595-
ring_buffer__free(rb);
596-
```
597-
598647
*Below implementation details of the internal ring buffer data structure are discussed.*
599648
600649
## Internal Ring Buffer

ebpfapi/Source.def

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,8 @@ EXPORTS
143143
ebpf_program_query_info
144144
ebpf_program_synchronize
145145
ebpf_ring_buffer__new
146+
ebpf_ring_buffer_get_buffer
147+
ebpf_ring_buffer_get_wait_handle
146148
ebpf_ring_buffer_map_map_buffer
147149
ebpf_ring_buffer_map_unmap_buffer
148150
ebpf_ring_buffer_map_write
@@ -161,5 +163,8 @@ EXPORTS
161163
libbpf_strerror
162164
perf_buffer__free
163165
perf_buffer__new
166+
ring_buffer__add
167+
ring_buffer__consume
164168
ring_buffer__free
165169
ring_buffer__new
170+
ring_buffer__poll

0 commit comments

Comments
 (0)