作者简介:本文面向Linux内核网络开发者与资深SRE。传统的网站测速多在应用层进行,难以区分时间消耗在内核协议栈的哪个环节。KKCE技术团队利用eBPF(Extended Berkeley Packet Filter)技术,实现了无需修改内核源码、无需重编译,即可在内核态实时捕获网络事件的黑科技。本文将提供完整的eBPF程序代码,带你精准测量TCP握手、内核调度、软中断及应用层读取的真实耗时,达成内核级的“快快测”。

在排查网站访问慢的问题时,我们常面临一个困境:应用层日志显示处理很快,但用户端却很慢。时间到底耗哪了?答案是内核协议栈。数据包从网卡接收到被应用层recv系统调用读取,中间经历了网卡驱动、软中断(SoftIRQ)、协议栈处理(IP/TCP)、套接字缓冲区(SKB)等多个阶段。传统的tcpdumpwireshark只能看到网卡进出流量,无法透视内核内部。

今天,KKCE将展示如何使用eBPF的kprobetracepoint,在内核的关键函数埋点,量化每一个阶段的耗时。

1. 技术原理:eBPF的内核透视眼

eBPF允许我们在内核中运行沙盒程序,挂载到特定的钩子(Hooks)上。

  • TC Ingress/Egress:在网络流量进入或离开网卡时触发。用于测量网络传输延迟。

  • Tracepoints:内核预定义的静态追踪点。例如tcp:tcp_probe用于探测TCP连接建立。

  • Kprobes:动态挂载到任意内核函数的入口或返回点。例如tcp_v4_do_rcv(TCP数据接收处理)或inet_csk_accept(TCP连接接受)。

2. 核心代码:eBPF程序(C语言)

我们需要编写一个eBPF程序,挂载到内核的TCP连接建立和数据处理函数上。这里使用BCC(BPF Compiler Collection)框架的语法,它允许我们用C编写eBPF代码,用Python编写用户态控制逻辑。

eBPF内核态代码(保存为 kkce_tcp_latency.c

// KKCE: 内核态eBPF代码,用于追踪TCP连接和数据处理的延迟
#include <uapi/linux/ptrace.h>
#include <net/sock.h>
#include <bcc/proto.h>

// 定义哈希映射,用于存储时间戳
// Key: PID + 套接字指针,Value: 时间戳
BPF_HASH(start_time, u64, u64);
// 定义Perf Event Array,用于向用户态发送数据
BPF_PERF_OUTPUT(kkce_events);

// 定义事件数据结构
struct kkce_event_t {
    u32 pid;
    char comm[TASK_COMM_LEN];
    u64 delta_us; // 耗时(微秒)
    u8 type;      // 1: TCP Accept Latency, 2: TCP Read Latency
};

// 追踪TCP连接建立的完成时刻
int kprobe__inet_csk_accept(struct pt_regs *ctx, struct sock *sk) {
    u64 pid_tgid = bpf_get_current_pid_tgid();
    u64 ts = bpf_ktime_get_ns();
    start_time.update(&pid_tgid, &ts);
    return 0;
}

// 追踪TCP数据被应用层读取的时刻
int kretprobe__inet_csk_accept(struct pt_regs *ctx) {
    u64 pid_tgid = bpf_get_current_pid_tgid();
    u64 *tsp = start_time.lookup(&pid_tgid);
    if (tsp) {
        struct kkce_event_t event = {};
        event.pid = pid_tgid >> 32;
        bpf_get_current_comm(&event.comm, sizeof(event.comm));
        event.delta_us = (bpf_ktime_get_ns() - *tsp) / 1000;
        event.type = 1; // Accept Latency
        kkce_events.perf_submit(ctx, &event, sizeof(event));
        start_time.delete(&pid_tgid);
    }
    return 0;
}

// 追踪tcp_v4_do_rcv,这是TCP数据包进入协议栈的核心函数
// 用于计算数据从到达网卡到被协议栈处理的时间
int kprobe__tcp_v4_do_rcv(struct pt_regs *ctx, struct sock *sk, struct sk_buff *skb) {
    u64 pid_tgid = bpf_get_current_pid_tgid();
    u64 ts = bpf_ktime_get_ns();
    // 使用SKB地址作为Key的一部分,确保唯一性
    u64 key = pid_tgid ^ (u64)skb;
    start_time.update(&key, &ts);
    return 0;
}

int kretprobe__tcp_v4_do_rcv(struct pt_regs *ctx) {
    struct sock *sk = (struct sock *)PT_REGS_PARM1(ctx);
    struct sk_buff *skb = (struct sk_buff *)PT_REGS_PARM2(ctx);
    u64 pid_tgid = bpf_get_current_pid_tgid();
    u64 key = pid_tgid ^ (u64)skb;
    u64 *tsp = start_time.lookup(&key);
    if (tsp) {
        struct kkce_event_t event = {};
        event.pid = pid_tgid >> 32;
        bpf_get_current_comm(&event.comm, sizeof(event.comm));
        event.delta_us = (bpf_ktime_get_ns() - *tsp) / 1000;
        event.type = 2; // Kernel Processing Latency
        kkce_events.perf_submit(ctx, &event, sizeof(event));
        start_time.delete(&key);
    }
    return 0;
}

3. 控制平面:Python用户态程序

Python代码负责加载eBPF程序,并处理从内核接收到的数据。

Python用户态代码(保存为 kkce_monitor.py

#!/usr/bin/python3
"""
KKCE 网站测速监控工具 - 基于eBPF
运行需root权限: sudo python3 kkce_monitor.py
"""
from bcc import BPF
from time import sleep
import argparse

# 加载eBPF C代码
with open("kkce_tcp_latency.c", "r") as f:
    bpf_program = f.read()

# 编译并加载eBPF程序
bpf = BPF(text=bpf_program)

# 定义打印回调函数
def print_event(cpu, data, size):
    event = bpf["kkce_events"].event(data)
    if event.type == 1:
        print(f"KKCE_ACCEPT_LATENCY: PID={event.pid:<6} COMM={event.comm.decode():<16} "
              f"Delay={event.delta_us:>8} us")
    elif event.type == 2:
        print(f"KKCE_KERNEL_PROC_LATENCY: PID={event.pid:<6} COMM={event.comm.decode():<16} "
              f"Delay={event.delta_us:>8} us")

# 打开Perf Buffer,绑定回调函数
bpf["kkce_events"].open_perf_buffer(print_event)

print("KKCE eBPF Monitor started... Ctrl+C to exit.")
print("Listening for TCP Accept and Kernel Processing latency...")
print("-" * 80)

# 轮询事件
try:
    while True:
        bpf.perf_buffer_poll()
except KeyboardInterrupt:
    print("\nExiting KKCE Monitor.")

# 清理资源
bpf.cleanup()

4. 实战操作:如何进行“快快测”

  1. 环境准备

    • 一台Linux服务器(内核版本 >= 4.15,推荐5.x+)。

    • 安装BCC工具集:sudo apt install bpfcc-tools python3-bpfcc (Ubuntu)。

    • 安装Python开发库:sudo apt install python3-dev

  2. 运行监控

    • 将上面的C代码保存为 kkce_tcp_latency.c

    • 将Python代码保存为 kkce_monitor.py

    • 打开一个终端,以root权限运行监控程序:

      sudo python3 kkce_monitor.py
  3. 触发测速

    • 打开另一个终端,使用curlab工具访问你的网站:

      curl http://your-website-domain.com
      # 或者使用压测工具
      ab -n 100 -c 10 http://your-website-domain.com/
  4. 数据分析

    • 观察第一个终端的输出。你会看到两类数据:

      • KKCE_ACCEPT_LATENCY:这是TCP三次握手完成后,内核Accept系统调用处理连接的耗时。如果这个值很大,说明服务器CPU繁忙,无法及时处理新连接。

      • KKCE_KERNEL_PROC_LATENCY:这是数据包到达网卡后,经过内核TCP/IP协议栈处理,最终放入套接字接收缓冲区的耗时。如果这个值很大,说明内核协议栈处理慢,可能是网卡队列、软中断或TCP拥塞控制算法的问题。

5. 深度解读:从数据到优化

通过KKCE的这套eBPF工具,你可以获得以下洞察:

  • 高Accept延迟:通常意味着服务器CPU资源耗尽,或者应用程序的accept()调用不及时(如单线程阻塞)。解决方案:优化应用并发模型,增加Worker进程数,或调整CPU亲和性。

  • 高Kernel Processing延迟

    • 检查softirq占比:使用top命令查看si列。如果过高,说明网卡中断处理不过来。

    • 开启RPS/RFS:对于单队列网卡,开启Receive Packet Steering和Receive Flow Steering,将软中断负载均衡到多核CPU。

    • 调整TCP参数:检查net.ipv4.tcp_rmemnet.ipv4.tcp_wmem,优化套接字缓冲区大小。

    • 检查MTU和GRO/LRO:不合理的巨型帧配置可能导致分片和重组开销。

总结

本文介绍的KKCE eBPF测速方案,突破了传统应用层监控的局限,将观测点下沉到了Linux内核协议栈内部。这种“零侵扰”的观测方式,能够精准量化网络数据包在内核中的流转耗时,帮助开发者定位那些隐藏在操作系统深处的性能瓶颈。

这不仅是一次测速,更是一次对操作系统网络栈的深度体检。掌握这项技术,你将在网站性能优化领域拥有降维打击的能力。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐