eBPF and Kernel Modules — What’s the Difference

The core of an operating system is very stable. It supports the entire OS, making changes complicated and time-consuming by design. How can we extend its functionality without risking instability?

The solution lies in Extended Berkeley Packet Filter (eBPF) programs and kernel modules—processes that can be dynamically loaded into and unloaded from the kernel on demand to extend or modify kernel functionality.

What is a Kernel Module?

A kernel module (or loadable kernel mode) is an object file containing code that extends kernel functionality at runtime. It can be loaded when needed and unloaded when no longer required. Device drivers typically operate as kernel modules, enabling the kernel to interact with connected hardware. Without modules, we would need to build monolithic kernels and add new functionality directly into the kernel image. This approach would not only result in larger kernels but also require rebuilding and rebooting the kernel for each new feature.

An example of a kernel module:

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>

MODULE_DESCRIPTION("My kernel module");
MODULE_AUTHOR("Me");
MODULE_LICENSE("GPL");

static int dummy_init(void)
{
        pr_debug("Hi\n");
        return 0;
}

static void dummy_exit(void)
{
        pr_debug("Bye\n");
}

module_init(dummy_init);
module_exit(dummy_exit);

The generated messages will not be displayed on the console but will be saved in a specially reserved memory area, from where they will be extracted by the logging daemon (syslog). To display kernel messages, you can use the dmesg command or inspect the logs:

# cat /var/log/syslog | tail -2
Feb 20 13:57:38 asgard kernel: Hi
Feb 20 13:57:43 asgard kernel: Bye

# sudo dmesg | tail -2
Hi
Bye

With great power comes great responsibility. A single wild pointer can wipe out your file system, and a core dump means a reboot.
Kernel Modules are very general purpose and can be used to write any type of program. There is no hand-holding or guardrails either.

However, eBPF is highly structured and follows strict rules. It offers several specific program types, including:

Each program type has restricted access to specific data and functions, as it's designed for a particular purpose. Before compilation, an eBPF program must pass a verifier that checks its correctness and complexity.

What is eBPF?

eBPF is a technology that enables developers to write efficient, safe, and non-intrusive programs that run directly in the Linux kernel. These programs are written in a restricted C subset and compiled into eBPF bytecode using tools such as LLVM. ****The bytecode consists of a restricted set of instructions that follow the eBPF instruction set architecture and prevent runtime errors.

eBPFs operate as virtual machines (VMs) inside the Linux kernel, working on a low-level instruction set architecture and run eBPF bytecode.

Primary components in an eBPF program:

An example of a kernel module:

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

SEC("xdp")
int  xdp_drop_all(struct xdp_md *ctx)
{
 return XDP_DROP;
}

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

These are the possible actions that XDP can perform:

/* User return codes for XDP prog type.
 * A valid XDP program must return one of these defined values. All other
 * return codes are reserved for future use. Unknown return codes will
 * result in packet drops and a warning via bpf_warn_invalid_xdp_action().
 */
enum xdp_action {
 XDP_ABORTED = 0,
 XDP_DROP,
 XDP_PASS,
 XDP_TX,
 XDP_REDIRECT,
};

Scope and Use-Cases of ePBF and kernel modules

eBPF's scope primarily focuses on security, observability, and networking. An eBPF program can perform various tasks such as overwriting parts of the Linux networking stack, making decisions about network packets before they reach the kernel, and monitoring kernel system calls from other programs.

Kernel modules have a broader scope than eBPF and can perform nearly everything eBPF programs can do and more. However, there are two key domains exclusive to kernel modules: device drivers and filesystems. This limitation exists because these use cases require direct hardware access.

There is, however, one notable exception where eBPF can do something kernel modules cannot. Modern network interface cards (NICs), particularly SmartNIC-based ones, can run XDP that processes packets immediately upon receipt, before they reach the kernel network stack. Even without direct NIC support, XDP programs can run on the CPU while still bypassing the Linux network stack for high-performance processing.

Conclusion

While eBPF is more restrictive than kernel modules, it offers simpler development through high-level libraries. Choose eBPF for networking, security, or observability tasks if your kernel supports it. For other use-cases, kernel modules remain the standard option.

In cloud environments, eBPF enhances application networking, firewall rules, and network performance. It enables faster development of complex applications with greater kernel independence.