跳转至

02-eBPF与系统调用追踪

重要性: ⭐⭐⭐⭐ 实用度: ⭐⭐⭐⭐⭐ 学习时间: 2天 必须掌握: 推荐了解


为什么学这一章?

eBPF(Extended Berkeley Packet Filter)是Linux内核的一项革命性技术,它允许你在内核中安全地运行自定义代码。学习eBPF能让你: - 实现高性能的系统监控和追踪 - 理解现代可观测性工具的底层原理 - 编写自定义的系统调用拦截器 - 掌握Linux内核编程的现代方法

学完这一章,你将能够: - ✅ 理解eBPF的工作原理和架构 - ✅ 使用bpftrace进行系统调用追踪 - ✅ 编写简单的eBPF程序 - ✅ 了解eBPF在云原生和可观测性领域的应用


📖 核心概念

1. 什么是eBPF?

Text Only
┌─────────────────────────────────────────────────────────────────────┐
│                    eBPF架构概览                                        │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  eBPF(Extended Berkeley Packet Filter)                              │
│  ├── 起源:1992年BSD包过滤器(BPF)                                   │
│  ├── 扩展:2014年Linux 3.18引入eBPF                                  │
│  └── 现状:Linux内核最重要的子系统之一                                │
│                                                                     │
│  核心特点:                                                           │
│  ┌───────────────────────────────────────────────────────────────┐ │
│  │                                                                │ │
│  │  1. 安全执行                                                     │ │
│  │     • 通过内核验证器(Verifier)检查程序安全性                   │ │
│  │     • 禁止无限循环、空指针解引用等危险操作                       │ │
│  │     • 保证程序不会崩溃内核                                       │ │
│  │                                                                │ │
│  │  2. 高性能                                                       │ │
│  │     • JIT编译:eBPF字节码编译为机器码                            │ │
│  │     • 内核态执行:无需用户态/内核态切换                          │ │
│  │     • 比传统系统调用追踪快10-100倍                               │ │
│  │                                                                │ │
│  │  3. 灵活性                                                       │ │
│  │     • 动态加载:无需重启内核或修改源码                           │ │
│  │     • 事件驱动:响应各种内核事件                                 │ │
│  │     • 可扩展:支持自定义数据结构(Map)                          │ │
│  │                                                                │ │
│  └───────────────────────────────────────────────────────────────┘ │
│                                                                     │
│  eBPF程序生命周期:                                                   │
│  ┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────────┐      │
│  │ 编写程序  │───→│ 编译成   │───→│ 内核验证  │───→│ JIT编译  │      │
│  │ (C/Rust) │    │ BPF字节码│    │ 安全检查  │    │ 机器码   │      │
│  └──────────┘    └──────────┘    └──────────┘    └──────────┘      │
│                                                      │              │
│                                                      ↓              │
│                                              ┌──────────────┐      │
│                                              │  附加到内核   │      │
│                                              │  钩子点执行   │      │
│                                              └──────────────┘      │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

2. eBPF的应用场景

Text Only
┌─────────────────────────────────────────────────────────────────────┐
│                    eBPF应用场景                                        │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  1. 可观测性(Observability)                                         │
│  ┌───────────────────────────────────────────────────────────────┐ │
│  │                                                                │ │
│  │  • 系统调用追踪:strace的现代化替代                              │ │
│  │  │   bpftrace -e 'tracepoint:syscalls:sys_enter_openat         │ │
│  │  │               { printf("%s opened %s\n", comm, str(args->filename)); }' │ │
│  │                                                                │ │
│  │  • 性能分析:CPU、内存、I/O分析                                  │ │
│  │  │   bpftrace -e 'kprobe:do_nanosleep                           │ │
│  │  │               { @start[tid] = nsecs; }                       │ │
│  │  │               kretprobe:do_nanosleep                         │ │
│  │  │               /@start[tid]/                                  │ │
│  │  │               { @latency = hist(nsecs - @start[tid]); }'    │ │
│  │                                                                │ │
│  │  • 网络监控:TCP连接、丢包分析                                   │ │
│  │  │   bpftrace -e 'kprobe:tcp_drop                               │ │
│  │  │               { printf("TCP drop: %s\n", kstack()); }'       │ │
│  │                                                                │ │
│  └───────────────────────────────────────────────────────────────┘ │
│                                                                     │
│  2. 网络安全                                                          │
│  ┌───────────────────────────────────────────────────────────────┐ │
│  │                                                                │ │
│  │  • 包过滤:XDP(eXpress Data Path)高速包处理                    │ │
│  │  │   - 在网卡驱动层处理包,绕过内核网络栈                        │ │
│  │  │   - 可达线速(10Gbps+)                                       │ │
│  │                                                                │ │
│  │  • 防火墙:Cilium基于eBPF的容器网络安全                          │ │
│  │  │   - 替代iptables,性能提升10倍+                               │ │
│  │  │   - 支持L3-L7层策略                                           │ │
│  │                                                                │ │
│  │  • DDoS防护:Cloudflare使用eBPF进行流量清洗                      │ │
│  │                                                                │ │
│  └───────────────────────────────────────────────────────────────┘ │
│                                                                     │
│  3. 性能优化                                                          │
│  ┌───────────────────────────────────────────────────────────────┐ │
│  │                                                                │ │
│  │  • TCP加速:Google的TCP BBR拥塞控制算法                          │ │
│  │  │   - 使用eBPF在内核中实现                                      │ │
│  │                                                                │ │
│  │  • 负载均衡:Facebook的Katran L4负载均衡器                       │ │
│  │  │   - 替换IPVS,性能提升10倍                                    │ │
│  │                                                                │ │
│  │  • 文件系统加速:eBPF加速ext4/xfs                                │ │
│  │                                                                │ │
│  └───────────────────────────────────────────────────────────────┘ │
│                                                                     │
│  4. 云原生                                                            │
│  ┌───────────────────────────────────────────────────────────────┐ │
│  │                                                                │ │
│  │  • 容器安全:Falco运行时安全检测                                 │ │
│  │  │   - 检测容器中的异常行为                                      │ │
│  │  │   - 监控文件系统、网络、进程活动                              │ │
│  │                                                                │ │
│  │  • 服务网格:Cilium替代Envoy实现服务网格                         │ │
│  │  │   - 数据平面使用eBPF                                          │ │
│  │  │   - 延迟降低50%+                                              │ │
│  │                                                                │ │
│  │  • 无服务器:AWS Lambda使用eBPF进行监控                          │ │
│  │                                                                │ │
│  └───────────────────────────────────────────────────────────────┘ │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

3. bpftrace快速入门

bpftrace是基于eBPF的高级追踪语言,类似awk的语法,非常适合快速编写追踪脚本。

安装bpftrace

Bash
# Ubuntu/Debian
sudo apt-get install bpftrace

# CentOS/RHEL
sudo yum install bpftrace

# macOS(使用Docker或VM)
brew install bpftrace

基础语法

Text Only
┌─────────────────────────────────────────────────────────────────────┐
│                    bpftrace语法结构                                    │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  bpftrace程序结构:                                                    │
│                                                                     │
│  探针(Probe) + 过滤条件(Predicate) + 动作(Action)               │
│                                                                     │
│  示例:                                                               │
│  ```bpftrace                                                        │
│  probe /filter/ { action }                                          │
│  ```                                                                │
│                                                                     │
│  探针类型:                                                           │
│  ┌───────────────────────────────────────────────────────────────┐ │
│  │  类型              │  示例                    │  说明          │ │
│  ├───────────────────────────────────────────────────────────────┤ │
│  │  kprobe            │  kprobe:do_sys_open      │  内核函数入口  │ │
│  │  kretprobe         │  kretprobe:do_sys_open   │  内核函数返回  │ │
│  │  tracepoint        │  tracepoint:syscalls:sys_enter_openat  │  内核跟踪点    │ │
│  │  uprobe            │  uprobe:/bin/bash:readline            │  用户函数入口  │ │
│  │  usdt              │  usdt:/usr/bin/python:function__entry │  用户静态跟踪点│ │
│  │  profile           │  profile:hz:99           │  定时采样      │ │
│  │  interval          │  interval:s:1            │  定时触发      │ │
│  │  software          │  software:page-faults    │  内核软件事件  │ │
│  │  hardware          │  hardware:cache-misses   │  硬件性能事件  │ │
│  └───────────────────────────────────────────────────────────────┘ │
│                                                                     │
│  内置变量:                                                           │
│  ┌───────────────────────────────────────────────────────────────┐ │
│  │  变量      │  说明                                              │ │
│  ├───────────────────────────────────────────────────────────────┤ │
│  │  pid       │  进程ID                                            │ │
│  │  tid       │  线程ID                                            │ │
│  │  comm      │  进程名                                            │ │
│  │  nsecs     │  当前时间(纳秒)                                  │ │
│  │  cpu       │  CPU编号                                           │ │
│  │  uid       │  用户ID                                            │ │
│  │  gid       │  组ID                                              │ │
│  │  args      │  函数参数(需要类型转换)                          │ │
│  │  retval    │  返回值(kretprobe)                               │ │
│  │  kstack    │  内核调用栈                                        │ │
│  │  ustack    │  用户调用栈                                        │ │
│  │  arg0-arg9 │  函数参数(整数)                                  │ │
│  └───────────────────────────────────────────────────────────────┘ │
│                                                                     │
│  内置函数:                                                           │
│  ┌───────────────────────────────────────────────────────────────┐ │
│  │  函数              │  说明                                      │ │
│  ├───────────────────────────────────────────────────────────────┤ │
│  │  printf(fmt, ...)  │  格式化输出                                │ │
│  │  str(char*)        │  将char*转换为字符串                       │ │
│  │  join(char*[])     │  将字符串数组用空格连接                    │ │
│  │  hist(int)         │  生成直方图(2的幂次分桶)                 │ │
│  │  lhist(int,...)    │  生成线性直方图                            │ │
│  │  count()           │  计数                                      │ │
│  │  sum(int)          │  求和                                      │ │
│  │  avg(int)          │  平均值                                    │ │
│  │  min(int)          │  最小值                                    │ │
│  │  max(int)          │  最大值                                    │ │
│  │  delete(@x)        │  删除map中的键                             │ │
│  │  clear(@x)         │  清空map                                   │ │
│  │  exit()            │  退出程序                                  │ │
│  │  time(fmt)         │  格式化时间                                │ │
│  └───────────────────────────────────────────────────────────────┘ │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

🧪 动手实验

实验1:系统调用追踪

目的:使用bpftrace追踪系统调用

步骤

  1. 追踪openat系统调用

    Bash
    # 追踪所有进程的文件打开操作
    sudo bpftrace -e 'tracepoint:syscalls:sys_enter_openat {
        printf("%s (PID %d) opened: %s\n", comm, pid, str(args->filename));
    }'
    
    # 输出示例:
    # bash (PID 1234) opened: /etc/passwd
    # ls (PID 5678) opened: /home/user/file.txt
    

  2. 统计系统调用频率

    Bash
    # 统计每个进程的系统调用次数
    sudo bpftrace -e 'tracepoint:raw_syscalls:sys_enter {
        @[comm] = count();
    }'
    
    # 按Ctrl+C后输出:
    # @[bash]: 1234
    # @[chrome]: 56789
    # @[python]: 345
    

  3. 追踪进程创建

    Bash
    # 追踪fork和exec
    sudo bpftrace -e 'tracepoint:syscalls:sys_enter_execve {
        printf("%s (PID %d) executing: %s\n",
               comm, pid, str(args->filename));
    }'
    

实验2:性能分析

目的:分析程序性能瓶颈

步骤

  1. CPU采样分析

    Bash
    # 每秒采样99次(避免与定时器中断冲突)
    sudo bpftrace -e 'profile:hz:99 {
        @[kstack] = count();
    }'
    
    # 输出内核热点函数调用栈
    

  2. 函数延迟直方图

    Bash
    # 测量read系统调用的延迟分布
    sudo bpftrace -e 'kprobe:ksys_read {
        @start[tid] = nsecs;
    }
    kretprobe:ksys_read /@start[tid]/ {
        @latency = hist(nsecs - @start[tid]);
        delete(@start[tid]);
    }'
    
    # 输出:
    # @latency:
    # [0, 1]          1234 |@@@@@@@@@@@@@@@@@@@@|
    # [2, 4)           567 |@@@@@@@@@|
    # [4, 8)           234 |@@@@|
    

  3. 文件系统I/O分析

    Bash
    # 追踪VFS读写操作
    sudo bpftrace -e 'kprobe:vfs_read, kprobe:vfs_write {
        @[func, comm] = sum(arg2);  // 统计读写字节数
    }'
    

实验3:网络追踪

目的:分析网络行为

步骤

  1. 追踪TCP连接

    Bash
    # 追踪TCP连接建立
    sudo bpftrace -e 'kprobe:tcp_v4_connect {
        printf("%s (PID %d) connecting to: ", comm, pid);
    }
    kprobe:inet_csk_complete_hashdance {
        printf("Connection established\n");
    }'
    

  2. 丢包分析

    Bash
    # 追踪TCP丢包
    sudo bpftrace -e 'kprobe:tcp_drop {
        printf("TCP packet dropped by %s\n", kstack());
    }'
    

  3. 网络延迟

    Bash
    # 测量网络RTT
    sudo bpftrace -e 'kprobe:tcp_sendmsg {
        @start[tid] = nsecs;
    }
    kprobe:tcp_recvmsg /@start[tid]/ {
        @rtt = hist(nsecs - @start[tid]);
        delete(@start[tid]);
    }'
    

实验4:编写eBPF C程序

目的:使用BCC编写自定义eBPF程序

步骤

  1. 安装BCC

    Bash
    # Ubuntu
    sudo apt-get install bpfcc-tools linux-headers-$(uname -r)  # $()命令替换:执行命令并获取输出
    
    # Python库
    pip install bcc
    

  2. 编写eBPF程序

    Python
    #!/usr/bin/env python3
    # hello_ebpf.py
    
    from bcc import BPF
    
    # eBPF C程序
    program = """
    #include <linux/sched.h>
    
    // 定义数据结构
    struct data_t {
        u32 pid;
        u32 uid;
        char comm[TASK_COMM_LEN];
        char message[12];
    };
    
    // 定义perf输出缓冲区
    BPF_PERF_OUTPUT(events);
    
    int hello(void *ctx) {
        struct data_t data = {};
    
        // 获取当前进程信息
        data.pid = bpf_get_current_pid_tgid() >> 32;
        data.uid = bpf_get_current_uid_gid() & 0xFFFFFFFF;
        bpf_get_current_comm(&data.comm, sizeof(data.comm));
    
        // 设置消息
        bpf_probe_read_kernel_str(&data.message, sizeof(data.message), "Hello eBPF!");
    
        // 提交事件到用户态
        events.perf_submit(ctx, &data, sizeof(data));
    
        return 0;
    }
    """
    
    # 加载eBPF程序
    b = BPF(text=program)
    
    # 附加到系统调用
    b.attach_kprobe(event=b.get_syscall_fnname("clone"), fn_name="hello")
    
    # 定义回调函数
    def print_event(cpu, data, size):
        event = b["events"].event(data)
        print(f"PID: {event.pid}, UID: {event.uid}, Comm: {event.comm.decode()}, Message: {event.message.decode()}")
    
    # 循环读取事件
    print("Tracing... Press Ctrl+C to exit")
    b["events"].open_perf_buffer(print_event)
    while True:
        try:  # try/except捕获异常
            b.perf_buffer_poll()
        except KeyboardInterrupt:
            break
    

  3. 运行程序

    Bash
    sudo python3 hello_ebpf.py
    
    # 在另一个终端执行:
    ls
    
    # 输出:
    # Tracing... Press Ctrl+C to exit
    # PID: 1234, UID: 1000, Comm: bash, Message: Hello eBPF!
    


💡 核心要点总结

eBPF vs 传统工具

特性 strace perf eBPF/bpftrace
性能开销 高(每次系统调用都中断) 低(JIT编译,内核态执行)
灵活性 低(固定功能) 高(可编程)
实时性 实时 采样 实时
学习曲线 中高
生产环境 不推荐 可用 推荐

eBPF最佳实践

  1. 性能优先
  2. 使用tracepoint而非kprobe(更稳定)
  3. 避免在内核中做复杂计算
  4. 使用per-CPU map减少锁竞争

  5. 安全考虑

  6. 始终检查eBPF程序的返回值
  7. 避免在生产环境直接运行未测试的程序
  8. 注意隐私合规(不要追踪敏感数据)

  9. 调试技巧

  10. 使用bpftrace -d查看调试输出
  11. 使用/sys/kernel/debug/tracing/trace_pipe查看内核日志
  12. 使用bpftool prog list查看加载的程序

常用工具链

Text Only
┌─────────────────────────────────────────────────────────────────────┐
│                    eBPF工具链                                          │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  高级工具(易用)                                                      │
│  ├── bpftrace:类awk的追踪语言,适合快速脚本                          │
│  ├── bcc:Python/C++库,适合复杂程序                                  │
│  └── kubectl-trace:Kubernetes中的bpftrace                            │
│                                                                     │
│  开发工具                                                            │
│  ├── libbpf:C/C++ eBPF加载库                                         │
│  ├── libbpf-bootstrap:eBPF程序模板                                   │
│  ├── bpftool:eBPF程序管理工具                                        │
│  └── LLVM/Clang:编译eBPF程序                                         │
│                                                                     │
│  可视化平台                                                          │
│  ├── Grafana + Prometheus + eBPF exporter                            │
│  ├── Pixie:Kubernetes可观测性平台                                    │
│  └── Groundcover:eBPF应用性能监控                                    │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

❓ 常见问题

Q1:eBPF和内核模块有什么区别?

A:主要区别: - 安全性:eBPF有验证器保证安全,内核模块可能崩溃系统 - 易用性:eBPF无需编译内核,可动态加载卸载 - 性能:eBPF有JIT编译,性能接近原生代码 - 灵活性:内核模块功能更强大,但风险更高

Q2:eBPF对内核版本有什么要求?

A: - 基础功能:Linux 4.1+ - BPF Map:Linux 4.3+ - BPF程序类型扩展:Linux 4.9+ - BPF trampoline:Linux 5.5+ - 推荐:使用Linux 5.10+(LTS版本)

Q3:eBPF程序会影响系统性能吗?

A: - 设计目标:eBPF追求最小性能开销 - 典型开销:<1% CPU(简单追踪) - 注意事项: - 避免高频事件(如每个包处理) - 减少map查找次数 - 使用per-CPU数据结构

Q4:如何学习eBPF编程?

A:学习路径: 1. 学习bpftrace(1-2天) 2. 学习BCC Python编程(1周) 3. 学习libbpf C编程(2周) 4. 阅读开源项目(Cilium、Falco等) 5. 实践:编写自己的工具


📚 扩展阅读

  1. 《Linux Observability with BPF》 - David Calavera
  2. BPF性能工具:brendangregg.com/bpf.html
  3. bpftrace参考指南:github.com/iovisor/bpftrace/blob/master/docs/reference_guide.md
  4. eBPF.io:ebpf.io(社区网站)
  5. Cilium文档:docs.cilium.io

🎯 下一步

继续学习操作系统交互的后续内容,深入了解进程调度、内存管理、文件系统等核心机制。