一、为什么每个 Linux 工程师都应该学习 eBPF?
在现代 Linux 系统运维和性能调优领域,工程师们长期面临一个根本性困境:要么使用静态插桩(如 SystemTap)——需要编译内核模块,部署复杂且风险极高;要么依赖传统工具(top、vmstat、iostat)——它们提供的是粗粒度系统级统计指标,无法深入追踪单个进程、单个系统调用或单个网络包的完整生命周期。
eBPF(Extended Berkeley Packet Filter)改变了这一切。作为 Linux 内核中一个革命性的运行时框架,它允许在不重新编译内核、不加载内核模块的前提下,安全地在内核态注入自定义逻辑。这意味着你可以实时观察系统的每一个角落——从 TCP 协议栈的拥塞控制决策,到文件系统页缓存的命中率,到容器编排层面每个 Pod 的 CPU 调度延迟——而性能损耗通常低于 1%。
Netflix 用 eBPF 构建了其内部网络观测平台,Cloudflare 用它做 DDoS 检测和性能分析,Facebook(Meta)用它做 L4 负载均衡(Katran 项目,单机处理 10Mpps+)。在国内,字节跳动、阿里巴巴、美团等公司的容器平台和可观测性体系中,eBPF 已成为核心技术组件。
本文将从零开始,带你深入理解 eBPF 的核心架构、工具链、编程模型和实战案例,让你能够将这项技术应用到日常工作中。
二、eBPF 核心架构深度解析
2.1 从 cBPF 到 eBPF:一场架构进化
eBPF 的前身是经典 BPF(Berkeley Packet Filter),由 Steven McCanne 和 Van Jacobson 于 1992 年在论文《The BSD Packet Filter: A New Architecture for User-level Packet Capture》中提出。cBPF 只有两个 32-bit 寄存器,功能仅限于网络包过滤。
2014 年,Alexei Starovoitov 将 BPF 扩展为 eBPF:引入了 10 个 64-bit 寄存器(R0-R10)、BPF Map 共享内存机制、辅助函数(Helper Call)体系,以及即时编译器(JIT)。这使得 eBPF 从一个简单的包过滤器,进化为通用的内核可编程框架。
2.2 eBPF 程序的生命周期
一个 eBPF 程序从编写到执行的完整流程如下:
- 编写 eBPF 代码:使用受限的 C 语言子集(或直接使用 BPF 字节码指令)
- 编译:通过 LLVM/Clang 编译为 eBPF 字节码(.bpf.o 对象文件)
- 加载:调用 bpf() 系统调用将字节码送入内核
- 验证:内核验证器(Verifier)执行深度静态分析,确保程序安全
- JIT 编译:验证通过后,JIT 编译器将字节码翻译为原生机器码
- 挂载:将 JIT 编译后的程序附加到指定 Hook 点(Kprobe、Tracepoint、XDP 等)
- 执行:事件触发时运行 eBPF 程序,通过 Map 或 Perf Buffer 输出数据
整个流程的关键是验证器——它是 eBPF 区别于内核模块的核心安全机制。验证器会模拟程序的所有执行路径,确保:
- 不会存在无条件跳转或死循环
- 不会访问未初始化的内存
- 不会访问超出栈范围的地址
- 不会持有锁后再次尝试获取同一锁
- 程序执行步数有上限(早期 4096 步,5.2+ 内核放宽至 100 万步)
2.3 BPF Map:内核态与用户态的桥梁
BPF Map 是 eBPF 程序与用户空间通信的核心机制,本质上是一种通用的 Key-Value 存储。内核提供了多种 Map 类型:
- BPF_MAP_TYPE_HASH:哈希表,O(1) 查找,适合存储动态数据(如连接状态、计数统计)
- BPF_MAP_TYPE_ARRAY:数组,索引访问,适合存储固定集合的统计数据
- BPF_MAP_TYPE_PERCPU_HASH / PERCPU_ARRAY:Per-CPU 版本,无锁读写,避免 CPU 竞争,适合高频计数器
- BPF_MAP_TYPE_LRU_HASH:基于 LRU 算法淘汰旧条目,适合需要限制内存使用的场景
- BPF_MAP_TYPE_RINGBUF(Linux 5.8+):环形缓冲区,取代 Perf Buffer 成为流式数据传输的首选
- BPF_MAP_TYPE_STACK_TRACE:用于存储栈帧,配合栈回溯功能使用
- BPF_MAP_TYPE_LPM_TRIE:最长前缀匹配树,适合路由表和防火墙规则
2.4 Helper 函数体系
eBPF 程序不能随意调用内核函数,只能通过预定义的 Helper 函数接口。当前 Linux 6.x 内核提供了 200+ 个 Helper 函数,按功能分类:
- 数据包修改:bpf_skb_store_bytes、bpf_l3_csum_replace(XDP/XPS 场景)
- 数据存储:bpf_map_lookup_elem、bpf_map_update_elem、bpf_map_delete_elem
- 输出与追踪:bpf_trace_printk、bpf_perf_event_output、bpf_ringbuf_output
- 随机与时间:bpf_get_prandom_u32、bpf_ktime_get_ns
- 进程信息:bpf_get_current_pid_tgid、bpf_get_current_comm
- 网络辅助:bpf_sock_addr_setsockopt、bpf_sk_lookup_tcp
- 尾调用:bpf_tail_call(用于拆分为多个程序以绕过指令限制)
三、工具链全景:从 BCC 到 libbpf
3.1 BCC(BPF Compiler Collection)
BCC 是目前最成熟的 eBPF 工具生态,由 Brendan Gregg 创建。它的设计思路是:用户编写精简的 BPF C 代码,嵌入到 Python 脚本中,运行时通过 LLVM JIT 编译加载。BCC 封装了底层细节,极大降低了入门门槛。
BCC 自带了大量开箱即用的工具,位于 /usr/share/bcc/tools/:
- execsnoop:追踪所有 execve() 调用——快速定位短命进程
- opensnoop:追踪所有 openat() 调用——知道谁在打开什么文件
- biosnoop:块 I/O 层追踪——每个 I/O 的延迟直方图
- tcplife:TCP 会话生命周期——完整追踪 socket 从创建到关闭的时延
- runqlat:CPU 调度器延迟直方图——量化调度争抢程度
- cachestat:文件系统页缓存命中率——快速判断是否需要加内存
- funclatency:统计任意内核函数的延迟分布
- stackcount:统计触发某函数的所有调用栈
- trace:类似 strace,但支持内核态函数追踪
- bpftrace:高级脚本语言(后文详述)
3.2 bpftrace:eBPF 的高级脚本语言
bpftrace 提供了一种类 awk 的单行脚本语言,让你不用写 C 就能完成大多数追踪任务。它的语法简洁直观:
# 每 5 秒输出一次各进程 read() 字节的直方图
bpftrace -e 'kretprobe:vfs_read { @bytes = hist(retval); } interval:s:5 { print(@bytes); clear(@bytes); }'
# 追踪所有 connect() 系统调用,按进程统计
bpftrace -e 'tracepoint:syscalls:sys_enter_connect { @[comm, pid] = count(); }'
# 追踪内核函数 do_nice_to_cmd 的执行路径
bpftrace -e 'kprobe:do_nice_to_cmd { @[kstack] = count(); }'
# 统计所有 openat() 系统调用的延迟
bpftrace -e 'tracepoint:syscalls:sys_enter_openat { @start[tid] = nsecs; }
tracepoint:syscalls:sys_exit_openat /@start[tid]/ {
@latency_us = hist((nsecs - @start[tid]) / 1000);
delete(@start[tid]);
}'
bpftrace 有一个特殊的语法元素——内置变量:
- pid:进程 PID
- tid:线程 TID
- uid/gid:用户/组 ID
- nsecs:当前纳秒时间戳
- elapsed:程序启动后的纳秒时间
- cpu:当前 CPU 编号
- comm:进程名称(最多 16 字节)
- arg0 ~ argN:函数参数
- retval:函数返回值
- kstack/ustack:内核/用户态调用栈
3.3 libbpf:现代化的 eBPF 加载与开发库
libbpf 是目前编写 eBPF 用户态加载程序的官方推荐库。它封装了 bpf() 系统调用、Map 操作、BTF(BPF Type Format)解析等底层细节。
libbpf 的核心优势在于支持 BPF CO-RE(Compile Once – Run Everywhere):传统 BCC 方案必须在目标机器上编译(依赖内核头文件和编译器),而 CO-RE 方案编译一次后通过 BTF 信息在不同内核版本上适配。
CO-RE 的工作流程:
- 编译时生成包含 BTF 重定位信息的 .bpf.o 文件
- 运行时加载目标机器的 BTF 信息(通常位于 /sys/kernel/btf/vmlinux)
- libbpf 根据目标内核的 BTF 信息自动修正内存偏移量
- 成功加载 eBPF 程序到不同版本的内核
3.4 bpftool:eBPF 系统管理神器
bpftool 是 Linux 内核提供的官方 eBPF 管理工具,随着内核一起分发。它让你无需额外安装任何工具,就能观察和操作系统中已加载的所有 eBPF 程序和 Map。
# 列出系统中所有已加载的 eBPF 程序
bpftool prog show
# 查看某个 eBPF 程序的详细信息和 JIT 编译后的机器码
bpftool prog show id 42 --pretty
# 列出系统中所有 BPF Map
bpftool map show
# 导出某个 Map 的内容
bpftool map dump id 42
# 将 eBPF 程序钉載到 bpffs 持久化存储(重启后存活)
bpftool prog pin id 42 /sys/fs/bpf/my_prog
# 从 bpffs 挂载并自动附加
bpftool prog loadall /opt/my_prog.o /sys/fs/bpf type kprobe
# 查看 eBPF 程序在 XDP 上的统计
bpftool net show
# 通过 BTF 信息查看某个内核结构体的字段偏移
bpftool btf dump file /sys/kernel/btf/vmlinux format c | grep task_struct
bpftool 还支持 JIT 反编译——将你写的 C 代码编译后,可以直接查看 JIT 编译出的原生 x86 汇编,帮助你理解内核做了哪些优化。
四、Hook 点全景:eBPF 可以挂在哪里?
eBPF 的威力来自于它的"全栈"插桩能力。从最底层的数据链路层(XDP)到最上层的应用层(Uprobe),几乎每一个路径点都可以挂载 eBPF 程序。
4.1 XDP(eXpress Data Path)
XDP 位于网卡驱动的最前端——数据包刚进入网卡、还未分配 sk_buff 之前就触发。这个位置的 eBPF 程序运行在硬中断上下文之前,拥有最低的延迟和最高的吞吐。Cloudflare 和 Netflix 用 XDP 实现高性能 DDoS 防御和 L4 负载均衡。
XDP 程序返回值决定了包的去向:
- XDP_PASS:继续交给内核协议栈处理
- XDP_DROP:直接丢弃
- XDP_TX:从同一网卡原路发送出去
- XDP_REDIRECT:转发到另一个网卡或 CPU
4.2 TC(Traffic Control)
TC Hook 位于内核协议栈的 traffic control 层,比 XDP 更深一层——此时数据包已经有完整的 sk_buff 结构,能访问 Protocol headers、关联的 Socket 等上下文。TC 支持 ingress 和 egress 两个方向,常用于基于 cgroup 的流量整形和容器网络隔离。
4.3 Kprobe / Kretprobe
Kprobe 可以在任意内核函数的入口点挂载,Kretprobe 则在函数返回时触发。理论上可以插入到内核的每一行代码(实际上 Verifier 会限制对部分核心函数的使用)。Kprobe 是最灵活的 Hook 类型,但缺点是没有稳定的 ABI——内核函数名可能随版本变化。
使用 Kprobe 时需要注意:
- 高版本内核推荐使用 Fentry/Fexit(Linux 5.5+)替代 Kprobe/Kretprobe,性能开销更低
- Kprobe 在所有但不包括被标记 notrace 的函数上可用
- 频繁调用的热点函数(如 kmalloc、schedule)用 Kprobe 可能产生显著性能影响
4.4 Tracepoint
Tracepoint 是内核在源代码中预定义的稳定 Hook 点,从内核 2.6 版本开始引入。相比 Kprobe,Tracepoint 的 ABI 稳定、运行开销相对较小。Linux 内核目前提供了 1000+ 个 Tracepoint。
常见的 Tracepoint 分类:
- sched:sched_switch:进程调度切换
- syscalls:sys_enter_*:系统调用入口
- net:net_dev_queue:数据包进入网卡发送队列
- block:block_rq_issue:块设备 I/O 请求
- skb:consume_skb:SKB 释放
- kmem:kmalloc:内核内存分配
- irq:irq_handler_entry:中断处理
4.5 Uprobe / Uretprobe
Uprobe 对应用户态函数的动态追踪。通过它,你可以追踪任何用户空间进程的函数调用,对性能问题做精确归因。典型场景包括:追踪 JVM 的热点方法调用、分析 Python/Go 应用的函数执行路径、监控 Redis 内部的关键操作等。
Uprobe 的挂载方法分为两种:
- 基于文件偏移:通过目标二进制文件的符号表计算函数偏移
- 基于 PID + 文件 offset:运行时解析指定进程的符号位置
注意:Uprobe 依赖于目标二进制保留符号信息(Debug Info),生产环境编译时最好保留 -g 参数。
4.6 Cgroup Hook
Cgroup eBPF 允许在容器的 cgroup 层面挂载程序,适用于容器网络的策略执行和资源限制。Docker、Kubernetes(Cilium)等容器平台广泛使用 Cgroup eBPF 技术。
常见类型:
- BPF_CGROUP_INET_INGRESS:进入容器网络接口的入站流量
- BPF_CGROUP_INET_EGRESS:出站流量
- BPF_CGROUP_SOCK_OPS:TCP 连接状态变化
- BPF_CGROUP_UDP_SENDMSG / RECVMSG:UDP 消息收发
- BPF_CGROUP_DEVICE:设备访问权限控制
4.7 LSM Hook(Linux Security Module)
LSM BPF(Linux 5.7+)允许在安全决策点插入 eBPF 程序,替代传统的 LSM 模块。它的出现使得安全策略的编写和部署变得前所未有的灵活——不再需要编译内核模块或修改内核源码。
典型应用:
- 细粒度文件访问控制(基于进程、用户、路径多维组合)
- 动态系统调用过滤(比 Seccomp 更灵活的 syscall filter)
- 网络策略(替代 iptables 的复杂规则链)
- 审计日志(精细到单个操作的行为审计)
五、实战案例
5.1 案例一:分布式追踪——TCP 连接失败自动溯源
生产环境中,ETIMEDOUT、ECONNREFUSED 这类 TCP 连接失败很常见,但因为中间可能跨多个网络段,定位困难。我们可以用 eBPF 在关键 Hook 点植入探针,自动构建完整的连接失败路径。
// BPF C 代码片段
SEC("kprobe/tcp_v4_connect")
int trace_connect_entry(struct pt_regs *ctx) {
u64 pid_tgid = bpf_get_current_pid_tgid();
u32 pid = pid_tgid >> 32;
struct sock *sk = (struct sock *)PT_REGS_PARM1(ctx);
// 记录连接四元组
struct conn_info info = {};
bpf_probe_read(&info.saddr, sizeof(info.saddr), &sk->__sk_common.skc_rcv_saddr);
bpf_probe_read(&info.daddr, sizeof(info.daddr), &sk->__sk_common.skc_daddr);
bpf_probe_read(&info.dport, sizeof(info.dport), &sk->__sk_common.skc_dport);
conn_map.update(&pid, &info);
return 0;
}
SEC("kretprobe/tcp_v4_connect")
int trace_connect_return(struct pt_regs *ctx) {
int ret = PT_REGS_RC(ctx);
if (ret == 0) return 0; // 连接成功,忽略
u64 pid_tgid = bpf_get_current_pid_tgid();
u32 pid = pid_tgid >> 32;
struct conn_info *info = conn_map.lookup(&pid);
if (!info) return 0;
// 输出失败信息到 Ring Buffer
struct event *e = bpf_ringbuf_reserve(&events, sizeof(*e), 0);
if (!e) return 0;
e->pid = pid;
bpf_get_current_comm(&e->comm, sizeof(e->comm));
e->ret = ret;
e->saddr = info->saddr;
e->daddr = info->daddr;
e->dport = info->dport;
bpf_ringbuf_submit(e, 0);
return 0;
}
配合用户态 Python 或 Go 程序消费 Ring Buffer 数据后,可以在失败时自动进行 nsenter 到目标容器的网络命名空间,执行 traceroute 或 ikprobe 深入追踪中间路径的每一跳。这种自动化分析机制在大型微服务体系中非常有价值。
5.2 案例二:实时分析容器 CPU 争抢
Kubernetes 环境中,一个节点上的多个"邻居容器"可能出现 CPU 争抢,导致关键服务延迟飙升。使用 eBPF 可以量化这种争抢效应。
// 通过 sched:sched_switch Tracepoint 计算 CPU 等待时间
SEC("tp_btf/sched_switch")
int BPF_PROG(trace_sched_switch, bool preempt, struct task_struct *prev,
struct task_struct *next, unsigned int prev_state) {
u32 cpu = bpf_get_smp_processor_id();
u64 ts = bpf_ktime_get_ns();
// 前一个进程从此刻开始等待
if (prev_state == TASK_RUNNING) {
u64 *wait_start = bpf_map_lookup_elem(&wait_start_map, &prev->pid);
if (wait_start) {
// 已经记录过等待开始时间,累计等待时长
u64 delta = ts - *wait_start;
u64 *total_wait = bpf_map_lookup_elem(&cpu_wait_map, &cpu);
if (total_wait) __sync_fetch_and_add(total_wait, delta);
} else {
// 记录等待开始时间
bpf_map_update_elem(&wait_start_map, &prev->pid, &ts, BPF_ANY);
}
}
// 后一个进程从等待中恢复
u64 *wait_start = bpf_map_lookup_elem(&wait_start_map, &next->pid);
if (wait_start) {
u64 delta = ts - *wait_start;
// 按 cgroup 聚合等待时间(识别容器归属)
u64 cgroup_id = bpf_get_current_cgroup_id();
bpf_map_update_elem(&cgroup_wait_map, &cgroup_id, &delta, BPF_ADD);
bpf_map_delete_elem(&wait_start_map, &next->pid);
}
return 0;
}
配合每秒读取一次 Map 数据并输出到 Prometheus/Grafana,你可以看到:
- 哪些容器在经历明显的 CPU 排队——等待时间 / 运行时间 的比值
- 哪些 CPU 核心的争抢最严重
- 随着时间推移,争抢趋势的变化
相比 cgroup CPU throttle 指标,这种方法更精确——它使用内核调度器的原生时间戳,纳秒级精度。
5.3 案例三:TCP 重传风暴实时检测与告警
TCP 重传是网络质量恶化的重要信号。传统手段依赖 ss -i 或网络层抓包分析,粒度粗糙且延迟大。用 eBPF 追踪 TCP 重传可以做到秒级、进程级精准告警。
// 当 TCP 重传发生时触发
SEC("kprobe/tcp_retransmit_skb")
int trace_tcp_retransmit(struct pt_regs *ctx) {
struct sock *sk = (struct sock *)PT_REGS_PARM1(ctx);
u16 family = 0;
bpf_probe_read(&family, sizeof(family), &sk->__sk_common.skc_family);
struct retrans_event e = {};
e.ts = bpf_ktime_get_ns();
e.pid = bpf_get_current_pid_tgid() >> 32;
bpf_get_current_comm(&e.comm, sizeof(e.comm));
bpf_probe_read(&e.sk_num, sizeof(e.sk_num), &sk->__sk_common.skc_num);
if (family == AF_INET) {
bpf_probe_read(&e.saddr, sizeof(e.saddr), &sk->__sk_common.skc_rcv_saddr);
bpf_probe_read(&e.daddr, sizeof(e.daddr), &sk->__sk_common.skc_daddr);
} else if (family == AF_INET6) {
bpf_probe_read(&e.saddr_v6, sizeof(e.saddr_v6), &sk->__sk_common.skc_v6_rcv_saddr);
bpf_probe_read(&e.daddr_v6, sizeof(e.daddr_v6), &sk->__sk_common.skc_v6_daddr);
}
// 按目的端口聚合,找出重传风暴源
u16 dport = 0;
bpf_probe_read(&dport, sizeof(dport), &sk->__sk_common.skc_dport);
e.dport = ntohs(dport);
bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &e, sizeof(e));
return 0;
}
消费者端每秒统计每个目的端口的重传次数,一旦超过阈值立即告警。配合 Pod 名称识别和流量关联分析,可以快速定位是硬件网络问题、交换机光模块老化还是内核 conntrack 表溢出导致的不稳定。
5.4 案例四:文件系统延迟热点分析
使用 VFS Tracepoint 可以追踪每个 read/write/open/close 系统调用的完整延迟,配合 Map 按进程聚合后,可以构建"延迟热力图"——哪些进程在消耗最多的 I/O 等待时间。
一个经典的诊断场景:
- 运维发现某台 MySQL 机器的 iowait 很高,通过 iotop 看到主要是 jbd2/sda1-8 内核线程
- 使用 eBPF 追踪 writeback、fdatasync 的延迟分布,99分位高达 200ms+
- 进一步分析发现这一批写入是由 MySQL checkpoint 集中触发
- 调优:设置 innodb_io_capacity_max 限制 checkpoint 频率,问题消失
六、性能开销与最佳实践
eBPF 不是零开销的,理解如何最小化其对生产系统的影响至关重要。
6.1 开销来源分析
eBPF 程序的主要性能开销来自三个环节:
- 上下文切换:每个事件触发都要从正常执行流切换到 eBPF 程序再返回。Kprobe 这一开销约 50ns。
- Map 操作:单次 Map 更新约 5-25ns(取决于 Map 类型和内存状态)。
- 数据输出:Perf Buffer/Ring Buffer 的用户态消费和数据传输。
6.2 降低开销的 8 个关键策略
- 选择合适的 Map 类型——能用 Per-CPU 就不用全局 HASH,避免 CAS 竞争
- 使用 Fentry/Fexit 替代 Kprobe——Fentry 直接内联调用,比 Kprobe 少 80% 开销
- 批量提交数据——避免对每个事件都触发 perf_submit,先在内核侧做聚合
- Map 预填充初始化——避免运行时动态分配内存
- 使用 BPF_MAP_TYPE_ARRAY_PERCPU 做直方图——直方图不需要精确全局排序
- 不追踪明确不需要的事件——在 BPF 程序内做早期过滤(if (pid != target) return 0;)
- 使用 ringbuf 替代 perf buffer——ringbuf 内存效率更高,避免丢失事件
- 批量管理 Map 条目——定期清理过期条目,避免 Map 过大
6.3 验证器优化技巧
当遇到验证器报错时,常见问题与解决:
- "backwards branch" 错误:手动展开循环(#pragma unroll)
- "unbounded loop" 错误:添加循环边界或使用宏控制迭代次数
- "invalid stack access" 错误:确保所有 Map 使用都通过 bpf_map_lookup_elem 而非直接访问 map->data
- "packet pointer arithmetic" 错误:高频数据包操作必须显式检查 pkt_end - pkt_start 边界
- "program too large" 错误:使用 BPF-to-BPF 函数调用或 bpf_tail_call 拆分程序
七、生产级 eBPF 项目推荐
- Cilium:基于 eBPF 的 Kubernetes 网络方案(CNI),提供网络策略、负载均衡、加密和可观测性
- Falco:容器运行时安全监控——通过 eBPF 在内核中读取进程行为,匹配安全规则告警
- Katran (Facebook/Meta):L4 负载均衡,生产环境单机处理数百万 PPS
- Parca / Pyroscope:基于 eBPF 的持续性能分析(Continuous Profiling)
- Cilium Tetragon:利用 eBPF + LSM 做进程级别的运行时安全检测
- bpftrace:最强大的单行 eBPF 脚本引擎
- Pixie:Kubernetes 原生可观测性平台,无需修改应用代码即可采集 telemetry 数据
八、总结与展望
eBPF 正在改变 Linux 生态的每一个角落:网络(Cilium、Istio Ambient Mesh)、安全(Falco、Tetragon)、可观测性(Pixie、Pyroscope、Parca)、性能分析(BCC、bpftrace)。随着 Linux 6.x 内核的持续演进,eBPF 的指令集更强、Map 类型更丰富、Hook 点更全面。
对于 Linux 工程师而言,掌握 eBPF 已经从"加分项"变成"必要项"。它在内核层面的可编程能力,让你可以构建出前一代技术栈(ftrace、perf、iptables、SystemTap)无法实现的复杂系统。
学习路线建议:先用 bpftrace 快速原型,满足日常临时调试需求;再用 BCC 解决系统性问题;最终用 libbpf + CO-RE 构建可部署的生产级程序。伴随阅读《BPF Performance Tools》(Brendan Gregg 著)和内核源码中的 samples/bpf、tools/testing/selftests/bpf 目录,你将逐步掌握这门足以改变系统编程范式的技术。

发表评论 取消回复