← Documents Documentation/bpf/ringbuf.rst GitHub 원문 ↗

Linux 6.18.37 · BPF

BPF ring buffer

CPU 간 메모리를 공유하면서 이벤트 순서를 보존하는 MPSC BPF 링 버퍼의 맵 의미, 예약·제출 API, 레코드 헤더와 알림 최적화 설계를 설명합니다.

Source pathDocumentation/bpf/ringbuf.rst
Source versionLinux v6.18.37
TranslationDUJINLABS 전문 번역 + 해설

요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.

1. 요약·해설

원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.

요약과 해설

ringbuf.rst:1-206

`BPF_MAP_TYPE_RINGBUF`는 모든 CPU가 공유할 수 있는 MPSC 링 버퍼로, per-CPU perf buffer의 메모리 중복을 줄이고 CPU를 넘나드는 이벤트의 시간 순서를 보존합니다.

고정 크기 레코드는 `reserve` 후 링 버퍼 메모리에 직접 작성하여 `commit` 또는 `discard`하고, 동적 크기 레코드는 추가 복사를 감수하고 `output`으로 제출합니다. verifier는 예약 수명을 추적하여 미제출 레코드와 범위 밖 접근을 막습니다.

예약은 spinlock으로 순서를 정하지만 commit은 lockless입니다. 이중 가상 메모리 매핑으로 원형 버퍼 경계 처리를 없애고, 소비자 진행 상태에 따른 자율 알림으로 불필요한 wakeup을 줄입니다.

2. 영어 원문 전체

번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.

원문 전체 펼치기
1 ===============
2 BPF ring buffer
3 ===============
4
5 This document describes BPF ring buffer design, API, and implementation details.
6
7 .. contents::
8 :local:
9 :depth: 2
10
11 Motivation
12 ----------
13
14 There are two distinctive motivators for this work, which are not satisfied by
15 existing perf buffer, which prompted creation of a new ring buffer
16 implementation.
17
18 - more efficient memory utilization by sharing ring buffer across CPUs;
19 - preserving ordering of events that happen sequentially in time, even across
20 multiple CPUs (e.g., fork/exec/exit events for a task).
21
22 These two problems are independent, but perf buffer fails to satisfy both.
23 Both are a result of a choice to have per-CPU perf ring buffer. Both can be
24 also solved by having an MPSC implementation of ring buffer. The ordering
25 problem could technically be solved for perf buffer with some in-kernel
26 counting, but given the first one requires an MPSC buffer, the same solution
27 would solve the second problem automatically.
28
29 Semantics and APIs
30 ------------------
31
32 Single ring buffer is presented to BPF programs as an instance of BPF map of
33 type ``BPF_MAP_TYPE_RINGBUF``. Two other alternatives considered, but
34 ultimately rejected.
35
36 One way would be to, similar to ``BPF_MAP_TYPE_PERF_EVENT_ARRAY``, make
37 ``BPF_MAP_TYPE_RINGBUF`` could represent an array of ring buffers, but not
38 enforce "same CPU only" rule. This would be more familiar interface compatible
39 with existing perf buffer use in BPF, but would fail if application needed more
40 advanced logic to lookup ring buffer by arbitrary key.
41 ``BPF_MAP_TYPE_HASH_OF_MAPS`` addresses this with current approach.
42 Additionally, given the performance of BPF ringbuf, many use cases would just
43 opt into a simple single ring buffer shared among all CPUs, for which current
44 approach would be an overkill.
45
46 Another approach could introduce a new concept, alongside BPF map, to represent
47 generic "container" object, which doesn't necessarily have key/value interface
48 with lookup/update/delete operations. This approach would add a lot of extra
49 infrastructure that has to be built for observability and verifier support. It
50 would also add another concept that BPF developers would have to familiarize
51 themselves with, new syntax in libbpf, etc. But then would really provide no
52 additional benefits over the approach of using a map. ``BPF_MAP_TYPE_RINGBUF``
53 doesn't support lookup/update/delete operations, but so doesn't few other map
54 types (e.g., queue and stack; array doesn't support delete, etc).
55
56 The approach chosen has an advantage of re-using existing BPF map
57 infrastructure (introspection APIs in kernel, libbpf support, etc), being
58 familiar concept (no need to teach users a new type of object in BPF program),
59 and utilizing existing tooling (bpftool). For common scenario of using a single
60 ring buffer for all CPUs, it's as simple and straightforward, as would be with
61 a dedicated "container" object. On the other hand, by being a map, it can be
62 combined with ``ARRAY_OF_MAPS`` and ``HASH_OF_MAPS`` map-in-maps to implement
63 a wide variety of topologies, from one ring buffer for each CPU (e.g., as
64 a replacement for perf buffer use cases), to a complicated application
65 hashing/sharding of ring buffers (e.g., having a small pool of ring buffers
66 with hashed task's tgid being a look up key to preserve order, but reduce
67 contention).
68
69 Key and value sizes are enforced to be zero. ``max_entries`` is used to specify
70 the size of ring buffer and has to be a power of 2 value.
71
72 There are a bunch of similarities between perf buffer
73 (``BPF_MAP_TYPE_PERF_EVENT_ARRAY``) and new BPF ring buffer semantics:
74
75 - variable-length records;
76 - if there is no more space left in ring buffer, reservation fails, no
77 blocking;
78 - memory-mappable data area for user-space applications for ease of
79 consumption and high performance;
80 - epoll notifications for new incoming data;
81 - but still the ability to do busy polling for new data to achieve the
82 lowest latency, if necessary.
83
84 BPF ringbuf provides two sets of APIs to BPF programs:
85
86 - ``bpf_ringbuf_output()`` allows to *copy* data from one place to a ring
87 buffer, similarly to ``bpf_perf_event_output()``;
88 - ``bpf_ringbuf_reserve()``/``bpf_ringbuf_commit()``/``bpf_ringbuf_discard()``
89 APIs split the whole process into two steps. First, a fixed amount of space
90 is reserved. If successful, a pointer to a data inside ring buffer data
91 area is returned, which BPF programs can use similarly to a data inside
92 array/hash maps. Once ready, this piece of memory is either committed or
93 discarded. Discard is similar to commit, but makes consumer ignore the
94 record.
95
96 ``bpf_ringbuf_output()`` has disadvantage of incurring extra memory copy,
97 because record has to be prepared in some other place first. But it allows to
98 submit records of the length that's not known to verifier beforehand. It also
99 closely matches ``bpf_perf_event_output()``, so will simplify migration
100 significantly.
101
102 ``bpf_ringbuf_reserve()`` avoids the extra copy of memory by providing a memory
103 pointer directly to ring buffer memory. In a lot of cases records are larger
104 than BPF stack space allows, so many programs have use extra per-CPU array as
105 a temporary heap for preparing sample. bpf_ringbuf_reserve() avoid this needs
106 completely. But in exchange, it only allows a known constant size of memory to
107 be reserved, such that verifier can verify that BPF program can't access memory
108 outside its reserved record space. bpf_ringbuf_output(), while slightly slower
109 due to extra memory copy, covers some use cases that are not suitable for
110 ``bpf_ringbuf_reserve()``.
111
112 The difference between commit and discard is very small. Discard just marks
113 a record as discarded, and such records are supposed to be ignored by consumer
114 code. Discard is useful for some advanced use-cases, such as ensuring
115 all-or-nothing multi-record submission, or emulating temporary
116 ``malloc()``/``free()`` within single BPF program invocation.
117
118 Each reserved record is tracked by verifier through existing
119 reference-tracking logic, similar to socket ref-tracking. It is thus
120 impossible to reserve a record, but forget to submit (or discard) it.
121
122 ``bpf_ringbuf_query()`` helper allows to query various properties of ring
123 buffer. Currently 4 are supported:
124
125 - ``BPF_RB_AVAIL_DATA`` returns amount of unconsumed data in ring buffer;
126 - ``BPF_RB_RING_SIZE`` returns the size of ring buffer;
127 - ``BPF_RB_CONS_POS``/``BPF_RB_PROD_POS`` returns current logical position
128 of consumer/producer, respectively.
129
130 Returned values are momentarily snapshots of ring buffer state and could be
131 off by the time helper returns, so this should be used only for
132 debugging/reporting reasons or for implementing various heuristics, that take
133 into account highly-changeable nature of some of those characteristics.
134
135 One such heuristic might involve more fine-grained control over poll/epoll
136 notifications about new data availability in ring buffer. Together with
137 ``BPF_RB_NO_WAKEUP``/``BPF_RB_FORCE_WAKEUP`` flags for output/commit/discard
138 helpers, it allows BPF program a high degree of control and, e.g., more
139 efficient batched notifications. Default self-balancing strategy, though,
140 should be adequate for most applications and will work reliable and efficiently
141 already.
142
143 Design and Implementation
144 -------------------------
145
146 This reserve/commit schema allows a natural way for multiple producers, either
147 on different CPUs or even on the same CPU/in the same BPF program, to reserve
148 independent records and work with them without blocking other producers. This
149 means that if BPF program was interrupted by another BPF program sharing the
150 same ring buffer, they will both get a record reserved (provided there is
151 enough space left) and can work with it and submit it independently. This
152 applies to NMI context as well, except that due to using a spinlock during
153 reservation, in NMI context, ``bpf_ringbuf_reserve()`` might fail to get
154 a lock, in which case reservation will fail even if ring buffer is not full.
155
156 The ring buffer itself internally is implemented as a power-of-2 sized
157 circular buffer, with two logical and ever-increasing counters (which might
158 wrap around on 32-bit architectures, that's not a problem):
159
160 - consumer counter shows up to which logical position consumer consumed the
161 data;
162 - producer counter denotes amount of data reserved by all producers.
163
164 Each time a record is reserved, producer that "owns" the record will
165 successfully advance producer counter. At that point, data is still not yet
166 ready to be consumed, though. Each record has 8 byte header, which contains the
167 length of reserved record, as well as two extra bits: busy bit to denote that
168 record is still being worked on, and discard bit, which might be set at commit
169 time if record is discarded. In the latter case, consumer is supposed to skip
170 the record and move on to the next one. Record header also encodes record's
171 relative offset from the beginning of ring buffer data area (in pages). This
172 allows ``bpf_ringbuf_commit()``/``bpf_ringbuf_discard()`` to accept only the
173 pointer to the record itself, without requiring also the pointer to ring buffer
174 itself. Ring buffer memory location will be restored from record metadata
175 header. This significantly simplifies verifier, as well as improving API
176 usability.
177
178 Producer counter increments are serialized under spinlock, so there is
179 a strict ordering between reservations. Commits, on the other hand, are
180 completely lockless and independent. All records become available to consumer
181 in the order of reservations, but only after all previous records where
182 already committed. It is thus possible for slow producers to temporarily hold
183 off submitted records, that were reserved later.
184
185 One interesting implementation bit, that significantly simplifies (and thus
186 speeds up as well) implementation of both producers and consumers is how data
187 area is mapped twice contiguously back-to-back in the virtual memory. This
188 allows to not take any special measures for samples that have to wrap around
189 at the end of the circular buffer data area, because the next page after the
190 last data page would be first data page again, and thus the sample will still
191 appear completely contiguous in virtual memory. See comment and a simple ASCII
192 diagram showing this visually in ``bpf_ringbuf_area_alloc()``.
193
194 Another feature that distinguishes BPF ringbuf from perf ring buffer is
195 a self-pacing notifications of new data being availability.
196 ``bpf_ringbuf_commit()`` implementation will send a notification of new record
197 being available after commit only if consumer has already caught up right up to
198 the record being committed. If not, consumer still has to catch up and thus
199 will see new data anyways without needing an extra poll notification.
200 Benchmarks (see tools/testing/selftests/bpf/benchs/bench_ringbufs.c) show that
201 this allows to achieve a very high throughput without having to resort to
202 tricks like "notify only every Nth sample", which are necessary with perf
203 buffer. For extreme cases, when BPF program wants more manual control of
204 notifications, commit/discard/output helpers accept ``BPF_RB_NO_WAKEUP`` and
205 ``BPF_RB_FORCE_WAKEUP`` flags, which give full control over notifications of
206 data availability, but require extra caution and diligence in using this API.
207

3. 한국어 전문 번역

영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.

공유 MPSC 링 버퍼의 동기

1-28

`BPF ring buffer` 문서는 BPF 링 버퍼의 설계, API와 구현 세부 사항을 설명합니다.

.. contents::
    :local:
    :depth: 2

기존 perf buffer가 충족하지 못한 두 가지 요구가 새로운 링 버퍼 구현을 만들게 했습니다.

  • CPU들이 링 버퍼를 공유하여 메모리를 더 효율적으로 사용하는 것
  • 여러 CPU에 걸쳐 있더라도 시간상 연속해서 발생한 이벤트의 순서를 보존하는 것. 예를 들어 한 태스크의 fork/exec/exit 이벤트 순서를 유지해야 합니다.

두 문제는 서로 독립적이지만 per-CPU perf ring buffer라는 선택 때문에 perf buffer는 둘 다 해결하지 못합니다. 이벤트 순서만이라면 커널 안의 별도 계수로 보완할 수도 있지만, 메모리 공유에는 MPSC 구현이 필요하므로 MPSC 링 버퍼 하나가 순서 문제도 함께 해결합니다.

맵 형식과 토폴로지

29-70

BPF 프로그램에는 단일 링 버퍼가 `BPF_MAP_TYPE_RINGBUF` 형식의 BPF 맵 인스턴스로 제공됩니다. 설계 과정에서 다른 두 가지 방식도 검토했지만 채택하지 않았습니다.

첫 번째 방식은 `BPF_MAP_TYPE_PERF_EVENT_ARRAY`처럼 `BPF_MAP_TYPE_RINGBUF`를 링 버퍼 배열로 만들되 같은 CPU만 사용해야 한다는 규칙을 강제하지 않는 것이었습니다. 기존 perf buffer 사용자에게 익숙하지만 임의 키로 링 버퍼를 찾는 고급 로직을 지원하지 못합니다. 현재 방식에서는 `BPF_MAP_TYPE_HASH_OF_MAPS`로 이를 해결합니다. 모든 CPU가 링 버퍼 하나를 공유하는 일반적인 구성에는 배열 방식이 지나치게 복잡하기도 합니다.

두 번째 방식은 조회·갱신·삭제의 키/값 인터페이스가 꼭 필요하지 않은 범용 `container` 객체를 BPF 맵과 별도로 도입하는 것이었습니다. 그러나 관찰성과 verifier 지원을 위한 기반 시설, libbpf 문법과 새로운 개념을 추가하면서도 맵 방식보다 실질적인 이점이 없습니다. `BPF_MAP_TYPE_RINGBUF`가 조회·갱신·삭제를 지원하지 않는 것처럼 queue, stack 등 일부 기존 맵도 모든 연산을 지원하지 않습니다.

채택된 맵 방식은 커널의 introspection API, libbpf 지원과 bpftool 같은 기존 BPF 맵 기반 시설과 도구를 재사용합니다. 단일 공유 링 버퍼는 단순하게 구성할 수 있고, 필요하면 `ARRAY_OF_MAPS`와 `HASH_OF_MAPS` map-in-maps를 조합해 CPU별 링 버퍼부터 태스크 `tgid` 해싱·샤딩까지 다양한 토폴로지를 만들 수 있습니다. 후자는 순서를 유지하면서 경합을 줄이는 작은 링 버퍼 풀에 적합합니다.

키 크기와 값 크기는 반드시 0이어야 합니다. `max_entries`는 링 버퍼 크기를 지정하며 power of 2 값이어야 합니다.

perf buffer와의 공통점 및 제출 API

71-100

새 BPF 링 버퍼와 `BPF_MAP_TYPE_PERF_EVENT_ARRAY` 기반 perf buffer는 다음 의미를 공유합니다.

  • 가변 길이 레코드
  • 공간이 없으면 블로킹하지 않고 예약 실패
  • 사용자 공간이 쉽게 고성능으로 소비할 수 있는 mmap 가능 데이터 영역
  • 새 데이터에 대한 epoll 알림
  • 최저 지연 시간이 필요할 때 새 데이터를 busy polling하는 기능

BPF ringbuf는 BPF 프로그램에 두 종류의 API를 제공합니다.

  • `bpf_ringbuf_output()`은 `bpf_perf_event_output()`과 비슷하게 다른 위치의 데이터를 링 버퍼로 복사합니다.
  • `bpf_ringbuf_reserve()`/`bpf_ringbuf_commit()`/`bpf_ringbuf_discard()`는 과정을 두 단계로 나눕니다. 먼저 고정 크기 공간을 예약해 링 버퍼 데이터 영역의 포인터를 얻고, 데이터를 작성한 뒤 메모리를 commit하거나 discard합니다. discard된 레코드는 소비자가 무시합니다.

`bpf_ringbuf_output()`은 레코드를 다른 곳에서 먼저 준비하므로 메모리 복사가 한 번 더 필요합니다. 대신 verifier가 미리 알 수 없는 길이의 레코드도 제출할 수 있고 `bpf_perf_event_output()`과 매우 비슷하여 마이그레이션이 쉽습니다.

예약, 폐기와 verifier 수명 추적

101-120

`bpf_ringbuf_reserve()`는 링 버퍼 메모리 포인터를 직접 반환해 추가 복사를 피합니다. BPF 스택보다 큰 레코드를 준비하기 위한 임시 per-CPU 배열도 필요하지 않습니다. 그 대신 verifier가 예약 영역 밖 접근이 없음을 검증할 수 있도록 컴파일 시 알려진 상수 크기만 예약할 수 있습니다. 동적 길이가 필요하면 조금 느리더라도 `bpf_ringbuf_output()`을 사용합니다.

commit과 discard의 차이는 작습니다. discard는 레코드를 폐기됨으로 표시하여 소비자가 건너뛰게 합니다. 이는 여러 레코드를 전부 제출하거나 전부 취소하는 처리, 또는 한 BPF 프로그램 호출 안에서 임시 `malloc()`/`free()` 동작을 흉내 내는 고급 용도에 유용합니다.

verifier는 기존 소켓 참조 추적과 비슷한 reference-tracking 로직으로 예약된 각 레코드를 추적합니다. 따라서 레코드를 예약한 뒤 submit 또는 discard하는 일을 잊을 수 없습니다.

상태 조회와 알림 제어

121-142

`bpf_ringbuf_query()` 도우미는 현재 다음 네 종류의 링 버퍼 속성을 조회합니다.

  • `BPF_RB_AVAIL_DATA`: 링 버퍼에서 아직 소비하지 않은 데이터 양
  • `BPF_RB_RING_SIZE`: 링 버퍼 크기
  • `BPF_RB_CONS_POS`/`BPF_RB_PROD_POS`: 소비자와 생산자의 현재 논리 위치

반환값은 순간적인 상태 스냅샷이므로 도우미가 돌아올 때는 이미 달라졌을 수 있습니다. 따라서 디버깅·보고 또는 값이 빠르게 변한다는 점을 고려하는 휴리스틱에만 사용해야 합니다.

한 가지 휴리스틱은 새 데이터에 대한 poll/epoll 알림을 더 세밀하게 제어하는 것입니다. output/commit/discard 도우미의 `BPF_RB_NO_WAKEUP`/`BPF_RB_FORCE_WAKEUP` 플래그를 함께 사용하면 효율적인 배치 알림 등을 구현할 수 있습니다. 다만 기본 self-balancing 전략도 대부분의 응용에서 안정적이고 효율적입니다.

다중 생산자 예약과 레코드 헤더

143-176

reserve/commit 구조에서는 다른 CPU 또는 같은 CPU와 같은 BPF 프로그램의 여러 생산자가 서로를 블로킹하지 않고 독립된 레코드를 예약하고 작성할 수 있습니다. 공유 링 버퍼를 쓰는 BPF 프로그램이 다른 프로그램에 의해 중단되어도 공간만 충분하면 둘 다 레코드를 예약하고 독립적으로 제출합니다. NMI 컨텍스트에도 적용되지만 예약 중 spinlock을 사용하므로 `bpf_ringbuf_reserve()`가 잠금을 얻지 못하면 링 버퍼가 가득 차지 않았어도 예약이 실패할 수 있습니다.

링 버퍼는 내부적으로 power of 2 크기의 원형 버퍼이며, 계속 증가하는 두 논리 카운터를 사용합니다. 32비트 아키텍처에서 wrap-around가 발생해도 문제없습니다.

  • consumer counter는 소비자가 데이터를 소비한 논리 위치까지를 나타냅니다.
  • producer counter는 모든 생산자가 예약한 데이터의 양을 나타냅니다.

레코드를 예약한 생산자는 producer counter를 전진시키지만 그 시점에는 아직 소비할 수 없습니다. 각 레코드에는 예약 길이, 작업 중임을 나타내는 busy bit, commit 시 폐기를 나타내는 discard bit가 들어 있는 8 byte header가 있습니다. 폐기된 레코드는 소비자가 건너뜁니다.

레코드 헤더는 링 버퍼 데이터 영역 시작점으로부터의 상대 페이지 오프셋도 인코딩합니다. 따라서 `bpf_ringbuf_commit()`과 `bpf_ringbuf_discard()`는 링 버퍼 포인터 없이 레코드 자체 포인터만 받으며, 링 버퍼 위치는 메타데이터 헤더에서 복원됩니다. 이 구조는 verifier와 API 사용을 모두 단순화합니다.

예약 순서와 이중 가상 메모리 매핑

177-193

producer counter 증가는 spinlock 아래 직렬화되므로 예약 사이에는 엄격한 순서가 있습니다. 반면 commit은 완전히 lockless하며 서로 독립적입니다. 모든 레코드는 예약 순서대로 소비자에게 공개되지만 앞선 레코드가 모두 commit된 뒤에만 보입니다. 따라서 느린 생산자가 나중에 예약·제출된 레코드를 일시적으로 막을 수 있습니다.

생산자와 소비자 구현을 단순하고 빠르게 만드는 핵심은 데이터 영역을 가상 메모리에 앞뒤로 두 번 연속 매핑하는 것입니다. 원형 버퍼 끝을 넘어가는 샘플도 마지막 데이터 페이지 다음에 첫 데이터 페이지가 다시 나타나므로 가상 메모리에서는 연속된 것처럼 보입니다. 자세한 설명과 ASCII diagram은 `bpf_ringbuf_area_alloc()`의 주석을 참조하십시오.

자율 조절 알림과 수동 플래그

194-206

BPF ringbuf를 perf ring buffer와 구별하는 또 다른 기능은 새 데이터 가용성에 대한 self-pacing 알림입니다. `bpf_ringbuf_commit()`은 소비자가 방금 commit하는 레코드까지 이미 따라잡았을 때만 새 레코드 알림을 보냅니다. 그렇지 않으면 소비자가 기존 데이터를 따라잡는 과정에서 새 데이터도 자연스럽게 보므로 추가 poll 알림이 필요하지 않습니다.

`tools/testing/selftests/bpf/benchs/bench_ringbufs.c`의 벤치마크는 이 방식이 perf buffer에서 필요한 `N번째 샘플마다 알림` 같은 편법 없이도 매우 높은 처리량을 달성함을 보여 줍니다. 극단적인 경우 commit/discard/output 도우미의 `BPF_RB_NO_WAKEUP`과 `BPF_RB_FORCE_WAKEUP`으로 알림을 완전히 제어할 수 있지만, API를 매우 신중하게 사용해야 합니다.