找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1132

积分

0

好友

164

主题
发表于 4 天前 | 查看: 11| 回复: 0

fentry是什么

fentryBPF_PROG_TYPE_TRACING程序类型下的一种“挂载点(Attach Type)”,主要用于内核函数追踪。

它的核心优势与革新体现在以下两点:

  • 性能极高:它不使用传统的断点指令(int3),而是直接在目标函数入口处通过Trampoline技术插入跳转指令,其性能开销几乎可以忽略不计,非常适合对性能敏感的Linux内核深度性能追踪场景。
  • 自带类型(BTF):这是fentry最核心的特性。当fentry程序被加载时,内核会通过BTF(BPF Type Format)信息,将目标函数的参数按照其原本的C语言类型直接准备好,传递给eBPF程序。这意味着开发者可以直接使用如const char *这样的类型化参数,而无需像使用kprobe那样从一堆原始寄存器值中去手动解析和转换。

它的前身是基于断点的kprobe。从字面意思理解,fentry即Function Entry(函数入口)。

fentry 与 kfunc

bpftrace等工具的语境下,kfunc 指的就是利用 fentry 机制来追踪内核函数

例如,在bpftrace中可以直接使用内核函数的参数名进行追踪:

# 直接使用内核参数名!bpftrace 会自动通过 BTF 知道 arg1 是 const char *
bpftrace -e 'kfunc:do_sys_open { printf("Opening: %s\n", args->filename); }'

上述bpftrace命令对应的原生eBPF程序写法如下:

SEC("fentry/do_sys_open")
int BPF_PROG(my_monitor, int dfd, const char *filename, int flags, int mode)
{
    ...
}

你可以通过以下命令查询系统中所有可用的kfunc(即fentry可追踪的内核函数):

sudo bpftrace -lv 'kfunc:*'
...
kfunc:cfg80211:nl80211_dump_survey
    struct sk_buff * skb
    struct netlink_callback * cb
    int retval

输出中的cfg80211表示函数所属的内核模块。所有内核模块的BTF信息可以在/sys/kernel/btf目录下查看。

实际应用例子:双方案兼容

在实际的eBPF工具开发中,为了兼容不同版本的内核,常会看到同时提供fentrykprobe两种挂载方式的代码。以开源项目bcc中的tcprtt.bpf.c为例:

SEC("fentry/tcp_rcv_established")
int BPF_PROG(tcp_rcv, struct sock *sk)
{
    return handle_tcp_rcv_established(sk);
}

SEC("kprobe/tcp_rcv_established")
int BPF_KPROBE(tcp_rcv_kprobe, struct sock *sk)
{
    return handle_tcp_rcv_established(sk);
}

这种设计实现了一种“双保险”策略:

  1. 首选方案 (Plan A):如果运行的操作系统内核版本较新,支持fentry,则优先使用性能更优的fentry挂载点。
  2. 备选方案 (Plan B):如果内核版本较老,不支持fentry,则程序会自动回退到使用传统的kprobe挂载点。

在用户态程序中,通常会有相应的逻辑来判断使用哪一种方案:

if (fentry_can_attach(“tcp_rcv_established”, NULL))
    // 如果fentry可用,则禁止自动加载kprobe版本的程序
    bpf_program__set_autoload(obj->progs.tcp_rcv_kprobe, false);
else
    // 如果fentry不可用,则禁止自动加载fentry版本的程序,使用kprobe
    bpf_program__set_autoload(obj->progs.tcp_rcv, false);

这里的fentry_can_attach函数会尝试查找目标函数的BTF信息(即kfunc),如果找到则说明当前内核支持fentry方式挂载。

业务逻辑分析

tcprtt工具为例,其核心函数handle_tcp_rcv_established主要执行以下步骤:

  1. 将通用的sock结构体指针转换为TCP专用的tcp_sock结构体。
  2. 读取srtt_us(平滑往返时间)。需要注意的是,Linux内核为了精度和性能,存储的srtt_us值是实际微秒数的8倍,因此读取后需要右移3位(即除以8)来获取真实值。
  3. 根据计算出的延时值,将其归类到对应的直方图区间(通常使用以2为底的对数log2来计算区间)。
  4. 通过原子操作更新直方图中对应区间的计数。

这个示例清晰地展示了fentry程序的典型工作流:利用其直接获取类型化参数的能力(此处为struct sock*),高效地读取内核数据结构,然后执行特定的业务逻辑(如网络延时统计),最后将结果存储或汇总到eBPF map中,供用户态程序读取展示。




上一篇:Jovis深度可视化:揭秘PostgreSQL查询优化器的内部决策过程
下一篇:Python字典核心内置方法详解:增删改查与高效遍历技巧
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-17 19:39 , Processed in 0.105272 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表