eBPF 概述

2025-11-13T14:11:02+08:00 | 36分钟阅读 | 更新于 2025-11-13T14:11:02+08:00

@

学习目标

学完本章你应该能够:

  1. 用自己的话讲清 eBPF 全称与来历:从经典 BPF(cBPF,一种报文过滤器)扩展而来,名字里虽叫"packet filter"(包过滤器),实际能力早已远超过滤,它的核心是让 Linux 内核可编程。
  2. 说清为什么需要 eBPF:对比"改内核 → 等社区合并 → 等发行版 → 需求已变化"的传统扩展内核流程,体会"动态加载、无需重启、沙箱化"的可观测性体验。
  3. 分清 用户空间(user space)与内核空间(kernel space),以及 eBPF 事件驱动 + 各种 hook(挂载点):tracepoint / kprobe / uprobe / network / perf_event 各适合做什么。
  4. 走一遍 eBPF 程序的完整生命周期:源码(AST) → LLVM IR → BPF 字节码 → 验证器(verifier) → JIT/解释器 → 运行,并能背下验证器检查的几类硬限制(死循环、非法内存、类型匹配、寄存器与溢出)。
  5. 理解 BTF(BPF Type Format) 与 JIT 即时编译 的作用,能区分验证器的 JIT 路径与解释器路径。
  6. 讲透 BPF Map 为什么是"无状态程序"的通信中枢,能根据访问模式选对类型(hash / array / percpu / LPM trie / ring buffer / LRU / map-in-map / device map / sockmap)。
  7. 讲清 XDP(eXpress Data Path) 在网络包处理流程中的位置(NIC→driver→XDP→tc→netfilter→协议栈→socket),三种挂载模式(native / offload / generic)的取舍,以及数据面加速在 DDoS 防御 / 负载均衡 / Cilium 里的价值。
  8. 说出 eBPF 相关生态项目:内核态(eBPF / XDP 内核技术)与用户态(bpftrace / BCC / gobpf / libbpf),以及 Cilium / Hubble / Falco 等上层项目,思考它们与 AIOps 根因定位的结合点。

前置知识:

  • 一点 Linux 系统知识:进程 / 系统调用 / CPU / 内存 / 文件的基本概念。
  • 一点点网络常识:网卡、以太帧、TCP/IP 协议栈、端口与 IP。
  • 会用 Git、能跑 gcc 或理解"编译成字节码再执行"即可,不要求会写内核模块。

本章你会动手做的事:

  • 用 bpftrace 一行命令,体验"零侵入"监控 do_nanosleep 函数被调用的进程。
  • 对照文中 10 步流水线,在你大脑里把"一段 C / bpftrace 脚本 → 内核可执行程序"完整走一遍。
  • 给我一台多核生产机画一张 BPF Map 选型草图:哪个场景该用 hash、哪个该用 percpu、哪个该用 ring buffer。
eBPF 概述eBPF 是什么技术细节BPF MapXDP生态与 AIOps经典 BPF → 扩展而来让内核可编程·沙箱事件驱动·hook 挂载AST → LLVM IR → 字节码verifier 安全验证BTF·JIT ·解释器hash / array / percpuLPM / ring buffer / LRUmap-in-map / sockmapNIC→driver→XDP→tcnative/offload/genericDDoS·负载均衡·Ciliumbpftrace / BCC / libbpfgobpf / Cilium / HubbleAIOps 内核视角补盲

一、什么是 eBPF:一个"包过滤器"的飞升

我们进入模块十三——eBPF 入门。这一讲从一个名字开始:eBPF。

eBPF 的全称与来历

eBPF 的全称是 extended Berkeley Packet Filter(扩展的伯克利报文过滤器)。

e 代表 extended(扩展的),而它背后的经典 BPF(cBPF / classic BPF)最初其实是 1992 年出自伯克利(Berkeley)的一个包过滤器——顾名思义,它用来在内核里快速匹配网络报文"要不要收、要不要放行"。我们经常在 tcpdump 里用的过滤表达式,底层就是经典 BPF。

flowchart LR
    CB[经典 BPF cBPF
1992 伯克利
包过滤器] -->|扩充指令集
寄存器架构
Map·验证器·全局可编程| EB[eBPF
extended BPF] EB --> V1[让内核可编程] EB --> V2[改变内核行为] EB --> V3[Hook 事件驱动]

名字是历史的影子:eBPF 保留"包过滤"这个老名字,但它的能力早已是一台内核里的虚拟机。就像"手表"现在不只是看时间一样,别被名字误导。

所以从名称看它是包过滤器,从能力看它是可扩展的内核程序执行引擎。核心价值就一句话:

eBPF 让 Linux 内核实现了"可编程化",甚至可以在不改内核源码的前提下改变内核的某些行为。

为什么需要 eBPF:传统扩展内核有多痛

为什么我们需要 eBPF?先看没有它时,你想给内核加一个新能力要走的流程(课程里反复强调这张图):

应用开发者提需求
  → 反馈给维护 Linux 内核的团队
  → 团队评估"这对所有人都好评"吗 → 在社区达成共识,被合并进主线
  → 一年后:最新内核交付该功能
  → 但实际业务用的是公司自己的发行版/旧版本,不是最新内核
  → 再过几年:Linux 发行商终于把能力打包交付
  → 这时开发者:"我们的需求早变了,已经不需要了 😅"

这条链路的问题一目了然:

  • 周期长:从提需求到真正用上,动辄以"年"为单位,跨了多轮评审、社区共识、发行版打包。
  • 代价高:改内核要冒稳定性和安全风险,动一次都是一次大手术。
  • 滞后性强:等你拿到能力,需求早已变化——典型的"等你修好墙,敌人已经换了路"。

eBPF 就是来解这个套的。

flowchart TB
    OLD[传统扩展内核
改源码·等合并·等发行版
以年计·需重启] ==> NEW[eBPF 扩展内核
写 BPF 程序·动态加载
几天搞定·无需重启] NEW --> P1[沙箱 sandbox
不改变内核本身] NEW --> P2[验证器 verifier
检查是否安全/死循环] NEW --> P3[动态机制
内核不必再那么固定]

当开发者需要一个内核还没有的能力时,他的体验变成:

  • 不需要去改内核、不需要给人提需求、不需要重启机器(reboot);
  • 自己写一段 eBPF 程序(几天甚至几小时就写完);
  • 动态加载进内核直接运行,改变内核行为的那段逻辑"即插即用";
  • eBPF 程序运行在**沙箱(sandbox)里,它不改变内核本身;内核还有验证器(verifier)**把关,检查程序会不会死循环、安不安全——所以你可以放心让它进内核跑。

这,就是 eBPF 回报给开发者的"动态内核机制"。

复习提示:类比(运维视角):把内核当成一台生产服务器。传统改内核好比要停机、换主板、reboot 才能升级,还要走采购流程等一年。而 eBPF 简直是给服务器上的"热插拔扩展卡"——插上去就用,拔下来内核原样,还有一道"验证器质检"保证不会把机器捅坏。对只想观测、不想动内核的运维来说,这太友好了。

用户空间(User Space)与内核空间(Kernel Space)

聊 eBPF 前,先复习两个最基础的概念,eBPF 的整个模型都建在这上面。

  • 用户空间(user space):所有应用程序(nginx、你的 Go 服务、MySQL……)运行的地方。
  • 内核空间(kernel space):位于用户空间和物理硬件(CPU / Memory / 磁盘设备等)之间。
flowchart TB
    APP[应用进程
谷歌浏览器·Go 服务] -->|syscall 系统调用| KERN[内核空间 kernel space] KERN --> HW[物理硬件
CPU / Memory / 磁盘]

用户空间的程序无法直接访问底层硬件。它们只能向内核空间发出系统调用(system call,syscall),由内核去处理底层的资源访问,例如:

  • 真正地处理 I/O(读盘 / 写网络)
  • 真正给 CPU 下达任务
  • 真正去访问内存地址里的内容

一句话:应用负责"说,我想要什么",内核负责"碰,底层的东西我来"。 eBPF 程序运行在内核空间,所以它能直接、高效地观测这些系统调用和内核事件。

eBPF 的工作原理:事件驱动 + hook 挂载

eBPF 的运行是**基于事件驱动(event-driven)**的:在特定事件发生时,触发一个内核里预留的 hook(钩子/挂载点),进而调用我们写好的 eBPF 程序去获取数据。

flowchart LR
    EV(EVENT 事件发生
比如某进程 open 一个文件) --> HOOK[Hook 挂载点被触发] HOOK --> P[eBPF 程序执行
记录文件名/时间/进程] P --> MAP[(BPF Map
把结果交给用户态)]

这些 hook 很有意思,内核空间和用户空间都能挂,而且既有**静态(static)的也有动态(dynamic)**的:

类别类型说明
静态 hooktracepoint内核开发者预先定义的事件点,稳定、是"官方路标"
静态 hookuser-space statically-defined tracepoints(UST)用户空间里预埋的静态探针
动态 hookkprobe(kernel probe)挂到内核任意函数入口/出口,最常用的观测手段之一
动态 hookuprobe(user probe)挂到用户空间程序(如某数据库的查询函数)上

除了上述,eBPF 程序还能挂载到很多事件上,课程强调的一类常见挂载点包括:

  • kprobe / uprobe:动态探针,最常用;
  • tracepoint:静态跟踪点;
  • network:处理网络数据包、出入方向的网络事件;
  • perf_event:监听 CPU / 内核的性能事件;
  • 以及 sockmap、xdp、lsm(Linux Security Module) 等更专用的挂载点(后面章节逐步展开)。

几个具体的"挂在哪、干什么"的例子:

  1. 监控文件打开(kprobe/tracepoint 挂到 open/do_sys_open 函数):当某个进程尝试打开文件时,eBPF 程序被触发,记录文件名、打开时间等信息。
  2. 监控数据库查询(uprobe 挂到用户态函数入口):你想监控某个数据库程序查询函数的调用情况,就可用 uprobe 挂到查询函数入口,记录查询语句等信息。市面上很多"无侵入的数据库性能监控工具",能监听 MySQL / PostgreSQL 底层的执行信息,背后就是这个原理。
  3. 跟踪文件读取(tracepoint 挂到 read):当系统执行 read 操作时可追踪文件读取情况,记录读取的数据大小、**进程 ID(PID)**等。
  4. 分析网络数据(network 挂载):例如用 eBPF 程序分析 TCP 数据包,过滤特定端口(比如想监听 80 端口的流量),记录访问的 IP 地址与数据包大小。
  5. 性能分析(perf_event):通过 CPU cycles 事件统计每个进程的 CPU 周期,了解哪个进程在某段时间里占用 CPU 资源最多。

小结要点:eBPF = 事件驱动 的 hook 挂载 + 内核态执行 + 沙箱与验证器,最终把观测数据交回用户空间。这四件事串起来,就是 eBPF 的骨架。


二、eBPF 实现的技术细节:从源码到机器码的一条链

这一讲我们顺着"跟踪与可观测"的高层流程图,把 eBPF 从你写的代码到最终在 CPU 上执行,一整套流水线走一遍。

高层的用户态与内核态分工

eBPF 程序要跑起来,用户态和内核态各司其职:

用户态 user space                    内核态 kernel space
───────────────────                  ──────────────────────
· BPF 程序(源码)   ──字节码──→      · BTF:数据结构类型信息
· 编译成 BPF 字节码                   · verifier:安全验证器
· event 配置(跟踪哪个函数/包/文件)   · BPF 引擎:运行 BPF 程序
· 输出 perf event data / stack       · buffer / map:存储传输数据
                                     · tracing 接口:静态/动态探针

**用户态(左半边)**包含:

  • program:你编写的 eBPF 程序(通常用 C 语言或 bpftrace 脚本编写),最终会被编译成 BPF 字节码(bytecode)。
  • event:用于做特定配置的跟踪事件——比如选择跟踪哪个函数、哪个数据包、哪个文件。
  • 输出:eBPF 程序执行后会输出数据,例如 perf_event data(每个事件的数据,如捕获的网络包信息、系统调用)和 stats / stack(统计信息与调用堆栈,用于性能监测与问题诊断)。

**内核态(右半边)**的几个关键件:

  • BTF(BPF Type Format):提供 eBPF 程序与内核之间数据结构之间的类型信息,帮助内核与 eBPF 程序理解数据结构及成员变量的类型(名字里的 type 就是这么来的)。有了它,eBPF 程序载入时无须在内核/用户态用相同的头文件就能理解对方的结构。
  • verifier(验证器):在真正把 eBPF 程序加载进内核之前检查程序的安全性,确保程序不会导致内核崩溃、不会影响内核安全。
  • BPF 引擎:负责运行用户加载的 BPF 程序,执行相应的跟踪 / 监控任务。
  • buffer 与 map:内核中的数据结构,用于存储以及传输 BPF 程序产生的数据(第三章展开)。
  • tracing 静态跟踪:tracepoint(静态预定义点)、以及其它静态探针;
  • dynamic tracing:kprobe 与 uprobe(前面提过)。
  • sampling / perf_event:用于采样与硬件性能监控(如 CPU 分析与性能瓶颈定位)。

有一个非常关键的点值得单独划重点:BPF 程序运行在内核空间。那么它产生的数据怎么传给用户空间去展示?就靠 Map——这是 BPF 程序把数据结构与用户态共享的桥梁。

你要掌握的 eBPF 技术栈:AST / LLVM / IR / JIT

课程里的例子会反复用到下面这些词,先理解了它们,后面才看得懂:

术语全称 / 含义作用
AST抽象语法树(Abstract Syntax Tree)编译器解析源码时生成的树状数据结构,表示代码结构。几乎所有编译器都有,eBPF 也一样
LLVM开源编译器基础设施把 eBPF 程序转成 AST 后再编译成各种平台的机器码 / 目标码
IR中间表示(Intermediate Representation)源码转成真正目标代码之前的中间格式,LLVM 先把代码转成 IR,再转成 BPF 字节码
JIT及时编译(Just-In-Time)把 eBPF 字节码动态编译成机器码,提高执行效率。Linux 内核支持对 eBPF 做 JIT
flowchart LR
    SRC[BPF 程序源码
C 语言 / bpftrace 脚本] --> AST[AST 抽象语法树] AST --> IR[LLVM IR 中间表示] IR --> BC[BPF 字节码 bytecode] BC --> VER[verifier 验证器] VER --> JIT[JIT 及时编译 → 机器码] JIT --> CPU[(CPU 执行)]

直觉上记住:AST 表示"这段代码的结构",IR 是"改一改能翻成机器码的中间态",字节码是"内核认的格式",JIT 是"临场把它翻译成这台 CPU 的母语"。前三步在用户态用 LLVM/Clang 完成,后两步在内核里完成。

复习提示:一句话记忆:eBPF 程序的"翻译官"是 LLVM/Clang,它把 C/bpftrace 源码先变成 AST、再变 IR、最后编成 BPF 字节码;内核收到字节码后先用 verifier 安检,再用 JIT 翻成本地机器码去跑。这一条链就是技术细节部分的主心骨。

实战例子:用 bpftrace 监控谁在睡懒觉

课程用一个动态追踪 + 事件输出的例子把流程具象化——用 bpftrace 脚本 trace do_nanosleep(内核里纳秒级睡眠的实现函数)。

kprobe:do_nanosleep
{
    printf("PID: %d sleeping\n", pid);
}
  • 挂载点 kprobe:do_nanosleep = 当内核执行 do_nanosleep(或你的程序执行 sleep 操作触到这里)时触发;
  • 程序体里用内置变量 pid 取到当前进程的 PID,并输出一句 “sleeping”。

当你在运行一些含 sleep 程序的机器上跑这段脚本,输出大致长这样:

PID: 1234 sleeping
PID: 1234 sleeping
PID: 5678 sleeping   ← 一个"实时监控器":能看到哪些进程正在调用 sleep 函数

看到这个输出,你就体会到它就像一个监控程序:无需改业务代码,就能观察到"此时此刻谁在内核里睡叫"。

为了让它跑起来,有三个目标要达成

有了上面这段 bpftrace 代码,要让它顺利执行,还有三个关键目标需要实现:

  1. 得到 BPF 字节码(BPF code):上面那行 script 是高级语言,不是给机器看的,必须先翻译成字节码。
  2. 把内核事件映射到字节码(event → bytecode):要清楚地知道"内核里的 do_nanosleep 事件"如何映射到我们这份字节码上。
  3. 把数据从内核传到用户空间并打印(user space print event):把内核中的事件数据(比如 pid)传给用户态程序并打印出来。

十步走:eBPF 程序的完整流水线

为了达成上述三个目标,课程把这条流水线拆成了 10 步。我们逐个拆解(前几步偏工具链细节,知道"发生了什么"即可,不用背每个函数名):

第 1~4 步 · 代码转化(用户态)

  1. 解析(Parse):BPF 程序的解析器把里脚本 / 源码转成 AST 抽象语法树。这一步剜用词法分析把代码逐字符扫描、拆成一个个 token(关键字、操作符、变量名,比如识别出内置变量 pid),再用**语法分析(yacc)**把 token 组织成 AST。
源代码文本
  → 词法分析(lex)逐字扫描 → 拆成 token(内置 pid、关键字…)
  → 语法分析(yacc)→ 组织成 AST 抽象语法树
  1. (例子中无结构成员引用,可跳过 BTFF 结构展开。)
  2. 语义分析(Semantic Analysis):做语法 / 语义检查,并能创建 Map、添加探测点(probe)。举例:假设我代码写错,把 pid 写成 pidD,语义分析器会直接报错"这是一个无法识别的属性 / 对象",把问题挡在编译早期。
  3. 生成 IR(中间代码):LLVM 把 AST 转化成中间表示 IR,这一步的目标产物就是 IR。

第 5~6 步 · 生成字节码

  1. LLVM/Clang 将 IR 经过优化,生成更高效的 BPF 字节码(这一过程取决于目标架构——例如 x86 / ARM64)。

每条 eBPF 指令是一个**64 位(8 字节)**的结构,组成大致如下:

BPF 指令(64 bit / 8 字节)
├── opcode     操作码(做什么:ALU 运算 / 跳转 / 访存…)
├── dst / src  源与目标寄存器
├── offset     偏移量
└── imm        立即数(常量)

这样设计的好处:可以跟现代 CPU 架构对齐,让 JIT 翻译起来更自然。了解即可,不用背。

第 7 步 · 验证器安检(内核态)

6.(上承第 5 步)字节码交给内核的 verifier 验证器做安全验证,确保程序不会影响内核稳定性、不会导致内核崩溃。

第 8 步 · JIT 编译

  1. 通过验证的字节码,会经过 JIT 即时编译转化为机器码(machine code),交给 CPU 执行。

第 9~10 步 · 数据回流(BPF Map + 用户态打印)

  1. eBPF 程序执行后,把追踪到的数据写入 Map(或 perf_event);这些数据随后交回用户空间处理。
  2. bpftrace 等用户态程序从内核接收到数据,通过 print(printf / print event / print perf_event)把它输出出来。
flowchart TD
    S[源码 bpftrace/C] --> A[1-4 解析→AST→语义分析→IR]
    A --> B[5-6 LLVM 生成 BPF 字节码]
    B --> C[7 内核 verifier 验证器安检
死循环/非法内存/类型/寄存器] C --> D[8 JIT 及时编译 → 机器码 → CPU] D --> E[9 追踪数据写入 BPF Map] E --> F[10 用户态程序读取并 printf 输出]

这就是"从你写的代码最终到执行"的完整链路,也是 eBPF 跟踪 / 可观测性得以成立的地基。

验证器到底在查什么:几个硬限制

verifier 是一个超过 9000 行代码、能返回 260+ 种错误的复杂审查机制,它会调用 check_reg、check_map_access、check_mem_access 等非常多方法去检查程序。我们要记住的,是它重点拦下的几类高危行为:

检查点含义通俗解释
死循环程序是否无限循环不能跑成"停不下来"把内核拖死
非法内存访问是否有非法内存访问越界读 / 写都是红线
关键内存写是否在写内核对关键内存不能让它碰内核的命根子
未初始化未初始化的寄存器 / 变量不能读"还没赋值"的数据
参数类型函数参数必须严格匹配指定类型类型不匹配的调用会在验证阶段直接被拒绝
寄存器使用禁止写某类特殊寄存器、检查溢出尤其是写 BPF 帧指针等受限寄存器

两条经常会踩的硬约束要单独记住:

  • 内存访问受限:内存访问受到严格限制,你只能读取已经初始化的内存;如果要访问内核内存,必须通过 bpf_probe_read 这类**辅助函数(helper)**来实现(直接解引用是常见的"verifier 拒绝"现场)。
  • Map 越界写入:使用 BPF Map 时,程序不能写入超出 Map 定义的数据结构。比如 Map 长度定义为 4096,你写超了,验证器过不了。
flowchart LR
    BC[BPF 字节码] -->|JIT enable| J[BPF JIT 及时编译
→ 本机机器指令 快] BC --> U{未启用 JIT} U --> I[内核 BPF 解释器
逐条解释执行 慢] J --> CPU[(CPU)] I --> CPU

执行两条路:eBPF 字节码在内核有两条执行路径——

  1. JIT 路径:开启 JIT 时,字节码编译成本地机器指令,直接运行,效率高;
  2. 解释器路径:没启用 JIT 时,由内核的 BPF解释器逐条解释执行,效率会低一些。

生产环境强烈建议开启 BPF JIT(net.core.bpf_jit_enable=1 等多处内核开关),这也是高吞吐观测/网络场景性能的关键。


三、BPF Map:内核与用户态通信的中枢

前面反复提到——**BPF 程序运行在内核空间,怎么把数据交给用户空间?靠 Map。**这一讲就把 Map 讲透。

Map 是什么:定义、属性与共享

BPF Map 是内核 / 用户空间共享数据的通用结构,用来存储不同种类的数据。它允许在以下对象之间共享数据:

  • eBPF 程序之间共享;
  • 内核与用户空间之间共享(如 bpftrace / BCC 程序与内核里的 BPF 程序)。

每个 Map 都有下面这些属性:

BPF Map 属性
├── type(类型)        这个 Map 是哪种(hash / array / ring buffer…)
├── max_entries(最大长度)能存多少条
├── key_size(key 大小)键的字节数
└── value_size(value 大小)值的字节数

类比:Map 就像一张传话用的白板。你在白板上写(内核程序往 Map 写数据),用户态程序读白板(把数据拿走做展示 / 分析)。白板定义了格子大小(key/value size)和能贴多少张(max_entries)。

常见 Map 类型逐个过一遍

1. hash map(哈希表)

  • 类似传统哈希表,支持高效的查找和更新,是最常用的一种 Map。
  • 场景:记录某个系统方法被调用的次数。键可以是 pid,值可以是调用次数。
  • 注意:声明时设了最大数量(比如 128)后,超过上限不会扩容,而是直接写入失败——这是它跟传统哈希表不一样、也是最容易踩的点。

2. array(数组)

  • 固定大小,通过索引快速读取,适合常规计数器 / 缓存。
  • 场景:存储一组 CPU 核心的负载信息——0 代表第一个 CPU、1 代表第二个,用索引访问更新每个 CPU 当前的负载。

3. percpu hash / array(每 CPU)

  • 比普通 hash / array 多了 percpu(每 CPU)维度。
  • 每个 CPU 拥有独立的数据副本,不会造成竞争冲突,在多核系统上表现更好。
  • 场景:高并发场景下统计每个 CPU 的独立数据(比如每个 CPU 处理的网络数据包数)。
flowchart TB
    subgraph HASH[普通 hash / array
所有 CPU 共用一个桶 → 有锁竞争] C0[CPU0] --> S[共享数据] C1[CPU1] --> S C2[CPU2] --> S end subgraph PERCPU[percpu hash / array
每 CPU 各一份 → 无竞争] P0[CPU0] --> D0[副本0] P1[CPU1] --> D1[副本1] P2[CPU2] --> D2[副本2] end

4. LPM trie(最长前缀匹配前缀树)

  • 适用于需要根据前缀快速做匹配的场景,常用于数据包处理与网络性能分析。
  • 场景:存储和查找基于 IP 地址前缀的路由记录,用来做网络流量引导——因为它的核心能力就是前缀快速匹配。

5. queue / stack(队列 / 栈)

  • 支持入队 / 出队、压栈 / 弹栈等基础操作。
  • 适合分段处理的数据流。
  • 场景:监控系统事件流,把事件按顺序存储并处理,或清理旧事件,充分利用队列 / 栈的机制。

6. ring buffer(环形缓冲区)

  • 一个环形的缓冲区,数据循环存储其中,用来收集并实时发送 eBPF 事件给用户空间。
  • 有高效的事件推送机制,特别适合高频但只需短暂存储的数据流。
  • 场景:实时跟踪进程上下文切换、网络包、日志等高频率事件。(后面课程实战会高频使用它。)
复习提示:为什么 ring buffer 这么受欢迎? 因为它的一半能力是"push":用户态不用轮询,数据一来就被推送出来,吞吐高、延迟低。这在做"进程上下文切换、高频系统调用跟踪"这类场景里,性价比极高。

7. LRU(哈希 + LRU 自动淘汰)

  • 名字有点"技术面试题"的味道——LRU(最近最少使用)。它其实是一个带自动淘汰的哈希表。
  • 当 Map 达到最大容量时,自动删除最久没有使用的数据,腾出空间。
  • 适用于存储资源有限的场景,比如 DNS / 筛选,以及流量监控、网络跟踪里"记录最近频繁使用的 IP / MAC"这种——旧的自动剔除,新的覆盖,非常方便。

8. map-in-map(array of maps / hash of maps)

  • 不加前缀的 array of maps 与 hash of maps,思路与普通 map 类似,但存的是一个 map 的引用(嵌套引用),而不是普通值。
  • 是多层的、动态的结构。
  • 场景:动态管理 / 切换多个 Map,在不同数据结构间切换;或做网络流量分析,根据时间段、网络分区、特定场景动态选择不同的 Map 来跟踪。

9. device map(设备映射)

  • 设备的映射,允许程序直接影响 / 控制特定网络设备的数据包转发行为。
  • 主要用于 XDP / eXpress Data Path 场景。
  • 场景:通过 eBPF + XDP 提供极快速的数据包转发 / 过滤 / 丢弃方案,比如 DDoS 攻击防御;又如把一个网络接口收到的数据包直接重新送到另一个接口。

10. sockmap(套接字映射)

  • 存储 socket(套接字) 的映射,允许 eBPF 程序与 socket 直接交互。
  • 可以对 TCP / UDP 的 socket 进行操作,比如转发应用数据流、修改行为、封装 / 拆装网络流量。
  • 典型场景:负载均衡器与 TCP 流管理,把不同数据流标记到不同 socket 去分配流量。

Map 类型速查表

Map 类型特点典型场景
hash高效查改,超限不扩容直接失败键=pid,值=调用次数
array固定大小,索引快速读按 CPU 索引存负载
percpu hash/array每 CPU 独立,无竞争高并发统计各 CPU 数据
LPM trie最长前缀匹配IP 前缀路由 / 流量引导
queue / stack队列 / 栈操作事件流顺序处理
ring buffer环形、实时 push高频短存事件(切上下文/包/日志)
LRU自动淘汰最久未用DNS、频繁 IP/MAC 记录
array/hash of maps存 map 引用、多层动态动态切换 map、分段流量分析
device map控制设备转发eBPF+XDP 快速转发 / DDoS
sockmap存 socket、与套接字交互负载均衡、TCP 流管理

选型口诀(简单版):要高并发多核、怕锁竞争 → percpu;要高频事件实时推送 → ring buffer;要按 IP 前缀过滤 → LPM;要省内存 + 冷数据自动清 → LRU。 剩下的业务键值存 hash / array 一般没错。

下面是一段**真实 BPF Map 定义(C / libbpf 风格)**的示例,帮初学者建立"Map 长啥样"的画面:

/* 定义一个 hash 类型的 BPF Map,值用 count 记录次数 */
struct {
    __uint(type, BPF_MAP_TYPE_HASH);   /* 类型:哈希表 */
    __uint(max_entries, 4096);         /* 最大元素数:4096,超出写失败 */
    __type(key, u32);                  /* key:32 位无符号整数(如 pid) */
    __type(value, u64);                /* value:64 位(如调用次数) */
} counts SEC(".maps");

内核态 BPF 程序向 Map 写一次、用户态 bpftool 读一把的对应命令:

# 查看已经加载的所有 BPF 程序与 Map
bpftool prog list
bpftool map list

# dump 某个名为 counts 的 map 内容
bpftool map dump name counts
flowchart TB
    K[内核态 BPF 程序
bpf_map_update_elem 写数据] -->|共享同一 Map| M[(BPF Map)] M -->|用户态程序读取展示| U1[bpftool] M -->|用户态程序读取展示| U2[bpftrace / BCC] M -->|用户态程序读取展示| U3[你的 Go 观测服务]

四、XDP:数据面最前排的"神速"拦截

XDP(eXpress Data Path,eXpress 数据面)是基于 eBPF 的一个框架,专门用来处理网络数据包。它允许你在接收网络数据包时,立即触发一个 eBPF 程序,对数据包执行一系列动作。

XDP 能对数据包做什么

收到一个包时,XDP 程序可以立即执行下面这些动作之一:

动作含义
drop把这个包直接丢掉(丢弃)
forward转发到其他网络设备
redirect把包重定向到别处
pass交给协议栈继续正常处理(处理完后 pass)
aborted / 暂存流中止当前处理,或暂时持留数据流
flowchart LR
    PKT[(网络数据包)] --> X[XDP 程序
eBPF 处理] X -->|drop| D[丢包] X -->|redirect/forward| R[转发到其他设备] X -->|pass| T[交给协议栈继续] X -->|aborted| A[中止处理]

XDP 的名言:在数据包刚进机器、还没被内核"大动干戈"地处理之前,就用最轻量的方式决定它的去留。 这让 XDP 拥有惊人的吞吐与极低的延迟。

引入 XDP 前:网络数据包的处理流程

要理解 XDP 放在哪,先看网络包在内核里的原始处理流程:

NIC 网卡收到数据(物理以太帧)
  → driver 网卡驱动(把物理层以太帧转换成计算机内部可理解的格式)
  → traffic control 流量控制(内核里的流量控制子系统:带宽管理 / 流量整形)
  → netfilter(Linux 内核网络数据包过滤框架:三层防火墙 / NAT 等,可修改/丢弃/放行包)
  → TCP/IP 协议栈(传输协议 TCP/UDP 处理 → 按应用层协议分发)
  → socket 接口 → 抵达目标应用程序
flowchart LR
    NIC[NIC 网卡
物理以太帧] --> DRV[driver 驱动
转成内核可理解格式] DRV --> TC[traffic control 流量控制
带宽/整形] TC --> NF[netfilter 过滤
三层防火墙/NAT] NF --> STACK[TCP/IP 协议栈
处理并分发] STACK --> SK[socket → 应用]

注意:driver 是网卡驱动,一般动不了它;但再往后的 traffic control 其实已经进入 Linux 内核了,所以我们可以在这里插一层。

XDP 插在哪:driver 与 traffic control 之间

引入 XDP 后,就是在 driver 与 traffic control 之间增加了一个流程——这一层是操作系统层面的,跟硬件无关。内核在网络路径上添加了一个检查点(checkpoint),把数据包交给 eBPF 程序,由它决定是丢、是放行 pass、还是转发。

flowchart LR
    NIC[NIC 网卡] --> DRV[driver 网卡驱动]
    DRV -->|XDP 在这里拦截| X[XDP 检查点
eBPF 决定去留] X -->|drop/redirect| EXIT[快速处理完毕
不进协议栈] X -->|pass| TC[traffic control] TC --> NF[netfilter] NF --> STACK[TCP/IP 协议栈] STACK --> SK[socket → 应用]

效能关键:数据包一旦在 XDP 这一层被 drop 或 redirect,就完全不用往下走进内核协议栈——省掉了海量的开销,这正是 XDP 能扛住超大流量 DDoS 的原因。

XDP 的三种挂载模式

XDP 有三种核心的挂载模式:

模式挂载位置说明性能
native(原生,最原始)直接挂到网卡设备的驱动(driver)上先于操作系统处理数据,性能非常高;前提是网卡驱动要支持高
offload(卸载)直接挂到可编程网卡硬件设备处于数据链路最前端,性能最强;前提是硬件设备要支持最高
generic(通用,最常用)挂到 driver 与 traffic control 之间(操作系统层面)与硬件无关,只要 Linux 内核满足版本要求即可用,人人都能用;因为数据包已进内核,效率比前两种低一点中

关键取舍:

  • offload / native 更快,但依赖特定硬件 / 驱动支持,跟设备强相关;
  • generic 模式与设备无关、在操作系统层面解决,人人都能用,只是效率略低——但能做到"兼容一切 Linux",可移植性最好,所以是最常用、最普及的模式。
flowchart TB
    XDP[XDP 三种挂载模式]
    XDP --> M1[native 原生
挂到网卡驱动
快·需驱动支持] XDP --> M2[offload 卸载
挂到可编程硬件
最快·需硬件支持] XDP --> M3[generic 通用
driver 与 tc 之间
与硬件无关·人人可用]

XDP 的使用场景

XDP 主要用在下面几类场景:

  1. DDoS 攻击防御 / 防火墙:用 XDP 的 drop 直接在内核最前排丢掉恶意数据包。Cloudflare(转录里写作 clo)这家公司强大的 DDoS 防攻击能力,很大程度上就用了 XDP 技术。
  2. 负载均衡:用 XDP 的 XDP_TX 和 redirect 对数据包做负载均衡与转发。
  3. 监控与流量采样:通过自定义元数据识别包,做监控采样。
  4. Cilium(转录里写作 CDM):这家公司的核心网络技术就是 XDP + eBPF,我们在下一课会实战用它。

知识点连线:XDP 上用于"真·应用层负载均衡"的 bpf_redirect 与 XDP_TX,device map 也在里面扮演重要角色——它们和第三章的 device map 正好呼应(XDP 程序把收到的包直接重定向到另一个网卡 / 接口)。


五、eBPF 相关生态项目与 AIOps 结合

最后一部分看 eBPF 的生态。课程把它分成内核空间与用户空间两块。

内核空间:会用即可

  • eBPF 内核技术:内核里加载 / 验证 / JIT 运行 BPF 程序的那一套。
  • XDP 内核技术:上章说的 XDP 数据面框架。

这两者是"地基",作为使用者我们会用就行,不需要深抠内核实现原理——重点是用户空间的工具怎么驱动它们。

用户空间:bpftrace / BCC / gobpf / libbpf

用户空间工具链是 eBPF 生态的重头戏(后面课程也会带大家用):

项目全称 / 定位特点
bpftrace基于高级装箱语言的 BPF 跟踪工具语法简单,类似 awk,适合快速诊断与临时观测
BCCBPF Compiler Collection(BPF 编译器集合)类似 bpftrace 的用户空间工具,用 Python/C 写观测程序并加载运行;下一课会实战体验
gobpfGo 语言版的 BCC用 Go 语言驱动 eBPF 开发观测程序
libbpf较底层的库用于高级应用程序与 BPF 通信的库,介于 user space 与 BPF 之间,相对更底层
flowchart TB
    APPS[观测应用] --> TOOL[用户态工具]
    TOOL --> BPT[bpftrace 高级脚本]
    TOOL --> BCC[BCC  Python/C]
    TOOL --> GOP[gobpf  Go 语言]
    TOOL --> LIB[libbpf 底层库]
    BPT --> LOAD[加载编译/加载]
    BCC --> LOAD
    GOP --> LOAD
    LIB --> LOAD
    LOAD --> K[内核 eBPF / XDP]
    LOAD --> V[verifier + JIT]

下面给一份 BCC Python 的入门示例,方便零基础的你建立一个"用户态程序怎么驱动内核 BPF 程序"的整体认知(BCC 帮我们完成了 编译→加载→attach→读 map 的很多杂活):

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from bcc import BPF   # 引入 BCC 库

# 1) 用 C 语言写好一段要灌进内核的 eBPF 程序(字符串形式)
bpf_src = """
int trace_sleep(void *ctx) {
    u32 pid = bpf_get_current_pid_tgid() >> 32;  // 取当前进程 PID
    bpf_trace_printk("PID %u sleeping\\n", pid); // 往跟踪管道写一行
    return 0;
}
"""

# 2) 交给 BCC:编译成字节码 → 内核验证 → 加载
b = BPF(text=bpf_src)

# 3) 挂到内核函数 do_nanosleep 的入口(kprobe 动态探针)
b.attach_kprobe(event="do_nanosleep", fn_name="trace_sleep")

print("正在追踪 do_nanosleep,Ctrl+C 退出……")
# 4) 用户态循环里读取内核 trace 管道里的输出并打印
while True:
    try:
        (task, pid, cpu, flags, ts, msg) = b.trace_fields()
        print(f"[ts={ts}] pid={pid} {msg}")
    except KeyboardInterrupt:
        exit()

编译到加载大致是 BCC 内部替你完成:C 源码 → BPF 字节码 → 内核 verifier → JIT → attach 到 kprobe → 数据回传到 trace 管道 → 用户态 print。这套流程,正是第二、三章讲过的流水线 + Map/管道通信的一个具体落地。

gobpf 与 libbpf 的角色如下:

// gobpf:在 Go 程序里加载并运行 BCC 风格 BPF 程序(示意)
import bpf "github.com/iovisor/gobpf/bcc"
module := bpf.NewModule(bpfSource, []string{})
kprobe, _ := module.LoadKprobeProgram("trace_sleep")
module.AttachKprobe("do_nanosleep", kprobe, -1)
复习提示:一句话区分四个项目:bpftrace = 运维临时"查快照"的瑞士军刀(语法最短);BCC = 用 Python 认真写观测工具的框架;gobpf = 用 Go 写 eBPF 观测 / Agent;libbpf = 更底层的 C 库,适合做生产级、可分发(CO-RE 兼容)的观测组件。

再往上的生态项目:Cilium / Hubble / Falco 与 AIOps

虽然转写稿这一份资料较简短,但基于 eBPF 生态现状,我们把课程 13→14(eBPF 零侵入可观测性实战)要衔接的两类上层项目补齐,方便你把 eBPF 与 AIOps 串起来:

项目定位与 eBPF 的关系
Cilium基于 eBPF 的容器 / K8s 网络方案用 eBPF + XDP 替代 kube-proxy,实现负载均衡、网络策略、透明加解密
HubbleCilium 的可观测性模块基于 eBPF 采集容器网络流与安全事件、绘制服务拓扑
Falco云原生运行时安全(falco 运行时行为监控)用 eBPF/kprobe 监控进程、系统调用与可疑行为,是"云原生入侵检测"的典型
bcc / bpftrace / libbpf观测编程工具链上节详述
P4可编程数据面语言(常与"可编程网卡/交换机数据面"并列讨论)与 eBPF 目标互补:P4 面向硬件数据面控制,eBPF 面向内核软件数据面;两者常做对比

为什么这些项目和 AIOps 有关:AIOps 的根因定位需要"多视角补盲"。传统以应用埋点为主(Prometheus / 链路追踪 / 日志),往往缺一层内核视角——网络丢包、系统调用异常、CPU 上下文切换抖动、TCP 重传超时等,恰恰是很多故障的"第一现场"。

flowchart TB
    APP[应用埋点
Prometheus·Tracing·日志] KERNEL[eBPF 内核视角
系统调用·网络·CPU·内存] APP --> MERGE[统一数据面板
多信号关联] KERNEL --> MERGE MERGE --> ROOT[AI/规则 根因定位
先本地聚合异常片段] ROOT --> LLM[再交由 LLM 推理
形成可解释结论]

落地思路(一句话):先用 eBPF 在你关心的内核事件上做低成本、零侵入、埋点少的采集,把异常片段(谁在调用、何时、多少次、自哪网络路径来)在本地聚合,再交给 LLM / 规则做根因推理——这正是把"内核观察力"注入 AIOps 的一种务实打法。下一课我们就用 eBPF 做零侵入可观测性实战、并在 Cilium 上动手。

到这里,eBPF 入门的概念、实现原理、Map、XDP 以及生态项目就都讲完了。下一模块进入真正的 eBPF 零侵入可观测性开发实战。


总结

这一章我们围绕 eBPF——让 Linux 内核可编程 这一个主线,讲清了它的前世今生与工程价值:

  • eBPF 是什么:extended Berkeley Packet Filter(扩展伯克利报文过滤器),从 1992 年的经典包过滤器(cBPF)扩展而来。名字叫"包过滤",实际能力是内核里的一台虚拟机——让内核可编程、可改变行为,且不改内核源码。
  • 为什么需要:传统扩展内核(改源码→社区共识→发行版打包→需求已变)以年计;eBPF 动态加载、沙箱化、验证器把关、无需重启,几天搞定。
  • 两大空间:用户空间发出系统调用,内核空间访问底层硬件与 I/O;eBPF 程序运行在内核态,恰好能观测这些调用与事件。
  • 事件驱动 + hook:kprobe / uprobe(动态探针)、tracepoint(静态跟踪点)、network、perf_event 等;监控文件、数据库查询、TCP 流量、CPU 周期都从这里入手。
  • 技术细节流水线:源码 → AST → LLVM IR → BPF 字节码 → verifier 安检(死循环 / 非法内存 / 类型 / 寄存器 / Map 越界)→ JIT 编译成机器码(或解释器路径)→ 运行 → 数据经 Map 回流用户态打印。BTF 提供内核与程序间的类型信息;JIT 极大提升执行效率。
  • BPF Map:内核与用户态共享数据的中枢,属性含 type / max_entries / key_size / value_size。常见类型:hash(超限不扩容)、array、percpu(每 CPU 无竞争)、LPM trie(前缀匹配)、queue/stack、ring buffer(高频实时 push)、LRU(自动淘汰)、array/hash of maps(嵌套动态)、device map(转发放 DDoS)、sockmap(套接字交互)。
  • XDP:基于 eBPF 的网络数据面框架,插在 driver 与 traffic control 之间;动作 drop / forward / redirect / pass / aborted。三种挂载:native / offload / generic(常用、与硬件无关)。场景:DDoS 防御(Cloudflare)、负载均衡(XDP_TX/redirect)、监控采样、Cilium 核心网络。
  • 生态项目:内核态(eBPF / XDP 会用即可);用户态 bpftrace / BCC / gobpf / libbpf(由易到难 / 由底层到语言绑定);分层更上层还有 Cilium / Hubble / Falco 等。它们为 AIOps 提供了宝贵的内核视角数据,与 LLM 根因推理结合,是把"观测"升级为"运维智能"的关键一环。

自测题与动手练习

自测题(合上书能答出来,才算真懂):

面试官
eBPF 的全称是什么?它和经典 BPF(cBPF)是什么关系?为什么说它"让 Linux 内核可编程"?
候选人
全称是 extended Berkeley Packet Filter(扩展伯克利报文过滤器),e 即 extended。它就是由 1992 年的经典包过滤器 cBPF 扩展而来。
它让内核可编程,是因为:① 可以在不改内核源码的前提下动态加载一份 BPF 程序进内核去执行;② 程序跑在沙箱 + 验证器保护里,安全;③ 可改变内核行为且无需重启机器。
面试官
传统扩展内核为什么那么复杂?eBPF 用哪几件事解决了这个问题?
候选人
传统流程是"改内核→社区评审达成共识→等发行版打包",周期以年计,等你拿到能力需求往往已变。
eBPF 的解法:动态加载(写完用几天可直接运行)、沙箱 sandbox(不改变内核本身)、验证器 verifier(检查死循环/安全性)、无需重启(改行为即插即用)。这本质上是"动态内核机制"取代"改内核的固定流程"。
面试官
用户空间和内核空间谁负责"碰"底层硬件?eBPF 程序运行在哪个空间?它靠什么把数据交给用户空间?
候选人
用户空间应用不能直接访问硬件,只能通过系统调用(syscall)请求内核;由内核空间去访问 CPU / 内存 / I/O 等底层资源。
BPF 程序
运行在内核空间
;它把追踪结果写入 BPF Map(或 perf_event / 环形缓冲区),再由用户态程序(bpftrace / BCC / 你的应用)读取。
面试官
eBPF 是事件驱动的。列举至少 3 类 hook 并说出一个对应场景。
候选人
① kprobe:挂到内核函数入口/出口,如监控 open/do_nanosleep;② uprobe:挂到用户态函数(如数据库的查询函数)入口;③ tracepoint:静态跟踪点,如挂在 read 追踪文件读取;④ network:分析 TCP 流量 / 按端口过滤;⑤ perf_event:靠 CPU cycles 统计哪个进程占用 CPU 最多。
面试官
把 eBPF 程序从源码到执行的主干走一遍。AST / IR / 字节码 / JIT 分别承担什么?
候选人
流程:源码(C / bpftrace)→ AST(抽象语法树,表示代码结构)→ LLVM IR(中间表示)→ BPF 字节码 → verifier 安检 → JIT 即时编译成机器码 → CPU 执行。
AST 表示代码结构、IR 是"能翻目标码的中间态"、字节码是内核认的格式、JIT 是临场把它翻译成本地机器指令以提升效率(未开 JIT 则走解释器逐条解释,较慢)。
面试官
verifier 主要拦下哪几类高危行为?说出至少 4 条(含两条常见硬约束)。
候选人
主要拦:死循环 / 无限循环、非法内存访问、对内核对关键内存的写入、未初始化寄存器/变量、函数参数类型不匹配、寄存器受限写与溢出。
两条常踩硬约束:① 内存访问受限,只能读已初始化内存,访问内核内存必须走 bpf_probe_read 等 helper;② BPF Map 不能写入超出定义的数据结构,否则 verifier 直接拒绝。
面试官
BPF Map 的四个基本属性是什么?hash、percpu、ring buffer、LRU 四种各自的特点 / 典型场景各说一个。
候选人

四个属性:type(类型)、max_entries(最大长度)、key_size、value_size。

  • hash:高效查改,超 max_entries 不扩容直接写失败,如"键=pid、值=调用次数";
  • percpu:每 CPU 独立副本、无锁竞争,适合多核高并发(如各 CPU 处理包数);
  • ring buffer:环形、实时把事件 push 给用户态,适合高频短存(切上下文/网络包/日志);
  • LRU:带自动淘汰(最久未用先删),适合 DNS、频繁 IP/MAC 记录、省内存场景。
面试官
XDP 插在网络包处理流程的哪个位置?它有哪些可执行的包动作?三种挂载模式及取舍是什么?
候选人
原始流程 NIC → driver → traffic control → netfilter → TCP/IP 协议栈 → socket;XDP 被插在 driver 与 traffic control 之间(操作系统层面、与硬件无关)。
动作:drop(丢弃)/ forward(转发)/ redirect(重定向)/ pass(交给协议栈)/ aborted(中止)。
三种模式:native(挂网卡驱动,快、需驱动支持)、offload(挂可编程硬件,最快、需硬件支持)、generic(挂 driver 与 tc 之间,与硬件无关、人人可用、大家最常用,但效率略低)。
面试官
XDP 有哪些典型使用场景?举一个"用 XDP 打 DDoS"的公司案例。
候选人
典型场景:DDoS 防御 / 防火墙(用 drop 在最前排丢包)、负载均衡(用 XDP_TX 和 redirect 转发)、监控与流量采样(用自定义元数据识别包)、以及 Cilium 的核心网络方案。
公司案例:Cloudflare(转录里写作 clo) 强大的 DDoS 防攻击能力很大程度依靠 XDP 技术,因为它能在数据包进入协议栈前就把它丢掉,吞吐高、延迟低。
面试官
eBPF 生态的用户空间项目有哪些?它们分别的定位是什么?这些生态和 AIOps 根因定位有什么关系?
候选人
用户空间:bpftrace(高级语言快速 BPF 跟踪工具)、BCC(BPF Compiler Collection,Python 写观测程序)、gobpf(Go 语言版 BCC)、libbpf(较底层的与 BPF 通信的库);再上层还有 Cilium / Hubble / Falco 等网络与安全项目。
与 AIOps 关系:传统以应用埋点为王的观测缺一层内核视角(系统调用、网络丢包、上下文切换、TCP 重传等"第一现场")。用 eBPF 低成本、零侵入采集内核事件并本地聚合异常片段,再交由 LLM/规则推理——是"观测升级为运维智能"的关键路径(也是下一课实战内容)。

动手练习(建议真做一遍):

  1. 给我画出 eBPF 程序完整生命周期 10 步流水线(源码 → AST → IR → 字节码 → verifier → JIT → 运行 → Map → 用户态打印),每步标注是"用户态"还是"内核态"。
  2. 在一台 Linux 上试跑 bpftrace -e 'kprobe:do_nanosleep { printf("PID %d sleeping\n", pid); }',观察有多少进程在内核里调用 sleep,体会"零侵入观测系统调用"。若没有 bpftrace,就用上面的 BCC Python 脚本体验。
  3. 我的多核高并发网卡服务要做网络观测,请做 BPF Map 选型:统计各 CPU 处理包数(选?)、实时推送高频事件(选?)、按 IP 前缀做路由引导(选?)、记录最近访问的 IP 且要省内存(选?),并说明理由。

本章小结

复习提示:
  • eBPF = extended BPF,由经典 cBPF 包过滤器扩展而来;名字叫"包过滤",本体是内核虚拟机——让内核可编程、可改行为,不改内核源码、无需重启。
  • 传统扩展内核以年计、滞后严重;eBPF 用 动态加载 + 沙箱 + 验证器 + 免重启 给出"动态内核机制"。
  • 用户空间发 syscall,内核空间碰底层硬件;BPF 程序在内核态运行,靠 BPF Map 把数据交回用户态。
  • 事件驱动 + hook:kprobe / uprobe / tracepoint / network / perf_event,覆盖"函数调用、文件、数据库查询、TCP 流量、CPU 周期"等观测。
  • 技术栈主干:AST → LLVM IR → BPF 字节码 → verifier → JIT(或解释器)→ 运行 → Map 回流;BTF 提供类型信息,JIT 提速,verifier 拦死循环/非法内存/类型/寄存器/超界写(内核内存访问需 bpf_probe_read)。
  • BPF Map 中枢:hash(超限不扩容)、array、percpu(无竞争)、LPM trie(前缀匹配)、queue/stack、ring buffer(高频 push)、LRU(自动淘汰)、array/hash of maps(嵌套动态)、device map(转发/DDoS)、sockmap(套接字交互)。
  • XDP:基于 eBPF 的 eXpress 数据面,插在 driver 与 traffic control 之间;动作 drop/forward/redirect/pass/aborted;三模式 native / offload / generic;场景 DDoS(Cloudflare)、负载均衡、Cilium。
  • 生态:内核态(eBPF / XDP 会用即可)+ 用户态(bpftrace / BCC / gobpf / libbpf)分层;上层 Cilium / Hubble / Falco。
  • AIOps 结合:eBPF 补充"内核视角"这一层低成本、零侵入数据,先本地聚合异常片段、再交 LLM 推理 → 根因定位,是运维智能的关键路径;下一课进入 eBPF 零侵入可观测性开发实战。
About Me

没什么想介绍的,一个很大众的码农…

喜欢代码,车,马,真的是 🐎

讨厌别人让我给自己的代码写注释 最厌烦别人的程序没有写注释

目标

学AI,加油!加油!