Advanced kernel surface
The modern (2024โ2026) BPF feature set: kfuncs, BPF tokens, workqueues, struct_ops, dynptr, BPF arenas, iterators, and user ring buffers.
Detaching and pinning: outliving the loader
Open the advanced part with the lifecycle question every earlier chapter dodged: what happens to a program when its loader exits? Learn how BPF objects are reference-counted, pin a program, map, and link to bpffs so they survive the loader, read the pinned map from a separate process, and detach by removing the pin.
Syscall programs: when BPF loads BPF
A program type with no hook: BPF_PROG_TYPE_SYSCALL programs run on demand and can call the bpf() syscall themselves via bpf_sys_bpf. Understand loader programs โ a 'strace of libbpf' replayed in-kernel โ and the light skeletons bpftool builds from them, the path toward libbpf-free and signed program loading.
The user ring buffer: a channel into BPF
Every ring buffer so far carried data kernel-to-user. The user ring buffer runs the other way: user space is the producer and the BPF program is the consumer, draining submitted samples with bpf_user_ringbuf_drain and a callback. Learn when to reach for it over a plain map, and meet the dynptr that each sample arrives as.
Userspace eBPF: the same bytecode, no kernel
eBPF is a portable bytecode ISA, and it doesn't need the kernel. Run an eBPF program in a user-space VM with the Rust rbpf crate โ interpreter and JIT, with a Rust helper the bytecode calls โ needing no root, no kernel, not even the lab VM. Then meet bpftime, the runtime that accelerates uprobes in user space.
kfuncs: calling the kernel, the modern way
The BPF helper list froze, so the kernel grew a better mechanism: kfuncs โ real kernel functions exposed to BPF through BTF, with typed pointer arguments and per-function flags. Understand why helpers stopped scaling, how the KF_ACQUIRE/KF_RELEASE/KF_RET_NULL/KF_TRUSTED_ARGS flags let the verifier reason about kernel objects, and write an Aya program that looks up a task by pid and is forced to release it.
The BPF token: delegating BPF into containers
Loading BPF has always demanded capabilities in the init namespace โ all or nothing for a container. The BPF token (kernel 6.9) breaks that open: a privileged runtime mounts a bpffs with four delegation axes, a trusted unprivileged container derives a token from it, and the kernel checks the token instead of init-namespace capabilities. Learn the capability problem it solves, the delegation model, and where it plugs into Aya.
Timers and workqueues: deferred work in the kernel
A BPF program runs to completion and cannot sleep or loop forever โ so how does it do something later, or periodically, or move expensive work off a hot probe? BPF timers (kernel 5.15) fire a callback in softirq context and can re-arm themselves; BPF workqueues (kernel 6.10) run a sleepable callback in process context. Learn the init/set_callback/start lifecycle, build an in-kernel periodic aggregator, and know when to reach for each.
struct_ops: BPF that implements the kernel
Most BPF observes or filters at a hook the kernel calls. struct_ops inverts that: the kernel defines an interface โ a struct of function pointers โ and BPF supplies the whole implementation, which the kernel then calls into like a built-in module. Understand the vtable model behind Part 6's schedulers, write a minimal TCP congestion-control algorithm in BPF, and see the same mechanism reused across the kernel.
Dynptrs and arenas: flexible memory for BPF
Classic BPF memory is rigid: fixed-size maps, fixed-size stack, bounds the verifier can prove at compile time. Two modern primitives loosen that. A dynptr is a verifier-tracked handle to a variable-length region โ the thing the user ring buffer handed you. A BPF arena is a sparse memory region shared zero-copy with user space, where BPF builds real pointer-based data structures. Learn both, and when each earns its place.
BPF iterators: walking the kernel
Closing the advanced part with one more inversion. Instead of reacting to an event, a BPF iterator is the body of an in-kernel loop: the kernel walks a set โ every task, every socket, every map entry โ and calls your program once per element, which emits through a seq_file you can cat. Learn the iterator program type, the open-coded iterators that put bounded loops anywhere, and why pushing iteration into the kernel beats dragging state to user space.