Tech

How eBPF Runs Your Code Inside the Linux Kernel

eBPF lets you run safe, user-written programs inside the Linux kernel. This article walks through the full journey: writing, verification, JIT compilation, attaching to events, and sharing data with user space via maps.

August 2026 8 min read 12 views 0 hearts

The Secret Life of eBPF: How Your Code Runs in the Linux Kernel

Have you ever wondered how network engineers can install firewall rules that inspect every packet, or how security teams can trace system calls without slowing down the server? The magic behind these capabilities is eBPF (extended Berkeley Packet Filter), and it's doing something that sounds almost impossible: running user-written programs safely inside the Linux kernel.

Let me walk you through what actually happens when an eBPF program goes from your terminal into the heart of the operating system.

The Old Way Was Painful

Before eBPF, if you wanted to observe or modify kernel behavior, you had to either: - Write a kernel module (one crash and your whole system goes down) - Use outdated tracing tools that were slow and limited - Accept that you couldn't dynamically change kernel behavior without a reboot

The Linux community needed something better. They needed a way to inject custom logic into the kernel without risking system stability. That's where eBPF enters the picture.

Step 1: Writing Your eBPF Program

When you write an eBPF program, you're not writing regular code that runs in user space. You're writing a special type of program that will execute in kernel space. Here's what a simple eBPF program looks like (don't worry if the syntax looks strange):

int syscall_execve(struct bpf_syscall_ctx *ctx) {
    pid_t pid = bpf_get_current_pid_tgid() >> 32;
    char comm[16];
    bpf_get_current_comm(comm, sizeof(comm));

    bpf_printk("PID %d executed: %s", pid, comm);
    return 0;
}

This program attaches to the execve system call and logs whenever a new program starts running. Simple, right? But the interesting part is what happens next.

Step 2: The Verifier - Your Code's Gatekeeper

This is where most people get surprised. Before your eBPF program can run, it must pass through the BPF verifier. Think of this as an extremely strict code reviewer that runs automatically.

The verifier does something remarkable: 1. It simulates every possible execution path through your program 2. It checks that your loops are bounded (must exit within a known number of iterations) 3. It verifies you only access valid memory 4. It confirms you're not introducing infinite loops or kernel crashes

If your program has even one potential issue, the verifier rejects it. No warnings, no second chances. Your program just won't load. This strictness is what makes eBPP so safe compared to kernel modules.

Step 3: Just-In-Time Compilation

Once your program passes the verifier, the kernel doesn't just run it as-is. Instead, it uses a Just-In-Time (JIT) compiler to translate your eBPF bytecode into native machine code for your specific CPU architecture.

Whether you're running on x86, ARM, or RISC-V, the JIT compiler generates optimized native instructions. This is why eBPF programs run almost as fast as natively compiled kernel code.

Step 4: Attaching to the Kernel

Your compiled eBPF program can attach to various kernel events: - Network packets arriving at any network interface - System calls being made by any process - Kernel functions being entered or exited - Performance monitoring counters triggering

The attachment is dynamic - you can load and unload programs without restarting anything. When you run tc filter add dev eth0 bpf da obj myprogram.o sec classifier, you're telling the kernel: "Take this eBPF program and run it for every packet on eth0."

Step 5: Data Sharing via Maps

Here's where eBPF really shines. Your kernel code can't print to the console or write to files (security restrictions). Instead, it communicates with user-space programs through BPF maps.

These are key-value data structures shared between kernel space and user space. Your eBPF program writes data into a map, and a user-space process reads it out. It's like a tunnel between two worlds.

A common pattern at PythonSkillset.com involves: - eBPF program collects packet counts or latency measurements - User-space Go or Python program reads these maps every second - Data gets formatted for Prometheus or another monitoring system

Real-World Example: The PythonSkillset Web Server

Let me give you a concrete scenario we've been working with. One of our web servers was experiencing mysterious latency spikes. Instead of guessing, we wrote a small eBPF program that:

  1. Attached to the accept() syscall to track new connections
  2. Used a map to store timestamps per connection
  3. Attached to close() syscall to calculate connection duration

The program collected millions of data points with zero perceptible overhead. Without eBPF, we would have needed to modify the web server application or use sampling-based tools that missed the infrequent spikes.

Why This Matters

The eBPF approach fundamentally changes what's possible in system programming. For the first time, we have: - Safety: Your program can't crash the kernel - Performance: Near-native execution speed - Dynamic loading: Load and unload without reboots - Observability: See everything happening in the kernel

Getting Started

If you want to try eBPF yourself, start with BCC (BPF Compiler Collection). The learning curve is real - you need to understand kernel internals, but the payoff is enormous. Write a simple program that counts system calls per process and watch your understanding of Linux deepen.

The next time someone mentions eBPF, you'll know the journey your code takes: from user-space source, through the strict verifier, into JIT-compiled machine code, running safely in the kernel, and reporting back through maps. It's one of the most elegant solutions Linux has ever developed.

Comments

Questions, corrections, and tips stay visible for everyone reading this page.

0 in thread

Join the discussion

Shown next to your comment.

Up to 4,000 characters

No comments yet

Be the first to leave a note — it helps the next reader.