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

Linux 6.18.37 · BPF

BPF Iterators

Open-coded BPF iterator와 iterator program의 API 계약, verifier 검증, kernel 등록 및 task·VMA parameter filtering을 설명합니다.

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

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

1. 요약·해설

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

요약과 해설

bpf_iterators.rst:1-589

BPF iterator에는 여러 program type에서 직접 사용하는 constructor·next·destructor kfunc 기반 open-coded iterator와, `seq_file`을 통해 userspace가 읽을 special file을 만드는 BPF iterator program type이 있습니다. 새 기능은 두 API가 iteration logic을 최대한 공유하도록 함께 제공하는 것이 원칙입니다.

open-coded iterator의 state type과 kfunc 이름·signature는 verifier가 엄격히 검사합니다. next kfunc가 결국 `NULL`을 반환한다는 계약과 반복 중 state equivalency를 결합해 verifier는 횟수가 큰 loop도 종료 가능한 안전한 logic으로 판단합니다.

iterator program은 `bpf_link_create()`, `bpf_iter_create()`, `read()` 순으로 trigger할 수 있고 bpffs에 pin할 수도 있습니다. kernel target은 `struct bpf_iter_reg`로 등록하며 task·file·VMA iterator는 attach option의 `pid`와 `tid`로 순회 범위를 줄여 호출량과 resource 사용을 절감할 수 있습니다.

2. 영어 원문 전체

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

원문 전체 펼치기
1 =============
2 BPF Iterators
3 =============
4
5 --------
6 Overview
7 --------
8
9 BPF supports two separate entities collectively known as "BPF iterators": BPF
10 iterator *program type* and *open-coded* BPF iterators. The former is
11 a stand-alone BPF program type which, when attached and activated by user,
12 will be called once for each entity (task_struct, cgroup, etc) that is being
13 iterated. The latter is a set of BPF-side APIs implementing iterator
14 functionality and available across multiple BPF program types. Open-coded
15 iterators provide similar functionality to BPF iterator programs, but gives
16 more flexibility and control to all other BPF program types. BPF iterator
17 programs, on the other hand, can be used to implement anonymous or BPF
18 FS-mounted special files, whose contents are generated by attached BPF iterator
19 program, backed by seq_file functionality. Both are useful depending on
20 specific needs.
21
22 When adding a new BPF iterator program, it is expected that similar
23 functionality will be added as open-coded iterator for maximum flexibility.
24 It's also expected that iteration logic and code will be maximally shared and
25 reused between two iterator API surfaces.
26
27 ------------------------
28 Open-coded BPF Iterators
29 ------------------------
30
31 Open-coded BPF iterators are implemented as tightly-coupled trios of kfuncs
32 (constructor, next element fetch, destructor) and iterator-specific type
33 describing on-the-stack iterator state, which is guaranteed by the BPF
34 verifier to not be tampered with outside of the corresponding
35 constructor/destructor/next APIs.
36
37 Each kind of open-coded BPF iterator has its own associated
38 struct bpf_iter_<type>, where <type> denotes a specific type of iterator.
39 bpf_iter_<type> state needs to live on BPF program stack, so make sure it's
40 small enough to fit on BPF stack. For performance reasons its best to avoid
41 dynamic memory allocation for iterator state and size the state struct big
42 enough to fit everything necessary. But if necessary, dynamic memory
43 allocation is a way to bypass BPF stack limitations. Note, state struct size
44 is part of iterator's user-visible API, so changing it will break backwards
45 compatibility, so be deliberate about designing it.
46
47 All kfuncs (constructor, next, destructor) have to be named consistently as
48 bpf_iter_<type>_{new,next,destroy}(), respectively. <type> represents iterator
49 type, and iterator state should be represented as a matching
50 `struct bpf_iter_<type>` state type. Also, all iter kfuncs should have
51 a pointer to this `struct bpf_iter_<type>` as the very first argument.
52
53 Additionally:
54 - Constructor, i.e., `bpf_iter_<type>_new()`, can have arbitrary extra
55 number of arguments. Return type is not enforced either.
56 - Next method, i.e., `bpf_iter_<type>_next()`, has to return a pointer
57 type and should have exactly one argument: `struct bpf_iter_<type> *`
58 (const/volatile/restrict and typedefs are ignored).
59 - Destructor, i.e., `bpf_iter_<type>_destroy()`, should return void and
60 should have exactly one argument, similar to the next method.
61 - `struct bpf_iter_<type>` size is enforced to be positive and
62 a multiple of 8 bytes (to fit stack slots correctly).
63
64 Such strictness and consistency allows to build generic helpers abstracting
65 important, but boilerplate, details to be able to use open-coded iterators
66 effectively and ergonomically (see libbpf's bpf_for_each() macro). This is
67 enforced at kfunc registration point by the kernel.
68
69 Constructor/next/destructor implementation contract is as follows:
70 - constructor, `bpf_iter_<type>_new()`, always initializes iterator state on
71 the stack. If any of the input arguments are invalid, constructor should
72 make sure to still initialize it such that subsequent next() calls will
73 return NULL. I.e., on error, *return error and construct empty iterator*.
74 Constructor kfunc is marked with KF_ITER_NEW flag.
75
76 - next method, `bpf_iter_<type>_next()`, accepts pointer to iterator state
77 and produces an element. Next method should always return a pointer. The
78 contract between BPF verifier is that next method *guarantees* that it
79 will eventually return NULL when elements are exhausted. Once NULL is
80 returned, subsequent next calls *should keep returning NULL*. Next method
81 is marked with KF_ITER_NEXT (and should also have KF_RET_NULL as
82 NULL-returning kfunc, of course).
83
84 - destructor, `bpf_iter_<type>_destroy()`, is always called once. Even if
85 constructor failed or next returned nothing. Destructor frees up any
86 resources and marks stack space used by `struct bpf_iter_<type>` as usable
87 for something else. Destructor is marked with KF_ITER_DESTROY flag.
88
89 Any open-coded BPF iterator implementation has to implement at least these
90 three methods. It is enforced that for any given type of iterator only
91 applicable constructor/destructor/next are callable. I.e., verifier ensures
92 you can't pass number iterator state into, say, cgroup iterator's next method.
93
94 From a 10,000-feet BPF verification point of view, next methods are the points
95 of forking a verification state, which are conceptually similar to what
96 verifier is doing when validating conditional jumps. Verifier is branching out
97 `call bpf_iter_<type>_next` instruction and simulates two outcomes: NULL
98 (iteration is done) and non-NULL (new element is returned). NULL is simulated
99 first and is supposed to reach exit without looping. After that non-NULL case
100 is validated and it either reaches exit (for trivial examples with no real
101 loop), or reaches another `call bpf_iter_<type>_next` instruction with the
102 state equivalent to already (partially) validated one. State equivalency at
103 that point means we technically are going to be looping forever without
104 "breaking out" out of established "state envelope" (i.e., subsequent
105 iterations don't add any new knowledge or constraints to the verifier state,
106 so running 1, 2, 10, or a million of them doesn't matter). But taking into
107 account the contract stating that iterator next method *has to* return NULL
108 eventually, we can conclude that loop body is safe and will eventually
109 terminate. Given we validated logic outside of the loop (NULL case), and
110 concluded that loop body is safe (though potentially looping many times),
111 verifier can claim safety of the overall program logic.
112
113 ------------------------
114 BPF Iterators Motivation
115 ------------------------
116
117 There are a few existing ways to dump kernel data into user space. The most
118 popular one is the ``/proc`` system. For example, ``cat /proc/net/tcp6`` dumps
119 all tcp6 sockets in the system, and ``cat /proc/net/netlink`` dumps all netlink
120 sockets in the system. However, their output format tends to be fixed, and if
121 users want more information about these sockets, they have to patch the kernel,
122 which often takes time to publish upstream and release. The same is true for popular
123 tools like `ss <https://man7.org/linux/man-pages/man8/ss.8.html>`_ where any
124 additional information needs a kernel patch.
125
126 To solve this problem, the `drgn
127 <https://www.kernel.org/doc/html/latest/bpf/drgn.html>`_ tool is often used to
128 dig out the kernel data with no kernel change. However, the main drawback for
129 drgn is performance, as it cannot do pointer tracing inside the kernel. In
130 addition, drgn cannot validate a pointer value and may read invalid data if the
131 pointer becomes invalid inside the kernel.
132
133 The BPF iterator solves the above problem by providing flexibility on what data
134 (e.g., tasks, bpf_maps, etc.) to collect by calling BPF programs for each kernel
135 data object.
136
137 ----------------------
138 How BPF Iterators Work
139 ----------------------
140
141 A BPF iterator is a type of BPF program that allows users to iterate over
142 specific types of kernel objects. Unlike traditional BPF tracing programs that
143 allow users to define callbacks that are invoked at particular points of
144 execution in the kernel, BPF iterators allow users to define callbacks that
145 should be executed for every entry in a variety of kernel data structures.
146
147 For example, users can define a BPF iterator that iterates over every task on
148 the system and dumps the total amount of CPU runtime currently used by each of
149 them. Another BPF task iterator may instead dump the cgroup information for each
150 task. Such flexibility is the core value of BPF iterators.
151
152 A BPF program is always loaded into the kernel at the behest of a user space
153 process. A user space process loads a BPF program by opening and initializing
154 the program skeleton as required and then invoking a syscall to have the BPF
155 program verified and loaded by the kernel.
156
157 In traditional tracing programs, a program is activated by having user space
158 obtain a ``bpf_link`` to the program with ``bpf_program__attach()``. Once
159 activated, the program callback will be invoked whenever the tracepoint is
160 triggered in the main kernel. For BPF iterator programs, a ``bpf_link`` to the
161 program is obtained using ``bpf_link_create()``, and the program callback is
162 invoked by issuing system calls from user space.
163
164 Next, let us see how you can use the iterators to iterate on kernel objects and
165 read data.
166
167 ------------------------
168 How to Use BPF iterators
169 ------------------------
170
171 BPF selftests are a great resource to illustrate how to use the iterators. In
172 this section, we’ll walk through a BPF selftest which shows how to load and use
173 a BPF iterator program. To begin, we’ll look at `bpf_iter.c
174 <https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git/tree/tools/testing/selftests/bpf/prog_tests/bpf_iter.c>`_,
175 which illustrates how to load and trigger BPF iterators on the user space side.
176 Later, we’ll look at a BPF program that runs in kernel space.
177
178 Loading a BPF iterator in the kernel from user space typically involves the
179 following steps:
180
181 * The BPF program is loaded into the kernel through ``libbpf``. Once the kernel
182 has verified and loaded the program, it returns a file descriptor (fd) to user
183 space.
184 * Obtain a ``link_fd`` to the BPF program by calling the ``bpf_link_create()``
185 specified with the BPF program file descriptor received from the kernel.
186 * Next, obtain a BPF iterator file descriptor (``bpf_iter_fd``) by calling the
187 ``bpf_iter_create()`` specified with the ``bpf_link`` received from Step 2.
188 * Trigger the iteration by calling ``read(bpf_iter_fd)`` until no data is
189 available.
190 * Close the iterator fd using ``close(bpf_iter_fd)``.
191 * If needed to reread the data, get a new ``bpf_iter_fd`` and do the read again.
192
193 The following are a few examples of selftest BPF iterator programs:
194
195 * `bpf_iter_tcp4.c <https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git/tree/tools/testing/selftests/bpf/progs/bpf_iter_tcp4.c>`_
196 * `bpf_iter_task_vmas.c <https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git/tree/tools/testing/selftests/bpf/progs/bpf_iter_task_vmas.c>`_
197 * `bpf_iter_task_file.c <https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git/tree/tools/testing/selftests/bpf/progs/bpf_iter_task_file.c>`_
198
199 Let us look at ``bpf_iter_task_file.c``, which runs in kernel space:
200
201 Here is the definition of ``bpf_iter__task_file`` in `vmlinux.h
202 <https://facebookmicrosites.github.io/bpf/blog/2020/02/19/bpf-portability-and-co-re.html#btf>`_.
203 Any struct name in ``vmlinux.h`` in the format ``bpf_iter__<iter_name>``
204 represents a BPF iterator. The suffix ``<iter_name>`` represents the type of
205 iterator.
206
207 ::
208
209 struct bpf_iter__task_file {
210 union {
211 struct bpf_iter_meta *meta;
212 };
213 union {
214 struct task_struct *task;
215 };
216 u32 fd;
217 union {
218 struct file *file;
219 };
220 };
221
222 In the above code, the field 'meta' contains the metadata, which is the same for
223 all BPF iterator programs. The rest of the fields are specific to different
224 iterators. For example, for task_file iterators, the kernel layer provides the
225 'task', 'fd' and 'file' field values. The 'task' and 'file' are `reference
226 counted
227 <https://facebookmicrosites.github.io/bpf/blog/2018/08/31/object-lifetime.html#file-descriptors-and-reference-counters>`_,
228 so they won't go away when the BPF program runs.
229
230 Here is a snippet from the ``bpf_iter_task_file.c`` file:
231
232 ::
233
234 SEC("iter/task_file")
235 int dump_task_file(struct bpf_iter__task_file *ctx)
236 {
237 struct seq_file *seq = ctx->meta->seq;
238 struct task_struct *task = ctx->task;
239 struct file *file = ctx->file;
240 __u32 fd = ctx->fd;
241
242 if (task == NULL || file == NULL)
243 return 0;
244
245 if (ctx->meta->seq_num == 0) {
246 count = 0;
247 BPF_SEQ_PRINTF(seq, " tgid gid fd file\n");
248 }
249
250 if (tgid == task->tgid && task->tgid != task->pid)
251 count++;
252
253 if (last_tgid != task->tgid) {
254 last_tgid = task->tgid;
255 unique_tgid_count++;
256 }
257
258 BPF_SEQ_PRINTF(seq, "%8d %8d %8d %lx\n", task->tgid, task->pid, fd,
259 (long)file->f_op);
260 return 0;
261 }
262
263 In the above example, the section name ``SEC(iter/task_file)``, indicates that
264 the program is a BPF iterator program to iterate all files from all tasks. The
265 context of the program is ``bpf_iter__task_file`` struct.
266
267 The user space program invokes the BPF iterator program running in the kernel
268 by issuing a ``read()`` syscall. Once invoked, the BPF
269 program can export data to user space using a variety of BPF helper functions.
270 You can use either ``bpf_seq_printf()`` (and BPF_SEQ_PRINTF helper macro) or
271 ``bpf_seq_write()`` function based on whether you need formatted output or just
272 binary data, respectively. For binary-encoded data, the user space applications
273 can process the data from ``bpf_seq_write()`` as needed. For the formatted data,
274 you can use ``cat <path>`` to print the results similar to ``cat
275 /proc/net/netlink`` after pinning the BPF iterator to the bpffs mount. Later,
276 use ``rm -f <path>`` to remove the pinned iterator.
277
278 For example, you can use the following command to create a BPF iterator from the
279 ``bpf_iter_ipv6_route.o`` object file and pin it to the ``/sys/fs/bpf/my_route``
280 path:
281
282 ::
283
284 $ bpftool iter pin ./bpf_iter_ipv6_route.o /sys/fs/bpf/my_route
285
286 And then print out the results using the following command:
287
288 ::
289
290 $ cat /sys/fs/bpf/my_route
291
292
293 -------------------------------------------------------
294 Implement Kernel Support for BPF Iterator Program Types
295 -------------------------------------------------------
296
297 To implement a BPF iterator in the kernel, the developer must make a one-time
298 change to the following key data structure defined in the `bpf.h
299 <https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git/tree/include/linux/bpf.h>`_
300 file.
301
302 ::
303
304 struct bpf_iter_reg {
305 const char *target;
306 bpf_iter_attach_target_t attach_target;
307 bpf_iter_detach_target_t detach_target;
308 bpf_iter_show_fdinfo_t show_fdinfo;
309 bpf_iter_fill_link_info_t fill_link_info;
310 bpf_iter_get_func_proto_t get_func_proto;
311 u32 ctx_arg_info_size;
312 u32 feature;
313 struct bpf_ctx_arg_aux ctx_arg_info[BPF_ITER_CTX_ARG_MAX];
314 const struct bpf_iter_seq_info *seq_info;
315 };
316
317 After filling the data structure fields, call ``bpf_iter_reg_target()`` to
318 register the iterator to the main BPF iterator subsystem.
319
320 The following is the breakdown for each field in struct ``bpf_iter_reg``.
321
322 .. list-table::
323 :widths: 25 50
324 :header-rows: 1
325
326 * - Fields
327 - Description
328 * - target
329 - Specifies the name of the BPF iterator. For example: ``bpf_map``,
330 ``bpf_map_elem``. The name should be different from other ``bpf_iter`` target names in the kernel.
331 * - attach_target and detach_target
332 - Allows for target specific ``link_create`` action since some targets
333 may need special processing. Called during the user space link_create stage.
334 * - show_fdinfo and fill_link_info
335 - Called to fill target specific information when user tries to get link
336 info associated with the iterator.
337 * - get_func_proto
338 - Permits a BPF iterator to access BPF helpers specific to the iterator.
339 * - ctx_arg_info_size and ctx_arg_info
340 - Specifies the verifier states for BPF program arguments associated with
341 the bpf iterator.
342 * - feature
343 - Specifies certain action requests in the kernel BPF iterator
344 infrastructure. Currently, only BPF_ITER_RESCHED is supported. This means
345 that the kernel function cond_resched() is called to avoid other kernel
346 subsystem (e.g., rcu) misbehaving.
347 * - seq_info
348 - Specifies the set of seq operations for the BPF iterator and helpers to
349 initialize/free the private data for the corresponding ``seq_file``.
350
351 `Click here
352 <https://lore.kernel.org/bpf/20210212183107.50963-2-songliubraving@fb.com/>`_
353 to see an implementation of the ``task_vma`` BPF iterator in the kernel.
354
355 ---------------------------------
356 Parameterizing BPF Task Iterators
357 ---------------------------------
358
359 By default, BPF iterators walk through all the objects of the specified types
360 (processes, cgroups, maps, etc.) across the entire system to read relevant
361 kernel data. But often, there are cases where we only care about a much smaller
362 subset of iterable kernel objects, such as only iterating tasks within a
363 specific process. Therefore, BPF iterator programs support filtering out objects
364 from iteration by allowing user space to configure the iterator program when it
365 is attached.
366
367 --------------------------
368 BPF Task Iterator Program
369 --------------------------
370
371 The following code is a BPF iterator program to print files and task information
372 through the ``seq_file`` of the iterator. It is a standard BPF iterator program
373 that visits every file of an iterator. We will use this BPF program in our
374 example later.
375
376 ::
377
378 #include <vmlinux.h>
379 #include <bpf/bpf_helpers.h>
380
381 char _license[] SEC("license") = "GPL";
382
383 SEC("iter/task_file")
384 int dump_task_file(struct bpf_iter__task_file *ctx)
385 {
386 struct seq_file *seq = ctx->meta->seq;
387 struct task_struct *task = ctx->task;
388 struct file *file = ctx->file;
389 __u32 fd = ctx->fd;
390 if (task == NULL || file == NULL)
391 return 0;
392 if (ctx->meta->seq_num == 0) {
393 BPF_SEQ_PRINTF(seq, " tgid pid fd file\n");
394 }
395 BPF_SEQ_PRINTF(seq, "%8d %8d %8d %lx\n", task->tgid, task->pid, fd,
396 (long)file->f_op);
397 return 0;
398 }
399
400 ----------------------------------------
401 Creating a File Iterator with Parameters
402 ----------------------------------------
403
404 Now, let us look at how to create an iterator that includes only files of a
405 process.
406
407 First, fill the ``bpf_iter_attach_opts`` struct as shown below:
408
409 ::
410
411 LIBBPF_OPTS(bpf_iter_attach_opts, opts);
412 union bpf_iter_link_info linfo;
413 memset(&linfo, 0, sizeof(linfo));
414 linfo.task.pid = getpid();
415 opts.link_info = &linfo;
416 opts.link_info_len = sizeof(linfo);
417
418 ``linfo.task.pid``, if it is non-zero, directs the kernel to create an iterator
419 that only includes opened files for the process with the specified ``pid``. In
420 this example, we will only be iterating files for our process. If
421 ``linfo.task.pid`` is zero, the iterator will visit every opened file of every
422 process. Similarly, ``linfo.task.tid`` directs the kernel to create an iterator
423 that visits opened files of a specific thread, not a process. In this example,
424 ``linfo.task.tid`` is different from ``linfo.task.pid`` only if the thread has a
425 separate file descriptor table. In most circumstances, all process threads share
426 a single file descriptor table.
427
428 Now, in the userspace program, pass the pointer of struct to the
429 ``bpf_program__attach_iter()``.
430
431 ::
432
433 link = bpf_program__attach_iter(prog, &opts);
434 iter_fd = bpf_iter_create(bpf_link__fd(link));
435
436 If both *tid* and *pid* are zero, an iterator created from this struct
437 ``bpf_iter_attach_opts`` will include every opened file of every task in the
438 system (in the namespace, actually.) It is the same as passing a NULL as the
439 second argument to ``bpf_program__attach_iter()``.
440
441 The whole program looks like the following code:
442
443 ::
444
445 #include <stdio.h>
446 #include <unistd.h>
447 #include <bpf/bpf.h>
448 #include <bpf/libbpf.h>
449 #include "bpf_iter_task_ex.skel.h"
450
451 static int do_read_opts(struct bpf_program *prog, struct bpf_iter_attach_opts *opts)
452 {
453 struct bpf_link *link;
454 char buf[16] = {};
455 int iter_fd = -1, len;
456 int ret = 0;
457
458 link = bpf_program__attach_iter(prog, opts);
459 if (!link) {
460 fprintf(stderr, "bpf_program__attach_iter() fails\n");
461 return -1;
462 }
463 iter_fd = bpf_iter_create(bpf_link__fd(link));
464 if (iter_fd < 0) {
465 fprintf(stderr, "bpf_iter_create() fails\n");
466 ret = -1;
467 goto free_link;
468 }
469 /* not check contents, but ensure read() ends without error */
470 while ((len = read(iter_fd, buf, sizeof(buf) - 1)) > 0) {
471 buf[len] = 0;
472 printf("%s", buf);
473 }
474 printf("\n");
475 free_link:
476 if (iter_fd >= 0)
477 close(iter_fd);
478 bpf_link__destroy(link);
479 return 0;
480 }
481
482 static void test_task_file(void)
483 {
484 LIBBPF_OPTS(bpf_iter_attach_opts, opts);
485 struct bpf_iter_task_ex *skel;
486 union bpf_iter_link_info linfo;
487 skel = bpf_iter_task_ex__open_and_load();
488 if (skel == NULL)
489 return;
490 memset(&linfo, 0, sizeof(linfo));
491 linfo.task.pid = getpid();
492 opts.link_info = &linfo;
493 opts.link_info_len = sizeof(linfo);
494 printf("PID %d\n", getpid());
495 do_read_opts(skel->progs.dump_task_file, &opts);
496 bpf_iter_task_ex__destroy(skel);
497 }
498
499 int main(int argc, const char * const * argv)
500 {
501 test_task_file();
502 return 0;
503 }
504
505 The following lines are the output of the program.
506 ::
507
508 PID 1859
509
510 tgid pid fd file
511 1859 1859 0 ffffffff82270aa0
512 1859 1859 1 ffffffff82270aa0
513 1859 1859 2 ffffffff82270aa0
514 1859 1859 3 ffffffff82272980
515 1859 1859 4 ffffffff8225e120
516 1859 1859 5 ffffffff82255120
517 1859 1859 6 ffffffff82254f00
518 1859 1859 7 ffffffff82254d80
519 1859 1859 8 ffffffff8225abe0
520
521 ------------------
522 Without Parameters
523 ------------------
524
525 Let us look at how a BPF iterator without parameters skips files of other
526 processes in the system. In this case, the BPF program has to check the pid or
527 the tid of tasks, or it will receive every opened file in the system (in the
528 current *pid* namespace, actually). So, we usually add a global variable in the
529 BPF program to pass a *pid* to the BPF program.
530
531 The BPF program would look like the following block.
532
533 ::
534
535 ......
536 int target_pid = 0;
537
538 SEC("iter/task_file")
539 int dump_task_file(struct bpf_iter__task_file *ctx)
540 {
541 ......
542 if (task->tgid != target_pid) /* Check task->pid instead to check thread IDs */
543 return 0;
544 BPF_SEQ_PRINTF(seq, "%8d %8d %8d %lx\n", task->tgid, task->pid, fd,
545 (long)file->f_op);
546 return 0;
547 }
548
549 The user space program would look like the following block:
550
551 ::
552
553 ......
554 static void test_task_file(void)
555 {
556 ......
557 skel = bpf_iter_task_ex__open_and_load();
558 if (skel == NULL)
559 return;
560 skel->bss->target_pid = getpid(); /* process ID. For thread id, use gettid() */
561 memset(&linfo, 0, sizeof(linfo));
562 linfo.task.pid = getpid();
563 opts.link_info = &linfo;
564 opts.link_info_len = sizeof(linfo);
565 ......
566 }
567
568 ``target_pid`` is a global variable in the BPF program. The user space program
569 should initialize the variable with a process ID to skip opened files of other
570 processes in the BPF program. When you parametrize a BPF iterator, the iterator
571 calls the BPF program fewer times which can save significant resources.
572
573 ---------------------------
574 Parametrizing VMA Iterators
575 ---------------------------
576
577 By default, a BPF VMA iterator includes every VMA in every process. However,
578 you can still specify a process or a thread to include only its VMAs. Unlike
579 files, a thread can not have a separate address space (since Linux 2.6.0-test6).
580 Here, using *tid* makes no difference from using *pid*.
581
582 ----------------------------
583 Parametrizing Task Iterators
584 ----------------------------
585
586 A BPF task iterator with *pid* includes all tasks (threads) of a process. The
587 BPF program receives these tasks one after another. You can specify a BPF task
588 iterator with *tid* parameter to include only the tasks that match the given
589 *tid*.
590

3. 한국어 전문 번역

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

두 가지 BPF iterator

1-25

BPF는 통틀어 "BPF iterator"라고 부르는 서로 다른 두 entity, 즉 BPF iterator program type과 open-coded BPF iterator를 지원합니다.

BPF iterator program type은 독립적인 BPF program type입니다. user가 attach하고 activate하면 순회하는 각 entity, 예를 들어 `task_struct`나 cgroup마다 한 번씩 호출됩니다. 반면 open-coded iterator는 iterator 기능을 구현하는 BPF-side API 집합이며 여러 BPF program type에서 사용할 수 있습니다.

open-coded iterator는 BPF iterator program과 비슷한 기능을 제공하면서 다른 모든 BPF program type에 더 많은 flexibility와 control을 줍니다. BPF iterator program은 `seq_file` 기능을 기반으로, attach된 BPF iterator program이 내용을 생성하는 anonymous 또는 BPF FS-mounted special file을 구현하는 데 사용할 수 있습니다. 구체적인 필요에 따라 둘 다 유용합니다.

새 BPF iterator program을 추가할 때는 최대한 유연하게 쓸 수 있도록 같은 기능의 open-coded iterator도 추가해야 합니다. 두 iterator API surface가 iteration logic과 code를 가능한 한 많이 공유하고 재사용하는 것도 기대됩니다.

Open-coded iterator type과 kfunc 이름

26-67

open-coded BPF iterator는 긴밀히 결합된 kfunc 세 개, 즉 constructor, next element fetch, destructor와 stack 위 iterator state를 나타내는 iterator별 type으로 구현합니다. BPF verifier는 해당 constructor, destructor, next API 밖에서 이 state를 변경할 수 없도록 보장합니다.

각 open-coded iterator 종류에는 대응하는 `struct bpf_iter_<type>`이 있습니다. `<type>`은 구체적인 iterator type을 뜻합니다. `bpf_iter_<type>` state는 BPF program stack에 있어야 하므로 stack에 들어갈 만큼 작아야 합니다.

성능을 위해 iterator state의 dynamic memory allocation을 피하고 필요한 모든 항목이 들어가도록 state struct를 충분히 크게 만드는 편이 좋습니다. 필요하다면 dynamic allocation으로 BPF stack 한계를 우회할 수 있습니다. state struct size는 iterator의 user-visible API 일부이므로 이를 바꾸면 backwards compatibility가 깨집니다. 따라서 설계할 때 신중해야 합니다.

constructor, next, destructor kfunc는 각각 `bpf_iter_<type>_{new,next,destroy}()` 형식으로 일관되게 이름을 붙여야 합니다. iterator state type은 일치하는 `struct bpf_iter_<type>`이어야 하며 모든 iterator kfunc는 이 struct의 pointer를 첫 번째 argument로 받아야 합니다.

추가 규칙은 다음과 같습니다.

  • constructor인 `bpf_iter_<type>_new()`는 임의 개수의 추가 argument를 받을 수 있고 return type도 제한하지 않습니다.
  • next method인 `bpf_iter_<type>_next()`는 pointer type을 return해야 하며 argument는 정확히 하나, `struct bpf_iter_<type> *`여야 합니다. const, volatile, restrict와 typedef는 무시합니다.
  • destructor인 `bpf_iter_<type>_destroy()`는 `void`를 return해야 하며 next method와 마찬가지로 argument가 정확히 하나여야 합니다.
  • `struct bpf_iter_<type>` size는 양수이고 stack slot에 올바르게 맞도록 8 bytes의 배수여야 합니다.

이처럼 엄격하고 일관된 규칙 덕분에 중요한 boilerplate detail을 추상화한 generic helper를 만들어 open-coded iterator를 효과적이고 ergonomic하게 사용할 수 있습니다. 예를 들어 libbpf의 `bpf_for_each()` macro가 있습니다. kernel은 kfunc registration 시점에 이 규칙을 강제합니다.

Constructor·next·destructor 계약

68-93

constructor, next, destructor 구현 계약은 다음과 같습니다.

  • constructor `bpf_iter_<type>_new()`는 stack의 iterator state를 항상 initialize합니다. input argument가 invalid해도 이후 `next()`가 `NULL`을 return하도록 state를 initialize해야 합니다. 즉 error가 나면 error를 return하면서 empty iterator를 construct합니다. constructor kfunc에는 `KF_ITER_NEW` flag를 표시합니다.
  • next method `bpf_iter_<type>_next()`는 iterator state pointer를 받아 element를 생성하고 항상 pointer를 return해야 합니다. BPF verifier와의 계약에 따라 element를 모두 소진하면 결국 반드시 `NULL`을 return해야 하며, 한 번 `NULL`을 return한 뒤의 next call도 계속 `NULL`을 return해야 합니다. next method에는 `KF_ITER_NEXT`를 표시하고, `NULL`을 return하는 kfunc이므로 물론 `KF_RET_NULL`도 표시해야 합니다.
  • destructor `bpf_iter_<type>_destroy()`는 constructor가 실패했거나 next가 아무것도 return하지 않았더라도 항상 한 번 호출됩니다. resource를 free하고 `struct bpf_iter_<type>`이 사용한 stack space를 다른 용도로 쓸 수 있게 표시합니다. destructor에는 `KF_ITER_DESTROY` flag를 표시합니다.

모든 open-coded BPF iterator 구현은 최소한 이 세 method를 구현해야 합니다. 각 iterator type에는 해당 type의 constructor, destructor, next만 호출할 수 있도록 강제됩니다. 예를 들어 verifier는 number iterator state를 cgroup iterator의 next method에 넘기지 못하게 합니다.

Verifier의 반복 종료성 검증

94-111

BPF verification을 높은 수준에서 보면 next method는 conditional jump를 검증할 때처럼 verification state가 갈라지는 지점입니다. verifier는 `call bpf_iter_<type>_next` instruction에서 branch를 만들고 `NULL`, 즉 iteration 종료와 non-`NULL`, 즉 새 element 반환이라는 두 결과를 simulate합니다.

먼저 `NULL` 결과를 simulate하며 loop 없이 exit에 도달해야 합니다. 그 뒤 non-`NULL` case를 검증합니다. 이 case는 실제 loop가 없는 간단한 예제라면 exit에 도달하거나, 이미 일부 검증된 state와 equivalent한 state로 또 다른 `call bpf_iter_<type>_next` instruction에 도달합니다.

그 지점의 state equivalency는 정해진 "state envelope" 밖으로 벗어나지 않은 채 기술적으로 영원히 loop할 수 있음을 뜻합니다. 이후 iteration이 verifier state에 새 knowledge나 constraint를 더하지 않으므로 1회, 2회, 10회, 100만 회 실행해도 차이가 없습니다.

하지만 iterator next method가 결국 `NULL`을 return해야 한다는 계약을 고려하면 loop body가 안전하고 마침내 종료된다고 결론 내릴 수 있습니다. verifier는 loop 바깥 logic인 `NULL` case를 검증했고 loop body도 안전하다고 확인했으므로 전체 program logic의 safety를 보장할 수 있습니다.

Kernel data 추출의 기존 한계

112-135

kernel data를 userspace로 dump하는 기존 방법은 몇 가지이며 가장 널리 쓰이는 것은 `/proc` system입니다. 예를 들어 `cat /proc/net/tcp6`는 system의 모든 tcp6 socket을, `cat /proc/net/netlink`는 모든 netlink socket을 dump합니다.

하지만 output format은 대체로 고정돼 있고 socket에 관한 정보를 더 원하면 kernel을 patch해야 합니다. upstream publish와 release에 시간이 걸릴 수 있습니다. `ss` 같은 도구도 추가 정보에 kernel patch가 필요하다는 점은 같습니다.

이 문제를 해결하려 kernel 변경 없이 data를 파고드는 `drgn` tool을 자주 사용합니다. 그러나 drgn은 kernel 안에서 pointer tracing을 할 수 없어 성능이 낮다는 큰 단점이 있습니다. pointer value를 validate할 수도 없어 pointer가 kernel 안에서 invalid해지면 잘못된 data를 읽을 수 있습니다.

BPF iterator는 각 kernel data object마다 BPF program을 호출해 task, `bpf_map` 등 수집할 data를 유연하게 선택함으로써 이 문제를 해결합니다.

BPF iterator program 동작 방식

136-165

BPF iterator는 특정 type의 kernel object를 user가 순회할 수 있게 하는 BPF program type입니다. 전통적인 BPF tracing program은 kernel execution의 특정 지점에서 callback을 호출하지만, BPF iterator는 여러 kernel data structure의 모든 entry마다 실행할 callback을 정의할 수 있습니다.

예를 들어 system의 모든 task를 순회하면서 각 task가 현재 사용한 총 CPU runtime을 dump할 수 있습니다. 다른 BPF task iterator는 각 task의 cgroup 정보를 dump할 수 있습니다. 이런 flexibility가 BPF iterator의 핵심 가치입니다.

BPF program은 항상 userspace process의 요청으로 kernel에 load됩니다. userspace process는 필요한 program skeleton을 open하고 initialize한 다음 syscall을 호출해 kernel이 BPF program을 verify하고 load하게 합니다.

전통적인 tracing program은 userspace가 `bpf_program__attach()`로 program의 `bpf_link`를 얻어 activate합니다. activate 뒤에는 main kernel에서 tracepoint가 trigger될 때마다 callback이 호출됩니다. BPF iterator program은 `bpf_link_create()`로 `bpf_link`를 얻으며, userspace에서 system call을 실행할 때 callback이 호출됩니다.

이제 iterator로 kernel object를 순회하고 data를 읽는 방법을 살펴봅니다.

Userspace에서 iterator load와 trigger

166-197

BPF selftest는 iterator 사용법을 보여 주는 좋은 자료입니다. 이 절에서는 BPF iterator program을 load하고 사용하는 BPF selftest를 살펴봅니다. userspace에서 BPF iterator를 load하고 trigger하는 방법은 `bpf_iter.c` 예제가 보여 주며, 뒤에서는 kernel space에서 실행하는 BPF program도 봅니다.

userspace에서 BPF iterator를 kernel에 load하는 일반 절차는 다음과 같습니다.

  • BPF program을 `libbpf`를 통해 kernel에 load합니다. kernel이 program을 verify하고 load하면 file descriptor(fd)를 userspace에 반환합니다.
  • kernel이 반환한 BPF program file descriptor를 지정해 `bpf_link_create()`를 호출하고 BPF program의 `link_fd`를 얻습니다.
  • 2단계에서 받은 `bpf_link`를 지정해 `bpf_iter_create()`를 호출하고 BPF iterator file descriptor인 `bpf_iter_fd`를 얻습니다.
  • 더 이상 data가 없을 때까지 `read(bpf_iter_fd)`를 호출해 iteration을 trigger합니다.
  • `close(bpf_iter_fd)`로 iterator fd를 닫습니다.
  • data를 다시 읽어야 하면 새 `bpf_iter_fd`를 얻어 read를 다시 수행합니다.

selftest BPF iterator program 예제는 다음과 같습니다.

  • `bpf_iter_tcp4.c`: https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git/tree/tools/testing/selftests/bpf/progs/bpf_iter_tcp4.c
  • `bpf_iter_task_vmas.c`: https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git/tree/tools/testing/selftests/bpf/progs/bpf_iter_task_vmas.c
  • `bpf_iter_task_file.c`: https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git/tree/tools/testing/selftests/bpf/progs/bpf_iter_task_file.c

task_file iterator context

198-228

kernel space에서 실행하는 `bpf_iter_task_file.c`를 살펴봅니다. 다음은 `vmlinux.h`에 있는 `bpf_iter__task_file` 정의입니다. `vmlinux.h`에서 `bpf_iter__<iter_name>` 형식인 struct name은 모두 BPF iterator를 나타내며 suffix `<iter_name>`은 iterator type입니다.

struct bpf_iter__task_file {
        union {
            struct bpf_iter_meta *meta;
        };
        union {
            struct task_struct *task;
        };
        u32 fd;
        union {
            struct file *file;
        };
};

위 code에서 `meta` field는 모든 BPF iterator program에 공통인 metadata입니다. 나머지 field는 iterator마다 다릅니다. `task_file` iterator의 경우 kernel layer가 `task`, `fd`, `file` 값을 제공합니다.

`task`와 `file`은 reference counted이므로 BPF program이 실행되는 동안 사라지지 않습니다.

task_file program과 output 전달

229-291

다음은 `bpf_iter_task_file.c` file의 일부입니다.

SEC("iter/task_file")
int dump_task_file(struct bpf_iter__task_file *ctx)
{
  struct seq_file *seq = ctx->meta->seq;
  struct task_struct *task = ctx->task;
  struct file *file = ctx->file;
  __u32 fd = ctx->fd;

  if (task == NULL || file == NULL)
    return 0;

  if (ctx->meta->seq_num == 0) {
    count = 0;
    BPF_SEQ_PRINTF(seq, "    tgid      gid       fd      file\n");
  }

  if (tgid == task->tgid && task->tgid != task->pid)
    count++;

  if (last_tgid != task->tgid) {
    last_tgid = task->tgid;
    unique_tgid_count++;
  }

  BPF_SEQ_PRINTF(seq, "%8d %8d %8d %lx\n", task->tgid, task->pid, fd,
          (long)file->f_op);
  return 0;
}

위 예제의 section name `SEC("iter/task_file")`은 모든 task의 모든 file을 순회하는 BPF iterator program임을 나타냅니다. program context는 `bpf_iter__task_file` struct입니다.

userspace program은 `read()` syscall을 실행해 kernel에서 동작하는 BPF iterator program을 호출합니다. 호출된 BPF program은 여러 BPF helper function으로 data를 userspace에 export할 수 있습니다.

formatted output이 필요하면 `bpf_seq_printf()`와 `BPF_SEQ_PRINTF` helper macro를, binary data가 필요하면 `bpf_seq_write()`를 사용할 수 있습니다. binary-encoded data는 userspace application이 필요에 맞게 처리할 수 있습니다.

formatted data는 BPF iterator를 bpffs mount에 pin한 뒤 `cat <path>`로 `/proc/net/netlink`와 비슷하게 출력할 수 있습니다. 나중에 `rm -f <path>`로 pinned iterator를 제거합니다.

다음 command는 `bpf_iter_ipv6_route.o` object file에서 BPF iterator를 만들고 `/sys/fs/bpf/my_route` path에 pin합니다.

$ bpftool iter pin ./bpf_iter_ipv6_route.o  /sys/fs/bpf/my_route

그 뒤 다음 command로 결과를 출력합니다.

$ cat /sys/fs/bpf/my_route

Kernel iterator type 등록

292-353

kernel에 BPF iterator를 구현하려면 developer가 `include/linux/bpf.h`의 핵심 data structure를 한 번 변경해야 합니다.

struct bpf_iter_reg {
          const char *target;
          bpf_iter_attach_target_t attach_target;
          bpf_iter_detach_target_t detach_target;
          bpf_iter_show_fdinfo_t show_fdinfo;
          bpf_iter_fill_link_info_t fill_link_info;
          bpf_iter_get_func_proto_t get_func_proto;
          u32 ctx_arg_info_size;
          u32 feature;
          struct bpf_ctx_arg_aux ctx_arg_info[BPF_ITER_CTX_ARG_MAX];
          const struct bpf_iter_seq_info *seq_info;
};

data structure field를 채운 뒤 `bpf_iter_reg_target()`을 호출해 iterator를 main BPF iterator subsystem에 등록합니다.

`struct bpf_iter_reg`의 각 field는 다음과 같습니다.

Field설명
targetBPF iterator 이름을 지정합니다. 예: `bpf_map`, `bpf_map_elem`. kernel의 다른 `bpf_iter` target name과 달라야 합니다.
attach_target, detach_target일부 target에는 특별한 처리가 필요할 수 있으므로 target-specific `link_create` action을 제공합니다. userspace `link_create` 단계에서 호출됩니다.
show_fdinfo, fill_link_infouser가 iterator 관련 link info를 얻으려 할 때 target-specific 정보를 채우도록 호출됩니다.
get_func_protoBPF iterator가 iterator-specific BPF helper에 access할 수 있게 합니다.
ctx_arg_info_size, ctx_arg_infoBPF iterator와 관련된 BPF program argument의 verifier state를 지정합니다.
featurekernel BPF iterator infrastructure의 특정 action request를 지정합니다. 현재는 `BPF_ITER_RESCHED`만 지원합니다. 다른 kernel subsystem, 예를 들어 RCU가 오동작하지 않도록 kernel function `cond_resched()`를 호출한다는 뜻입니다.
seq_infoBPF iterator의 seq operation 집합과 대응하는 `seq_file`의 private data를 initialize/free하는 helper를 지정합니다.

kernel의 `task_vma` BPF iterator 구현 예는 `https://lore.kernel.org/bpf/20210212183107.50963-2-songliubraving@fb.com/`에서 볼 수 있습니다.

Task iterator filtering 동기

354-365

기본적으로 BPF iterator는 system 전체에서 지정한 type의 모든 object, 즉 process, cgroup, map 등을 순회해 관련 kernel data를 읽습니다.

하지만 특정 process 안의 task만 순회하는 것처럼 전체보다 훨씬 작은 kernel object subset만 필요할 때가 많습니다. BPF iterator program은 attach 시 userspace가 program을 configure하게 해 iteration 대상 object를 filter할 수 있습니다.

BPF task_file iterator program

366-399

다음 BPF iterator program은 iterator의 `seq_file`을 통해 file과 task 정보를 출력합니다. iterator의 모든 file을 방문하는 표준 BPF iterator program이며 뒤의 예제에서 사용합니다.

#include <vmlinux.h>
#include <bpf/bpf_helpers.h>

char _license[] SEC("license") = "GPL";

SEC("iter/task_file")
int dump_task_file(struct bpf_iter__task_file *ctx)
{
      struct seq_file *seq = ctx->meta->seq;
      struct task_struct *task = ctx->task;
      struct file *file = ctx->file;
      __u32 fd = ctx->fd;
      if (task == NULL || file == NULL)
              return 0;
      if (ctx->meta->seq_num == 0) {
              BPF_SEQ_PRINTF(seq, "    tgid      pid       fd      file\n");
      }
      BPF_SEQ_PRINTF(seq, "%8d %8d %8d %lx\n", task->tgid, task->pid, fd,
                      (long)file->f_op);
      return 0;
}

PID·TID parameter로 file 제한

400-440

특정 process의 file만 포함하는 iterator를 만드는 방법을 살펴봅니다. 먼저 다음처럼 `bpf_iter_attach_opts` struct를 채웁니다.

LIBBPF_OPTS(bpf_iter_attach_opts, opts);
union bpf_iter_link_info linfo;
memset(&linfo, 0, sizeof(linfo));
linfo.task.pid = getpid();
opts.link_info = &linfo;
opts.link_info_len = sizeof(linfo);

`linfo.task.pid`가 0이 아니면 kernel은 지정한 `pid` process가 연 file만 포함하는 iterator를 만듭니다. 이 예에서는 자신의 process file만 순회합니다. `linfo.task.pid`가 0이면 모든 process가 연 모든 file을 방문합니다.

마찬가지로 `linfo.task.tid`는 process가 아닌 특정 thread가 연 file을 방문하는 iterator를 만들도록 지시합니다. thread가 별도 file descriptor table을 가질 때만 `linfo.task.tid`와 `linfo.task.pid`의 결과가 다릅니다. 대부분의 경우 process의 모든 thread가 하나의 file descriptor table을 공유합니다.

이제 userspace program에서 struct pointer를 `bpf_program__attach_iter()`에 넘깁니다.

link = bpf_program__attach_iter(prog, &opts);
iter_fd = bpf_iter_create(bpf_link__fd(link));

`tid`와 `pid`가 모두 0이면 이 `bpf_iter_attach_opts` struct로 만든 iterator는 system, 정확히는 해당 namespace의 모든 task가 연 모든 file을 포함합니다. 이는 `bpf_program__attach_iter()`의 두 번째 argument로 `NULL`을 넘긴 것과 같습니다.

Parameter iterator 전체 userspace 예제

441-520

전체 program은 다음과 같습니다.

#include <stdio.h>
#include <unistd.h>
#include <bpf/bpf.h>
#include <bpf/libbpf.h>
#include "bpf_iter_task_ex.skel.h"

static int do_read_opts(struct bpf_program *prog, struct bpf_iter_attach_opts *opts)
{
      struct bpf_link *link;
      char buf[16] = {};
      int iter_fd = -1, len;
      int ret = 0;

      link = bpf_program__attach_iter(prog, opts);
      if (!link) {
              fprintf(stderr, "bpf_program__attach_iter() fails\n");
              return -1;
      }
      iter_fd = bpf_iter_create(bpf_link__fd(link));
      if (iter_fd < 0) {
              fprintf(stderr, "bpf_iter_create() fails\n");
              ret = -1;
              goto free_link;
      }
      /* not check contents, but ensure read() ends without error */
      while ((len = read(iter_fd, buf, sizeof(buf) - 1)) > 0) {
              buf[len] = 0;
              printf("%s", buf);
      }
      printf("\n");
free_link:
      if (iter_fd >= 0)
              close(iter_fd);
      bpf_link__destroy(link);
      return 0;
}

static void test_task_file(void)
{
      LIBBPF_OPTS(bpf_iter_attach_opts, opts);
      struct bpf_iter_task_ex *skel;
      union bpf_iter_link_info linfo;
      skel = bpf_iter_task_ex__open_and_load();
      if (skel == NULL)
              return;
      memset(&linfo, 0, sizeof(linfo));
      linfo.task.pid = getpid();
      opts.link_info = &linfo;
      opts.link_info_len = sizeof(linfo);
      printf("PID %d\n", getpid());
      do_read_opts(skel->progs.dump_task_file, &opts);
      bpf_iter_task_ex__destroy(skel);
}

int main(int argc, const char * const * argv)
{
      test_task_file();
      return 0;
}

program output은 다음과 같습니다.

PID 1859

   tgid      pid       fd      file
   1859     1859        0 ffffffff82270aa0
   1859     1859        1 ffffffff82270aa0
   1859     1859        2 ffffffff82270aa0
   1859     1859        3 ffffffff82272980
   1859     1859        4 ffffffff8225e120
   1859     1859        5 ffffffff82255120
   1859     1859        6 ffffffff82254f00
   1859     1859        7 ffffffff82254d80
   1859     1859        8 ffffffff8225abe0

Parameter 없이 BPF program에서 filtering

521-571

parameter가 없는 BPF iterator가 system의 다른 process file을 건너뛰는 방법을 살펴봅니다. 이 경우 BPF program이 task의 `pid` 또는 `tid`를 직접 검사해야 하며, 그렇지 않으면 현재 `pid` namespace의 모든 열린 file을 받습니다. 보통 BPF program에 global variable을 추가해 `pid`를 전달합니다.

BPF program은 다음과 같은 형태입니다.

......
int target_pid = 0;

SEC("iter/task_file")
int dump_task_file(struct bpf_iter__task_file *ctx)
{
      ......
      if (task->tgid != target_pid) /* Check task->pid instead to check thread IDs */
              return 0;
      BPF_SEQ_PRINTF(seq, "%8d %8d %8d %lx\n", task->tgid, task->pid, fd,
                      (long)file->f_op);
      return 0;
}

userspace program은 다음과 같은 형태입니다.

......
static void test_task_file(void)
{
      ......
      skel = bpf_iter_task_ex__open_and_load();
      if (skel == NULL)
              return;
      skel->bss->target_pid = getpid(); /* process ID.  For thread id, use gettid() */
      memset(&linfo, 0, sizeof(linfo));
      linfo.task.pid = getpid();
      opts.link_info = &linfo;
      opts.link_info_len = sizeof(linfo);
      ......
}

`target_pid`는 BPF program의 global variable입니다. BPF program이 다른 process의 열린 file을 건너뛰도록 userspace program이 이 variable을 process ID로 initialize해야 합니다.

BPF iterator를 parameterize하면 iterator가 BPF program을 호출하는 횟수가 줄어 상당한 resource를 절약할 수 있습니다.

VMA iterator parameter

572-581

기본적으로 BPF VMA iterator는 모든 process의 모든 VMA를 포함합니다. 하지만 특정 process나 thread를 지정해 그 대상의 VMA만 포함할 수 있습니다.

file과 달리 Linux 2.6.0-test6 이후 thread는 별도 address space를 가질 수 없습니다. 따라서 여기서는 `tid`를 사용해도 `pid`를 사용하는 것과 차이가 없습니다.

Task iterator의 pid와 tid

582-589

`pid` parameter가 있는 BPF task iterator는 process의 모든 task, 즉 thread를 포함하며 BPF program은 이 task들을 차례로 받습니다.

`tid` parameter로 BPF task iterator를 지정하면 주어진 `tid`와 일치하는 task만 포함할 수 있습니다.