土法炼钢兴趣小组的算法知识备份

【Linux 网络子系统深度拆解】内核网络追踪工具箱:bpftrace/perf/ftrace 实战

文章导航

分类入口
linuxnetworking
标签入口
#kernel#bpftrace#perf#ftrace#tracepoint#kprobe#tcp_probe#kfree_skb#softirq#flame-graph

目录

线上网络故障排查中,最常见的困境是”知道有问题,但不知道问题在哪一层”。一个 TCP 连接延迟 200ms,可能是应用层阻塞、socket 缓冲区满、softirq 调度延迟、拥塞控制退避、丢包重传、网卡队列溢出中的任意一个。传统的 netstatsstcpdump 只能看到结果(重传了多少包),而看不到原因(在哪个内核函数、因为什么条件触发了重传)。

内核提供了三层可观测基础设施——tracepoint(静态插桩)、kprobe/fentry(动态插桩)和 PMC(硬件性能计数器)。bpftrace、perf 和 ftrace 是操作这三层基础设施的三大工具。本文从网络诊断实战出发,逐个展示它们在不同场景下的用法和选择策略。

下图展示了三大追踪工具与内核网络 tracepoint 的关系:

内核网络追踪工具与 Tracepoint 全景

一、内核网络 tracepoint 全景

内核在 include/trace/events/ 目录下为网络子系统定义了一组静态 tracepoint,覆盖了收发包、TCP 状态、丢包、NAPI 等关键路径。

1.1 网络相关 tracepoint 分类

trace 子系统 头文件 关键 tracepoint 用途
skb trace/events/skb.h kfree_skbconsume_skbskb_copy_datagram_iovec 丢包追踪、正常消费追踪
tcp trace/events/tcp.h tcp_retransmit_skbtcp_send_resettcp_receive_resettcp_destroy_socktcp_rcv_space_adjusttcp_retransmit_synacktcp_probetcp_bad_csumtcp_cong_state_set TCP 重传、RST、拥塞状态
net trace/events/net.h net_dev_start_xmitnet_dev_xmitnetif_receive_skbnetif_rx 收发包路径追踪
napi trace/events/napi.h napi_poll NAPI 轮询效率
sock trace/events/sock.h inet_sock_set_statesock_exceed_buf_limitsock_rcvqueue_full socket 状态变化、缓冲区溢出
neigh trace/events/neigh.h neigh_createneigh_updateneigh_event_send ARP/NDP 邻居子系统

1.2 kfree_skb:最重要的丢包 tracepoint

kfree_skb tracepoint 在每次内核因错误丢弃 sk_buff 时被触发,携带丢弃原因:

/* include/trace/events/skb.h:24 */
TRACE_EVENT(kfree_skb,
    TP_PROTO(struct sk_buff *skb, void *location,
             enum skb_drop_reason reason),
    TP_ARGS(skb, location, reason),
    TP_STRUCT__entry(
        __field(void *,     skbaddr)
        __field(void *,     location)     /* 调用 kfree_skb 的内核函数地址 */
        __field(unsigned short, protocol)
        __field(enum skb_drop_reason, reason)  /* 丢弃原因枚举 */
    ),
    ...
);

location 字段是调用 kfree_skb_reason() 的内核函数地址,可以通过 ksym() 解析为函数名。reason 字段是 enum skb_drop_reason,在 include/net/dropreason-core.h 中定义了 80+ 种丢弃原因:

/* include/net/dropreason-core.h - 部分常见原因 */
SKB_DROP_REASON_NOT_SPECIFIED     /* 未指定 */
SKB_DROP_REASON_NO_SOCKET         /* 找不到目标 socket */
SKB_DROP_REASON_TCP_CSUM          /* TCP 校验和错误 */
SKB_DROP_REASON_SOCKET_FILTER     /* socket filter 丢弃 */
SKB_DROP_REASON_UDP_CSUM          /* UDP 校验和错误 */
SKB_DROP_REASON_NETFILTER_DROP    /* Netfilter 丢弃 */
SKB_DROP_REASON_SOCKET_RCVBUFF    /* socket 接收缓冲区满 */
SKB_DROP_REASON_PROTO_MEM         /* 协议内存压力 */
SKB_DROP_REASON_TCP_FLAGS         /* TCP 标志位异常 */
SKB_DROP_REASON_TCP_ZEROWINDOW    /* TCP 零窗口 */
SKB_DROP_REASON_TCP_OLD_DATA      /* TCP 旧数据 */
SKB_DROP_REASON_TCP_OVERWINDOW    /* TCP 超窗口 */
SKB_DROP_REASON_XDP               /* XDP 程序丢弃 */
SKB_DROP_REASON_TC_INGRESS        /* TC ingress 丢弃 */
SKB_DROP_REASON_TC_EGRESS         /* TC egress 丢弃 */
SKB_DROP_REASON_QDISC_DROP        /* qdisc 队列满丢弃 */
SKB_DROP_REASON_CPU_BACKLOG       /* per-CPU backlog 满 */
SKB_DROP_REASON_FULL_RING         /* 网卡 ring buffer 满 */

1.3 tcp_probe:TCP 拥塞状态快照

tcp_probe tracepoint 是 TCP 诊断的核心工具,在每次收到数据包时记录完整的 TCP 拥塞状态快照:

/* include/trace/events/tcp.h:238 */
TRACE_EVENT(tcp_probe,
    TP_PROTO(struct sock *sk, struct sk_buff *skb),
    TP_STRUCT__entry(
        __array(__u8, saddr, sizeof(struct sockaddr_in6))
        __array(__u8, daddr, sizeof(struct sockaddr_in6))
        __field(__u16, sport)
        __field(__u16, dport)
        __field(__u32, mark)
        __field(__u16, data_len)
        __field(__u32, snd_nxt)      /* 下一个发送序列号 */
        __field(__u32, snd_una)      /* 最小未确认序列号 */
        __field(__u32, snd_cwnd)     /* 拥塞窗口 */
        __field(__u32, ssthresh)     /* 慢启动阈值 */
        __field(__u32, snd_wnd)      /* 发送窗口 */
        __field(__u32, srtt)         /* 平滑 RTT(微秒) */
        __field(__u32, rcv_wnd)      /* 接收窗口 */
        __field(__u64, sock_cookie)  /* socket 唯一标识 */
    ),
    ...
);

这个 tracepoint 提供了诊断 TCP 性能问题所需的所有关键指标——cwnd 变化、RTT 波动、窗口大小,且开销极低。

二、bpftrace:网络诊断的瑞士军刀

bpftrace 是基于 eBPF 的高级追踪语言,支持 tracepoint、kprobe、kretprobe、fentry/fexit 等多种挂载点。它的优势在于一行命令就能完成复杂的内核追踪。

2.1 TCP 重传根因分析

TCP 重传是最常见的网络性能问题。以下脚本同时追踪重传事件和拥塞状态:

bpftrace -e '
tracepoint:tcp:tcp_retransmit_skb {
    $sk = (struct sock *)args->skaddr;
    $inet = (struct inet_sock *)$sk;
    printf("RETRANSMIT %s:%d -> %s:%d state=%d\n",
        ntop(args->saddr), args->sport,
        ntop(args->daddr), args->dport,
        args->state);
    @retrans_by_state[args->state] = count();
    @retrans_by_dst[ntop(args->daddr)] = count();
}
'

进阶:结合 tcp_probe 追踪重传前后的 cwnd 变化:

bpftrace -e '
tracepoint:tcp:tcp_retransmit_skb {
    @retrans[args->sport, args->dport] = count();
}

tracepoint:tcp:tcp_probe {
    if (@retrans[args->sport, args->dport] > 0) {
        printf("POST_RETRANS %s:%d cwnd=%u ssthresh=%u srtt=%u\n",
            ntop(args->daddr), args->dport,
            args->snd_cwnd, args->ssthresh, args->srtt);
    }
}
'

2.2 收包路径延迟剖析

追踪包从网卡到 socket 的完整路径延迟:

bpftrace -e '
kprobe:__netif_receive_skb_core {
    @start[arg0] = nsecs;
}

kprobe:tcp_v4_rcv {
    $skb = arg0;
    if (@start[$skb]) {
        @rx_to_tcp_ns = hist(nsecs - @start[$skb]);
        delete(@start[$skb]);
    }
}
'

追踪 NAPI poll 效率——每次 poll 处理的包数量分布:

bpftrace -e '
tracepoint:napi:napi_poll {
    @napi_work = hist(args->work);
    @napi_budget[args->budget] = count();
    if (args->work == args->budget) {
        @napi_exhausted = count();
    }
}
'

2.3 softirq 延迟定位

网络包处理在 softirq 上下文中执行。当 softirq 被延迟(被其他高优先级任务抢占),网络延迟会显著增加:

bpftrace -e '
tracepoint:irq:softirq_entry /args->vec == 3/ {
    /* NET_RX_SOFTIRQ = 3 */
    @softirq_start[tid] = nsecs;
}

tracepoint:irq:softirq_exit /args->vec == 3/ {
    if (@softirq_start[tid]) {
        $dur = nsecs - @softirq_start[tid];
        @net_rx_softirq_dur_us = hist($dur / 1000);
        if ($dur > 1000000) {  /* > 1ms */
            printf("LONG NET_RX_SOFTIRQ: %d us on CPU %d\n",
                $dur / 1000, cpu);
        }
        delete(@softirq_start[tid]);
    }
}
'

追踪 softirq 被阻塞的时间——从 raise 到实际执行的延迟:

bpftrace -e '
tracepoint:irq:softirq_raise /args->vec == 3/ {
    @raise[cpu] = nsecs;
}

tracepoint:irq:softirq_entry /args->vec == 3 && @raise[cpu]/ {
    $delay = nsecs - @raise[cpu];
    @softirq_delay_us = hist($delay / 1000);
    delete(@raise[cpu]);
}
'

2.4 socket 缓冲区压力监控

bpftrace -e '
tracepoint:sock:sock_rcvqueue_full {
    @rcvq_full_by_proto[args->protocol] = count();
}

tracepoint:sock:sock_exceed_buf_limit {
    @buf_limit[args->protocol, str(args->kind)] = count();
}
'

2.5 conntrack 表满监控

bpftrace -e '
kprobe:nf_conntrack_tuple_taken {
    @ct_lookups = count();
}

kretprobe:nf_conntrack_alloc /retval == 0/ {
    @ct_alloc_fail = count();
}

kprobe:nf_ct_resolve_clash {
    @ct_clash = count();
}

interval:s:5 {
    printf("--- conntrack stats ---\n");
    print(@ct_lookups);
    print(@ct_alloc_fail);
    print(@ct_clash);
    clear(@ct_lookups);
    clear(@ct_alloc_fail);
    clear(@ct_clash);
}
'

三、perf:统计采样与火焰图

perf 的优势在于基于 PMC 的统计采样和硬件事件追踪,适合全局性能分析而非单点追踪。

3.1 网络包处理 CPU 火焰图

# 采样 30 秒的 CPU 栈,聚焦网络相关函数
perf record -a -g --call-graph dwarf -F 99 -- sleep 30

# 生成火焰图(需要 FlameGraph 工具)
perf script | stackcollapse-perf.pl | flamegraph.pl > net-cpu.svg

更精准地过滤网络相关栈:

# 只采样 ksoftirqd 线程
perf record -g --call-graph dwarf -F 99 \
    -p $(pgrep -d, ksoftirqd) -- sleep 30

# 只采样 NET_RX_SOFTIRQ
perf record -a -g --call-graph dwarf -F 99 \
    -e 'irq:softirq_entry' --filter 'vec == 3' -- sleep 30

3.2 perf 追踪丢包事件

# 记录所有 kfree_skb 事件
perf record -a -g --call-graph dwarf \
    -e 'skb:kfree_skb' -- sleep 60

# 查看丢包栈
perf script | head -100

# 统计丢包原因分布
perf script | grep 'reason:' | awk '{print $NF}' | sort | uniq -c | sort -rn

3.3 perf stat 网络计数器

# 快速统计网络相关内核函数调用次数
perf stat -a -e 'skb:kfree_skb,tcp:tcp_retransmit_skb,napi:napi_poll,net:netif_receive_skb' -- sleep 10

# 示例输出
#  12,345  skb:kfree_skb
#     456  tcp:tcp_retransmit_skb
# 567,890  napi:napi_poll
# 1,234,567  net:netif_receive_skb

3.4 TCP 重传热图

# 记录 tcp_retransmit_skb 事件及其调用栈
perf record -a -g -e 'tcp:tcp_retransmit_skb' -- sleep 300

# 提取重传的时间分布
perf script -F time,event | awk '{print int($1)}' | uniq -c | \
    awk '{printf "%d %d\n", $2, $1}'

四、ftrace:最轻量的内核追踪

ftrace 是内核内建的追踪框架,无需安装额外工具,通过 debugfs/tracefs 文件系统操作。适合在最小化环境(容器、嵌入式)中使用。

4.1 function_graph:可视化调用链

# 追踪收包路径的完整函数调用链
cd /sys/kernel/debug/tracing

# 设置过滤函数
echo '__netif_receive_skb_core' > set_graph_function
echo function_graph > current_tracer
echo 1 > tracing_on

# 查看结果
cat trace | head -100

# 关闭
echo 0 > tracing_on
echo nop > current_tracer

function_graph 输出示例:

 3)               |  __netif_receive_skb_core() {
 3)               |    __netif_receive_skb_one_core() {
 3)   0.156 us    |      skb_pfmemalloc_protocol();
 3)               |      ip_rcv() {
 3)               |        ip_rcv_core() {
 3)   0.094 us    |          ip_compute_csum();
 3)   0.732 us    |        }
 3)               |        nf_hook_slow() {
 3)   0.246 us    |          iptable_filter_hook();
 3)   0.518 us    |        }
 3)               |        ip_local_deliver() {
 3)               |          ip_local_deliver_finish() {
 3)               |            tcp_v4_rcv() {
 3)   ...

4.2 event tracing:轻量级事件追踪

# 启用 TCP 重传 tracepoint
echo 1 > /sys/kernel/debug/tracing/events/tcp/tcp_retransmit_skb/enable

# 启用丢包 tracepoint
echo 1 > /sys/kernel/debug/tracing/events/skb/kfree_skb/enable

# 读取事件
cat /sys/kernel/debug/tracing/trace_pipe

# 关闭
echo 0 > /sys/kernel/debug/tracing/events/tcp/tcp_retransmit_skb/enable
echo 0 > /sys/kernel/debug/tracing/events/skb/kfree_skb/enable

4.3 kprobe event:动态追踪任意内核函数

# 创建 kprobe 追踪 tcp_v4_connect 的返回值
echo 'r:tcp_connect_ret tcp_v4_connect ret=$retval' > \
    /sys/kernel/debug/tracing/kprobe_events

# 启用
echo 1 > /sys/kernel/debug/tracing/events/kprobes/tcp_connect_ret/enable

# 读取
cat /sys/kernel/debug/tracing/trace_pipe

# 清理
echo 0 > /sys/kernel/debug/tracing/events/kprobes/tcp_connect_ret/enable
echo '-:tcp_connect_ret' >> /sys/kernel/debug/tracing/kprobe_events

4.4 trace-cmd:ftrace 的友好前端

# 记录 TCP 和丢包事件
trace-cmd record -e tcp -e skb:kfree_skb -e napi:napi_poll sleep 30

# 分析
trace-cmd report | head -200

# 只查看重传
trace-cmd report | grep tcp_retransmit_skb

# 统计事件分布
trace-cmd report --stat

五、工具选择决策指南

5.1 三大工具对比

维度 bpftrace perf ftrace
安装需求 bpftrace 包 + kernel headers perf-tools 包 内核内建(无需安装)
编程模型 AWK-like 脚本语言 命令行 + perf script debugfs 文件操作
聚合能力 内核态聚合(map、hist) 用户态后处理 无(需外部工具)
CPU 开销 低(eBPF JIT) 极低(采样)/ 中(tracepoint) 低(function tracer)/ 极低(event)
适合场景 条件过滤、聚合统计、实时告警 全局采样、火焰图、硬件计数器 最小化环境、函数调用链
输出格式 终端直出、直方图 perf.data 二进制 + perf script 文本 trace

5.2 场景选择决策树

需要追踪什么?
├── 全局 CPU 热点 → perf record + 火焰图
├── 特定事件计数 → perf stat -e 'tracepoint:...'
├── 条件过滤 + 聚合 → bpftrace
│   ├── "只看目标 IP 为 10.0.0.1 的重传" → bpftrace filter
│   ├── "统计每个 drop_reason 的直方图" → bpftrace hist
│   └── "实时打印 cwnd < 10 的连接" → bpftrace printf
├── 函数调用链可视化 → ftrace function_graph
├── 最小化环境(无法安装工具) → ftrace event tracing
└── 长期监控 + 告警 → bpftrace 脚本 + 定时输出

5.3 性能开销参考

追踪方式 每事件开销 适合频率
tracepoint(无 BPF) ~50-100 ns 高频(100K+/s)
tracepoint + bpftrace ~100-200 ns 高频(100K+/s)
kprobe + bpftrace ~200-500 ns 中频(10K-100K/s)
fentry + bpftrace ~100-200 ns 高频(100K+/s)
perf 采样(99 Hz) 几乎为零 不限
ftrace function_graph ~200-500 ns/函数 低频/短时间

关键原则:tracepoint 优于 kprobe(稳定 ABI、更低开销);fentry 优于 kprobe(更低开销、可用 BTF);采样优于追踪(开销固定,不随事件频率增长)。

六、实战案例:TCP 连接延迟 200ms 的根因分析

以一个真实案例演示三大工具的协同使用。

第一步:perf stat 快速定界

perf stat -a -e 'tcp:tcp_retransmit_skb,skb:kfree_skb,napi:napi_poll' -- sleep 10

如果 tcp_retransmit_skb 计数远高于预期,说明问题在 TCP 重传。

第二步:bpftrace 定位重传连接

bpftrace -e '
tracepoint:tcp:tcp_retransmit_skb {
    printf("%s:%d -> %s:%d state=%d\n",
        ntop(args->saddr), args->sport,
        ntop(args->daddr), args->dport, args->state);
    @by_dst[ntop(args->daddr)] = count();
}
interval:s:5 { print(@by_dst); clear(@by_dst); }
'

发现集中在某个目标 IP,说明是到特定目的地的链路问题。

第三步:tcp_probe 追踪 cwnd 变化

bpftrace -e '
tracepoint:tcp:tcp_probe
/args->dport == 8080/ {
    printf("cwnd=%u ssthresh=%u srtt=%u snd_wnd=%u\n",
        args->snd_cwnd, args->ssthresh, args->srtt, args->snd_wnd);
    @cwnd = hist(args->snd_cwnd);
    @srtt = hist(args->srtt);
}
'

如果看到 cwnd 频繁从高值骤降,且 srtt 波动剧烈,说明链路存在间歇性丢包。

第四步:kfree_skb 确认丢包位置

bpftrace -e '
tracepoint:skb:kfree_skb {
    @drop_reason[args->reason] = count();
    @drop_location[ksym(args->location)] = count();
}
interval:s:10 { print(@drop_reason); print(@drop_location); exit(); }
'

如果 reason 集中在 QDISC_DROPFULL_RING,说明是出口队列或网卡缓冲区溢出。

第五步:perf 火焰图确认 CPU 瓶颈

perf record -a -g --call-graph dwarf -F 99 -- sleep 30
perf script | stackcollapse-perf.pl | flamegraph.pl > net-flame.svg

在火焰图中查找 sch_direct_xmitdev_hard_start_xmit 等函数占比,确认是否 CPU 在发包路径上成为瓶颈。

七、常用 bpftrace 单行脚本速查表

场景 bpftrace 命令
TCP 重传统计 bpftrace -e 'tracepoint:tcp:tcp_retransmit_skb { @[ntop(args->daddr)] = count(); }'
RST 包追踪 bpftrace -e 'tracepoint:tcp:tcp_send_reset { printf("%s:%d\n", ntop(args->daddr), args->dport); }'
丢包原因分布 bpftrace -e 'tracepoint:skb:kfree_skb { @[args->reason] = count(); }'
socket 状态变化 bpftrace -e 'tracepoint:sock:inet_sock_set_state { printf("%d->%d\n", args->oldstate, args->newstate); }'
NAPI poll 分布 bpftrace -e 'tracepoint:napi:napi_poll { @work = hist(args->work); }'
发包延迟 bpftrace -e 'kprobe:dev_queue_xmit { @s[arg0]=nsecs; } kretprobe:dev_queue_xmit /@s[arg0]/ { @ns=hist(nsecs-@s[arg0]); delete(@s[arg0]); }'
softirq 延迟 bpftrace -e 'tracepoint:irq:softirq_entry /args->vec==3/ { @s[tid]=nsecs; } tracepoint:irq:softirq_exit /args->vec==3 && @s[tid]/ { @us=hist((nsecs-@s[tid])/1000); delete(@s[tid]); }'
TCP cwnd 分布 bpftrace -e 'tracepoint:tcp:tcp_probe { @cwnd = hist(args->snd_cwnd); }'
拥塞状态变化 bpftrace -e 'tracepoint:tcp:tcp_cong_state_set { @[args->cong_state] = count(); }'

八、注意事项与最佳实践

8.1 生产环境安全准则

  1. 先在测试环境验证:任何追踪脚本都应先在非生产环境测试
  2. 限制追踪时间:使用 timeout 或 bpftrace 的 interval:s:N { exit(); } 防止遗忘关闭
  3. 避免高频 printf:每秒百万级事件场景下,printf 会导致 perf buffer 溢出。改用 @map 聚合
  4. 注意 kprobe 稳定性:kprobe 挂载的函数可能在内核升级后改名。优先使用 tracepoint(稳定 ABI)
  5. 内存限制:bpftrace 的 map 有内存上限(默认 4096 entries),高基数场景需 -DBPF_MAX_ENTRIES=65536 调整

8.2 容器环境特殊考虑

在容器中追踪网络需要注意:

8.3 与上一篇的联系

本文介绍的追踪工具是上一篇 eBPF 网络钩子的”只读”版本——钩子用于修改数据面行为,追踪工具用于观察数据面行为。两者使用相同的 eBPF 基础设施(verifier、JIT、map),但追踪程序不修改包内容,只读取和记录。

参考文献

  1. Linux 内核源码 include/trace/events/tcp.h:TCP tracepoint 定义
  2. Linux 内核源码 include/trace/events/skb.hkfree_skb tracepoint 定义
  3. Linux 内核源码 include/trace/events/net.h:收发包 tracepoint 定义
  4. Linux 内核源码 include/trace/events/napi.h:NAPI poll tracepoint 定义
  5. Linux 内核源码 include/trace/events/sock.h:socket 状态 tracepoint 定义
  6. Linux 内核源码 include/net/dropreason-core.henum skb_drop_reason 完整定义

上一篇eBPF 网络钩子全景:TC/XDP/socket/cgroup

下一篇网络丢包定位:从 drop_monitor 到 kfree_skb 追踪

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2025-07-22 · linux / networking

【Linux 网络子系统深度拆解】eBPF 网络钩子全景:TC/XDP/socket/cgroup

从内核源码全面拆解 eBPF 在网络子系统中的所有挂载点:TC BPF direct-action 模式与 bpf_mprog 多程序链、XDP 驱动级钩子回顾、socket ops 回调与 TCP 生命周期事件、cgroup BPF 策略控制、sk_msg/sk_skb 的 sockmap 重定向引擎、struct_ops 实现自定义拥塞控制,以及 bpftrace 可观测实战。


By .