驱动数字化 质变

从权威的技术洞察,到精准的软硬配置,为企业的每一次转型提供决策支持。

架构师笔记
[架构实战] 抓不到的“幽灵故障”?用 eBPF 在 Linux 边缘端实现零侵入 Modbus 延迟追踪 (附 BCC 脚本)

2026-03-03 10:19:00

#eBPF #BCC #Linux内核 #网络抖动 #ModbusTCP #可观测性 #故障排查


一、 场景痛点:一天出现一次的“超时”

在最近的一个自动化物流分拣线项目中,我们遇到了一个极其棘手的 Bug:

  • 现象:上位机(跑在 Linux 网关上)通过 Modbus TCP 控制 PLC。每天大概有 2-3 次,PLC 响应会突然变慢,导致 ReadTimeout 报错,分拣臂动作停顿。

  • 排查僵局

  1. 应用日志:只记录了“超时”,没记录“为什么超时”。

  2. Tcpdump:为了抓这几次偶发故障,必须 24 小时开着抓包。几百 GB 的 PCAP 文件把网关存储写爆了,而且分析起来是大海捞针。

  3. 猜测:是网络交换机抖动?是 Linux 内核调度延迟?还是 Python 垃圾回收卡顿?没人知道。

架构师指令:我们需要“上帝视角”。使用 eBPF 技术。它允许我们在 Linux 内核的网络协议栈关键函数(如 tcp_sendmsg 和 tcp_recvmsg)上挂载“探针”,只记录我们关心的指标(如延迟 > 100ms 的包),且对系统性能几乎零影响。


二、 架构设计:内核态过滤,用户态统计

传统的监控是在用户态(User Space)做的,只能看到结果。eBPF 让我们深入内核态(Kernel Space)。

  • Kprobe (内核探针):在 TCP 发送和接收函数入口挂载钩子。

  • BPF Map (高效存储):在内核内存中维护一个 Hash 表,记录 (Sock, Seq) -> Timestamp。

  • 计算逻辑

  1. 检测到 502 端口(Modbus)的包发出,记录时间 T1。

  2. 检测到对应的 ACK 或响应包回来,记录时间 T2。

  3. 计算 Delta = T2 - T1。

  4. 关键过滤:只有当 Delta > 100ms 时,才将事件推送到用户态打印出来。

拓扑图


应用层 (Modbus Client) --(Syscall)--> [Linux Kernel (TCP Stack) + eBPF Probe] --(异常事件)--> [Python BCC 脚本] -> 告警


三、 核心实施步骤 (Copy & Paste)

我们将使用 BCC (BPF Compiler Collection) 工具包,它允许用 Python 编写 eBPF 前端,C 编写内核后端。

1. 环境准备

确保你的网关运行的是 Linux 4.19+ (推荐 5.10+),并安装 BCC。


(以 Ubuntu/Debian 为例)


Bash

sudo apt-get install bpfcc-tools linux-headers-$(uname -r)


2. 编写 eBPF 追踪脚本 (modbus_latency.py)

这段代码会实时追踪 Modbus TCP 通讯,并打印出所有耗时超过 100ms 的慢请求。


Python
#!/usr/bin/python3
from bcc import BPF
import time

# 1. 定义内核态 C 代码 (eBPF Program)
bpf_text = """
#include <uapi/linux/ptrace.h>
#include <net/sock.h>
#include <bcc/proto.h>

struct key_t {
    u32 pid;
    u32 seq;
};

// 记录发送时间
BPF_HASH(start, struct key_t);

// 挂载到 tcp_sendmsg (发送)
int trace_send(struct pt_regs *ctx, struct sock *sk, struct msghdr *msg, size_t size) {
    u16 sport = sk->__sk_common.skc_num;
    u16 dport = sk->__sk_common.skc_dport;
    
    // 只追踪 502 端口 (注意:内核中端口是大端序)
    if (sport != 502 && dport != 502) return 0;

    struct key_t key = {};
    key.pid = bpf_get_current_pid_tgid();
    // 简化处理:实际应获取 TCP Seq,这里简单用 PID 做 demo
    
    u64 ts = bpf_ktime_get_ns();
    start.update(&key, &ts);
    return 0;
}

// 挂载到 tcp_recvmsg (接收完成)
int trace_recv(struct pt_regs *ctx, struct sock *sk) {
    struct key_t key = {};
    key.pid = bpf_get_current_pid_tgid();

    u64 *tsp, delta;
    tsp = start.lookup(&key);
    if (tsp == 0) return 0; // 没找到发送记录

    delta = bpf_ktime_get_ns() - *tsp;
    start.delete(&key); // 清除记录

    // 2. 核心过滤:只输出超过 100ms (100,000,000 ns) 的请求
    if (delta > 100000000) {
        bpf_trace_printk("Slow Modbus: %d ms\\n", delta / 1000000);
    }
    return 0;
}
"""

# 3. 加载并挂载探针
b = BPF(text=bpf_text)
b.attach_kprobe(event="tcp_sendmsg", fn_name="trace_send")
b.attach_kretprobe(event="tcp_recvmsg", fn_name="trace_recv")

print("eBPF Tracer running... detecting Modbus latency > 100ms")

# 4. 循环读取内核输出
while True:
    try:
        (task, pid, cpu, flags, ts, msg) = b.trace_fields()
        print(f"[{time.strftime('%H:%M:%S')}] PID {pid}: {msg.decode('utf-8')}")
    except KeyboardInterrupt:
        exit()


3. 运行效果

运行脚本 sudo python3 modbus_latency.py,然后静静等待。


当故障发生时,你会在屏幕上看到:


[14:02:15] PID 1234: Slow Modbus: 350 ms

破案:如果 eBPF 捕获到了 350ms 的延迟,说明是网络层或对端 PLC 慢;如果 eBPF 显示网络仅耗时 5ms,但应用层却报超时,说明是应用层代码(如 GC 或 锁竞争) 卡住了。


四、 踩坑复盘 (Red Flags)

1. 内核版本的门槛

  • :很多老旧的 ARM 工控机还在跑 Linux 3.10 或 4.4 内核。

  • 后果:eBPF 无法运行,或者功能严重受限(不支持 BPF Map)。

  • 对策:选型时,OS 内核版本是硬指标。2026 年务必选择支持 Linux 5.10 LTS 以上的硬件平台(如 RK3588, i.MX8M Plus)。

2. JIT 编译的性能开销

  • 注意:BCC 需要在目标机器上实时编译 C 代码(JIT),这需要安装 Kernel Headers 并且启动时会消耗 CPU。

  • 优化:生产环境推荐使用 Libbpf + CO-RE (Compile Once – Run Everywhere) 技术。在开发机编译好二进制 eBPF 程序,直接分发到网关运行,无需现场编译。

3. 容器环境的权限

  • 现象:在 Docker 里运行报错 Operation not permitted。

  • 原因:eBPF 需要 CAP_BPF 或 CAP_SYS_ADMIN 权限。

  • 解决:Docker 启动时必须加 --privileged 或 --cap-add=SYS_ADMIN,并挂载 /sys/kernel/debug。


五、 关联资源与选型

要玩转 eBPF,硬件的 OS 支持度是关键。

  • 硬件推荐

    • 研华 UNO-2271G V2 (Intel Elkhart Lake):x86 架构对 eBPF 生态支持最好,主流发行版(Ubuntu/Debian)均可直接安装 BCC。


    • 香橙派 5 Plus (RK3588):国产 ARM 板中的内核更新先锋,社区版 Armbian 对 eBPF 支持较好。



进阶工具

不想写代码?


我们打包了一个 "Edge-SRE 工具箱" Docker 镜像。


内置了 biosnoop (查磁盘慢)、tcptracer (查网络慢)、execsnoop (查谁在重启进程) 等 20+ 个实用的 eBPF 脚本,开箱即用。