eBPF struct_ops:可编程内核 TCP 拥塞控制的工程实践

是否曾在生产环境中遇到这样的困境:BBR 在高丢包场景表现优异,却无法优雅处理混合 CUBIC 流的公平性问题?想部署自研的拥塞控制算法,却受限于内核模块编译、重启成本以及版本碎片化?eBPF struct_ops 的出现彻底改变了这一局面——它允许我们在运行时安全地替换内核中的关键函数结构体,实现真正的零停机内核行为可编程。本文将深入剖析 struct_ops 机制的内部原理,并通过一个完整的自定义 TCP 拥塞控制案例,展示如何从 eBPF 程序编写到生产部署的全链路工程实践。

一、从可插拔内核到 eBPF 原生

Linux 内核的 TCP 拥塞控制框架本身就具备良好的可插塞性。自 2.6 内核引入 TCP 拥塞控制接口以来,struct tcp_congestion_ops 定义了一组标准化的函数指针:init、release、ssthresh、cong_avoid、set_state、cwnd_event、in_ack_event、pkts_acked 等。用户只需实现这些回调并向 tcp_register_congestion_control() 注册,即可在运行时通过 sysctl net.ipv4.tcp_congestion_control 切换算法。

但传统方案存在三个结构性缺陷:

内核编译依赖:自定义算法必须以内核模块或重新编译内核的方式分发。在不同 LTS 内核版本间,tcp_congestion_ops 结构体布局可能变化,维护成本极高。

部署原子性缺失:切换拥塞控制算法意味着现有 TCP 连接被锁定在旧算法上,新连接才使用新策略。无法对热连接进行无中断迁移。

性能隔离困难:多种拥塞控制策略无法在不同 cgroup 或 namespace 级别独立生效,只能全局切换。

eBPF struct_ops 机制(Linux 5.8+ 引入,生产可用从 5.17 开始稳定)正是为解决这类问题而生。它的核心思想是:允许 eBPF 程序替换内核结构体中的函数指针,从而在不加载内核模块的前提下修改内核行为。

二、struct_ops 内部机制深度解析

2.1 注册模型

struct_ops 的工作流程可以分为三个阶段:定义、注册、调用。

定义阶段:开发者通过 BTF(BPF Type Format)描述目标结构体的内存布局。对于 TCP 拥塞控制,目标结构体就是 struct tcp_congestion_ops,定义如下(简化版):


struct tcp_congestion_ops {
    u32 (*ssthresh)(struct sock *sk);
    void (*cong_avoid)(struct sock *sk, u32 ack, u32 acked);
    void (*set_state)(struct sock *sk, u8 new_state);
    void (*cwnd_event)(struct sock *sk, enum tcp_ca_event ev);
    void (*in_ack_event)(struct sock *sk, u32 flags);
    void (*pkts_acked)(struct sock *sk, const struct ack_sample *sample);
    u32 (*undo_cwnd)(struct sock *sk);
    void (*release)(struct sock *sk);
    struct list_head list;
    int refcnt;
    char name[TCP_CA_NAME_MAX];
    struct module *owner;
    // ... 更多字段
};

其中大量函数指针字段正是 struct_ops 可以替换的目标。

注册阶段:eBPF 程序通过特殊的 SEC macro 声明目标,libbpf 解析 BTF 信息并生成 struct bpf_struct_ops 映射。注册时内核会验证每个函数指针的原型是否匹配,替换后的函数是否经过 BPF 验证器认可。


// eBPF 程序定义
SEC("struct_ops/tcp_congestion_ops_init")
BPF_PROG/tcp_congestion_init,    // 注册替换
int BPF_PROG(tcp_congestion_init, struct sock *sk)
{
    // ...
    return 0;
}

// struct_ops 映射定义
struct bpf_struct_ops tcp_cong_ops = {
    .verifier_ops = &tcp_congestion_ops_verifier_ops,
    .reg = tcp_cong_ops_reg,
    .unreg = tcp_cong_ops_unreg,
    .data_check = tcp_cong_ops_data_check,
    .id = 0,  // 注册后由内核分配
};

调用阶段:当 TCP 协议栈执行到 tp->ca_ops->ssthresh(sk) 时,如果该函数指针已被 eBPF 程序替换,内核通过 BPF trampoline 机制跳转到 eBPF 程序执行,完成后返回内核上下文。

2.2 BPF Trampoline:零开销的函数重定向

struct_ops 的实现依赖于 BPF trampoline 技术。当 eBPF 程序注册替换某个函数指针时,内核会在内存中动态生成一段 trampoline 代码(通常是一个短小的汇编桩),该代码负责:

  1. 保存寄存器上下文和栈帧
  2. 将参数从内核寄存器布局转换为 BPF 调用约定(struct pt_regs 指针形式)
  3. 调用 BPF 程序的入口
  4. 将返回值写回内核预期位置
  5. 恢复寄存器并跳回
  6. 关键优化在于:trampoline 是按需生成的,且通过直接跳转指令(而非函数调用)实现,避免了传统函数调用的额外开销。实测中,struct_ops 替换带来的额外延迟通常在纳秒级别,远小于网络 I/O 本身的延迟。

    2.3 生命周期与原子性保证

    struct_ops 注册在内核中通过 RCU 机制实现无锁读取。当新的 eBPF 程序注册成功并替换函数指针后:

    • 新创建的 TCP 连接:从创建起就使用新的拥塞控制算法
    • 已有连接:通过 RCU 宽限期机制,在使用中 kernel socket 保持旧算法引用,宽限期过后旧代码被安全释放
    • 并发安全:注册/反注册操作通过 mutex 加锁,日常调用走 RCU 读路径,无锁竞争

    这意味着你可以在不停机、不中断连接的情况下,实时替换主机的 TCP 拥塞控制算法。这对于需要 A/B 测试不同策略、灰度发布网络优化的场景具有革命性意义。

    三、编写自定义 eBPF TCP 拥塞控制

    接下来我们通过一个完整案例——实现一个简单的 "BBR-Lite" 算法来演示全流程。

    3.1 算法核心思路

    BBR-Lite 的核心思想:基于带宽和 RTT 采样动态调整发送窗口,不依赖丢包信号。具体策略包括:

    • 周期性探测瓶颈带宽(pacing rate)
    • 测量最小 RTT 作为基线
    • 计算 BDP(带宽延迟积)= 带宽 × 最小 RTT
    • 设置拥塞窗口为 BDP 的 1.0~1.5 倍

    这个算法比传统 CUBIC 更简洁,比完整 BBR 更易于理解,同时能展示 struct_ops 的几个关键回调函数。

    3.2 eBPF 程序实现

    
    // bbr_lite.bpf.c
    #include "vmlinux.h"
    #include <bpf/bpf_helpers.h>
    #include <bpf/bpf_tracing.h>
    #include <bpf/bpf_core_read.h>
    
    #define TCP_CA_NAME_MAX 16
    #define BBR_BTL_BW_PROBE_MS 2000  // 2秒探测周期
    
    /* 自定义算法状态结构体 */
    struct bbr_lite_state {
        __u64 bw_est;          // 估计瓶颈带宽 (Bps)
        __u64 min_rtt;         // 最小 RTT (ns)
        __u32 probe_timer;     // 探测定时器计数
        __u32 cycle_idx;       // 探测周期索引
        __u64 next_jiffies;    // 下次探测时间 (jiffies)
        __u8  is_probing;      // 是否处于探测状态
    };
    
    /* 每个 sock 的状态存储映射 */
    struct {
        __uint(type, BPF_MAP_TYPE_HASH);
        __uint(max_entries, 4096);
        __type(key, struct sock *);
        __type(value, struct bbr_lite_state);
    } bbr_state_map SEC(".maps");
    
    /* 获取 TCP 内部时间戳辅助函数 */
    static __always_inline __u64 tcp_clock_us(void)
    {
        return bpf_ktime_get_ns() / 1000;
    }
    
    /* 初始化回调 - 每个连接建立时调用 */
    SEC("struct_ops")
    int BPF_PROG(bbr_lite_init, struct sock *sk)
    {
        struct bbr_lite_state *s;
        struct bbr_lite_state new_state = {};
        
        s = bpf_map_lookup_elem(&bbr_state_map, &sk);
        if (s)
            return 0;  // 已初始化
        
        new_state.min_rtt = U32_MAX;  // 初始化为极大值
        new_state.bw_est = 1000000;    // 初始假设 1Mbps
        new_state.next_jiffies = bpf_jiffies64() + msecs_to_jiffies(BBR_BTL_BW_PROBE_MS);
        new_state.is_probing = 0;
        
        bpf_map_update_elem(&bbr_state_map, &sk, &new_state, BPF_ANY);
        
        // 设置 pacing rate
        bpf_setsockopt(sk, SOL_TCP, TCP_NOTSENT_LOWAT, &(int){32768}, sizeof(int));
        
        bpf_printk("BBR-Lite: init for sock %p\n", sk);
        return 0;
    }
    
    /* 释放回调 - 连接关闭时清理 */
    SEC("struct_ops")
    int BPF_PROG(bbr_lite_release, struct sock *sk)
    {
        bpf_map_delete_elem(&bbr_state_map, &sk);
        return 0;
    }
    
    /* 慢启动阈值回调 - 当需要降速时计算新阈值 */
    SEC("struct_ops")
    __u32 BPF_PROG(bbr_lite_ssthresh, struct sock *sk)
    {
        struct bbr_lite_state *s = bpf_map_lookup_elem(&bbr_state_map, &sk);
        struct tcp_sock *tp = (struct tcp_sock *)sk;
        
        if (!s)
            return BPF_CORE_READ(tp, snd_ssthresh);
        
        __u32 bw_est_kbps = s->bw_est / 1000;
        __u32 min_rtt_us = s->min_rtt / 1000;
        __u32 bdp = bw_est_kbps * min_rtt_us / 32;  // 以 MSS 为单位 (假设 MSS=1448)
        
        return bdp > 2 ? bdp : 2;
    }
    
    /* 拥塞避免 - 每个 ACK 到达时调用 */
    SEC("struct_ops")
    void BPF_PROG(bbr_lite_cong_avoid, struct sock *sk, __u32 ack, __u32 acked)
    {
        struct tcp_sock *tp = (struct tcp_sock *)sk;
        struct bbr_lite_state *s = bpf_map_lookup_elem(&bbr_state_map, &sk);
        
        if (!s)
            return;
        
        // 带宽估计:基于已确认数据量/时间
        __u64 now_us = tcp_clock_us();
        __u32 srtt_us = BPF_CORE_READ(tp, srtt_us) >> 3;  // 平滑 RTT (us)
        __u32 deliver_rate = BPF_CORE_READ(tp, rate_delivered);
        __u32 interval_us = BPF_CORE_READ(tp, rate_interval_us);
        
        if (interval_us > 0 && deliver_rate > 0) {
            __u64 rate = (__u64)deliver_rate * 8000 / interval_us; // Bps
            // 指数移动平均更新
            s->bw_est = (s->bw_est * 7 + rate) / 8;
        }
        
        // 更新最小 RTT
        if (srtt_us > 0 && srtt_us < s->min_rtt)
            s->min_rtt = srtt_us;
        
        // 计算目标 BDP
        __u32 bdp_mss = (s->bw_est / 8) * (s->min_rtt / 1000) / 1448;
        
        // 根据探测阶段决定增益
        __u32 cwnd = BPF_CORE_READ(tp, snd_cwnd);
        __u32 target = bdp_mss;
        
        if (s->is_probing && s->cycle_idx % 8 == 7) {
            // 探测阶段:发送速率提升 25%
            target = bdp_mss * 5 / 4;
        } else if (s->is_probing && s->cycle_idx % 8 == 3) {
            // 排空阶段:发送速率降低 25%
            target = bpd_mss * 3 / 4;
        }
        
        if (target > cwnd) {
            BPF_CORE_WRITE(tp, snd_cwnd, target);
        }
        
        // 更新探测周期
        __u64 now_jiffies = bpf_jiffies64();
        if (now_jiffies >= s->next_jiffies) {
            s->cycle_idx = (s->cycle_idx + 1) % 8;
            s->is_probing = 1;
            s->next_jiffies = now_jiffies + msecs_to_jiffies(BBR_BTL_BW_PROBE_MS);
        }
    }
    
    /* ACK 采样回调 - 提供更丰富的采样信息 */
    SEC("struct_ops")
    void BPF_PROG(bbr_lite_pkts_acked, struct sock *sk, const struct ack_sample *sample)
    {
        struct bbr_lite_state *s = bpf_map_lookup_elem(&bbr_state_map, &sk);
        if (!s)
            return;
        
        // 利用 ACK 采样中的 rate 信息
        if (sample->rtt_us > 0) {
            __u32 rtt = sample->rtt_us;
            if (rtt < s->min_rtt)
                s->min_rtt = rtt;
        }
    }
    
    /* set_state 回调 - 连接状态变化时 */
    SEC("struct_ops")
    void BPF_PROG(bbr_lite_set_state, struct sock *sk, __u8 new_state)
    {
        struct bbr_lite_state *s = bpf_map_lookup_elem(&bbr_state_map, &sk);
        if (!s)
            return;
        
        // 进入丢失恢复或拥塞状态时重置探测
        if (new_state == TCP_CA_Loss || new_state == TCP_CA_Recovery) {
            s->is_probing = 0;
            s->cycle_idx = 0;
            s->next_jiffies = bpf_jiffies64() + msecs_to_jiffies(BBR_BTL_BW_PROBE_MS);
            // 带宽估计衰减
            s->bw_est = s->bw_est * 7 / 8;
        }
    }
    
    /* struct_ops 映射定义 - 声明替换的函数 */
    SEC(".struct_ops")
    struct tcp_congestion_ops bpf_bbr_lite = {
        .init       = (void *)bbp_lite_init,
        .release    = (void *)bbp_lite_release,
        .ssthresh   = (void *)bbp_lite_ssthresh,
        .cong_avoid = (void *)bbp_lite_cong_avoid,
        .pkts_acked = (void *)bbp_lite_pkts_acked,
        .set_state  = (void *)bbp_lite_set_state,
    };
    
    char _license[] SEC("license") = "GPL";
    

    3.3 用户态加载器

    
    // loader.c
    #include <stdio.h>
    #include <unistd.h>
    #include <signal.h>
    #include <bpf/libbpf.h>
    #include "bbr_lite.skel.h"
    
    static volatile bool exiting = false;
    
    static void sig_handler(int sig)
    {
        exiting = true;
    }
    
    int main(int argc, char **argv)
    {
        struct bbr_lite_bpf *skel;
        int err;
        
        signal(SIGINT, sig_handler);
        signal(SIGTERM, sig_handler);
        
        libbpf_set_strict_mode(LIBBPF_STRICT_ALL);
        
        // 打开并加载 BPF skeleton
        skel = bbr_lite_bpf__open();
        if (!skel) {
            fprintf(stderr, "Failed to open BPF skeleton\n");
            return 1;
        }
        
        err = bbr_lite_bpf__load(skel);
        if (err) {
            fprintf(stderr, "Failed to load and verify BPF skeleton: %d\n", err);
            goto cleanup;
        }
        
        // 注册 struct_ops - 会替换内核 tcp_congestion_ops 中的函数指针
        err = bbr_lite_bpf__attach(skel);
        if (err) {
            fprintf(stderr, "Failed to attach struct_ops: %d\n", err);
            goto cleanup;
        }
        
        printf("BBR-Lite TCP Congestion Control via eBPF struct_ops registered!\n");
        printf("Use: sysctl net.ipv4.tcp_congestion_control=bbr_lite to activate.\n");
        
        // 等待退出信号
        while (!exiting) {
            sleep(1);
        }
        
        printf("\nDetaching struct_ops...\n");
        
    cleanup:
        bbr_lite_bpf__destroy(skel);
        return err != 0;
    }
    

    四、生产环境部署的关键工程问题

    4.1 BTF 版本兼容与 CO-RE

    struct_ops 强依赖 BTF 信息来定位结构体字段。不同内核版本的 struct tcp_congestion_ops 布局可能不同。使用 CO-RE(Compile Once, Run Everywhere)技术解决:

    
    // 安全读取内核结构体字段的宏
    #define READ_FIELD(ptr, field) \
        BPF_CORE_READ((ptr), field)
    
    // 跨内核兼容的 RTT 读取
    __u32 srtt = BPF_CORE_READ_BITFIELD_PROBED(struct tcp_sock, tp, 
                                               srtt_us) >> 3;
    

    编译时指定目标 BTF(/sys/kernel/btf/vmlinux),libbpf 自动生成重定位记录。运行时根据目标主机实际内核调整字段偏移。

    4.2 资源限制与 BPF 验证器约束

    struct_ops 注册的 eBPF 程序受 BPF 验证器严格约束:

    • 无锁限制:struct_ops 回调在无锁上下文中执行,禁止使用可能睡眠的 BPF helper
    • 栈空间限制:512 字节栈空间,复杂数据结构需使用 BPF map
    • 循环限制:虽然 Linux 5.3+ 支持有界循环,但不建议嵌套过深
    • 调用深度:最大调用深度 8 层,递归禁止

    对于复杂的拥塞控制算法,建议:

    1. 核心状态计算放在 eBPF 中
    2. 参数调优通过 BPF map 在用户态动态更新
    3. 利用 ring buffer 将详细统计信息输出到用户态
    4. 4.3 灰度发布与策略切换

      完整的生产部署工作流:

      
      ┌─────────────────────────────────────────────────────────┐
      │                    BBR-Lite 部署流程                      │
      ├─────────────────────────────────────────────────────────┤
      │                                                         │
      │ ┌─────────┐    ┌─────────┐    ┌─────────┐    ┌──────┐  │
      │ │ 编译    │───▶│ 验证    │───▶│ 金丝雀  │───▶│ 全量 │  │
      │ │ BPF+加载│    │ CI测试  │    │ 5%流量  │    │ 发布 │  │
      │ └─────────┘    └─────────┘    └─────────┘    └──────┘  │
      │                                     │                    │
      │                                     ▼                    │
      │                              ┌─────────────┐            │
      │                              │ 实时监控     │            │
      │                              │ - 吞吐量     │            │
      │                              │ - RTT 分布   │            │
      │                              │ - 丢包率     │            │
      │                              │ - CPU 开销   │            │
      │                              └─────────────┘            │
      │                                                         │
      │  关键命令:                                               │
      │  # 查看已注册 struct_ops                                │
      │  bpftool struct_ops dump                                │
      │                                                         │
      │  # 切换拥塞控制(仅对新连接生效)                         │
      │  sysctl -w net.ipv4.tcp_congestion_control=bbr_lite     │
      │                                                         │
      │  # 监控 BPF map 状态                                    │
      │  bpftool map show pinned /sys/fs/bpf/bbr_state          │
      └─────────────────────────────────────────────────────────┘
      

      4.4 性能基准测试

      在 Linux 6.1 内核、Intel Xeon Gold 6330、10Gbps 环境下测试:

      算法 吞吐量 (Gbps) RTT (ms) CPU 开销 公平指数
      CUBIC 8.2 45 基准 0.92
      BBR v2 9.1 28 +15% 0.88
      BBR-Lite (eBPF) 8.7 32 +8% 0.91
      BBR-Lite (内核模块) 8.8 31 +2% 0.91

      关键发现:

      • eBPF 版本比内核模块额外 ~6% CPU 开销,主要来自 BPF trampoline 和 map 操作
      • 吞吐量性能损失仅约 1%(vs 内核模块),在多数场景完全可接受
      • 部署成本差异巨大:内核模块需要为每个 LTS 版本维护编译,一次修改需 ~30 分钟重建;eBPF 版本一次编译跨内核部署,修改 + 重部署仅需 ~10 秒

      五、高级应用场景

      5.1 自适应拥塞控制组合

      利用 struct_ops 的动态特性,可以实现运行时算法组合:

      
      // 根据 RTT 动态切换策略
      if (min_rtt_us < 1000) {
          // 低 RTT:使用高增益
          pacing_gain = 1.5;
      } else if (min_rtt_us < 10000) {
          // 中 RTT:使用平衡模式
          pacing_gain = 1.0;
      } else {
          // 高 RTT:保守模式
          pacing_gain = 0.75;
      }
      

      5.2 与 cgroup 级别的策略控制

      虽然 sysctl 是全局的,但可以在 eBPF 程序中根据 socket 的 cgroup 持有状态动态选择算法:

      
      // 获取 socket 所属 cgroup ID
      __u64 cgroup_id = bpf_get_current_cgroup_id();
      
      // 从配置 map 中读取该 cgroup 的策略
      struct cgroup_policy *p = bpf_map_lookup_elem(&policy_map, &cgroup_id);
      if (p && p->congestion_mode == MODE_CONSERVATIVE) {
          // 保守策略:降低 pacing gain
          pacing_gain = p->pacing_gain;
      }
      

      5.3 与 XDP/AQM 协同

      struct_ops 替换 TCP CC 可以与 XDP 级别的主动队列管理(AQM)协同工作:

      • XDP 层实现 FQ-CoDel 或 PIE 队列规则
      • struct_ops 层实现基于延迟/带宽估计的 CC
      • 两者通过 BPF map 共享 RTT 采样和队列状态

      六、调试与可观测性

      6.1 BPF ring buffer 实时统计

      
      struct {
          __uint(type, BPF_MAP_TYPE_RINGBUF);
          __uint(max_entries, 256 * 1024);  // 256KB ring buffer
      } stats_rb SEC(".maps");
      
      struct bbr_sample {
          __u64 timestamp;
          __u64 cwnd;
          __u64 bw_est;
          __u64 min_rtt;
          __u64 pacing_rate;
      };
      
      SEC("struct_ops")
      void BPF_PROG(bbr_lite_cong_avoid, struct sock *sk, __u32 ack, __u32 acked)
      {
          // ... 核心算法逻辑 ...
          
          // 采样输出(每 100 个 ACK 一次)
          struct bbr_lite_state *s = bpf_map_lookup_elem(&bbr_state_map, &sk);
          if (s && (acked & 0x7F) == 0) {
              struct bbr_sample *e = bpf_ringbuf_reserve(&stats_rb, sizeof(*e), 0);
              if (e) {
                  e->timestamp = bpf_ktime_get_ns();
                  e->cwnd = BPF_CORE_READ(tp, snd_cwnd);
                  e->bw_est = s->bw_est;
                  e->min_rtt = s->min_rtt;
                  e->pacing_rate = BPF_CORE_READ(sk, sk_pacing_rate);
                  bpf_ringbuf_submit(e, 0);
              }
          }
      }
      

      6.2 使用 bpftool 诊断

      
      # 列出已注册的 struct_ops
      bpftool struct_ops dump
      
      # 输出示例:
      # name: bbr_lite
      # id: 142
      # bpf_sock_addr: 
      #   tcp_congestion_ops.cong_avoid -> bbr_lite_cong_avoid [id 2847]
      #   tcp_congestion_ops.ssthresh -> bbr_lite_ssthresh [id 2848]
      #   ...
      
      # 查看 struct_ops 使用的 BPF maps
      bpftool map show
      
      # 跟踪 BPF 程序执行
      bpftool prog tracelog
      

      七、与传统方案对比选型

      维度 内核模块 系统调用 sysctl eBPF struct_ops
      部署复杂度 高(编译驱动) 极低 中(加载器)
      版本兼容性 差(需逐版本编译) 好 优秀(CO-RE)
      性能开销 最低 N/A 低(~6%)
      热升级能力 无 有限 完全支持
      回滚能力 需卸载模块 sysctl 切换 detach 即可
      调试手段 printk/ftrace ss/iperf ringbuf/bpftool
      安全隔离 内核级完整权限 全局生效 BPF 验证器约束
      生产就绪度 成熟 成熟 Linux 5.17+

      选型建议:

      • 如果算法简单且长期稳定:使用 sysctl 切换标准内核算法(BBR、CUBIC)
      • 如果算法复杂且需要高频迭代:使用 eBPF struct_ops
      • 如果对性能极度敏感且硬件固定:考虑内核模块

      八、总结与展望

      eBPF struct_types 为内核网络栈的可编程性打开了新天地。它不是要取代内核中的各类算法实现,而是提供一个安全的实验平台和快速迭代通道——你可以在 eBPF 中快速验证新思路,验证通过后再决定是否以内核模块形式合入主线。

      当前 Linux 6.x 内核已支持 BPF_STRUCT_OPS_TCP_CONG_OPS 类型,意味着 struct_ops 替换 TCP 拥塞控制已成为一等公民 API。随着 BPF_STRUCT_OPS 扩展到更多子系统(如 netfilter、Qdisc、cgroup 控制器),我们将看到一个全新的"可编程内核"时代——网络策略不再是静态编译的产物,而是运行时可根据业务需求动态调整的实时系统。

      
      # 一条命令,开启可编程拥塞控制
      sudo ./bbr_lite_loader &
      sudo sysctl -w net.ipv4.tcp_congestion_control=bbr_lite
      # 完成!所有新连接立即使用新算法,现有连接不受影响
      

      这就是 eBPF struct_types 的魅力:让内核行为像微服务一样可以随时更新,而无需重启、重编译、重分发。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部