当前位置:首页>Linux>Linux 内核 eBPF 图解:理解内核扩展的核心机制

Linux 内核 eBPF 图解:理解内核扩展的核心机制

  • 2026-08-19 18:23:49
Linux 内核 eBPF 图解:理解内核扩展的核心机制

大家好,我是蟹老板~

很多人刚接触 eBPF 的时候,都会觉得这东西太难了吧,好像不懂内核就根本学不了。

我一开始也是这样。资料打开没看几页,里面全是内核、指令、验证器之类的概念。人就已经懵了。

后来断断续续看了大半年文档,也写了不少测试代码。折腾得多了以后,才慢慢明白 eBPF 到底是怎么工作的,之前那些看起来很复杂的概念,也逐渐能串起来了。

这篇文章就是想把我当时的学习过程重新梳理一遍,从最基础的地方开始讲。比如传统 BPF 有什么限制,eBPF 是怎么发展过来的,它在内核里大概是怎么运行的,Map 怎么用,程序可以挂载到哪些位置,平时开发可以用哪些工具。

后面也会讲一些实际使用中的问题,包括怎么落地、怎么排查错误、怎么做性能优化,以及不同内核版本之间的兼容问题。

一、从经典 BPF 到现代 eBPF

1.1 传统 BPF 机制与局限性

1.1.1 BPF 的设计初衷与数据包过滤机制

BPF 全称 Berkeley Packet Filter,最早解决的是网络数据包过滤问题。

抓包工具不可能把网卡收到的全部数据都复制到用户态再慢慢判断。流量稍微大一点,用户态进程还没开始分析,内存复制和上下文切换就先把机器拖趴了。

经典 BPF 的思路很直接。用户态提交一段过滤指令,内核在数据包进入用户态之前执行它,只把符合条件的数据交给抓包程序。

例如 tcpdump 中的过滤表达式

tcpdump -i eth0 'tcp port 443 and host 10.0.0.8'

这段表达式会被编译成 BPF 指令。内核逐条执行,检查协议、端口和地址。条件不匹配的数据包当场被丢弃,不再复制到 tcpdump 进程。

这个设计放在当年非常聪明。过滤逻辑靠近数据源,少复制,少切换,也不用为了换一个过滤条件重新编译内核。

1.1.2 经典 BPF 的运行机制

经典 BPF 更像一台非常小的虚拟机。

它主要依赖累加器 A、索引寄存器 X、有限的临时存储空间以及跳转指令。程序通常是线性的,功能集中在加载数据、执行算术运算、比较字段和决定数据包是否保留。

Linux 后来逐步把经典 BPF 指令翻译到新的内部表示中执行,但经典模型的能力边界并没有因此消失。

它适合回答的问题大多类似下面这些。

  • • 这个包是不是 IPv4。
  • • 目标端口是不是 80。
  • • TCP 标志位有没有 SYN。
  • • 数据包长度是否超过某个阈值。

可一旦问题变成“统计每个进程的系统调用耗时”“记录文件访问行为”“重定向套接字连接”“实现 Kubernetes 服务负载均衡”,经典 BPF 就明显不够用了。

1.1.3 传统 BPF 的核心缺陷

经典 BPF 最大的问题并不是慢,而是表达能力太弱

寄存器少,指令能力有限,数据结构访问方式僵硬,也缺乏通用的内核事件挂载模型。它围绕网络数据包过滤而设计,很难自然扩展到调度、文件系统、安全和用户态函数追踪。

安全能力也比较原始。传统验证主要围绕程序是否终止、内存访问是否合法展开,无法支撑复杂指针类型、跨函数调用和大量内核对象访问。

扩展性同样尴尬。没有今天这种通用 Map 体系,没有丰富的 helper 和 kfunc,也没有成熟的程序类型约束。

说白了,经典 BPF 是一把非常好用的网络过滤刀。后来大家偏要拿它去修汽车、做手术、盖房子。那肯定不行,于是 eBPF 来了。

1.2 eBPF 技术演进与内核版本迭代

1.2.1 eBPF 的诞生背景与升级方向

eBPF 最初的字母 e 表示 extended,也就是扩展版 BPF。发展到今天,社区往往直接使用 BPF 指代现代 eBPF,因为它早就不再只是经典 BPF 的小修小补。

eBPF 采用面向 64 位处理器设计的指令集。程序可见的寄存器名称为 R0 到 R10,其中 R10 是只读帧指针,R1 到 R5 用于传递参数,R0 保存返回值,R6 到 R9 可跨调用保留。这样的调用约定更贴近现代处理器 ABI,也方便 JIT 编译器把虚拟寄存器映射到真实硬件寄存器。

它还获得了几项决定性能力。

  • • 一套通用的 bpf() 系统调用负责创建 Map、加载程序、查询对象和建立链接。
  • • 不同程序类型对应不同内核上下文。
  • • Map 提供长期状态和用户态通信。
  • • helper 与 kfunc 提供受控的内核能力。
  • • 验证器通过符号执行检查程序安全。
  • • JIT 把字节码翻译为本机机器指令。

这些能力组合起来后,BPF 才真正从“包过滤器”变成“受约束的内核扩展运行时”。

1.2.2 4.x、5.x、6.x 的关键变化

4.x 阶段解决的是能不能用。

这一时期逐步形成了 kprobe、tracepoint、perf event、tc、XDP、cgroup BPF 等主要程序类型。Map 类型也快速丰富,出现 Per-CPU Hash、LRU Hash、Map in Map 等结构。例如 Per-CPU Hash 在 4.6 引入,LRU Hash 在 4.10 引入,Map in Map 在 4.12 引入。

XDP 的出现尤其关键。它让 BPF 程序能够在网络驱动接收路径的早期处理数据包,不必等数据包完整穿过协议栈。防火墙、负载均衡、限流和 DDoS 清洗从此有了新的实现位置。

5.x 阶段解决的是能不能大规模工程化。

BTF 与 CO-RE 逐渐成熟,程序不再必须在目标机器现场编译。有限循环、BPF 子程序、全局变量、LSM BPF、bpf_link、ring buffer、BPF iterator、sleepable BPF、内核 kfunc 等能力陆续进入工程实践。

BPF_RINGBUF 解决了多 CPU 事件流共享内存和全局事件顺序问题。相比每 CPU perf buffer,它能让多个 CPU 共同写入一个 MPSC 环形缓冲区,内存利用率通常更好,跨 CPU 事件顺序也更自然。

6.x 阶段解决的是能不能写得更像一套完整系统。

dynptr 改善了动态数据访问,open-coded iterator 让程序能够安全表达复杂迭代,kfunc 和带类型的内核对象扩展了受控调用能力。BPF token、arena、对象引用跟踪、异常处理、struct_ops 也让 BPF 的状态管理和模块化能力越来越强。

调度领域甚至出现了 sched_ext。它允许通过一组 BPF 程序定义调度策略,并在程序异常、任务停滞或主动退出时回退到默认调度器。这个回退设计很重要,否则一段写烂的调度程序就能把整台机器变成昂贵的暖手宝。

不过版本号不能当唯一依据。云厂商和 Linux 发行版经常回移 BPF 特性。判断环境能力时,最好用 bpftool feature probe,别只看 uname -r

uname -rsudo bpftool feature probe kernelsudo bpftool feature probe kernel |    grep -E 'program_type|map_type|helper'

1.2.3 eBPF、传统内核钩子与内核模块的差异

传统 tracepoint、netfilter hook、LSM hook 是内核提供的固定接入点。它们本身只是入口,还需要代码去使用。

内核模块则直接运行在内核最高权限环境。能力非常强,但风险同样直接。空指针、越界写、锁顺序错误、错误的引用计数,任何一个都可能把系统带走。

eBPF 位于两者之间。

它借助现有钩子进入内核路径,却不允许程序随意访问内存。程序能做什么,取决于程序类型、上下文、helper、kfunc 和验证器状态。

内核模块更像拿到整栋楼的总钥匙。eBPF 更像一张权限被精确限制的门禁卡。 门禁系统也会有漏洞,但工程风险完全不是一个级别。

二、eBPF 内核核心架构与运行原理

2.1 eBPF 的整体架构分层

2.1.1 用户态与内核态交互模型

一个完整的 eBPF 应用通常包含两部分。

  • • 内核态程序负责快速处理事件。它应该短小、确定、低开销
  • • 用户态程序负责加载、配置、读取数据、聚合计算和输出结果。

用户态通过 bpf() 系统调用创建 Map、加载程序。程序成功加载后会得到文件描述符。后续再通过 perf event、BPF link、netlink、cgroup 文件描述符或其他接口完成挂载。

Map 也以文件描述符表示。只要引用还存在,对象就可以继续存活。对象还可以 pin 到 bpffs,使其脱离原始进程生命周期。

sudo mount -t bpf bpf /sys/fs/bpfsudo bpftool prog showsudo bpftool map showsudo bpftool link showsudo bpftool map pin id 42 /sys/fs/bpf/request_count

2.1.2 指令集、虚拟机、验证器、加载器与 Map

eBPF 指令集是程序的中间表示。

虚拟机负责解释执行字节码,或者把执行交给 JIT 后端。

验证器决定程序是否有资格进入内核。

加载器负责解析 ELF、创建 Map、处理重定位、加载程序和建立挂载关系。

Map 保存跨事件、跨 CPU 或跨用户态进程共享的数据。

漏掉任何一个都不行。尤其是加载器。很多人刚接触时以为写一个 .bpf.c 就算完工,后来才发现 ELF section、BTF、重定位、pin、attach、link 生命周期全在后面排队等着呢。

2.1.3 程序的完整执行链路

以一个 tracepoint 程序为例,执行链路大致如下。

  1. 1. Clang 把 C 代码编译为 BPF ELF 对象。
  2. 2. libbpf 解析 ELF 中的程序、Map、BTF 和重定位信息。
  3. 3. 加载器调用 BPF_MAP_CREATE 创建 Map。
  4. 4. 加载器处理 CO-RE 重定位与 Map 文件描述符替换。
  5. 5. 程序通过 BPF_PROG_LOAD 进入内核。
  6. 6. 验证器分析所有可达路径。
  7. 7. 验证通过后,解释器或 JIT 准备执行代码。
  8. 8. 程序通过 BPF link 挂到 tracepoint。
  9. 9. 事件发生时,内核调用 BPF 程序。
  10. 10. 程序更新 Map 或写入 ring buffer。
  11. 11. 用户态轮询数据并输出。

这条链路看着长,libbpf skeleton 已经帮我们封装了大半。内核官方文档也建议使用 skeleton 保持对象文件、程序和 Map 定义同步,减少字符串查找带来的错配。

2.2 eBPF 指令集与虚拟机机制

2.2.1 寄存器设计与寻址方式

eBPF 是 64 位 RISC 风格指令集。常见指令包括加载、存储、算术运算、位运算、条件跳转、函数调用和退出。

  • • R0 保存返回值。
  • • R1 到 R5 负责函数参数。
  • • R6 到 R9 是被调用者保存寄存器。
  • • R10 是只读栈帧指针。

典型程序栈大小依旧很受限制。不要把内核态程序当普通用户态 C 程序写,更不要随手声明几 KB 的局部数组。 验证器会用一种很冷静的方式提醒你,想多了。

内存访问必须基于验证器认识的指针,包括上下文指针、Map value 指针、栈指针、数据包指针和带 BTF 类型的内核对象指针。

2.2.2 虚拟机与 JIT 执行

验证通过的程序可以由解释器逐条执行,也可以通过架构相关 JIT 编译器转换成本机指令。

现代生产环境通常启用 JIT。

sysctl net.core.bpf_jit_enablesysctl net.core.bpf_jit_hardensysctl net.core.bpf_jit_kallsyms

eBPF 指令集设计时就考虑了与 64 位硬件寄存器的一对一映射。LLVM 可以生成优化后的 BPF 字节码,内核 JIT 再将其转成本机指令。内核文档认为这种映射能够让程序性能接近本机编译代码

2.2.3 JIT 对性能的影响

JIT 省掉了解释器循环、指令分派和部分间接访问开销。

但 JIT 不是魔法。

一段每个包都执行三百条指令的 XDP 程序,JIT 后仍然要执行那三百条对应机器指令。一段疯狂查 Map、频繁写 ring buffer 的追踪程序,也不会因为启用了 JIT 就突然变轻。

真正的优化仍然来自减少热路径指令、提前过滤、合理选择 Map、避免不必要的数据复制。

2.3 eBPF 内核验证器

2.3.1 验证器承担的安全职责

验证器并不运行程序,而是对程序进行静态分析和符号执行

它会模拟每条指令,跟踪每个寄存器和栈槽在不同分支下的状态。一个寄存器到底是普通整数、上下文指针、Map value 指针、可能为空的指针,验证器必须清楚。

它还会记录标量值可能的上下界。某个长度变量被证明小于数据包剩余长度后,相应内存访问才可能被放行。

这种分析很保守。验证器无法证明安全,不等于程序一定不安全,只意味着证据不够。

2.3.2 循环、边界、指针与递归限制

现代 eBPF 已经支持可证明有界的循环,不再要求所有循环完全展开。

问题在于,循环边界过大或分支过多时,验证状态可能指数增长。程序运行时只循环一百次,验证器却可能要探索成千上万种状态。

  • • 所有数据包访问都要检查 data + offset <= data_end
  • • Map lookup 返回值通常可能为 NULL使用前必须判断
  • • 栈内容必须先写后读。
  • • 指针不能随意与另一个指针做算术。
  • • 普通递归不会像用户态程序那样被允许无限展开。
  • • BPF 子程序调用深度和复杂度也受到限制。

下面这段 XDP 代码缺少边界检查,加载时就会被拒绝。

SEC("xdp")int broken_parser(struct xdp_md *ctx){    void *data = (void *)(long)ctx->data;struct ethhdr *eth = data;    /* 没有检查 eth + 1 是否越过 data_end */    return eth->h_proto == bpf_htons(ETH_P_IP)        ? XDP_PASS        : XDP_DROP;}

正确写法至少要补上数据边界判断。

SEC("xdp")int safe_parser(struct xdp_md *ctx){    void *data = (void *)(long)ctx->data;    void *data_end = (void *)(long)ctx->data_end;struct ethhdr *eth = data;    if ((void *)(eth + 1) > data_end)        return XDP_ABORTED;    if (eth->h_proto != bpf_htons(ETH_P_IP))        return XDP_PASS;    return XDP_PASS;}

验证器会检查指针类型、访问边界、对齐和栈初始化状态。Map lookup 返回的可空指针在完成非空判断后,才会转换成可安全访问的 Map value 指针。

2.3.3 验证器报错的排查方法

最怕的不是报错,是一屏寄存器状态看不懂。

排查时别从最后一行机械地往前猜。先找到被拒绝的指令,再看该指令使用了哪个寄存器。继续向上追踪这个寄存器的来源、类型和范围。

常见报错包括这些。

  • • invalid mem access 'scalar' 往往说明验证器把目标当普通整数,不认为它是合法指针。
  • • R0 invalid mem access 'map_value_or_null' 通常是 Map lookup 后没判空。
  • • invalid access to packet 多半是数据包边界检查不完整,或者检查后的指针关系被后续运算破坏。
  • • unbounded loop 表示循环终止条件无法被证明。
  • • stack limit exceeded 说明局部变量、函数内联或调用栈占用太大。

加载时应把 libbpf 日志级别打开。

static int libbpf_log(enum libbpf_print_level level,                      const char *format,                      va_list args){    return vfprintf(stderr, format, args);}int main(void){    libbpf_set_print(libbpf_log);    /* open、load、attach */}

2.4 程序加载与生命周期管理

2.4.1 用户态加载流程

原始加载流程围绕 bpf() 系统调用展开。

  • • Map 使用 BPF_MAP_CREATE 创建。
  • • 程序使用 BPF_PROG_LOAD 加载。
  • • 对象信息可以通过 BPF_OBJ_GET_INFO_BY_FD 查询。
  • • 对象可以通过 BPF_OBJ_PIN 保存到 bpffs。

内核官方文档明确把 Map 视为内核与用户态共享数据的通用存储,并通过 bpf() 系统调用完成创建、查询、更新和删除。

libbpf 会替开发者处理大部分细节。

struct open_count_bpf *skel;int err;skel = open_count_bpf__open();if (!skel)    return 1;skel->rodata->target_tgid = target_pid;err = open_count_bpf__load(skel);if (err)    goto cleanup;err = open_count_bpf__attach(skel);if (err)    goto cleanup;/* 读取 Map 或轮询 ring buffer */cleanup:open_count_bpf__destroy(skel);

2.4.2 挂载、运行与卸载

程序成功加载不等于已经执行。

它还必须挂载到目标位置。 挂载后,内核事件触发程序运行。销毁 link 或关闭对应文件描述符后,挂载关系会被解除。

bpf_link 让挂载关系成为显式内核对象。它比早期那种“程序文件描述符加一堆隐式 attach 状态”的方式更容易管理。

要让程序跨进程存活,可以 pin link、program 或 map。

sudo bpftool prog load monitor.bpf.o \    /sys/fs/bpf/monitor type tracingsudo bpftool prog show pinned /sys/fs/bpf/monitorsudo rm /sys/fs/bpf/monitor

2.4.3 资源回收与内存管理

BPF 对象依赖引用计数管理生命周期。

程序引用 Map,link 引用程序,文件描述符引用对象,bpffs pin 也会增加持久引用。只有所有引用都释放后,对象才会被真正销毁。

这也意味着对象泄漏并不一定表现为传统用户态内存泄漏。某个遗忘在 /sys/fs/bpf 下的 pin,可能让旧 Map 和旧程序一直活着。

排查升级后资源异常时,记得看下面几个地方。

sudo bpftool prog showsudo bpftool map showsudo bpftool link showsudo find /sys/fs/bpf -maxdepth 3 -type f -o -type d

三、eBPF 核心数据结构——BPF Map 详解

3.1 Map 的作用与设计思想

3.1.1 内核态与用户态交互载体

eBPF 程序不能把大量结果直接 printf 到终端。

Map 就是它的状态仓库。

程序可以把计数器、连接状态、策略规则、调用栈 ID、进程信息或配置参数放入 Map。用户态可以读取、更新或删除这些数据。

同一个 Map 还可以被多个 BPF 程序共享。入口程序记录时间戳,返回程序读取时间戳并计算延迟,这是很典型的模式。

3.1.2 安全隔离与并发设计

Map 不是一块任意共享内存。

创建 Map 时必须声明类型、key 大小、value 大小、最大条目数和 flags。验证器根据这些元信息检查程序访问。

不同 Map 类型拥有不同并发语义。并不存在一个万能的“Map 全局读写锁”。

Hash 元素替换可以是原子的,但多个字段组成的 value 并不会自动获得事务语义。需要一致更新时,可以使用 bpf_spin_lock、原子指令、Per-CPU Map 或版本号机制。

3.2 主流 Map 类型与适用场景

3.2.1 HASH、ARRAY 与 Per-CPU 版本

HASH 适合稀疏 key,例如 PID、五元组、文件 inode 和 cgroup ID。

ARRAY 适合连续整数索引,访问路径简单,内存通常预分配。

PERCPU_HASH 和 PERCPU_ARRAY 为每个 CPU 保存独立 value。高频计数时特别好用,因为不同 CPU 不必争用同一个缓存行。

struct counter {    __u64 packets;    __u64 bytes;};struct {    __uint(type, BPF_MAP_TYPE_PERCPU_HASH);    __uint(max_entries, 65536);    __type(key, __u32);    __type(value, struct counter);} stats SEC(".maps");

内核程序查到的是当前 CPU 对应的 value。

static __always_inline void account(__u32 key, __u64 bytes){struct counter zero = {};struct counter *value;    value = bpf_map_lookup_elem(&stats, &key);    if (!value) {        bpf_map_update_elem(&stats, &key, &zero, BPF_NOEXIST);        value = bpf_map_lookup_elem(&stats, &key);    }    if (value) {        value->packets++;        value->bytes += bytes;    }}

用户态读取 Per-CPU Map 后,需要把所有 CPU 的 value 汇总。Per-CPU Hash 的 value 在内核内部按 CPU 分槽存储,程序侧 lookup 会自动访问当前 CPU 对应槽位。

3.2.2 RINGBUF 与 PERF BUFFER

PERF BUFFER 通常是每 CPU 一个缓冲区。优点是成熟,兼容旧内核,而且每个 CPU 独立写入时竞争较少。

缺点也明显。CPU 数量多时,每 CPU 分配固定空间可能浪费内存。跨 CPU 事件的全局顺序也难保证。

RINGBUF 使用共享的多生产者、单消费者模型。 多个 CPU 可以向同一个缓冲区写入,事件预留顺序能够反映跨 CPU 顺序。

struct event {    __u64 ts;    __u32 pid;    __u32 uid;    char comm[16];    char filename[256];};struct {    __uint(type, BPF_MAP_TYPE_RINGBUF);    __uint(max_entries, 1 << 24);} events SEC(".maps");

写事件时可以选择 reserve 加 submit,减少一次复制。

struct event *e;e = bpf_ringbuf_reserve(&events, sizeof(*e), 0);if (!e)    return 0;e->ts = bpf_ktime_get_ns();e->pid = bpf_get_current_pid_tgid() >> 32;e->uid = bpf_get_current_uid_gid();bpf_get_current_comm(&e->comm, sizeof(e->comm));bpf_probe_read_user_str(e->filename,                        sizeof(e->filename),                        filename);bpf_ringbuf_submit(e, 0);

事件大小不固定,或者数据在提交前可能放弃时,reserve 模式很灵活。只是 reserve 后必须走 submit 或 discard,遗漏任何一条路径都会被验证器拒绝。

3.2.3 STACK_TRACE、LPM_TRIE、PROG_ARRAY 与 MAP_IN_MAP

STACK_TRACE 保存调用栈,每条栈信息通过 ID 引用。性能分析工具通常把栈 ID 与 PID、时间戳、计数器组合,再由用户态解析符号。

LPM_TRIE 适合最长前缀匹配。CIDR 黑名单、路由策略、IP 归属判断经常用它。

PROG_ARRAY 用于 tail call。当前程序可以根据索引跳转到另一个 BPF 程序,适合把巨大逻辑拆成多个阶段。

struct {    __uint(type, BPF_MAP_TYPE_PROG_ARRAY);    __uint(max_entries, 16);    __type(key, __u32);    __type(value, __u32);} stages SEC(".maps");static __always_inline void jump_next(void *ctx, __u32 stage){    bpf_tail_call(ctx, &stages, stage);}

MAP_IN_MAP 允许外层 Map 保存内层 Map 引用。它适合策略热切换、分租户状态、双缓冲配置。

例如用户态先构造一张完整的新策略 Map,再把外层 Map 指向新版本。数据面无需逐条等待更新。

3.2.4 内核结构与性能差异

ARRAY 的索引直接,访问成本稳定,但会按最大条目数占用空间。

HASH 适合稀疏 key,不过存在哈希计算、桶查找和并发更新成本。

LRU_HASH 能自动淘汰条目,适合连接跟踪和临时缓存。淘汰过程可能涉及全局或 Per-CPU LRU 链表,容量接近上限时性能会更抖。

PERCPU Map 用更多内存换更少竞争。CPU 数量非常多时,value 体积需要谨慎控制。

Map 选型别只看功能。估算内存时必须乘上最大条目数、value 对齐开销和可能的 CPU 数量。

3.3 Map 并发控制与数据同步

3.3.1 Per-CPU 隔离

高频统计最怕所有 CPU 更新同一个计数器。

共享计数器即使使用原子加,也会让缓存行在多个 CPU 间来回迁移。每次操作看着只加一,背后却可能触发缓存一致性流量。

Per-CPU Map 让每个 CPU 写自己的 value。读取时再聚合。

这套方案特别适合包数量、字节数、系统调用次数和直方图桶。

不适合的情况也有。连接状态、全局限额、唯一所有者记录不能简单拆成每 CPU 副本。

3.3.2 锁与原子操作

从 Linux 5.1 开始,部分 Map value 可以使用 struct bpf_spin_lock

struct value {struct bpf_spin_lock lock;    __u64 packets;    __u64 bytes;};struct value *v = bpf_map_lookup_elem(&stats, &key);if (v) {    bpf_spin_lock(&v->lock);    v->packets++;    v->bytes += len;    bpf_spin_unlock(&v->lock);}

锁内不能随便调用 helper,临界区也不应该很长。 否则高频事件一来,数据还没统计完,程序先开始排队。

单字段计数优先使用原子操作或 Per-CPU Map。多字段必须一致更新时再考虑 spin lock。

3.3.3 双向数据交互

用户态写 Map,内核态读 Map,适合动态配置。

例如用户态把目标 PID 写入配置 Map,内核程序只追踪该进程。

内核态写 Map,用户态读 Map,适合统计和事件采集。

双向交互时要考虑结构体版本。用户态与 BPF 程序对 value 布局理解不一致,结果往往比直接报错更烦,因为它可能只是读出一堆看似合理的垃圾数据。

四、eBPF 程序类型与内核挂载点

4.1 跟踪观测类程序

4.1.1 kprobe 与 kretprobe

kprobe 可以动态挂到内核函数入口,kretprobe 则观察函数返回。

优点是覆盖面广。只要符号和架构支持,很多内部函数都能追。

缺点是稳定性差。内核内部函数名、参数和实现可能变化,不属于稳定 ABI。 跨版本部署时必须认真处理兼容性。

现在更推荐在可用时使用 fentry 和 fexit。它们依赖 BTF 类型信息,开销通常更低,参数信息也更清楚。

4.1.2 uprobe 与 uretprobe

uprobe 用于用户态程序或动态库函数。

可以追踪 malloc、OpenSSL、数据库客户端、语言运行时和自研业务函数。

挂载时要注意二进制路径、符号偏移、PIE、动态库版本和符号裁剪。函数被编译器内联后,uprobes 可能根本找不到你想追的入口。

sudo bpftrace -e 'uprobe:/usr/lib/x86_64-linux-gnu/libc.so.6:malloc{    @[comm] = count();}'

4.1.3 tracepoint

tracepoint 是内核主动提供的静态埋点。

相较 kprobe,它的接口通常更稳定。系统调用、调度、块设备、网络和文件系统都有大量 tracepoint。

查看现有事件可以使用

sudo find /sys/kernel/tracing/events \    -mindepth 2 -maxdepth 2 -type d | headsudo cat \  /sys/kernel/tracing/events/syscalls/sys_enter_openat/format

4.1.4 perf_event 与 raw_tracepoint

perf_event 可以结合硬件性能计数器、软件事件和定时采样。CPU 火焰图、缓存未命中、分支预测失败等分析经常依赖它。

raw_tracepoint 提供更原始的参数形式,少一层转换,开销更低,但可读性和类型安全也更差。

我的习惯很朴素。能用稳定 tracepoint 就不用 kprobe,能用类型清晰的接口就不碰 raw。 除非性能或者覆盖范围逼得没办法。

4.2 网络处理类程序

4.2.1 XDP

XDP 在网络接收路径早期执行。 程序可以返回 XDP_PASSXDP_DROPXDP_TXXDP_REDIRECT 或 XDP_ABORTED

它适合做快速丢包、DDoS 预过滤、四层负载均衡和报文重定向。

SEC("xdp")int block_udp_53(struct xdp_md *ctx){    void *data = (void *)(long)ctx->data;    void *data_end = (void *)(long)ctx->data_end;struct ethhdr *eth = data;    if ((void *)(eth + 1) > data_end)        return XDP_ABORTED;    if (eth->h_proto != bpf_htons(ETH_P_IP))        return XDP_PASS;struct iphdr *ip = (void *)(eth + 1);    if ((void *)(ip + 1) > data_end)        return XDP_ABORTED;    if (ip->protocol != IPPROTO_UDP)        return XDP_PASS;struct udphdr *udp = (void *)ip + ip->ihl * 4;    if ((void *)(udp + 1) > data_end)        return XDP_ABORTED;    if (udp->dest == bpf_htons(53))        return XDP_DROP;    return XDP_PASS;}

XDP_REDIRECT 可以把数据包导向其他网卡、CPU、devmap、cpumap 或 AF_XDP socket。内核会在 NAPI 批处理中完成重定向队列刷新。

4.2.2 SCHED_CLS 与 SCHED_ACT

tc BPF 的执行位置比 XDP 更靠后,已经能够访问 skb 及更多协议栈元数据。

它适合流量分类、策略执行、封装解封装、重定向和带宽控制。

XDP 更早、更快,但上下文有限。tc 稍晚一些,却更容易处理完整网络语义。

Cilium 会在容器 veth 的 tc ingress 挂载 BPF 程序,用于身份识别、策略执行、服务负载均衡和流量重定向。

4.2.3 sockops 与 sk_lookup

sockops 挂在 cgroup 上,能够观察 TCP 状态变化并调整部分套接字行为。

sk_lookup 介入本地传输层的 socket 查找。程序可以从 SOCKMAP 或 SOCKHASH 中选择目标 socket,再通过 bpf_sk_assign() 改变连接交付对象。

这些程序类型不处理完整数据包,而是在更高层的 socket 语义上工作。服务负载均衡、透明代理和本地连接加速经常会用到。

4.3 安全与系统管控类程序

4.3.1 LSM BPF

LSM BPF 允许程序挂到 Linux Security Module 钩子,实现审计或强制访问控制。

SEC("lsm/file_mprotect")int BPF_PROG(check_mprotect,             struct vm_area_struct *vma,             unsigned long reqprot,             unsigned long prot,             int ret){    if (ret)        return ret;    if ((prot & PROT_WRITE) && (prot & PROT_EXEC)) {        __u32 pid = bpf_get_current_pid_tgid() >> 32;        if (!is_allowed_pid(pid))            return -EPERM;    }    return 0;}

这类程序可以拒绝行为,因此风险比纯观测程序更高。策略错误不会让内核崩溃,却可能让正常业务集体报权限错误。

LSM BPF 借助 BTF 获取上下文类型,验证器还能依据类型检查字段访问。

4.3.2 cgroup BPF

cgroup BPF 能把程序作用范围限制在特定 cgroup。

容器通常对应 cgroup 层级,因此可以按工作负载执行连接控制、socket option 管理、设备访问限制和网络策略。

相比全局钩子后再通过 PID 判断归属,cgroup 挂载更自然,也更不容易漏掉线程和子进程。

4.3.3 进程、文件与网络行为审计

进程执行可以从 sched_process_exec、LSM hook 或系统调用路径观察。

文件行为可以追踪 open、unlink、rename、mprotect 和权限检查。

网络行为可以从 connect、bind、socket lookup、tc、XDP 或 LSM 网络钩子观察。

安全产品一般不会只依赖一个挂载点。入口事件、返回结果、进程身份、容器信息和网络上下文需要组合,才能形成可信事件。

4.4 其他特殊程序类型

4.4.1 调度类与时间类程序

struct_ops 允许 BPF 程序实现由内核定义的操作表。TCP 拥塞控制和 sched_ext 都使用了类似思路。

sched_ext 可以动态加载和卸载 BPF 调度器。发生错误或任务长时间得不到调度时,内核会退出自定义调度器并恢复默认调度路径。

这不是普通业务团队上来就该玩的东西。观测程序写错了,少几条数据。调度器写错了,全公司会一起感受你的代码风格。

4.4.2 Iterator 与 sleepable BPF

BPF iterator 可以遍历 task、cgroup、Map、socket 等内核对象,并通过 seq_file 或用户态读取输出。

open-coded iterator 则把构造、获取下一个元素、销毁这套迭代协议带进普通 BPF 程序。验证器依据迭代器契约判断循环最终会终止。

sleepable BPF 允许程序在特定挂载类型和上下文中调用可能睡眠的 kfunc。它扩展了能力边界,但不能在中断或其他不可睡眠上下文里乱用。

五、eBPF 开发体系与工具链

5.1 主流开发模式

5.1.1 原生 C 开发

内核态 BPF 程序通常使用受限制的 C。

  • • 不能随意调用 libc。
  • • 不能动态分配普通用户态堆内存。
  • • 函数、循环、指针和栈使用都会受到验证器约束。

编译流程一般是

clang -O2 -g \    -target bpf \    -D__TARGET_ARCH_x86 \    -I./include \    -c monitor.bpf.c \    -o monitor.bpf.o

-O2 很重要。不开优化时,Clang 生成的中间代码可能包含验证器难以接受的栈访问和分支。

-g 不只是为了调试。BTF 和 CO-RE 也依赖调试类型信息链路。

LLVM 从 3.7 开始默认提供正式 BPF 后端,可通过 Clang 的 -target bpf 或 llc 的 -march=bpf 生成 BPF 字节码。

5.1.2 BCC

BCC 把 Clang、LLVM 和加载逻辑封装起来,用户可以通过 Python 或 Lua 快速编写工具。

优点是开发快。特别适合临时诊断和教学。

缺点是目标环境通常需要编译器、内核头文件和较大的运行时依赖。程序启动时现场编译,也会增加部署复杂度。

早些年我很喜欢 BCC。复制一段 Python,改个函数名,几分钟就能看数据。后来要把工具塞进几十种发行版、几千台机器,我就没那么快乐了。

5.1.3 libbpf

libbpf 更接近当前标准工程方案。

它加载预编译 BPF ELF,处理 Map、BTF、CO-RE 重定位和挂载。配合 skeleton 后,用户态代码可以直接访问程序、Map、rodata、bss 和 link。

产物小,目标机器不需要携带完整 LLVM。

代价是开发门槛更高。编译系统、BTF、内核配置、架构宏和 skeleton 生成都需要自己理解。

5.1.4 Go 与 Python 封装

Go 常见方案包括 cilium/ebpf、libbpfgo 和 libbpf-rs 类似的跨语言封装思路。

纯 Go 库部署方便,适合云原生 Agent。基于 libbpf 的封装则能继承 CO-RE 和 skeleton 生态。

Python 更适合分析层、控制层和实验工具,不太适合承担高性能事件消费主循环。

语言不是重点。关键在于底层是否正确处理对象生命周期、内存对齐、Per-CPU value、ring buffer 丢失和版本兼容。

5.2 核心工具链

5.2.1 LLVM 与 Clang

Clang 前端负责把受限制 C 转成 LLVM IR,BPF backend 再生成 BPF 指令。

检查字节码可以使用

llvm-objdump -S monitor.bpf.ollvm-objdump -h monitor.bpf.ollvm-readelf -a monitor.bpf.o

优化时不要只看 C 源码行数。反汇编后的 BPF 指令数量、分支结构和 helper 调用次数更有意义。

5.2.2 bpftool

bpftool 是排查 BPF 环境的瑞士军刀。

sudo bpftool feature probe kernelsudo bpftool prog showsudo bpftool prog dump xlated id 18sudo bpftool prog dump jited id 18sudo bpftool map showsudo bpftool map dump id 27sudo bpftool link showsudo bpftool netsudo bpftool btf dump \    file /sys/kernel/btf/vmlinux \    format c > vmlinux.h

dump xlated 能看到验证后的 BPF 指令。

dump jited 能看到 JIT 机器码。

feature probe 比猜内核版本靠谱得多。

5.2.3 perf 与 bpftrace

perf 擅长采样、硬件事件和调用栈。

bpftrace 擅长一行命令快速追踪。

sudo bpftrace -e 'tracepoint:syscalls:sys_enter_openat{    @[comm] = count();}'sudo bpftrace -e 'kprobe:vfs_read{    @start[tid] = nsecs;}kretprobe:vfs_read/@start[tid]/{    @latency = hist(nsecs - @start[tid]);    delete(@start[tid]);}'

bpftrace 很适合验证思路,但复杂生产 Agent 仍建议使用 libbpf 或成熟框架。

5.3 libbpf 最佳实践

5.3.1 CO-RE 的跨内核兼容原理

CO-RE 全称 Compile Once–Run Everywhere。

它不是让任何程序在任何内核上无条件运行。它解决的是内核类型布局变化问题。

编译时,Clang 把字段访问的重定位信息写入 BTF.ext。运行时,libbpf 读取目标内核的 BTF,找到对应类型和字段,再调整指令中的偏移。

例如 task_struct 中字段位置改变,只要目标字段仍存在且语义兼容,程序就不必重新编译。

内核通过 /sys/kernel/btf/vmlinux 暴露权威 BTF。libbpf 将程序记录的类型和重定位信息与运行内核 BTF 匹配,再修正字段访问。

struct task_struct *task;__u32 tgid;task = (struct task_struct *)bpf_get_current_task_btf();tgid = BPF_CORE_READ(task, tgid);

5.3.2 BTF 类型机制

BTF 是紧凑的类型元数据格式。

它可以描述整数、指针、数组、结构体、联合体、枚举、函数原型和变量,也能携带函数信息、源码行信息和 CO-RE relocation。

生成 vmlinux.h 的常用方式如下。

bpftool btf dump \    file /sys/kernel/btf/vmlinux \    format c > vmlinux.h

这样 BPF 程序通常不必包含一堆容易冲突的内核头文件。

5.3.3 全局变量与标准化异常处理

libbpf 会把只读全局变量放入 rodata,把初始化可写变量放入 data,未初始化变量放入 bss。

用户态可以在加载前设置 rodata。

const volatile __u32 target_tgid = 0;volatile __u64 dropped_events = 0;

用户态代码

skel = monitor_bpf__open();skel->rodata->target_tgid = pid;if (monitor_bpf__load(skel))    goto cleanup;if (monitor_bpf__attach(skel))    goto cleanup;

错误处理不要偷懒。open、load、attach、ring buffer 创建、poll 都可能失败。每一步都要输出明确上下文,不然线上只留下一句“load failed”,跟没说差不多。

六、eBPF 核心应用场景

6.1 系统性能观测与调优

6.1.1 调度、IO 与内存瓶颈

CPU 使用率低不代表任务没卡。

线程可能在等待调度,可能频繁缺页,可能被锁阻塞,也可能卡在块设备队列。

调度延迟可以通过记录 runnable 与实际运行时间差分析。

块 IO 可以在请求插入、下发、完成等 tracepoint 记录时间。

内存问题可以观察分配、回收、缺页、OOM 和 slab 行为。

eBPF 的优势在于把这些不同子系统的事件按 PID、cgroup、容器和调用栈关联起来。

6.1.2 系统调用耗时与异常追踪

下面是一个简化的 openat 延迟统计思路。

struct {    __uint(type, BPF_MAP_TYPE_HASH);    __uint(max_entries, 16384);    __type(key, __u64);    __type(value, __u64);} start SEC(".maps");SEC("tracepoint/syscalls/sys_enter_openat")int enter_openat(struct trace_event_raw_sys_enter *ctx){    __u64 tid = bpf_get_current_pid_tgid();    __u64 now = bpf_ktime_get_ns();    bpf_map_update_elem(&start, &tid, &now, BPF_ANY);    return 0;}SEC("tracepoint/syscalls/sys_exit_openat")int exit_openat(struct trace_event_raw_sys_exit *ctx){    __u64 tid = bpf_get_current_pid_tgid();    __u64 *begin = bpf_map_lookup_elem(&start, &tid);    if (!begin)        return 0;    __u64 delta = bpf_ktime_get_ns() - *begin;    record_latency(delta, ctx->ret);    bpf_map_delete_elem(&start, &tid);    return 0;}

真实工程还要处理线程退出、Map 容量、重复进入、事件采样和丢失统计。

6.1.3 全链路性能监控

所谓全链路,并不是挂一个 kprobe 就能自动拥有分布式追踪。

通常需要把用户态协议、socket、TCP、调度、系统调用和容器元数据串起来。

连接四元组、进程身份、cgroup ID、namespace、时间戳和 trace ID 都可能成为关联键。

零侵入 APM 最难的不是采到数据,而是准确还原请求边界。HTTP/1.1、HTTP/2、TLS、连接复用、异步框架、用户态网络栈都会增加难度。

6.2 高性能网络

6.2.1 XDP 转发、限流与 DDoS 防护

XDP 可以在数据包进入完整协议栈前执行过滤。

简单黑名单可通过 LPM_TRIE 查询源地址。

速率限制可以使用 Per-CPU Map 记录时间窗口,再对超过阈值的流量执行 XDP_DROP

负载均衡可以改写目标地址和端口,再通过 devmap 或其他重定向机制转发。

真正的难点往往不在转发代码,而在连接一致性、校验和、MTU、分片、邻居解析、路由、健康检查和状态同步。

数据包跑得快很容易。跑得快还不丢业务语义,就开始夯爆脑壳了。

6.2.2 Cilium 的底层思路

Cilium 将 BPF 程序挂到容器 veth、主机接口、cgroup socket hook 等位置。

用户态 Agent 监听 Kubernetes 对象变化,再把身份、服务后端、策略和连接状态写入 BPF Map。

数据包进入节点后,内核态数据面通过 Map 查询完成身份识别、策略判断、服务选择和重定向。

Cilium 的 kube-proxy replacement 会利用 socket 层和 tc 层 BPF hook 实现服务地址转换与负载均衡。其官方文档也明确说明 socket load balancer 依赖 cgroup BPF hook。

这类架构把低频控制逻辑留在用户态,把高频转发逻辑放进内核。

6.2.3 无 Sidecar 流量管控

传统服务网格通常为每个 Pod 注入 Sidecar。流量需要经过用户态代理,带来额外进程、内存和路径开销。

eBPF 可以在 socket、cgroup 或 tc 路径提前完成透明流量重定向,减少 iptables 链路,甚至把部分四层能力直接留在内核。

不过“无 Sidecar”不等于完全没有代理。

HTTP、gRPC、Kafka 等七层协议解析仍可能需要 Envoy 一类用户态代理。Cilium 官方方案也是由 eBPF 负责 IP、TCP、UDP 等内核数据面,再把需要七层处理的流量透明交给 Envoy。

6.3 内核安全与行为审计

6.3.1 恶意进程与文件操作

安全 Agent 可以观察进程执行链、父子关系、命令行、凭据变化、namespace 切换和文件访问。

单一事件通常不能说明恶意。

  • • 一个进程调用 mprotect(PROT_EXEC) 可能是 JIT,也可能是载荷执行。
  • • 进程读取 /etc/shadow 可能是合法认证程序,也可能是凭据窃取。

真正有价值的是上下文组合。 谁启动了它,它在哪个容器,之前访问了什么文件,之后连接了哪个地址。

6.3.2 网络权限管控与入侵拦截

cgroup connect hook 可以在连接建立前检查目标地址。

LSM socket hook 可以实施更通用的安全策略。

tc 与 XDP 适合数据包层过滤。

在内核中直接拒绝的优势是路径短。缺点也明显,策略更新和回滚必须极其可靠。

生产系统最好保留审计模式、强制模式和紧急关闭开关。别一上来就 return -EPERM,勇气和鲁莽只差一个回滚方案。

6.3.3 容器安全隔离

容器安全不是只看 namespace。

eBPF 可以读取 cgroup ID、namespace 标识、进程凭据、容器网络和文件行为,再把内核事件映射到 Pod、工作负载和镜像。

Falco 的 modern eBPF probe 会采集内核事件并交给用户态规则引擎。当前官方文档要求目标环境具备 BPF ring buffer 与 BTF,通常 5.8 及以上内核能够满足,但发行版回移特性后也可能支持更旧版本。

6.4 云原生与可观测性生态

6.4.1 Kubernetes 精细化监控

传统节点监控容易停留在 CPU、内存和网络吞吐量。

eBPF 可以继续下钻到 Pod 的系统调用、TCP 重传、DNS 延迟、文件 IO、调度延迟和应用协议。

cgroup 是把内核事件映射到 Kubernetes 工作负载的重要桥梁。

用户态 Agent 再结合 Kubernetes API,把 cgroup ID 转换成 namespace、Pod、容器和 workload。

6.4.2 零侵入 APM

零侵入 APM 会从 socket 或用户态函数观察请求。

明文 HTTP 可以解析请求方法、路径、状态码和耗时。

TLS 流量可能需要在加密前后的库函数处使用 uprobe,或者依赖语言运行时和 TLS 库版本适配。

这也是零侵入 APM 的现实边界。它不是在任何语言、任何协议、任何加密实现上都能自动成功。

6.4.3 Pixie、Tetrate 与 Falco

Pixie 在每个 Kubernetes 节点部署 PEM,利用 eBPF 自动采集网络、资源、应用请求和性能数据,再在集群内完成存储和查询。官方架构中,PEM 负责节点采集,Vizier 负责集群查询与管理。

Falco 更偏运行时安全。eBPF probe 负责采集系统调用等内核事件,用户态 Falco 引擎根据规则判断可疑行为。

严格说,Tetrate 是公司与服务网格产品生态,并不是一个单独的 eBPF 开源项目。它的方案会把 eBPF 用于 Kubernetes 网络、可观测性和流量路径优化,同时继续依赖 Istio、Envoy 等七层能力。

如果原大纲里的 “Tetrate” 实际想表达的是 “Tetragon”,那就更典型。Tetragon 是 Cilium 生态里的安全观测与运行时策略项目,核心思路是把过滤和部分策略执行提前到 eBPF 内核路径。

七、eBPF 性能优化、稳定性与问题排查

7.1 性能优化

7.1.1 JIT 与指令精简

生产环境应检查 JIT 是否开启。

同时观察程序运行统计。

sudo sysctl net.core.bpf_jit_enable=1sudo sysctl kernel.bpf_stats_enabled=1sudo bpftool prog show

优化时优先做这些事。

  • • 把 PID、cgroup、协议和端口过滤放在前面。
  • • 不需要的字段别复制。
  • • 避免在每个事件里读取长字符串。
  • • 把复杂聚合交给用户态。
  • • 减少 helper 调用。
  • • 减少深层分支和重复 Map lookup。

不要为了代码看起来优雅,把热路径拆成十几个函数。编译器内联后的真实指令才是最后答案。

7.1.2 Map 选型

  • • 高频计数用 Per-CPU Array 或 Per-CPU Hash。
  • • 稀疏状态用 Hash。
  • • 容量不确定且允许淘汰时考虑 LRU Hash。
  • • IP 前缀策略用 LPM Trie。
  • • 事件流优先评估 Ring Buffer。
  • • 配置热切换可以考虑 Map in Map。

Map 过大不仅占内存,还会影响缓存局部性和遍历时间。Map 太小则会出现更新失败、LRU 抖动或事件关联缺失。

7.1.3 Per-CPU 与中断上下文

程序可能运行在不同上下文,包括进程上下文、软中断、NAPI 或其他不可睡眠路径。

热路径中不要做大对象复制,也不要假设当前总有普通进程语义。

Per-CPU Map 能减少跨 CPU 竞争,但用户态聚合成本会上升。极高 CPU 数量下,这部分开销也不能假装看不见。

7.1.4 ring buffer 替代 perf buffer

ring buffer 的共享设计能够改善内存利用率,也能保持跨 CPU 事件预留顺序。

但它不一定在所有场景都更快。

共享生产者位置仍然可能发生竞争。每 CPU perf buffer 在极端并行写入下具有天然隔离优势。

选择时要看事件大小、CPU 数量、顺序要求、内存预算和消费者速度,而不是看到新类型就立刻迁移。

7.2 稳定性与安全保障

7.2.1 验证器避免越权和死循环

验证器能够防止未初始化栈读取、非法指针访问、越界数据包访问和不可证明终止的控制流。

它并不能保证业务逻辑正确。

  • • 一个通过验证的 XDP 程序仍然可以把所有数据包合法地 XDP_DROP
  • • 一个通过验证的 LSM 程序仍然可以合法地拒绝所有文件访问。

安全验证解决的是“不会非法破坏内核内存”,不是“不会把业务搞挂”。

7.2.2 异常降级与资源熔断

观测系统需要考虑自身故障。

  • • ring buffer 满时应该统计丢失,而不是继续做无意义工作。
  • • Map 更新失败时要记录计数。
  • • 用户态消费者断开后,内核程序应能够低成本继续或自动停用。
  • • 高频事件可以采样。
  • • 策略执行系统应保留审计模式和紧急 bypass。

sched_ext 的设计值得参考。自定义调度器异常时,内核会恢复默认调度行为。

7.2.3 内核崩溃与内存泄漏规避

普通 eBPF 程序受到验证器保护,风险远低于内核模块,但并不表示整个生态没有风险。

JIT、helper、kfunc、驱动和内核本身都可能存在缺陷。

生产环境应使用维护中的内核版本,及时升级安全补丁,并限制非特权 BPF。

程序和 Map 的 pin 需要统一命名空间与清理策略。

升级 Agent 时要明确旧 link、旧 Map 和新程序之间的引用关系,避免一边更新,一边留下几 GB 的“历史遗迹”。

7.3 常见问题与排查

7.3.1 加载失败

建议按以下路径检查。

uname -rgrep -E 'CONFIG_BPF|CONFIG_BPF_SYSCALL|CONFIG_BPF_JIT|CONFIG_DEBUG_INFO_BTF' \    /boot/config-$(uname -r)ls -l /sys/kernel/btf/vmlinuxsudo bpftool feature probe kernelsudo dmesg | tail -n 100

再打开完整 verifier log。

若是 CO-RE relocation 失败,检查目标字段是否存在,类型名称是否匹配,内核是否暴露 BTF。

若是 helper 不支持,确认当前程序类型是否允许该 helper。helper 不是全局通用函数,不同程序类型的能力列表不同。

7.3.2 数据丢失、挂载失效与性能抖动

数据丢失先看消费者是不是太慢。

然后检查 ring buffer 容量、事件大小、采样率、CPU 数量和用户态 poll 逻辑。

挂载失效要看 link 是否被销毁,原进程是否退出,对象是否 pin,目标网络设备或 cgroup 是否被重建。

性能抖动需要关联程序运行次数、平均运行时间、Map 容量、LRU 淘汰、CPU 热点和用户态消费延迟。

bpftool prog show 在启用 BPF stats 后可以提供运行次数和累计运行时间。它不完美,但比纯猜强多了。

7.3.3 内核版本兼容

别把兼容性写成一句 kernel >= 5.8 就交差。

发行版可能回移 BTF、ring buffer 或特定 helper。

同样的版本号,不同厂商内核配置也可能不同。

CO-RE 只能解决类型布局变化,不能凭空创造目标内核没有的 helper、kfunc、Map 类型和挂载点。

成熟程序通常需要能力探测、弱重定位、自动降级和多挂载方案。

if (bpf_program__autoload(skel->progs.fentry_handler)) {    /* 尝试 fentry */}/* 不支持时切换 tracepoint 或 kprobe */

工程上要把“最低支持版本”改写成“最低能力集合”。

八、eBPF 新特性

8.1 新一代核心能力

8.1.1 Sleepable 与异步执行

sleepable BPF 允许特定程序在可睡眠上下文调用带 KF_SLEEPABLE 约束的 kfunc。

这让 BPF 可以参与更复杂的安全、文件系统和对象管理逻辑。

不过它不会让所有 BPF 程序突然变成普通内核线程。XDP、tc、硬件事件等原子或高频上下文仍然不能随便睡眠。

8.1.2 模块化、函数调用与复用

BPF 子程序、全局函数、kfunc、struct_ops 和 tail call 正在共同提升模块化能力。

helper 是稳定、编号化的通用接口。

kfunc 更贴近具体内核子系统,并由 BTF 描述类型。内核模块也可以向 BPF 程序暴露受控 kfunc 与带引用语义的对象。

这种能力很强,但兼容性要求更高。kfunc 并不天然拥有与 UAPI 相同的长期稳定承诺。

8.1.3 类型安全与调试升级

BTF 让内核类型、函数原型和源码行信息进入 BPF 工具链。

验证器对对象引用、可信指针、可空指针、dynptr 和 iterator 状态的理解也越来越深。

新的 BPF signing 文档已经进入 Linux next 文档树,目标是对 BPF 程序进行密码学签名,并让 LSM 根据签名结果和加载上下文执行策略。这个方向说明 BPF 的治理重点正在从“程序是否安全”继续走向“谁发布、谁授权、能否审计”。

8.2 生态发展

8.2.1 开源工具与企业落地

网络领域有 Cilium、Katran、XDP 工具链。

可观测性领域有 Pixie、Parca、Inspektor Gadget 和各类 eBPF APM。

安全领域有 Falco、Tetragon、Tracee。

调度领域有 sched_ext 及其 scx 工具生态。

eBPF 已经不再是少数内核开发者的实验项目,而是云原生节点 Agent、网络数据面和运行时安全系统的重要基础。

8.2.2 标准化方向

BPF 的影响已经超出 Linux 内核单一实现。

Linux 内核文档中已经提供 BPF ABI 推荐约定,并记录与 IETF 标准化相关的工作。

标准化有助于工具链、指令集和可移植对象格式形成更稳定边界。

不过别太早幻想“一份 BPF 程序跑遍所有操作系统”。内核对象、hook、helper 和安全模型仍然具有强烈的平台特性。

8.3 局限与演进方向

8.3.1 当前技术边界

eBPF 不是内核模块的完全替代品。

  • • 程序能力由内核预先开放的 hook、helper、kfunc 和 Map 类型决定。
  • • 验证器复杂度仍然是开发门槛。
  • • 内核版本和发行版差异依旧存在。
  • • 高频追踪会产生真实开销。
  • • 加密协议和用户态网络栈可能绕过常见观测位置。
  • • 安全产品还必须处理数据真实性、事件丢失、时间顺序和策略回滚。

这些边界不影响 eBPF 强大。反而是承认边界后,系统设计才不会上头。

总结

eBPF 最容易被误解成一种“在 Linux 内核里运行 C 代码的技术”。

这样说不算错,但漏掉了最重要的东西。

它真正提供的是一套受约束的内核可编程模型。

  • • 程序通过 Clang 和 LLVM 编译成 BPF 字节码。
  • • 加载器解析 ELF、BTF、Map 和重定位。
  • • 验证器证明指针、边界、控制流和对象生命周期处于允许范围。
  • • JIT 把程序转换为机器指令。
  • • 挂载点决定程序在什么事件发生时运行。
  • • Map 保存状态并连接用户态。
  • • helper 与 kfunc 决定程序能调用哪些内核能力。

这几部分缺一个,eBPF 都不会成为今天的 eBPF。

从工程实践看,最重要的也不是背下所有程序类型和 Map 类型,而是形成一套稳定思维。

  • • 先确定事件发生在哪里。
  • • 再选择最稳定、最靠近目标语义的挂载点。
  • • 让内核态程序只做必要的过滤、关联和采集。
  • • 把复杂计算与持久化留给用户态。
  • • 用 Per-CPU 结构减少竞争。
  • • 用 ring buffer 处理事件流。
  • • 用 BTF 与 CO-RE 应对结构变化。
  • • 用 feature probe 判断能力,不迷信版本号。
  • • 把 verifier log 当证据链,不要靠玄学改代码。
  • • 给强制策略留审计、降级和紧急关闭路径。

最新文章

随机文章

基本 文件 流程 错误 SQL 调试
  1. 请求信息 : 2026-08-21 17:48:03 HTTP/2.0 GET : https://f.mffb.com.cn/a/507984.html
  2. 运行时间 : 0.205950s [ 吞吐率:4.86req/s ] 内存消耗:4,944.86kb 文件加载:140
  3. 缓存信息 : 0 reads,0 writes
  4. 会话信息 : SESSION_ID=a9867671c12e0b2483bef331b9e775de
  1. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/public/index.php ( 0.79 KB )
  2. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/autoload.php ( 0.17 KB )
  3. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_real.php ( 2.49 KB )
  4. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/platform_check.php ( 0.90 KB )
  5. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/ClassLoader.php ( 14.03 KB )
  6. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_static.php ( 4.90 KB )
  7. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper.php ( 8.34 KB )
  8. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/helper.php ( 2.19 KB )
  9. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/helper.php ( 1.47 KB )
  10. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/stubs/load_stubs.php ( 0.16 KB )
  11. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Exception.php ( 1.69 KB )
  12. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Facade.php ( 2.71 KB )
  13. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/deprecation-contracts/function.php ( 0.99 KB )
  14. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap.php ( 8.26 KB )
  15. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap80.php ( 9.78 KB )
  16. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/Resources/functions/dump.php ( 1.49 KB )
  17. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-dumper/src/helper.php ( 0.18 KB )
  18. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/VarDumper.php ( 4.30 KB )
  19. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/App.php ( 15.30 KB )
  20. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Container.php ( 15.76 KB )
  21. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/container/src/ContainerInterface.php ( 1.02 KB )
  22. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/provider.php ( 0.19 KB )
  23. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Http.php ( 6.04 KB )
  24. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Str.php ( 7.29 KB )
  25. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Env.php ( 4.68 KB )
  26. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/common.php ( 0.03 KB )
  27. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/helper.php ( 18.78 KB )
  28. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Config.php ( 5.54 KB )
  29. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/app.php ( 0.95 KB )
  30. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cache.php ( 0.78 KB )
  31. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/console.php ( 0.23 KB )
  32. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cookie.php ( 0.56 KB )
  33. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/database.php ( 2.48 KB )
  34. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Env.php ( 1.67 KB )
  35. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/filesystem.php ( 0.61 KB )
  36. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/lang.php ( 0.91 KB )
  37. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/log.php ( 1.35 KB )
  38. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/middleware.php ( 0.19 KB )
  39. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/route.php ( 1.89 KB )
  40. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/session.php ( 0.57 KB )
  41. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/trace.php ( 0.34 KB )
  42. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/view.php ( 0.82 KB )
  43. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/event.php ( 0.25 KB )
  44. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Event.php ( 7.67 KB )
  45. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/service.php ( 0.13 KB )
  46. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/AppService.php ( 0.26 KB )
  47. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Service.php ( 1.64 KB )
  48. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Lang.php ( 7.35 KB )
  49. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/lang/zh-cn.php ( 13.70 KB )
  50. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/Error.php ( 3.31 KB )
  51. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/RegisterService.php ( 1.33 KB )
  52. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/services.php ( 0.14 KB )
  53. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/PaginatorService.php ( 1.52 KB )
  54. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ValidateService.php ( 0.99 KB )
  55. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ModelService.php ( 2.04 KB )
  56. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Service.php ( 0.77 KB )
  57. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Middleware.php ( 6.72 KB )
  58. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/BootService.php ( 0.77 KB )
  59. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Paginator.php ( 11.86 KB )
  60. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/Validate.php ( 63.20 KB )
  61. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Model.php ( 23.55 KB )
  62. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Attribute.php ( 21.05 KB )
  63. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/AutoWriteData.php ( 4.21 KB )
  64. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Conversion.php ( 6.44 KB )
  65. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/DbConnect.php ( 5.16 KB )
  66. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/ModelEvent.php ( 2.33 KB )
  67. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/RelationShip.php ( 28.29 KB )
  68. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Arrayable.php ( 0.09 KB )
  69. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Jsonable.php ( 0.13 KB )
  70. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/contract/Modelable.php ( 0.09 KB )
  71. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Db.php ( 2.88 KB )
  72. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/DbManager.php ( 8.52 KB )
  73. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Log.php ( 6.28 KB )
  74. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Manager.php ( 3.92 KB )
  75. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerTrait.php ( 2.69 KB )
  76. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerInterface.php ( 2.71 KB )
  77. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cache.php ( 4.92 KB )
  78. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/simple-cache/src/CacheInterface.php ( 4.71 KB )
  79. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Arr.php ( 16.63 KB )
  80. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/driver/File.php ( 7.84 KB )
  81. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/Driver.php ( 9.03 KB )
  82. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php ( 1.99 KB )
  83. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/Request.php ( 0.09 KB )
  84. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Request.php ( 55.78 KB )
  85. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/middleware.php ( 0.25 KB )
  86. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Pipeline.php ( 2.61 KB )
  87. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/TraceDebug.php ( 3.40 KB )
  88. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/middleware/SessionInit.php ( 1.94 KB )
  89. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Session.php ( 1.80 KB )
  90. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/driver/File.php ( 6.27 KB )
  91. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php ( 0.87 KB )
  92. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/Store.php ( 7.12 KB )
  93. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Route.php ( 23.73 KB )
  94. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleName.php ( 5.75 KB )
  95. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Domain.php ( 2.53 KB )
  96. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleGroup.php ( 22.43 KB )
  97. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Rule.php ( 26.95 KB )
  98. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleItem.php ( 9.78 KB )
  99. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/route/app.php ( 1.72 KB )
  100. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Route.php ( 4.70 KB )
  101. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/dispatch/Controller.php ( 4.74 KB )
  102. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Dispatch.php ( 10.44 KB )
  103. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/controller/Index.php ( 4.81 KB )
  104. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/BaseController.php ( 2.05 KB )
  105. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/facade/Db.php ( 0.93 KB )
  106. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/connector/Mysql.php ( 5.44 KB )
  107. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/PDOConnection.php ( 52.47 KB )
  108. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Connection.php ( 8.39 KB )
  109. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/ConnectionInterface.php ( 4.57 KB )
  110. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/builder/Mysql.php ( 16.58 KB )
  111. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Builder.php ( 24.06 KB )
  112. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseBuilder.php ( 27.50 KB )
  113. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Query.php ( 15.71 KB )
  114. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseQuery.php ( 45.13 KB )
  115. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php ( 7.43 KB )
  116. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php ( 3.26 KB )
  117. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php ( 20.07 KB )
  118. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ParamsBind.php ( 3.66 KB )
  119. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ResultOperation.php ( 7.01 KB )
  120. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/WhereQuery.php ( 19.37 KB )
  121. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php ( 7.11 KB )
  122. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php ( 2.63 KB )
  123. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/Transaction.php ( 2.77 KB )
  124. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/driver/File.php ( 5.96 KB )
  125. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php ( 0.86 KB )
  126. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/Channel.php ( 3.89 KB )
  127. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/event/LogRecord.php ( 1.02 KB )
  128. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/Collection.php ( 16.47 KB )
  129. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/View.php ( 1.70 KB )
  130. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/View.php ( 4.39 KB )
  131. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Response.php ( 8.81 KB )
  132. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/response/View.php ( 3.29 KB )
  133. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cookie.php ( 6.06 KB )
  134. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-view/src/Think.php ( 8.38 KB )
  135. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php ( 1.60 KB )
  136. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/Template.php ( 46.61 KB )
  137. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/driver/File.php ( 2.41 KB )
  138. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/contract/DriverInterface.php ( 0.86 KB )
  139. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/runtime/temp/067d451b9a0c665040f3f1bdd3293d68.php ( 11.98 KB )
  140. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Html.php ( 4.42 KB )
  1. CONNECT:[ UseTime:0.001164s ] mysql:host=127.0.0.1;port=3306;dbname=f_mffb;charset=utf8mb4
  2. SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.001497s ]
  3. SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.000709s ]
  4. SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.000696s ]
  5. SHOW FULL COLUMNS FROM `set` [ RunTime:0.001371s ]
  6. SELECT * FROM `set` [ RunTime:0.000632s ]
  7. SHOW FULL COLUMNS FROM `article` [ RunTime:0.001474s ]
  8. SELECT * FROM `article` WHERE `id` = 507984 LIMIT 1 [ RunTime:0.001318s ]
  9. UPDATE `article` SET `lasttime` = 1787305683 WHERE `id` = 507984 [ RunTime:0.018345s ]
  10. SELECT * FROM `fenlei` WHERE `id` = 67 LIMIT 1 [ RunTime:0.000709s ]
  11. SELECT * FROM `article` WHERE `id` < 507984 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.001288s ]
  12. SELECT * FROM `article` WHERE `id` > 507984 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.001172s ]
  13. SELECT * FROM `article` WHERE `id` < 507984 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.001961s ]
  14. SELECT * FROM `article` WHERE `id` < 507984 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.001769s ]
  15. SELECT * FROM `article` WHERE `id` < 507984 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.002510s ]
0.209625s