news 2026/6/4 18:04:28

基于eBPF的零开销Agent Harness可观测性

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于eBPF的零开销Agent Harness可观测性

基于eBPF的零开销Agent Harness可观测性:从内核“偷窥”到全栈可视化的革命


一、 引言 (Introduction)

1.1 钩子 (The Hook)

你有没有遇到过这样的场景?

深夜三点,你的公司核心电商应用的订单转化率骤降30%,告警邮件炸了你的手机。你第一时间打开Prometheus查看CPU、内存、磁盘IO的指标——正常得离谱;再打开Jaeger找追踪——核心服务的平均延迟明明标了50ms,但用户端反馈是“支付页面加载30秒还在转圈”;最后你翻遍了ELK里的所有应用日志——没有ERROR,甚至连WARN都少得可怜,只有一堆“INFO: Payment processing initiated”这种没用的流水账。

你挠破了头,重启了应用容器——没用;扩容了K8s节点——没用;升级了中间件版本——还是没用。直到运维同事随手敲了个sar -n DEV 1,发现某个Pod的入站流量居然全是TCP重传包!原来核心支付服务所在的宿主机网卡队列被某个后台日志收集Agent打满了,导致正常业务包根本传不进去。

那你有没有想过,如果我们能在内核层面“悄无声息”地监控网卡队列、CPU调度、网络重传、内存分配这些底层细节,而不是依赖那些消耗大量业务资源的用户态Agent,刚才的问题是不是5分钟就能定位,甚至提前2小时就收到预警?

再换个场景:你是一家金融公司的SRE,根据监管要求,你必须审计所有容器里的进程执行、文件读写、网络连接操作。但传统的审计工具(比如auditd)要么性能损耗太大(开启后业务CPU飙升20%-50%,根本不敢在生产环境全量开),要么只能监控宿主机,无法穿透容器隔离;而云原生可观测性工具(比如Falco)虽然用了eBPF,但功能太聚焦安全,做全栈性能/可用性可观测性时要么数据不全,要么需要部署多个Agent(Falco管安全,Prometheus Node Exporter管指标,SkyWalking Agent管追踪,Filebeat管日志),数据碎片化严重,而且多个Agent加起来的性能损耗可能比一个auditd还大。

那你有没有想过,有没有一种技术,可以让我们只部署一个「内核级的、零业务感知的、几乎没有性能损耗的统一可观测性框架」——这个框架就像Agent的“Harness(马具/ harness/ harness框架)”一样,能把底层内核的全量可观测数据(指标、日志、追踪的原生种子数据**)“套”出来,然后按需喂给上层的Falco、Prometheus、SkyWalking、ELK这些工具,同时上层工具再也不用自己写钩子、埋点、收集数据,只需要专注于分析和展示?**

没错,这就是今天我们要聊的核心主题:基于eBPF的零开销Agent Harness可观测性


1.2 定义问题/阐述背景 (The “Why”)

1.2.1 传统可观测性的三大“原罪”

在深入eBPF之前,我们必须先搞清楚:为什么我们需要“零开销Agent Harness”?传统可观测性到底哪里出了问题?

根据CNCF 2024年《云原生可观测性调查报告》,全球有超过87%的企业正在使用至少3种云原生可观测性工具,超过62%的企业正在使用5种以上。但与此同时,有高达79%的企业认为“可观测性工具的性能损耗是生产环境最大的痛点”,有68%的企业认为“数据碎片化严重,无法实现全栈根因分析”,还有57%的企业认为“埋点、部署、维护可观测性Agent的成本太高”。

这三大痛点,本质上就是传统可观测性的“三大原罪”:

  1. 原罪一:高侵入性、高性能损耗(“偷业务资源的小偷”)
    传统可观测性主要依赖用户态埋点(Instrumentation)用户态采样(Sampling)

    • 埋点:无论是手动埋点(比如在Java代码里加Span.current())还是自动埋点(比如用SkyWalking的Java Agent字节码注入),都会修改业务代码的执行流程,增加额外的CPU、内存、网络开销——根据CNCF的测试数据,自动埋点的性能损耗通常在5%-30%之间,手动埋点如果写得不好甚至可能达到50%以上;
    • 采样:为了降低性能损耗,很多企业会对追踪数据进行采样(比如1%的采样率)——但这又会导致“长尾问题”(比如只在0.5%的请求里出现的支付超时,采样率1%可能刚好漏掉,或者只收集到很少的数据,根本无法定位);
    • 用户态钩子:还有一些传统工具(比如Node Exporter的某些插件、Filebeat的某些收集器)会用ptracekprobes的旧版本(非eBPF实现的)、或者读取/proc文件系统来收集数据——ptrace的性能损耗极高(每执行一条指令都要陷入内核,开启后业务CPU可能飙升100%),/proc文件系统本质上是内核导出的“只读快照”,读取频率高了也会消耗大量内核CPU(比如每秒读1000次/proc/[pid]/stat,内核CPU可能增加5%-10%)。
  2. 原罪二:数据碎片化严重(“盲人摸象的困境”)
    传统可观测性工具通常是“各司其职”的:

    • 指标工具(Prometheus、Grafana、InfluxDB):负责收集和展示CPU、内存、磁盘IO、网络IO这些“宏观”指标;
    • 追踪工具(Jaeger、Zipkin、SkyWalking):负责收集和展示请求的“微观”调用链;
    • 日志工具(ELK、Loki、PLG):负责收集和展示应用的“文本”日志;
    • 安全工具(Falco、Trivy、Aqua):负责收集和展示应用的“安全”事件。

    这些工具的数据格式、时间戳精度、数据来源都不一样——指标数据通常是1秒/10秒/1分钟的聚合数据,时间戳精度到秒;追踪数据通常是纳秒级的原始数据,但采样率低;日志数据通常是毫秒级的文本数据,但可能没有统一的TraceID;安全工具的数据通常是内核级的原始数据,但只关注安全相关的事件。

    这就导致了一个问题:当出现故障时,你需要在多个工具之间来回切换,手动关联数据——比如先用Prometheus找到CPU飙升的时间点,再用Jaeger找到那个时间点附近的请求,再用ELK找到那些请求的日志,再用Falco看看有没有安全事件——整个过程可能需要几十分钟甚至几个小时,而且很容易漏掉关键线索。

  3. 原罪三:部署、维护成本高(“养不起的工具链”)
    传统可观测性工具链的部署和维护成本非常高:

    • 部署成本:你需要在每个K8s节点上部署至少3-5个DaemonSet(Node Exporter、Falco、Filebeat、SkyWalking OAP的 sidecar?不对,SkyWalking的Java Agent是sidecar或者静态注入,但OAP是集中式的),在每个应用Pod里部署至少2-3个sidecar(Filebeat收集容器日志、SkyWalking Agent做字节码注入、Envoy做服务网格?不对,Envoy虽然也算可观测性的一部分,但主要是服务治理);
    • 维护成本:你需要定期升级这些Agent的版本,修复安全漏洞,调整配置(比如采样率、日志收集路径、指标收集维度),排查Agent自身的故障(比如Agent OOM、Agent卡死、Agent和后端断开连接);
    • 学习成本:你需要学习每个工具的使用方法、配置语法、API接口——PromQL、LogQL、Jaeger的查询界面、SkyWalking的拓扑图,这些都是有学习门槛的;
    • 成本开销:除了人力成本,还有硬件成本和云服务成本——你需要部署大量的Prometheus Server、Grafana、Elasticsearch、Jaeger Collector、SkyWalking OAP这些后端组件,这些组件需要消耗大量的CPU、内存、磁盘IO;如果用云服务厂商的可观测性服务(比如AWS CloudWatch、GCP Cloud Monitoring、阿里云ARMS),成本可能更高——根据AWS的定价,CloudWatch Logs的存储成本是0.03美元/GB/月,数据摄入成本是0.50美元/GB,指标数据的摄入成本是0.30美元/百万个指标,这对于一个有1000个节点、10000个Pod的企业来说,每月的可观测性成本可能高达数万美元甚至数十万美元。
1.2.2 eBPF:内核级可观测性的“银色子弹”?

就在传统可观测性陷入“三大原罪”的泥潭时,eBPF(extended Berkeley Packet Filter)技术横空出世,被誉为“内核级可观测性的银色子弹”、“云原生可观测性的未来”。

那么,eBPF到底是什么?它为什么能解决传统可观测性的问题?

简单来说,eBPF是一个运行在Linux内核中的“迷你虚拟机”——它允许用户在内核的“安全沙箱”里运行自定义的程序,而不需要修改内核源代码、不需要加载内核模块(LKM)。

eBPF程序可以挂载到内核的各种“钩子点”(Hook Points)上:

  • 网络钩子点:XDP(eXpress Data Path,网卡驱动层)、TC(Traffic Control,网络协议栈层)、socket钩子点;
  • 内核函数钩子点:kprobe(内核函数入口)、kretprobe(内核函数返回)、fentry/fexit(比kprobe/kretprobe更安全、更高效的内核函数钩子点,Linux 5.5+引入);
  • 用户态函数钩子点:uprobe(用户态函数入口)、uretprobe(用户态函数返回);
  • 跟踪点钩子点:tracepoint(内核预定义的、稳定的钩子点,比如sched_switchnetif_receive_skb);
  • perf事件钩子点:perf_event(硬件性能计数器事件,比如CPU cycles、cache misses)。

当内核执行到这些钩子点时,就会触发对应的eBPF程序执行——eBPF程序可以收集内核的各种数据(比如进程ID、线程ID、时间戳、函数参数、函数返回值、网络包内容、内存分配信息),然后把这些数据存储到eBPF的“映射表”(Maps)里,或者通过“ perf事件缓冲区”(Perf Event Buffer)、“ ring buffer”(Linux 5.8+引入的更高效的数据传输机制)发送给用户态的程序。

eBPF的这些特性,完美地解决了传统可观测性的“三大原罪”:

  1. 零(极低)侵入性、零(极低)性能损耗
    • eBPF程序运行在内核的“安全沙箱”里,不需要修改业务代码的执行流程,不需要加载内核模块;
    • fentry/fexit、tracepoint这些钩子点的性能损耗极低——根据Linux内核社区的测试数据,fentry/fexit的性能损耗只有约0.1%(每执行一次钩子点只需要几十纳秒),tracepoint的性能损耗甚至更低(只有约0.01%);
    • ring buffer的性能损耗比perf event buffer低约30%-50%,而且支持多CPU并发写入;
    • 不需要采样——因为性能损耗极低,所以可以收集全量的内核级可观测数据。
  2. 统一的全栈可观测数据来源
    • eBPF程序可以收集从硬件层(CPU cycles、cache misses、网卡队列)、内核层(CPU调度、内存分配、文件系统、网络协议栈)、容器层(cgroup、namespace、容器生命周期)、应用层(通过uprobe/uretprobe收集用户态函数的调用信息,或者通过解析内核的系统调用信息还原应用的行为)的全量可观测数据;
    • 这些数据都有统一的纳秒级时间戳进程ID/线程ID容器ID/Pod ID/Namespace ID——可以轻松地实现全栈数据的关联。
  3. 低部署、低维护成本
    • 只需要部署一个基于eBPF的统一可观测性Agent Harness DaemonSet——这个DaemonSet运行在每个K8s节点上,负责加载eBPF程序、收集全量的内核级可观测数据、然后按需喂给上层的可观测性工具;
    • 上层的可观测性工具不需要自己写钩子、埋点、收集数据,只需要专注于分析和展示;
    • 不需要在每个应用Pod里部署sidecar——除非上层工具需要特殊的功能(比如SkyWalking的Java Agent需要做全链路的TraceID传递,但eBPF也可以通过解析网络包的Header来还原TraceID,比如HTTP的X-Request-IDX-B3-TraceId,或者gRPC的grpc-trace-bin)。
1.2.3 Agent Harness:可观测性工具的“通用马具”

虽然eBPF很强大,但它也有一个缺点:eBPF的学习门槛非常高——你需要精通Linux内核、C语言(或者Rust语言,现在有很多eBPF工具链支持Rust)、eBPF的指令集、eBPF的验证器(Verifier)规则、eBPF的映射表、eBPF的数据传输机制。

而且,现在的eBPF可观测性工具通常是“单一功能”的:

  • Cilium Hubble:专注于网络可观测性;
  • Pixie:专注于应用层可观测性(但现在已经被CNCF归档了);
  • BCC:一个eBPF工具集,包含很多单一功能的工具(比如execsnoopopensnooptcpconnlat);
  • bpftrace:一个高级的eBPF跟踪语言,类似于awk和DTrace,但功能还是比较单一;
  • Falco:专注于安全可观测性;
  • Parca:专注于持续性能分析(Continuous Profiling)。

这就导致了一个问题:如果你想实现全栈可观测性,你还是需要部署多个eBPF工具——Cilium Hubble管网络,Falco管安全,Parca管性能,BCC/bpftrace管临时排查——这些工具的数据格式、数据传输机制都不一样,还是存在数据碎片化的问题,而且多个eBPF程序挂载到同一个钩子点上会不会有性能损耗?

答案是肯定的——虽然单个eBPF程序的性能损耗很低,但如果有10个eBPF程序挂载到同一个sched_switch钩子点上,每个程序都要执行一遍,性能损耗就会叠加到1%左右,虽然还是比传统工具低,但还是有优化空间的。

这时候,Agent Harness的概念就应运而生了。

那么,什么是Agent Harness?

简单来说,Agent Harness是一个基于eBPF的统一可观测性框架——它的核心思想是:

  1. 统一加载eBPF程序:Agent Harness只加载一组“通用的、模块化的、可配置的”eBPF程序,这些程序挂载到内核的所有关键钩子点上,收集全量的内核级可观测数据;
  2. 统一存储和过滤数据:Agent Harness把收集到的全量数据存储到本地的环形缓冲区或者内存数据库里,然后根据上层工具的“订阅规则”(比如“只订阅容器ID为abc123的Pod的网络连接事件”、“只订阅CPU使用率超过80%的进程的内存分配信息”),过滤出上层工具需要的数据;
  3. 统一传输数据:Agent Harness把过滤后的数据转换成上层工具需要的格式(比如Prometheus的OpenMetrics格式、Jaeger的OpenTelemetry格式、Falco的JSON格式、ELK的JSON格式),然后通过统一的API接口或者消息队列发送给上层工具;
  4. 统一管理eBPF程序和订阅规则:Agent Harness提供了一个统一的控制平面,可以动态地加载/卸载eBPF程序、调整订阅规则、查看Agent Harness自身的状态(比如CPU使用率、内存使用率、数据收集速率、数据传输速率)。

可以把Agent Harness想象成一个**“可观测性数据的自来水厂”**:

  • 内核的各种钩子点是**“水源”**;
  • Agent Harness加载的通用eBPF程序是**“抽水泵”**——负责把水源里的水(全量可观测数据)抽出来;
  • Agent Harness的本地存储和过滤模块是**“蓄水池和净水器”**——负责把水存储起来,然后根据用户的需求过滤出干净的水(上层工具需要的数据);
  • Agent Harness的数据转换和传输模块是**“水管和水龙头”**——负责把过滤后的水转换成用户需要的格式(比如自来水、热水、纯净水),然后通过水管送到用户家里(上层工具);
  • Agent Harness的控制平面是**“自来水厂的中控室”**——负责控制抽水泵的开关、调整净水器的过滤规则、查看蓄水池的水位、查看水管的流量。

1.3 亮明观点/文章目标 (The “What” & “How”)

1.3.1 亮明观点

本文的核心观点是:基于eBPF的零开销Agent Harness可观测性是云原生可观测性的未来——它可以彻底解决传统可观测性的“三大原罪”,实现全栈、统一、零开销、低维护的可观测性。

1.3.2 文章目标

读完这篇文章,你将能够:

  1. 深入理解eBPF的核心概念、工作原理、优势和局限性
  2. 深入理解Agent Harness的核心概念、架构设计、关键技术
  3. 从零开始,用Rust和Aya(一个Rust语言的eBPF工具链)构建一个简单的基于eBPF的零开销Agent Harness可观测性框架
  4. 把这个简单的Agent Harness框架和Prometheus、Falco、OpenTelemetry这三个主流的可观测性工具集成起来
  5. 了解基于eBPF的零开销Agent Harness可观测性的最佳实践、常见陷阱、行业发展和未来趋势

1.4 文章结构预告

本文将按照以下结构展开:

  1. 引言:介绍传统可观测性的痛点、eBPF的优势、Agent Harness的概念,亮明观点和文章目标;
  2. 基础知识/背景铺垫:深入讲解eBPF的核心概念、工作原理、工具链、优势和局限性,深入讲解可观测性的三大支柱(指标、日志、追踪)的核心概念,深入讲解云原生环境下的可观测性挑战;
  3. Agent Harness的核心概念与架构设计:深入讲解Agent Harness的核心概念、设计目标、架构设计(数据平面、控制平面、管理平面)、关键技术(模块化eBPF程序、高效数据存储和过滤、统一数据转换和传输、动态管理);
  4. 核心内容/实战演练:从零开始构建基于eBPF的零开销Agent Harness:用Rust和Aya构建一个简单的Agent Harness框架,包括模块化eBPF程序的开发、数据平面的开发、控制平面的开发、管理平面的开发;
  5. 进阶探讨/最佳实践:Agent Harness与主流可观测性工具的集成:把我们构建的简单Agent Harness框架和Prometheus、Falco、OpenTelemetry集成起来,实现全栈可观测性;
  6. 常见陷阱与避坑指南:介绍基于eBPF的零开销Agent Harness可观测性的常见陷阱(比如eBPF验证器的限制、数据传输的性能瓶颈、容器隔离的穿透、安全问题)以及如何避免这些陷阱;
  7. 行业发展与未来趋势:介绍基于eBPF的零开销Agent Harness可观测性的行业发展历史、现状、未来趋势(比如eBPF的CO-RE(Compile Once - Run Everywhere)技术、eBPF的WASM集成、eBPF的AI/ML集成、可观测性的“自动驾驶”);
  8. 结论:总结文章的核心要点,展望未来,发出行动号召。

二、 基础知识/背景铺垫 (Foundational Concepts)

2.1 前言

在深入讲解基于eBPF的零开销Agent Harness可观测性之前,我们必须先掌握一些必备的基础知识——这些知识就像盖房子的“地基”一样,没有地基,房子就盖不起来;地基不牢,房子就会倒塌。

本章将分为三个部分:

  1. eBPF深度解析:深入讲解eBPF的核心概念、工作原理、工具链、优势和局限性;
  2. 可观测性三大支柱深度解析:深入讲解指标(Metrics)、日志(Logs)、追踪(Traces)的核心概念、数据模型、主流工具;
  3. 云原生环境下的可观测性挑战:深入讲解云原生环境(容器、Kubernetes、微服务、服务网格)给可观测性带来的新挑战。

2.2 eBPF深度解析

2.2.1 eBPF的起源:从BPF到eBPF

要理解eBPF,我们必须先了解它的“前身”——BPF(Berkeley Packet Filter)

2.2.1.1 BPF的诞生:1992年

1992年,加州大学伯克利分校的Steven McCanne和Van Jacobson发表了一篇名为《The BSD Packet Filter: A New Architecture for User-level Packet Capture》的论文——这篇论文标志着BPF的诞生。

在BPF诞生之前,用户态的数据包捕获工具(比如最早的tcpdump的前身snoop)通常采用的是“拷贝所有数据包到用户态,然后在用户态过滤”的策略——这种策略的性能损耗非常大,因为每个数据包都要从内核态拷贝到用户态,即使这个数据包最终会被过滤掉。

而BPF采用的是“在内核态过滤数据包,只拷贝符合过滤条件的数据包到用户态”的策略——这种策略的性能损耗非常小,因为只有少数符合过滤条件的数据包才会被拷贝到用户态。

BPF的核心组件是:

  1. BPF虚拟机:一个运行在内核态的“迷你虚拟机”,有自己的指令集(类似RISC指令集,只有约30条指令);
  2. BPF过滤器:用BPF虚拟机的指令集编写的程序,负责在内核态过滤数据包;
  3. BPF映射表(Maps):后来才加入的组件,用于存储BPF程序的数据;
  4. BPF数据传输机制:用于把符合过滤条件的数据包从内核态拷贝到用户态。
2.2.1.2 BPF的发展:2011年之前

从1992年到2011年,BPF的发展非常缓慢——它主要被用于数据包捕获工具(比如tcpdump、Wireshark)和防火墙工具(比如iptables的-m bpf模块)。

在这期间,BPF只做了一些小的改进:

  1. 2003年:Linux内核2.5.75版本加入了BPF映射表(Maps)的雏形——sock_filterfd字段;
  2. 2008年:Linux内核2.6.27版本加入了TPACKET_V3,提高了BPF数据传输的性能;
  3. 2010年:Linux内核2.6.37版本加入了seccomp-bpf,允许用BPF程序过滤系统调用。
2.2.1.3 eBPF的诞生:2014年

2014年,Linux内核3.18版本加入了eBPF(extended Berkeley Packet Filter)——这标志着BPF进入了一个全新的时代。

eBPF的核心贡献者是Daniel BorkmannAlexei StarovoitovIngo Molnar等Linux内核社区的顶级开发者。

eBPF对BPF进行了革命性的扩展

  1. 扩展了指令集:从原来的约30条RISC指令集扩展到了约100条指令集,包括64位算术运算指令、跳转指令、函数调用指令、内存访问指令等;
  2. 扩展了寄存器:从原来的2个32位寄存器扩展到了11个64位寄存器(r0-r10,其中r10是只读的栈指针寄存器);
  3. 扩展了映射表(Maps):提供了多种类型的映射表(比如哈希表、数组、环形缓冲区、LRU哈希表、栈、队列等),可以存储大量的数据,支持用户态程序和eBPF程序并发访问;
  4. 扩展了钩子点(Hook Points):从原来的只有网络钩子点(XDP、TC、socket)扩展到了内核函数钩子点(kprobe、kretprobe、fentry/fexit)、用户态函数钩子点(uprobe、uretprobe)、跟踪点钩子点(tracepoint)、perf事件钩子点(perf_event)、cgroup钩子点、LSM(Linux Security Module)钩子点等;
  5. 加入了eBPF验证器(Verifier):这是eBPF最重要的组件之一——它负责在加载eBPF程序之前,检查eBPF程序是否安全(比如是否会访问非法内存、是否会陷入无限循环、是否会修改内核的关键数据结构),只有通过验证的eBPF程序才能被加载到内核中运行;
  6. 加入了eBPF JIT(Just-In-Time)编译器:这是eBPF提高性能的关键组件之一——它负责把eBPF程序的字节码编译成主机的原生机器码(比如x86_64、ARM64、RISC-V的机器码),这样eBPF程序的执行速度就和原生内核代码差不多了;
  7. 加入了CO-RE(Compile Once - Run Everywhere)技术:这是eBPF在云原生环境下普及的关键技术之一——它允许我们把eBPF程序编译成一次字节码,然后在不同版本的Linux内核上运行,而不需要重新编译(之前的eBPF程序需要针对每个版本的Linux内核重新编译,因为内核的数据结构定义可能会变化)。
2.2.2 eBPF的核心概念

要理解eBPF的工作原理,我们必须先掌握eBPF的核心概念:

  1. eBPF程序(eBPF Program)
  2. eBPF映射表(eBPF Map)
  3. eBPF钩子点(eBPF Hook Point)
  4. eBPF验证器(eBPF Verifier)
  5. eBPF JIT编译器(eBPF JIT Compiler)
  6. eBPF辅助函数(eBPF Helper Functions)
  7. eBPF CO-RE技术
  8. eBPF数据传输机制

下面我们将逐一深入讲解这些核心概念。

2.2.2.1 eBPF程序(eBPF Program)

eBPF程序是用C语言、Rust语言或者bpftrace语言编写的、运行在Linux内核中的“迷你程序”

eBPF程序的编写流程通常是:

  1. 用高级语言编写eBPF程序的源代码:比如用C语言、Rust语言或者bpftrace语言;
  2. 把高级语言的源代码编译成eBPF字节码:比如用clang(C语言)、rustc+aya-ebpf(Rust语言)、bpftrace编译器(bpftrace语言);
  3. 把eBPF字节码加载到内核中:比如用bpf()系统调用、或者用libbpf库、或者用aya库;
  4. 把eBPF程序挂载到内核的钩子点上
  5. 当内核执行到钩子点时,触发对应的eBPF程序执行
  6. eBPF程序执行完毕后,返回钩子点继续执行内核的原生代码

eBPF程序的类型通常由它挂载的钩子点决定——不同类型的eBPF程序有不同的输入参数、不同的返回值、不同的可用辅助函数。

根据Linux内核的官方文档,eBPF程序的类型主要有以下几种:

eBPF程序类型(enum bpf_prog_type)挂载的钩子点主要用途可用的Linux内核版本
BPF_PROG_TYPE_SOCKET_FILTERsocket数据包过滤(传统BPF的用途)3.18+
BPF_PROG_TYPE_KPROBEkprobe/kretprobe内核函数跟踪3.18+
BPF_PROG_TYPE_SCHED_CLSTC ingress网络流量分类3.18+
BPF_PROG_TYPE_SCHED_ACTTC egress网络流量控制3.18+
BPF_PROG_TYPE_TRACEPOINTtracepoint内核预定义事件跟踪4.7+
BPF_PROG_TYPE_XDPXDP高性能数据包处理(网卡驱动层)4.8+
BPF_PROG_TYPE_PERF_EVENTperf_event硬件性能计数器事件跟踪4.9+
BPF_PROG_TYPE_CGROUP_SKBcgroup skbcgroup级别的数据包过滤4.10+
BPF_PROG_TYPE_CGROUP_SOCKcgroup sockcgroup级别的socket操作控制4.10+
BPF_PROG_TYPE_LWT_INLWT ingress轻量级隧道入口处理4.10+
BPF_PROG_TYPE_LWT_OUTLWT egress轻量级隧道出口处理4.10+
BPF_PROG_TYPE_LWT_XMITLWT xmit轻量级隧道发送处理4.10+
BPF_PROG_TYPE_SOCK_OPScgroup sock_opscgroup级别的socket选项控制4.13+
BPF_PROG_TYPE_SK_SKBsocket skbsocket级别的数据包处理4.14+
BPF_PROG_TYPE_CGROUP_DEVICEcgroup devicecgroup级别的设备访问控制4.15+
BPF_PROG_TYPE_SK_MSGsocket msgsocket级别的消息处理4.17+
BPF_PROG_TYPE_RAW_TRACEPOINTraw_tracepoint内核原始跟踪点跟踪(比tracepoint更高效,但更不稳定)4.17+
BPF_PROG_TYPE_CGROUP_SOCK_ADDRcgroup sock_addrcgroup级别的socket地址控制4.17+
BPF_PROG_TYPE_LSMLSM hook安全模块钩子点(比如文件访问控制、进程执行控制)5.7+
BPF_PROG_TYPE_SK_REUSEPORTsocket reuseport端口复用的socket选择5.7+
BPF_PROG_TYPE_FLOW_DISSECTORflow dissector网络流量解析5.8+
BPF_PROG_TYPE_CGROUP_SYSCTLcgroup sysctlcgroup级别的sysctl参数控制5.10+
BPF_PROG_TYPE_EXT扩展eBPF程序扩展已有的eBPF程序5.10+
BPF_PROG_TYPE_LIRC_MODE2LIRC mode2红外遥控器数据处理5.11+
BPF_PROG_TYPE_SK_LOOKUPsocket lookupsocket查找拦截5.13+
BPF_PROG_TYPE_SYSCALLsyscall系统调用拦截(比seccomp-bpf更强大)5.14+
BPF_PROG_TYPE_NETFILTERnetfilternetfilter钩子点5.16+
BPF_PROG_TYPE_KPROBE_MULTIkprobe_multi批量内核函数跟踪5.18+
BPF_PROG_TYPE_UPROBE_MULTIuprobe_multi批量用户态函数跟踪5.18+

(表1:eBPF程序类型及主要用途)

下面我们来看一个最简单的eBPF程序——这个程序是用C语言编写的,挂载到sched_switch跟踪点上,当内核切换进程时,打印出旧进程的PID和新进程的PID。

// sched_switch.bpf.c#include"vmlinux.h"// 包含Linux内核的数据结构定义,CO-RE技术需要#include<bpf/bpf_helpers.h>// 包含eBPF辅助函数的声明#include<bpf/bpf_tracing.h>// 包含eBPF跟踪点的宏定义#include<bpf/bpf_core_read.h>// 包含CO-RE技术的宏定义// 定义一个eBPF程序,挂载到sched_switch跟踪点上SEC("tracepoint/sched/sched_switch")intsched_switch_trace(structtrace_event_raw_sched_switch*ctx){// 获取旧进程的PIDu32 old_pid=BPF_CORE_READ(ctx,prev_pid);// 获取新进程的PIDu32 new_pid=BPF_CORE_READ(ctx,next_pid);// 获取旧进程的名称charold_comm[16];bpf_core_read_str(old_comm,sizeof(old_comm),BPF_CORE_READ(ctx,prev_comm));// 获取新进程的名称charnew_comm[16];bpf_core_read_str(new_comm,sizeof(new_comm),BPF_CORE_READ(ctx,next_comm));// 打印日志到内核的trace buffer(用户态可以用dmesg或者bpftool trace查看)bpf_printk("sched_switch: old_pid=%d, old_comm=%s, new_pid=%d, new_comm=%s\n",old_pid,old_comm,new_pid,new_comm);return0;}// eBPF许可证(必须是GPL或者BSD,否则无法使用某些辅助函数)charLICENSE[]SEC("license")="GPL";

(代码1:最简单的eBPF程序——sched_switch跟踪)

这个eBPF程序虽然简单,但它已经包含了eBPF程序的所有核心要素:

  1. 包含头文件vmlinux.h(CO-RE技术需要)、bpf/bpf_helpers.h(辅助函数声明)、bpf/bpf_tracing.h(跟踪点宏定义)、bpf/bpf_core_read.h(CO-RE宏定义);
  2. 定义eBPF程序:用SEC()宏定义eBPF程序的类型和挂载点(这里是tracepoint/sched/sched_switch);
  3. 获取输入参数:eBPF程序的输入参数ctx是一个指向sched_switch跟踪点的原始数据结构的指针;
  4. 使用CO-RE宏读取内核数据:用BPF_CORE_READ()宏读取内核数据结构的字段(这样可以在不同版本的Linux内核上运行);
  5. 使用辅助函数:用bpf_printk()辅助函数打印日志到内核的trace buffer;
  6. 定义许可证:用SEC("license")宏定义eBPF程序的许可证(必须是GPL或者BSD,否则无法使用bpf_printk()等辅助函数)。
2.2.2.2 eBPF映射表(eBPF Map)

eBPF映射表是一个用于存储eBPF程序数据的“内核态数据结构”——它可以被用户态程序和eBPF程序并发访问,是用户态程序和eBPF程序之间通信的“桥梁”。

eBPF映射表的类型非常多,每种类型都有自己的特点和适用场景——下面我们来看一下eBPF映射表的主要类型:

eBPF映射表类型(enum bpf_map_type)数据结构特点适用场景可用的Linux内核版本
BPF_MAP_TYPE_HASH哈希表支持任意类型的key和value,查找、插入、删除的时间复杂度是O(1)存储键值对数据(比如存储进程的PID和进程的名称、存储网络连接的五元组和网络连接的延迟)3.18+
BPF_MAP_TYPE_ARRAY数组支持整数类型的key(索引),查找、插入、删除的时间复杂度是O(1),比哈希表更高效存储固定大小的索引数据(比如存储每个CPU的统计数据、存储每个网络接口的统计数据)3.18+
BPF_MAP_TYPE_PROG_ARRAY程序数组存储eBPF程序的文件描述符(fd),支持通过索引调用对应的eBPF程序实现eBPF程序的“跳转表”(比如XDP程序根据数据包的协议类型调用不同的处理程序)4.2+
BPF_MAP_TYPE_PERF_EVENT_ARRAYperf事件数组存储perf事件的文件描述符(fd),支持通过索引把数据发送到对应的perf事件缓冲区eBPF程序和用户态程序之间的高效数据传输(之前的主要数据传输机制)4.3+
BPF_MAP_TYPE_PERCPU_HASH每CPU哈希表每个CPU都有一个独立的哈希表,查找、插入、删除的时间复杂度是O(1),不需要锁(因为每个CPU只访问自己的哈希表),比普通哈希表更高效存储需要高并发访问的键值对数据(比如存储每个CPU的网络连接统计数据)4.6+
BPF_MAP_TYPE_PERCPU_ARRAY每CPU数组每个CPU都有一个独立的数组,查找、插入、删除的时间复杂度是O(1),不需要锁,比普通数组更高效存储需要高并发访问的固定大小的索引数据(比如存储每个CPU的CPU cycles统计数据)4.6+
BPF_MAP_TYPE_STACK_TRACE栈跟踪表存储栈跟踪信息存储程序的调用栈(比如性能分析、故障排查)4.6+
BPF_MAP_TYPE_CGROUP_ARRAYcgroup数组存储cgroup的文件描述符(fd),支持通过索引访问对应的cgroupcgroup级别的网络流量控制、资源统计4.8+
BPF_MAP_TYPE_LRU_HASHLRU哈希表带LRU(Least Recently Used,最近最少使用)淘汰策略的哈希表存储需要淘汰旧数据的键值对数据(比如存储最近10000个网络连接的信息)4.10+
BPF_MAP_TYPE_LRU_PERCPU_HASH每CPU LRU哈希表每个CPU都有一个独立的带LRU淘汰策略的哈希表存储需要高并发访问且需要淘汰旧数据的键值对数据4.10+
BPF_MAP_TYPE_LPM_TRIELPM前缀树带LPM(Longest Prefix Match,最长前缀匹配)策略的前缀树存储IP地址前缀数据(比如防火墙规则、路由规则)4.11+
BPF_MAP_TYPE_ARRAY_OF_MAPS映射表数组存储其他eBPF映射表的文件描述符(fd)实现多层映射表(比如先根据网络接口的索引找到对应的哈希表,再根据网络连接的五元组找到对应的信息)4.12+
BPF_MAP_TYPE_HASH_OF_MAPS映射表哈希表存储其他eBPF映射表的文件描述符(fd)实现多层映射表(比如先根据容器的ID找到对应的哈希表,再根据进程的PID找到对应的信息)4.12+
BPF_MAP_TYPE_DEVMAP设备映射表存储网络设备的索引XDP程序的数据包重定向4.14+
BPF_MAP_TYPE_SOCKMAPsocket映射表存储socket的文件描述符(fd)socket级别的消息重定向4.17+
BPF_MAP_TYPE_CPUMAPCPU映射表存储CPU的索引XDP程序的数据包重定向到其他CPU4.15+
BPF_MAP_TYPE_XSKMAPAF_XDP socket映射表存储AF_XDP socket的文件描述符(fd)XDP程序的数据包重定向到AF_XDP socket4.18+
BPF_MAP_TYPE_SOCKHASHsocket哈希表存储socket的文件描述符(fd),支持任意类型的keysocket级别的消息重定向4.18+
BPF_MAP_TYPE_CGROUP_STORAGEcgroup存储表存储cgroup级别的数据cgroup级别的资源统计4.19+
BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE每CPU cgroup存储表每个CPU都有一个独立的cgroup存储表cgroup级别的高并发资源统计4.19+
BPF_MAP_TYPE_QUEUE队列FIFO(先进先出)数据结构存储需要按顺序处理的数据4.20+
BPF_MAP_TYPE_STACKLIFO(后进先出)数据结构存储需要按逆序处理的数据4.20+
BPF_MAP_TYPE_RINGBUF环形缓冲区多CPU并发写入、单CPU或多CPU并发读取的环形缓冲区,比BPF_MAP_TYPE_PERF_EVENT_ARRAY更高效eBPF程序和用户态程序之间的高效数据传输(现在的主要数据传输机制)5.8+
BPF_MAP_TYPE_INODE_STORAGEinode存储表存储inode级别的数据文件级别的资源统计5.10+
BPF_MAP_TYPE_TASK_STORAGE任务存储表存储task_struct级别的数据(每个进程/线程都有一个task_struct)进程/线程级别的资源统计5.11+
`BPF_MAP_TYPE_BLO
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/4 18:01:41

保姆级教程:手动下载Hugging Face的bert-base-chinese模型文件,告别网络卡顿

突破网络限制&#xff1a;高效获取Hugging Face中文BERT模型的完整指南在自然语言处理领域&#xff0c;BERT模型已成为基础架构般的存在。但对于国内开发者而言&#xff0c;直接从Hugging Face平台下载模型文件常常面临速度缓慢甚至失败的困扰。本文将提供一套完整的解决方案&a…

作者头像 李华
网站建设 2026/6/4 18:01:14

Windows鼠标自动化革命:AutoClicker如何解放你的双手

Windows鼠标自动化革命&#xff1a;AutoClicker如何解放你的双手 【免费下载链接】AutoClicker AutoClicker is a useful simple tool for automating mouse clicks. 项目地址: https://gitcode.com/gh_mirrors/au/AutoClicker 还在为重复的鼠标点击任务感到厌倦吗&…

作者头像 李华
网站建设 2026/6/4 18:00:15

Arduino在工业控制中的应用:低成本替代PLC的实战指南

1. 项目概述与核心思路在工业自动化领域干了十几年&#xff0c;从最早跟着老师傅调试继电器柜&#xff0c;到后来满世界跑项目&#xff0c;用遍了西门子、罗克韦尔、三菱这些主流PLC。这些年下来&#xff0c;我最大的感触是&#xff0c;技术方案没有绝对的好坏&#xff0c;只有…

作者头像 李华
网站建设 2026/6/4 17:56:19

Cursor Pro破解工具2025:5步解决AI编程助手试用限制的完整方案

Cursor Pro破解工具2025&#xff1a;5步解决AI编程助手试用限制的完整方案 【免费下载链接】cursor-free-vip [Support 0.45]&#xff08;Multi Language 多语言&#xff09;自动注册 Cursor Ai &#xff0c;自动重置机器ID &#xff0c; 免费升级使用Pro 功能: Youve reached …

作者头像 李华