fentry是什么
fentry是BPF_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工具开发中,为了兼容不同版本的内核,常会看到同时提供fentry和kprobe两种挂载方式的代码。以开源项目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);
}
这种设计实现了一种“双保险”策略:
- 首选方案 (Plan A):如果运行的操作系统内核版本较新,支持fentry,则优先使用性能更优的fentry挂载点。
- 备选方案 (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主要执行以下步骤:
- 将通用的
sock结构体指针转换为TCP专用的tcp_sock结构体。
- 读取
srtt_us(平滑往返时间)。需要注意的是,Linux内核为了精度和性能,存储的srtt_us值是实际微秒数的8倍,因此读取后需要右移3位(即除以8)来获取真实值。
- 根据计算出的延时值,将其归类到对应的直方图区间(通常使用以2为底的对数
log2来计算区间)。
- 通过原子操作更新直方图中对应区间的计数。
这个示例清晰地展示了fentry程序的典型工作流:利用其直接获取类型化参数的能力(此处为struct sock*),高效地读取内核数据结构,然后执行特定的业务逻辑(如网络延时统计),最后将结果存储或汇总到eBPF map中,供用户态程序读取展示。