HiHuo
首页
博客
手册
工具
首页
博客
手册
工具
  • 系统底层修炼

    • 操作系统核心知识学习指南
    • CPU调度与上下文切换
    • CFS调度器原理与源码
    • 内存管理与虚拟内存
    • PageCache与内存回收
    • 文件系统与IO优化
    • 零拷贝与Direct I/O
    • 网络子系统架构
    • TCP协议深度解析
    • TCP问题排查实战
    • 网络性能优化
    • epoll与IO多路复用
    • 进程与线程管理
    • Go Runtime调度器GMP模型
    • 系统性能分析方法论
    • DPDK与用户态网络栈
    • eBPF与内核可观测性
    • 综合实战案例

eBPF与内核可观测性

章节概述

eBPF (extended Berkeley Packet Filter) 是Linux内核中革命性的技术,它允许在内核中安全地运行自定义程序,为系统可观测性、网络、安全等领域带来了前所未有的能力。本章将介绍eBPF的基础知识和实用工具。

学习目标:

  • 理解eBPF的工作原理和应用场景
  • 掌握bcc工具集的使用
  • 学会使用eBPF追踪内核事件
  • 能够编写简单的eBPF程序

核心概念

1. 什么是eBPF?

传统调试方法的问题:

strace:
- 只能追踪系统调用
- 性能开销大
- 无法追踪内核内部

perf:
- 采样式追踪
- 精度有限
- 难以追踪特定事件

内核模块:
- 需要重新编译
- 不安全
- 难以部署

eBPF的优势:

安全性:
- 验证器检查代码
- 不会导致内核崩溃
- 沙箱执行

性能:
- 内核态执行,开销小
- JIT编译
- 高效的事件过滤

灵活性:
- 动态加载/卸载
- 无需重启
- 支持多种事件源

架构图:

┌─────────────────────────────────────┐
│        用户空间应用                  │
│   (bpftrace, bcc tools)              │
└──────────────┬──────────────────────┘
               │ bpf系统调用
┌──────────────▼──────────────────────┐
│         eBPF虚拟机                   │
│  ┌────────────────────────────────┐ │
│  │  eBPF验证器                    │ │
│  │  - 检查代码安全性              │ │
│  │  - 确保有限循环                │ │
│  └────────────────────────────────┘ │
│  ┌────────────────────────────────┐ │
│  │  JIT编译器                     │ │
│  │  - 编译为机器码                │ │
│  └────────────────────────────────┘ │
│  ┌────────────────────────────────┐ │
│  │  Maps (数据存储)               │ │
│  │  - hash, array, perf_buffer   │ │
│  └────────────────────────────────┘ │
└──────────────┬──────────────────────┘
               │
┌──────────────▼──────────────────────┐
│      内核事件钩子                    │
│  - kprobe (内核函数)                 │
│  - tracepoint (静态追踪点)          │
│  - uprobe (用户态函数)              │
│  - XDP (网络包处理)                 │
└─────────────────────────────────────┘

2. eBPF程序类型

kprobe / kretprobe:

作用:追踪内核函数
- kprobe: 函数入口
- kretprobe: 函数返回

示例:
- 追踪sys_read系统调用
- 追踪tcp_v4_connect

tracepoint:

作用:内核静态追踪点
- 稳定的接口
- 不依赖函数名

示例:
- sched:sched_process_exec
- net:net_dev_queue

uprobe / uretprobe:

作用:追踪用户态函数
- uprobe: 函数入口
- uretprobe: 函数返回

示例:
- 追踪libc的malloc
- 追踪Go程序的函数

XDP (eXpress Data Path):

作用:网络包处理
- 最早的包处理点
- 极高性能
- 可以丢弃/转发/修改包

示例:
- DDoS防护
- 负载均衡

3. Maps - 数据存储

Map类型:

BPF_HASH:
- 哈希表
- 用于存储统计数据

BPF_ARRAY:
- 数组
- 固定大小

BPF_PERF_OUTPUT:
- 环形缓冲区
- 向用户空间传递事件

BPF_STACK_TRACE:
- 栈追踪
- 用于火焰图

️ BCC工具集

1. 常用工具

execsnoop - 追踪进程执行:

# 追踪所有exec系统调用
sudo execsnoop

# 输出示例:
# PCOMM            PID    PPID   RET ARGS
# ls               12345  1234     0 /bin/ls -l
# python           12346  1234     0 /usr/bin/python test.py

opensnoop - 追踪文件打开:

# 追踪open系统调用
sudo opensnoop

# 输出:
# PID    COMM               FD ERR PATH
# 1234   nginx               3   0 /var/log/nginx/access.log
# 1235   mysqld              4   0 /var/lib/mysql/data.db

biolatency - 块I/O延迟:

# 追踪磁盘I/O延迟分布
sudo biolatency

# 输出:
#      usecs               : count     distribution
#          0 -> 1          : 0        |                        |
#          2 -> 3          : 0        |                        |
#          4 -> 7          : 10       |*                       |
#          8 -> 15         : 50       |******                  |
#         16 -> 31         : 100      |*************           |
#         32 -> 63         : 200      |**************************|

tcpconnect - 追踪TCP连接:

# 追踪主动TCP连接
sudo tcpconnect

# 输出:
# PID    COMM         IP SADDR            DADDR            DPORT
# 1234   curl         4  192.168.1.100    1.2.3.4          80
# 1235   mysql        4  192.168.1.100    192.168.1.200    3306

tcpretrans - 追踪TCP重传:

# 追踪TCP重传包
sudo tcpretrans

# 输出:
# TIME     PID    IP LADDR:LPORT          T> RADDR:RPORT          STATE
# 10:30:15 0      4  192.168.1.100:45678  R> 1.2.3.4:80            ESTABLISHED

tcptop - TCP流量Top:

# 实时显示TCP连接流量
sudo tcptop

# 输出:
# PID    COMM         LADDR           LPORT RADDR           RPORT  RX_KB  TX_KB
# 1234   nginx        192.168.1.100   80    1.2.3.4         45678  1000   5000
# 1235   mysql        192.168.1.100   3306  192.168.1.200   56789  2000   500

2. 网络相关工具

# tcplife - TCP会话统计
sudo tcplife

# tcpdrop - TCP丢弃包
sudo tcpdrop

# tcpsynbl - SYN backlog
sudo tcpsynbl

# tcpsubnet - 按子网统计流量
sudo tcpsubnet

# sofdsnoop - Socket FD分配追踪
sudo sofdsnoop

3. 文件系统工具

# vfsstat - VFS统计
sudo vfsstat

# filelife - 文件生命周期
sudo filelife

# filetop - 文件I/O Top
sudo filetop

# fileslower - 慢文件I/O
sudo fileslower 10  # 大于10ms的I/O

4. 内存工具

# memleak - 内存泄漏检测
sudo memleak -p <pid>

# slabratetop - slab分配率
sudo slabratetop

# oomkill - OOM杀进程追踪
sudo oomkill

编写eBPF程序

1. 使用bpftrace

bpftrace是高级追踪语言:

# 安装
sudo apt install bpftrace

# 简单示例:追踪openat系统调用
sudo bpftrace -e 'tracepoint:syscalls:sys_enter_openat { printf("%s %s\n", comm, str(args->filename)); }'

示例1:追踪进程CPU时间:

sudo bpftrace -e '
tracepoint:sched:sched_switch
{
    @time[pid] = nsecs;
}

tracepoint:sched:sched_switch
/@time[args->prev_pid]/
{
    @cpu_ns[args->prev_pid, comm] = sum(nsecs - @time[args->prev_pid]);
}

END
{
    clear(@time);
}
'

示例2:统计系统调用:

sudo bpftrace -e '
tracepoint:raw_syscalls:sys_enter
{
    @syscall[args->id] = count();
}

interval:s:5
{
    print(@syscall);
    clear(@syscall);
}
'

示例3:TCP连接延迟:

sudo bpftrace -e '
kprobe:tcp_v4_connect
{
    @start[tid] = nsecs;
}

kretprobe:tcp_v4_connect
/@start[tid]/
{
    $lat = (nsecs - @start[tid]) / 1000;
    @latency_us = hist($lat);
    delete(@start[tid]);
}
'

2. 使用BCC (Python接口)

示例:追踪read系统调用

#!/usr/bin/python3
# readlatency.py

from bcc import BPF
from time import sleep

# eBPF程序 (C代码)
prog = """
#include <uapi/linux/ptrace.h>

BPF_HASH(start, u32);
BPF_HISTOGRAM(dist);

int do_entry(struct pt_regs *ctx)
{
    u32 pid = bpf_get_current_pid_tgid();
    u64 ts = bpf_ktime_get_ns();
    
    start.update(&pid, &ts);
    return 0;
}

int do_return(struct pt_regs *ctx)
{
    u32 pid = bpf_get_current_pid_tgid();
    u64 *tsp, delta;
    
    tsp = start.lookup(&pid);
    if (tsp == 0) {
        return 0;
    }
    
    delta = bpf_ktime_get_ns() - *tsp;
    dist.increment(bpf_log2l(delta / 1000));
    start.delete(&pid);
    
    return 0;
}
"""

# 加载eBPF程序
b = BPF(text=prog)
b.attach_kprobe(event="__x64_sys_read", fn_name="do_entry")
b.attach_kretprobe(event="__x64_sys_read", fn_name="do_return")

print("Tracing read() latency... Hit Ctrl-C to end.")

try:
    sleep(99999999)
except KeyboardInterrupt:
    pass

# 打印直方图
print("\nread() latency (us):")
b["dist"].print_log2_hist("usecs")

运行:

sudo python3 readlatency.py

# 输出:
# Tracing read() latency... Hit Ctrl-C to end.
# ^C
# read() latency (us):
#      usecs               : count     distribution
#          0 -> 1          : 100      |**********          |
#          2 -> 3          : 200      |********************|
#          4 -> 7          : 150      |***************     |
#          8 -> 15         : 50       |*****               |

3. 性能分析示例

CPU火焰图:

# 采集60秒的CPU栈
sudo /usr/share/bcc/tools/profile -F 99 -f 60 > profile.stacks

# 生成火焰图
git clone https://github.com/brendangregg/FlameGraph
cd FlameGraph
./flamegraph.pl < profile.stacks > cpu_flame.svg

# 在浏览器中打开cpu_flame.svg

Off-CPU火焰图:

# 追踪阻塞事件
sudo /usr/share/bcc/tools/offcputime -df -p <pid> 60 > offcpu.stacks

# 生成火焰图
./flamegraph.pl --color=io --title="Off-CPU Time" < offcpu.stacks > offcpu_flame.svg

实战场景

场景1:排查网络延迟

# 1. 追踪TCP连接延迟
sudo bpftrace -e '
kprobe:tcp_v4_connect { @start[tid] = nsecs; }
kretprobe:tcp_v4_connect /@start[tid]/ {
    printf("Connection latency: %d us\n", (nsecs - @start[tid]) / 1000);
    delete(@start[tid]);
}
'

# 2. 追踪TCP重传
sudo tcpretrans

# 3. 查看丢包
sudo tcpdrop

场景2:排查磁盘I/O慢

# 1. I/O延迟分布
sudo biolatency -m

# 2. 慢I/O追踪
sudo biosnoop | awk '$4 > 10'

# 3. 文件级I/O追踪
sudo filetop
sudo fileslower 10

场景3:排查内存泄漏

# 1. 追踪内存分配
sudo memleak -p <pid>

# 输出:
# [10:30:15] Top 10 stacks with outstanding allocations:
#     128 bytes in 8 allocations from stack
#         0x7f8b9c0a1b20 malloc+0x0
#         0x5555555551a0 leak_func+0x10
#         0x555555555200 main+0x20

# 2. 追踪特定函数
sudo memleak -p <pid> -t -o 10000 -c 'malloc,free'

常见问题

Q1: eBPF对性能有影响吗?

A: 有影响,但很小:

  • 采样式追踪:影响< 1%
  • 每事件追踪:影响1-5%
  • 复杂过滤:影响可能更大
  • 建议:生产环境谨慎使用

Q2: eBPF需要什么内核版本?

A: 基本要求:

  • Linux 4.x+: 基本eBPF支持
  • Linux 4.14+: BTF (BPF Type Format)
  • Linux 5.x+: 更多特性

检查:

uname -r
# 4.15.0 或更高

# 检查eBPF支持
zgrep CONFIG_BPF /proc/config.gz

Q3: bpftrace和bcc有什么区别?

A:

  • bpftrace: 单行脚本,快速诊断
  • bcc: Python/C编程,功能强大
  • 选择:临时诊断用bpftrace,复杂工具用bcc

Q4: eBPF程序会导致内核崩溃吗?

A: 不会:

  • 验证器检查所有代码
  • 禁止不安全操作
  • 强制有限循环
  • 但bug可能导致性能下降

复习题

选择题

  1. eBPF的主要优势是?

    • A. 比strace快
    • B. 安全且灵活
    • C. 使用简单
    • D. 无性能开销
  2. kprobe和tracepoint的区别?

    • A. kprobe更稳定
    • B. tracepoint更稳定
    • C. 没有区别
    • D. kprobe性能更好
  3. BCC工具集使用什么语言?

    • A. Shell
    • B. Python
    • C. Go
    • D. Rust

简答题

  1. 解释eBPF的工作原理。
  2. 列举至少5个常用的BCC工具及其用途。
  3. 什么是Maps?它在eBPF中的作用是什么?
  4. 如何使用eBPF排查TCP重传问题?

实战题

使用bpftrace编写一个程序,统计每个进程的系统调用次数,并按次数排序输出。


下一章预告: 综合实战案例,将所有知识点串联起来解决实际问题。

Prev
DPDK与用户态网络栈
Next
综合实战案例