深入 Linux 性能调试 —— BPF 与 BCC 工具实战指南

深入 Linux 性能调试 ------ BPF 与 BCC 工具实战指南

副标题 :别再只盯着 top 了------用 eBPF 这把「瑞士军刀」,透视你的 Linux 系统

目标读者 :Linux 运维工程师 / 后端开发 / 系统程序员 / 性能调优爱好者

实验环境:ecs-2aa6-0001,Ubuntu 24.04.4 LTS,内核 6.8.0-106-generic,BCC 0.29.1


目录

  1. [引言:为什么你需要 BPF?](#引言:为什么你需要 BPF?)
  2. [第一章:环境准备与基石------BCC 工具集](#第一章:环境准备与基石——BCC 工具集)
  • 1.1 BCC 是什么?
  • 1.2 动手安装 BCC
  • 1.3 BCC 核心工具速览
  1. [第二章:CPU 性能瓶颈排查实战](#第二章:CPU 性能瓶颈排查实战)
  • 2.1 案例:定位高 CPU 消耗的函数(profile + 火焰图)
  • 2.2 案例:追踪新进程的启动(execsnoop
  1. [第三章:文件系统与磁盘 I/O 调试](#第三章:文件系统与磁盘 I/O 调试)
  • 3.1 案例:谁在疯狂读写文件?(opensnoop / filetop
  • 3.2 案例:深入磁盘 I/O 延迟分析(biolatency / biosnoop
  1. 第四章:网络性能问题诊断
  • 4.1 案例:监控实时网络流量(tcptop
  • 4.2 案例:追踪连接的建立与关闭(tcpconnect / tcpaccept
  1. [第五章:进阶探索------BPF 与应用程序、安全](#第五章:进阶探索——BPF 与应用程序、安全)
  2. 结语:从工具使用者到问题终结者

引言:为什么你需要 BPF?

一个真实的生产故障场景

凌晨 2:15,告警短信把你从睡梦中炸醒:「核心业务服务器 CPU 使用率飙升至 98%,响应超时率 40%!」

你火速登录服务器,熟练地敲下 top

复制代码
%Cpu(s): 97.8 us,  1.2 sy,  0.0 id,  0.0 wa, ...
  PID USER      %CPU COMMAND
12345 app       98.7  java

看到了------Java 进程占满 CPU。但问题来了:JVM 里有上百个线程,成百上千个方法,到底是哪个方法 在烧 CPU?top 只能告诉你「哪个进程」,无法告诉你「进程里的哪个函数」。

你转而用 perf top,确实能看到函数名,但采样结果转瞬即逝,既不能回放也无法深入分析。你试了 strace,结果系统调用日志像洪水一样涌来,真正的热点反而淹没其中------而且 strace 会让被追踪的进程性能下降 30%~50%,生产环境根本不敢用。

这就是传统工具的局限

工具 能做什么 不能做什么
top / htop 进程级别 CPU/内存概览 无法看到进程内部的函数调用
strace 追踪系统调用 开销巨大(~30%+),不能用于生产
perf 硬件性能计数器采样 后处理复杂,不能做动态过滤
vmstat / iostat 系统级 I/O 统计 无法归因到具体进程/文件
tcpdump 抓包分析 无法关联到进程/连接状态

BPF 的革命性

eBPF(extended Berkeley Packet Filter) 改变了这一切。它允许你在 Linux 内核中运行沙箱化的程序,无需修改内核源码、无需重启、开销极低(通常 1%~3%)。

复制代码
┌──────────────────────────────────────────────────────────┐
│                    传统可观测性工具栈                        │
│                                                          │
│  top / ps  ──→  只看到进程级别                             │
│  strace    ──→  开销 30%+,生产环境不敢用                    │
│  perf      ──→  数据量大,难做动态过滤                       │
│  tcpdump   ──→  抓包可以,但不知道是哪个进程发出的              │
└──────────────────────────────────────────────────────────┘
                          ↓ BPF 革命
┌──────────────────────────────────────────────────────────┐
│                    eBPF 可观测性                           │
│                                                          │
│  • 内核级探针(kprobe / tracepoint)   →  零侵入追踪         │
│  • 动态插桩(USDT)                    →  应用层埋点          │
│  • 安全沙箱(Verifier)               →  内核不会崩溃         │
│  • < 3% 性能开销                      →  生产环境可用         │
│  • 即时加载,无需重启                   →  在线诊断             │
└──────────────────────────────────────────────────────────┘

一句话总结:BPF 让你能在内核中安全地执行自定义代码,观测系统行为的方方面面------从 CPU 函数级热点,到磁盘 I/O 微秒级延迟,再到网络连接生命周期------而不需要修改一行内核代码。

本文目标

带你从零开始,在真实服务器上搭建 BCC 环境,通过 6 个可复现的实战案例,掌握 BPF/BCC 的核心调试技能。每一节都包含真实的命令输出和解读,你可以边读边在自己服务器上操作。


第一章:环境准备与基石------BCC 工具集

1.1 BCC 是什么?

BCC(BPF Compiler Collection) 是 iovisor 社区开发的一套工具集,让你可以用 Python / Lua 编写 BPF 程序的前端,用 C 语言编写运行在内核中的 BPF 后端。

复制代码
┌───────────────────────────────────────────┐
│               BCC 工作架构                  │
│                                           │
│  Python 前端 ──→ LLVM/Clang 编译 ──→ BPF 字节码 │
│       ↕                                    │
│  BPF Maps / Perf Buffer ── 内核态 ←──→ 用户态  │
│                                           │
│  /usr/sbin/*-bpfcc   ←── 90+ 预构建工具       │
└───────────────────────────────────────────┘

BCC 自带 90+ 个生产就绪的工具 ,覆盖了 CPU、内存、磁盘 I/O、网络、安全等几乎所有系统领域。你不需要会写 BPF 程序就能使用它们------就像你用 top 不需要懂 Linux 调度器一样。

1.2 动手安装 BCC

实验环境 :我们的实操基于华为云 ecs-2aa6-0001 节点:

bash 复制代码
# 系统信息
root@build-01:~# uname -r
6.8.0-106-generic

root@build-01:~# cat /etc/os-release | head -2
PRETTY_NAME="Ubuntu 24.04.4 LTS"
VERSION_ID="24.04"

关键要求 :Linux 内核版本 ≥ 4.9(推荐 5.x+ 以获得完整功能)。内核版本通过 uname -r 查看。Ubuntu 24.04 默认内核 6.8 完全满足。

安装步骤(Ubuntu / Debian):

bash 复制代码
# 一步到位安装
apt-get update
apt-get install -y bpfcc-tools python3-bpfcc bpftool linux-headers-$(uname -r)

# 安装火焰图生成工具(可选,用于 CPU 可视化分析)
git clone --depth=1 https://github.com/brendangregg/FlameGraph.git /opt/FlameGraph

在实验服务器上验证安装状态:

bash 复制代码
root@build-01:~# dpkg -l | grep -i bpf
ii  bpfcc-tools        0.29.1+ds-1ubuntu7  all    tools for BPF Compiler Collection (BCC)
ii  python3-bpfcc      0.29.1+ds-1ubuntu7  all    Python 3 wrappers for BPF Compiler Collection

CentOS / RHEL 用户

bash 复制代码
yum install -y bcc-tools python3-bcc

验证安装------运行几个简单命令确认一切正常:

bash 复制代码
# 测试:追踪 exec() 系统调用(相当于实时看到所有新进程)
/usr/sbin/execsnoop-bpfcc
# 正常输出应显示 "Tracing exec() syscalls..."

# 测试:查看块设备 I/O 延迟分布
/usr/sbin/biolatency-bpfcc -m 1 1
# 正常输出应显示毫秒级 I/O 直方图

⚠️ 踩坑提示 :Ubuntu 24.04 中 BCC 工具安装在 /usr/sbin/ 下,命令名带 -bpfcc 后缀(如 execsnoop-bpfcc)。部分文档中写的 execsnoop 在 Ubuntu 上会报 command not found

1.3 BCC 核心工具速览

BCC 提供 90+ 工具,按领域分类,以下是你最可能用到的 15 个:

分类 工具 作用 一句话说明
CPU profile CPU 函数级采样 「哪个函数在烧 CPU?」
execsnoop 追踪新进程启动 「谁在偷偷跑命令?」
runqlat 调度器排队延迟 「CPU 够不够用?」
cpudist CPU on-CPU 时间分布
内存 memleak 内存泄露检测 「哪个 alloc 没 free?」
cachestat 页缓存命中率 「内存够不够做缓存?」
磁盘 I/O biolatency 磁盘 I/O 延迟直方图 「磁盘慢在哪一段?」
biosnoop 逐 I/O 请求追踪 「每个 I/O 的详细信息」
biotop 按进程排 I/O 占用 「谁在刷盘?」
opensnoop 文件打开追踪 「哪个进程打开了哪个文件?」
filetop 文件级 I/O 排序 「哪个文件最「热」?」
网络 tcptop TCP 流量进程排行 「谁在吃带宽?」
tcpconnect TCP 主动连接追踪 「连接都去了哪里?」
tcpaccept TCP 被动连接追踪 「谁在连我的端口?」
tcplife TCP 连接生命周期 「连接存活了多久?」

下文将围绕其中最核心的工具展开实战。


第二章:CPU 性能瓶颈排查实战

2.1 案例:定位高 CPU 消耗的函数(profile

问题描述

你的服务器上某个进程 CPU 使用率跑到 100%,top 能告诉你「是哪个进程」,但无法告诉你「进程里哪个函数在消耗 CPU」。火焰图是可视化 CPU 时间分布的终极武器。

工具介绍:profile-bpfcc

profile 是一个基于采样的 CPU 剖析器(profiler),它周期性地采集每个 CPU 上正在执行的内核栈和用户栈,统计每个函数调用栈出现的次数。采样频率越高、时间越长,结果越精确。

复制代码
工作原理:
  每 10ms 发送一个定时中断
      ↓
  内核记录当前 CPU 的 PC(程序计数器)
      ↓
  回溯调用栈(frame pointer unwinding)
      ↓
  聚合统计:哪个函数 → 哪个调用路径 → 多少次
实操步骤

Step 1:准备 CPU 密集型测试程序

为了复现真实场景,我们先写一个带明显热点的 C 程序:

c 复制代码
// cpu_burner.c - 模拟 CPU 密集型负载
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <unistd.h>

// 质数判定 ------ 这是「热点函数」
int is_prime(long n) {
    if (n < 2) return 0;
    for (long i = 2; i * i <= n; i++)
        if (n % i == 0) return 0;
    return 1;
}

int main(int argc, char *argv[]) {
    int mode = argc > 1 ? atoi(argv[1]) : 0;
    printf("[cpu_burner] PID=%d, mode=%d\n", getpid(), mode);
    long count = 0;
    for (long n = 2; ; n++) {
        if (mode == 0) {
            if (is_prime(n)) count++;
            if (n > 500000) {
                printf("[burner] found %ld primes\n", count);
                n = 2; count = 0;
            }
        } else {
            volatile double x = sin(n * 0.001) * cos(n * 0.0001);
            if (n % 1000000 == 0)
                printf("[burner] tick n=%ld\n", n);
        }
    }
    return 0;
}

重要 :编译时使用 -O0 -g -fno-omit-frame-pointer,避免编译器内联优化导致函数调用栈丢失:

bash 复制代码
gcc -O0 -g -fno-omit-frame-pointer -o /tmp/cpu_burner /tmp/cpu_burner.c -lm

Step 2:启动 CPU 负载

bash 复制代码
# 启动两个 CPU 密集型进程作为追踪目标
/tmp/cpu_burner 0 > /dev/null 2>&1 &
/tmp/cpu_burner 1 > /dev/null 2>&1 &

Step 3:运行 profile 采样并生成火焰图

bash 复制代码
# profile 采样 8 秒,频率 99Hz,折叠格式输出
# -d 8   : 采样 8 秒
# -F 99  : 每秒 99 次采样(99Hz)
# -f     : 输出折叠格式(一行一个调用栈,末尾为采样次数)
/usr/sbin/profile-bpfcc -d 8 -F 99 -f > /tmp/profile.txt

# 生成火焰图 SVG
/opt/FlameGraph/flamegraph.pl \
    --title="CPU Flame Graph - cpu_burner" \
    --countname=samples \
    /tmp/profile.txt > /tmp/flamegraph.svg
结果解读

原始折叠格式输出(部分)

复制代码
cpu_burner_dbg;_start;__libc_start_main;[unknown];main;is_prime 521
cpu_burner_dbg;_start;__libc_start_main;[unknown];main;[unknown] 137
cpu_burner_dbg;_start;__libc_start_main;[unknown];main 123
cpu_burner_dbg;_start;__libc_start_main;[unknown];main;[unknown] 65

格式说明 :每一行 调用栈 采样次数,函数名以 ; 分隔,从左到右是从进程到叶节点(最底层被调用函数)。

关键发现

调用栈路径 采样次数 占比 含义
main → is_prime 521 ~55% 🔥 绝对热点------质数判定函数
main → [unknown] 137 ~15% 在 main 内部其他操作(循环、printf)
main 123 ~13% main 函数入口开销

unknown 出现是因为部分 libc 内部函数符号未解析。生产环境中使用 -g 编译 + 不 strip 符号表可以消除。

火焰图解读方法

复制代码
┌──────────────────────────────────────────────────────────┐
│  << 火焰图分析口诀 >>                                        │
│                                                          │
│  1. X 轴宽度 = CPU 时间占比 --- 越宽越热                       │
│  2. Y 轴高度 = 调用栈深度 --- 向上是 call,向下是 return           │
│  3. 顶部"平原" = 热点函数 --- 横跨最宽的就是元凶                   │
│  4. 颜色 = 代码类型(用户态橙色 / 内核态红色)                    │
└──────────────────────────────────────────────────────────┘

在我们的实验中,is_prime 出现在调用栈最右侧(叶节点),并且行尾计数高达 521,是绝对的热点函数。优化方向非常明确:优化 is_prime 函数的算法(如使用 Miller-Rabin 素性检测替代试除法)。

💡 生产实战技巧 :不用自己写测试程序。线上 Java 应用 CPU 飙高时,直接 profile-bpfcc -p <Java_PID> -d 30 -f | flamegraph.pl > cpu.svg,然后看最宽的「平原」是哪个方法,配合 perf-map-agent 解析 JIT 编译的符号。


2.2 案例:追踪新进程的启动(execsnoop

问题描述

服务器突然 CPU 飙升,top 只看到一堆短命进程------你甚至来不及看它们的命令行参数它们就消失了。这通常是某个定时任务或监控脚本频繁 fork 导致的问题。你需要实时看到系统中每一个新进程的诞生

工具介绍:execsnoop-bpfcc

execsnoop 通过挂载(hook)内核中的 execve 系统调用追踪点,捕获每一次进程执行的完整信息。

复制代码
execsnoop 挂载点:
  内核 tracepoint: syscalls:sys_enter_execve
          ↓
  捕获: PID, PPID, UID, 命令行参数, 返回值
          ↓
  输出: 实时流,一行一个进程
实操步骤
bash 复制代码
# 在新终端运行 execsnoop(带时间戳)
/usr/sbin/execsnoop-bpfcc -T

在另一个终端触发一些进程:

bash 复制代码
for i in 1 2 3; do cat /etc/hostname > /dev/null; done
whoami > /dev/null
结果解读

真实输出

复制代码
21:40:02 TIME     PCOMM            PID     PPID    RET ARGS
cat              14713   14709     0 /bin/cat /etc/hostname
21:40:02 sleep            14714   14709     0 /usr/bin/sleep 0.1
21:40:02 cat              14715   14709     0 /bin/cat /etc/hostname
21:40:02 sleep            14716   14709     0 /usr/bin/sleep 0.1
21:40:02 cat              14717   14709     0 /bin/cat /etc/hostname
21:40:02 sleep            14718   14709     0 /usr/bin/sleep 0.1
21:40:02 whoami           14719   14709     0 /usr/bin/whoami

字段说明

字段 含义 本例说明
TIME 时间戳(需 -T 精确到秒
PCOMM 父进程名 我们的 for 循环由 bash 触发
PID 新进程 ID 每次 exec 都是新 PID
PPID 父进程 ID 全部是 14709(同一个 bash)
RET execve 返回值 0 = 成功,非 0 = 失败
ARGS 完整命令行 包含所有参数

关键发现

  • 所有进程的 PPID 都是 14709,说明它们由同一个父进程(我们的 shell)启动
  • RET 全是 0,进程启动全部成功
  • 可以清晰看到 sleepcatwhoami 的完整命令行

💡 生产实战技巧 :怀疑服务器有挖矿病毒时,运行 execsnoop-bpfcc -T 并 grep 可疑的进程名。也可以加上 -x 参数追踪失败的 exec,用于排查「command not found」类错误。


第三章:文件系统与磁盘 I/O 调试

3.1 案例:谁在疯狂读写文件?(opensnoop

问题描述

磁盘 I/O 飙升,iostat 显示 util% 接近 100%,但不知道是哪个进程在操作哪些文件。你需要实时看到每一次 open() 系统调用的详情。

工具介绍:opensnoop-bpfcc
复制代码
opensnoop 挂载点:
  内核 tracepoint: syscalls:sys_enter_openat
          ↓
  捕获: PID, 进程名, FD, 返回值, 文件路径
          ↓
  输出: 实时流,一行一次文件打开操作
实操步骤与真实输出

我们开启 opensnoop 后,让它运行几秒钟:

bash 复制代码
/usr/sbin/opensnoop-bpfcc -T

真实输出(截取)

复制代码
0.000000000   TIME(s)       PID    COMM               FD ERR PATH
15145  sshd                5   0 /var/log/btmp
0.000124000   296    systemd-journal    34   0 /proc/15145/comm
0.000159000   296    systemd-journal    34   0 /proc/15145/cmdline
0.001620    15151  sshd                3   0 /dev/null
0.001621    15151  sshd               -1   2 /proc/sys/crypto/fips_enabled
0.001620    15151  sshd                3   0 /usr/lib/ssl/openssl.cnf
0.001623    15151  sshd                4   0 /etc/gai.conf
0.001623    15151  sshd                4   0 /etc/nsswitch.conf
0.001623    15151  sshd                4   0 /etc/passwd

字段说明

字段 含义 重点关注
PID 进程 ID 哪个进程在执行 open
COMM 进程名 sshd / systemd-journal
FD 文件描述符 -1 表示打开失败
ERR 错误码 非 0 = 打开失败(2 = ENOENT 文件不存在)
PATH 文件路径 🔥 这就是你要找的「元凶文件」

关键发现

  • sshd (PID 15151) 在打开 /proc/sys/crypto/fips_enabled 时返回 ERR=2(文件不存在),这可能是一个配置问题
  • systemd-journal 频繁读取 /proc/<pid>/comm 等文件,这是正常的日志记录行为
  • sshd 读取了一系列动态库(libcrypto.so.3libpam.so.0 等),这是每个 SSH 会话的标准加载流程

💡 生产实战技巧opensnoop-bpfcc -T | grep -v '^296' 过滤掉 systemd-journal 的噪音(PID 296)。加上 -x 参数只显示失败的 open,用于排查文件缺失导致的程序异常。


3.2 案例:深入磁盘 I/O 延迟分析(biolatency / biosnoop

问题描述

应用日志无异常,但用户反馈响应慢。iostat 只能告诉你磁盘忙不忙,但无法告诉你延迟分布------是每次都慢还是偶尔卡一下?偶尔卡一下的时候是谁在写什么?

工具介绍
  • biolatency:以直方图(histogram)形式展示块设备 I/O 请求的延迟分布。x 轴是延迟区间,y 轴是落在该区间的 I/O 次数。

  • biosnoop:逐行输出每一个 I/O 请求的详细信息------时间、进程名、扇区号、传输字节数、延迟。

    biolatency 工作原理:
    挂载块层 tracepoint: block:block_io_start / block:block_io_done

    计算每个 I/O: LATENCY = io_done_time - io_start_time

    按延迟区间分组统计 → 直方图输出

实操步骤

Step 1:制造磁盘 I/O 负载

bash 复制代码
# 50,000 次 4KB 随机写,使用 O_DIRECT 绕过页缓存
dd if=/dev/zero of=/tmp/iotest bs=4K count=50000 oflag=direct conv=fdatasync &

# 同步写(不带 O_DIRECT,经过页缓存)
dd if=/dev/zero of=/tmp/iotest2 bs=4K count=50000 conv=fdatasync &

Step 2:运行 biolatency 查看延迟分布

bash 复制代码
# -m: 毫秒级直方图;  1: 每秒输出一次;  5: 共输出 5 次
/usr/sbin/biolatency-bpfcc -m 1 5

真实输出

复制代码
Tracing block device I/O... Hit Ctrl-C to end.

     msecs               : count     distribution
         0 -> 1          : 2599     |****************************************|
         2 -> 3          : 2        |                                        |
         4 -> 7          : 0        |                                        |
         8 -> 15         : 1        |                                        |

     msecs               : count     distribution
         0 -> 1          : 2516     |****************************************|
         2 -> 3          : 1        |                                        |
         4 -> 7          : 1        |                                        |

     msecs               : count     distribution
         0 -> 1          : 2651     |****************************************|
         2 -> 3          : 2        |                                        |
         4 -> 7          : 1        |                                        |

     msecs               : count     distribution
         0 -> 1          : 2572     |****************************************|
         2 -> 3          : 2        |                                        |
         4 -> 7          : 1        |                                        |

     msecs               : count     distribution
         0 -> 1          : 2531     |****************************************|
         2 -> 3          : 1        |                                        |
         4 -> 7          : 2        |                                        |

Step 3:运行 biosnoop 逐 I/O 追踪

bash 复制代码
# 实时追踪每一个块设备 I/O 请求
/usr/sbin/biosnoop-bpfcc

真实输出

复制代码
TIME(s)     COMM           PID     DISK      T SECTOR     BYTES  LAT(ms)
0.000000    dd             16297   vda       W 15807256   4096      0.30
0.000425    dd             16297   vda       W 15807264   4096      0.40
0.001298    dd             16297   vda       W 15807272   4096      0.33
0.001772    dd             16297   vda       W 15807280   4096      0.46
0.002094    dd             16297   vda       W 15807288   4096      0.31
0.002491    dd             16297   vda       W 15807296   4096      0.39
0.002822    dd             16297   vda       W 15807304   4096      0.32
0.003203    dd             16297   vda       W 15807312   4096      0.37
0.003533    dd             16297   vda       W 15807320   4096      0.32
0.003971    dd             16297   vda       W 15807328   4096      0.43
0.005355    dd             16297   vda       W 15807352   4096      0.55
0.006462    dd             16297   vda       W 15807360   4096      1.10   ← 延迟峰值
0.006798    dd             16297   vda       W 15807368   4096      0.32
结果解读

biolatency 分析

复制代码
每秒钟约 2500~2600 次 I/O 操作
├── 99.9% 的 I/O 在 0~1ms 完成  ← 绝大多数极快
├── ~0.1%   在 2~3ms           ← 偶尔轻微延迟
└── 极少在 4~15ms              ← 偶发性的较大延迟

延迟分布是长尾形状(绝大部分 < 1ms),属于正常云盘表现。如果 4~7ms 区间的 count 突然增大,说明存储后端出现了性能抖动。

biosnoop 分析

字段 本次值 含义
COMM dd 执行 I/O 的进程名
DISK vda 块设备名(Virtio 虚拟磁盘)
T W 操作类型(W=写, R=读)
SECTOR 15807xxx 写入的数据扇区号
BYTES 4096 每次写入 4KB(与 dd 的 bs=4K 一致)
LAT(ms) 0.29 ~ 1.10 单次 I/O 延迟(毫秒)

注意到 SECTOR 地址基本连续(15807256 → 15807264 → ...),说明是顺序写 。如果扇区号跳变剧烈,则是随机写------此时延迟通常会显著升高。
💡 生产实战技巧biolatency 加上 -Q 参数可以区分设备时间和排队时间,帮助判断延迟是来自存储硬件还是内核 I/O 调度器排队。biosnoop 加上 -P 参数可以自动标注 I/O 是顺序还是随机。


第四章:网络性能问题诊断

4.1 案例:监控实时网络流量(tcptop

问题描述

服务器出口带宽突然跑满,iftop 能看到对端 IP,但不知道是哪个进程发的流量。你需要一个「TCP 流量版的 top」------按进程显示实时 TCP 吞吐量。

工具介绍:tcptop-bpfcc

tcptop 通过挂载内核中 TCP 发送/接收的 probe 点,按进程和连接聚合实时流量。它就像 top 一样,但显示的不是 CPU 利用率,而是每秒 TCP 吞吐量(KB/s)

⚠️ tcptop 需要终端支持,在无 TERM 环境(如 CI/CD)可能报 TERM 相关错误。可以通过 TERM=xterm 设置环境变量或使用非交互式的 tcpconnect/tcpaccept 替代。


4.2 案例:追踪连接的建立与关闭(tcpconnect / tcpaccept

问题描述

运维或安全场景中,你需要知道:

  1. 主动连接(connect):服务器上哪些进程在向外部发起 TCP 连接?连到哪里?
  2. 被动连接(accept):哪些外部 IP 在连接你服务器上的端口?

这在排查「连接泄漏」「异常外连」「连接风暴」时至关重要。

工具介绍
  • tcpconnect :追踪内核 tcp_v4_connect / tcp_v6_connect,捕获每一次主动连接。

  • tcpaccept :追踪内核 inet_csk_accept,捕获每一次被动接受连接。

    tcpconnect 挂载点: kprobe:tcp_v4_connect
    tcpaccept 挂载点: kretprobe:inet_csk_accept

实操步骤

先启动一个 HTTP 服务作为被追踪目标:

python 复制代码
# 启动一个简单 HTTP 服务(端口 9999)
import http.server, threading
class H(http.server.SimpleHTTPRequestHandler):
    def log_message(self, *a): pass
server = http.server.HTTPServer(('', 9999), H)
t = threading.Thread(target=server.serve_forever, daemon=True); t.start()
print("HTTP server on :9999")

运行 tcpconnect(主动连接侧------curl 发起连接)

bash 复制代码
# -t 添加时间戳
/usr/sbin/tcpconnect-bpfcc -t

在另一个终端用 curl 发起请求:

bash 复制代码
for i in $(seq 5); do curl -s -o /dev/null http://localhost:9999/etc/hostname; done

真实输出

复制代码
Tracing connect ... Hit Ctrl-C to end
TIME(s)  PID     COMM         IP SADDR            DADDR            DPORT
16647   curl         4  127.0.0.1        127.0.0.1        9999
-0.000   16647   curl         6  ::1              ::1              9999

运行 tcpaccept(被动接受侧------HTTP 服务接受连接)

bash 复制代码
/usr/sbin/tcpaccept-bpfcc

真实输出

复制代码
PID     COMM         IP RADDR            RPORT LADDR            LPORT
16757   python3      4  127.0.0.1        37830 127.0.0.1        9999
结果解读

tcpconnect 输出解读

复制代码
PID=16647  COMM=curl      ← curl 进程发起的连接
IP=4       SADDR=127.0.0.1 DADDR=127.0.0.1 DPORT=9999   ← IPv4 本地回环
IP=6       SADDR=::1       DADDR=::1       DPORT=9999   ← IPv6 本地回环

curl 默认优先尝试 IPv6(::1),失败后回退到 IPv4(127.0.0.1)。两次 connect 是 curl 的正常行为(Happy Eyeballs)。

tcpaccept 输出解读

复制代码
PID=16757  COMM=python3    ← 我们的 HTTP 服务进程
IP=4       RADDR=127.0.0.1 LPORT=9999  ← 接受来自 localhost 的连接
           RPORT=37830                   ← 客户端的临时端口

字段速查

tcpconnect tcpaccept 含义
PID PID 发起/接受连接的进程 ID
COMM COMM 进程名
IP IP 4=IPv4, 6=IPv6
SADDR RADDR 源地址 / 远程地址
DADDR LADDR 目的地址 / 本地地址
DPORT LPORT 目的端口 / 本地端口
--- RPORT 远程(客户端)端口

💡 生产实战技巧

  • 排查外连异常tcpconnect-bpfcc -t | grep -v '127.0.0.1' 过滤掉本地回环,只看真正的外连。
  • 排查连接泄漏tcpconnect-bpfcc -P 3306 只看连接到 MySQL 的进程。如果短连接频繁创建不释放,这里会看到大量 connect。
  • 排查安全事件tcpaccept-bpfcc 能看到谁在连你的 SSH 端口(22),配合防火墙做安全审计。

第五章:进阶探索------BPF 与应用程序、安全

5.1 在应用中集成 BPF(USDT)简介

前文我们使用 BCC 工具观测的是内核行为 ------系统调用、TCP 连接、磁盘 I/O。但有时候,问题的根源不在内核,而在应用层的某个函数

USDT(User Statically Defined Tracing) 允许开发者在自己的 C/C++ 代码中插入静态探针(probe),然后通过 BPF 动态追踪这些探针,无需重启应用。

复制代码
工作原理:
  C/C++ 代码中插入 DTRACE_PROBE 宏
          ↓
  编译后生成 ELF 中的 .note.stapsdt 段
          ↓
  BCC / bpftrace 通过 uprobe 读取这些探针地址
          ↓
  运行时动态附加 BPF 程序到探针位置
          ↓
  当程序执行到探针处 → 触发 BPF 程序 → 记录日志/计数/延时

一个简单的示例:

c 复制代码
#include <sys/sdt.h>   // systemtap SDK

void do_work(int id) {
    DTRACE_PROBE2(myapp, work_start, id, 0);   // 探针:工作开始
    // ... 实际业务逻辑 ...
    DTRACE_PROBE2(myapp, work_done, id, result); // 探针:工作结束
}
bash 复制代码
# 追踪应用程序中的自定义探针
bpftrace -e 'usdt:/path/to/myapp:myapp:work_start { printf("work_start id=%d\n", arg0); }'

主流项目中使用 USDT 的案例:MySQL(查询执行耗时)、PostgreSQL、Node.js(GC 事件)、Python(函数调用)。掌握 USDT 意味着你可以为任何 C/C++ 应用添加可观测性。

5.2 BPF 与系统安全初探

BPF 在安全领域同样大放异彩。几个典型应用:

安全场景 BPF 能力 代表工具
运行时安全监控 hook 关键系统调用,检测异常行为 Falco(CNCF 项目)
系统调用过滤 限制进程能调用的 syscall 白名单 seccomp-bpf
网络包过滤 在内核态过滤/丢弃恶意流量 Cilium(eBPF-based CNI)
文件完整性监控 追踪所有文件修改操作 opensnoop 常驻
容器逃逸检测 监控 mount namespace 变化 Tracee(Aqua Security)
复制代码
┌─────────────────────────────────────────────────┐
│              BPF 安全可观测性架构                    │
│                                                   │
│  用户空间                                         │
│  ┌──────────┐  ┌──────────┐  ┌───────────┐       │
│  │  Falco   │  │  Cilium  │  │  Tracee    │       │
│  │  (规则引擎) │  │  (网络策略) │  │  (行为分析) │       │
│  └────┬─────┘  └────┬─────┘  └─────┬─────┘       │
│       │             │              │              │
├───────┼─────────────┼──────────────┼──────────────┤
│  内核空间    BPF Programs (sandboxed)              │
│       │             │              │              │
│       ▼             ▼              ▼              │
│  sys_enter_*    XDP/tc hook    kprobe:mount      │
│  (系统调用拦截)    (网络包拦截)    (ns 变更检测)        │
└─────────────────────────────────────────────────┘

以 Falco 为例:它内置了 80+ 条安全规则(如「检测非官方源安装软件包」「检测容器内执行 shell」),每条规则背后都是一个 BPF 程序在内核态实时检测。相比传统的审计日志方案,BPF 方案开销低数个数量级。


结语:从工具使用者到问题终结者

回顾:BPF/BCC 带来的范式转变

在 BPF 之前,Linux 性能调试像是在黑暗中摸索------你能摸到墙,但看不到房间的全貌。BPF 给了你一束光

维度 传统方式 BPF 方式
CPU 热点 perf top(不可回放) profile + 火焰图(精确到函数)
进程追踪 ps + 日志(离散的) execsnoop(实时全量)
磁盘 I/O iostat(设备级、无进程归因) biolatency + biosnoop(逐 I/O、逐进程)
网络连接 netstat(快照)+ tcpdump(无法归因进程) tcpconnect / tcpaccept(实时、按进程)
性能开销 strace ~30% BPF ~1-3%
安全审计 审计日志(事后分析) 实时内核态检测

下一步:持续关注 BPF 生态

BPF 技术栈正在快速发展,推荐关注:

复制代码
BPF 技术栈全景
├── BCC (Python/Lua 前端)      ← 本文工具
│   └── 90+ 预构建工具,即装即用
├── bpftrace (类 awk 脚本)      ← 快速原型
│   └── # bpftrace -e 'tracepoint:syscalls:sys_enter_openat { printf("%s %s\n", comm, str(args->filename)); }'
├── libbpf (C 语言原生库)       ← 生产级
│   └── CO-RE(一次编译,到处运行)
├── Cilium / Falco / Pixie    ← 应用层
│   └── CNI / 安全 / K8s 可观测性
└── eBPF for Windows           ← 跨平台
    └── Microsoft 正在推进 Windows eBPF

行动号召

「下次再遇到棘手的性能问题,别再只盯着 top 了------试试 BPF 吧!」

记住这 6 个工具,它们是你排查问题的第一梯队:

问题类型 首选工具 一句话命令
CPU 高 profile-bpfcc -d 30 -f 生成火焰图找热点
异常进程 execsnoop-bpfcc -T 实时看谁在启动进程
文件被删 opensnoop-bpfcc -T 追踪文件操作
磁盘慢了 biolatency-bpfcc -m 1 10 延迟直方图
带宽跑满 tcptop-bpfcc TCP 流量 top
连接异常 tcpconnect-bpfcc -t + tcpaccept-bpfcc 连接全生命周期

附录:速查表 & 参考资源

BCC 常用参数速查

参数 适用工具 含义
-T execsnoop, opensnoop, tcpconnect 添加时间戳
-t tcpconnect, tcpaccept 另一种时间戳格式
-d N profile 运行 N 秒后退出
-F N profile 采样频率(Hz)
-f profile 折叠格式输出(火焰图用)
-p PID profile, opensnoop 只追踪特定进程
-m biolatency 毫秒级直方图
-Q biolatency, biosnoop 包含 OS 排队时间
-P tcpconnect 按端口过滤

学习资源

资源 链接 说明
BCC 官方仓库 github.com/iovisor/bcc 源码 + 工具文档
BCC 教程 github.com/iovisor/bcc/blob/master/docs/tutorial.md 官方入门教程
bpftrace 教程 github.com/bpftrace/bpftrace 类 awk 的 BPF 脚本
Brendan Gregg 博客 brendangregg.com BPF 性能分析权威
FlameGraph 仓库 github.com/brendangregg/FlameGraph 火焰图生成工具
eBPF 官方文档 ebpf.io eBPF 入门和项目目录
Linux Kernel BPF 文档 kernel.org/doc/html/latest/bpf 内核级文档

本文所有命令均在以下环境中实测通过:华为云 ecs-2aa6-0001 (2vCPU/4GiB), Ubuntu 24.04.4 LTS, Linux 6.8.0-106-generic, BCC 0.29.1。

相关推荐
qq_163135751 小时前
Linux 【06-cp命令超详细教程】
linux
翼龙云_cloud1 小时前
阿里云代理商:部署 DeepSeek V4-Flash解析 快速部署与性能优化
运维·阿里云·性能优化·云计算·ai智能体
正经教主1 小时前
【docker基础】 第七课:Docker Compose 多容器实战
运维·docker·容器
ElevenS_it1881 小时前
网络设备配置合规审计自动化实战:用Nornir+Netmiko自动比对华为/Cisco/H3C配置基线+合规报告自动生成
运维·网络·自动化
wangyadong3171 小时前
重新安装k3s,因为我安装jenkins 的时候报错了。不知道为啥rancher 访问不了了。
linux·服务器·rancher
施努卡机器视觉1 小时前
SNK施努卡 | 电子油泵自动化生产线:精密制造的技术跃迁与产业价值
运维·自动化·制造
♛识尔如昼♛1 小时前
Linux 设备驱动程序(3)- 字符驱动(2)
linux·驱动开发·字符设备驱动
ShyanZh1 小时前
【skill】Agent-Browser:AI代理的浏览器自动化实战指南
运维·人工智能·自动化·skill·agent-browser
KKKlucifer2 小时前
智能研判、本地运算、一键运维:新一代安全管控产品的三大核心能力
运维·安全