mirror of
https://github.com/eunomia-bpf/bpf-developer-tutorial.git
synced 2026-11-01 21:24:12 +08:00
update kfunc example
This commit is contained in:
@@ -1,146 +1,177 @@
|
||||
# 将 eBPF 的边界拓展:内核模块中的自定义 kfuncs
|
||||
# 超越 eBPF 的极限:在内核模块中定义自定义 kfunc
|
||||
|
||||
你是否曾感到 eBPF 的功能受限?也许你遇到过现有的 eBPF 功能无法完成你的目标的情况。可能你需要与内核进行更深入的交互,或者面临标准 eBPF 运行时无法解决的性能问题。如果你曾希望在 eBPF 程序中拥有更多的灵活性和强大功能,本教程将为你提供帮助。
|
||||
你是否曾经觉得 eBPF 的能力有限?也许你遇到了现有 eBPF 功能无法实现目标的情况。或许你需要与内核进行更深层次的交互,或者标准 eBPF 运行时无法解决的性能问题。如果你曾经希望在 eBPF 程序中拥有更多的灵活性和强大功能,那么本教程正适合你。
|
||||
|
||||
## 引言:通过 kfuncs 打破 eBPF 运行时的限制
|
||||
## 引言:添加 `strstr` kfunc 以突破 eBPF 运行时的限制
|
||||
|
||||
**eBPF(扩展伯克利数据包过滤器)** 通过允许开发者在内核中运行受限的程序,彻底改变了 Linux 系统编程。它在网络、安全和可观察性方面带来了革命性的变化,使得无需修改内核源代码或加载传统的内核模块即可实现强大的功能。
|
||||
**eBPF(扩展伯克利包过滤器)** 通过允许开发者在内核中运行受沙箱限制的程序,彻底改变了 Linux 系统编程。它在网络、安全和可观测性方面具有革命性的作用,能够实现强大的功能,而无需修改内核源代码或加载传统的内核模块。
|
||||
|
||||
然而,尽管 eBPF 非常强大,它也有其局限性:
|
||||
但尽管 eBPF 非常强大,它也并非没有局限性:
|
||||
|
||||
- **功能差距:** 有时,eBPF 运行时现有的功能无法提供你所需的特定能力。
|
||||
- **功能差距:** 有时,eBPF 运行时的现有功能无法提供你所需的特定能力。
|
||||
- **复杂需求:** 某些任务需要更复杂的内核交互,而 eBPF 无法开箱即用地处理这些需求。
|
||||
- **性能问题:** 在某些情况下,eBPF 运行时的开销会引入延迟,或者在高性能需求下效率不够。
|
||||
|
||||
这些挑战源于 **整个 eBPF 运行时** 的限制,而不仅仅是其辅助函数。那么,如何在不更改内核本身的情况下克服这些障碍呢?
|
||||
这些挑战源于**整个 eBPF 运行时的限制**,而不仅仅是其辅助函数。那么,如何在不修改内核本身的情况下克服这些障碍呢?
|
||||
|
||||
引入 **kfuncs(BPF 内核函数)**。通过在内核模块中定义你自己的 kfuncs,你可以将 eBPF 的功能扩展到其默认限制之外。这种方法让你能够:
|
||||
引入**kfunc(BPF 内核函数)**。通过在内核模块中定义你自己的 kfunc,可以将 eBPF 的能力扩展到默认限制之外。这种方法让你能够:
|
||||
|
||||
- **增强功能:** 引入标准 eBPF 运行时中不可用的新操作。
|
||||
- **自定义行为:** 根据你的具体需求调整内核交互。
|
||||
- **提升性能:** 通过在内核上下文中直接执行自定义代码来优化关键路径。
|
||||
- **定制行为:** 根据你的特定需求定制内核交互。
|
||||
- **提升性能:** 通过在内核上下文中直接执行自定义代码,优化关键路径。
|
||||
|
||||
最重要的是,你无需修改核心内核,从而保持系统稳定性和代码安全性。
|
||||
**在本教程中,我们将特别添加一个 `strstr` kfunc。** 由于 eBPF 的验证器限制,直接在 eBPF 中实现字符串搜索是具有挑战性的,而将其定义为 kfunc 则允许我们安全高效地绕过这些限制,执行更复杂的操作。
|
||||
|
||||
在本教程中,我们将向你展示如何定义自定义 kfuncs,以填补 eBPF 功能中的任何空白。我们将逐步创建一个引入新 kfuncs 的内核模块,并演示如何在你的 eBPF 程序中使用它们。无论你是希望克服性能瓶颈,还是需要 eBPF 运行时未提供的功能,自定义 kfuncs 都能为你的项目解锁新的可能性。
|
||||
最棒的是,你可以在不修改核心内核的情况下实现这一目标,保持系统的稳定性和代码的安全性。
|
||||
|
||||
## 理解 kfuncs:将 eBPF 拓展到辅助函数之外
|
||||
在本教程中,我们将展示如何定义自定义 kfunc 以填补 eBPF 功能的任何空白。我们将逐步讲解如何创建一个引入新 kfunc 的内核模块,并演示如何在 eBPF 程序中使用它们。无论你是希望克服性能瓶颈,还是需要 eBPF 运行时未提供的功能,自定义 kfunc 都能为你的项目解锁新的可能性。
|
||||
|
||||
### 什么是 kfuncs?
|
||||
## 理解 kfunc:扩展 eBPF 超越辅助函数
|
||||
|
||||
**BPF 内核函数(kfuncs)** 是 Linux 内核中的专用函数,供 eBPF 程序使用。与标准 eBPF 辅助函数不同,kfuncs 没有稳定的接口,且可能在内核版本之间有所不同。这种可变性意味着使用 kfuncs 的 BPF 程序需要与内核更新同步更新,以保持兼容性和稳定性。
|
||||
### 什么是 kfunc?
|
||||
|
||||
**BPF 内核函数(kfuncs)** 是 Linux 内核中的专用函数,供 eBPF 程序使用。与标准的 eBPF 辅助函数不同,kfuncs 没有稳定的接口,并且在不同的内核版本之间可能有所变化。这种可变性意味着使用 kfuncs 的 BPF 程序需要与内核更新同步更新,以保持兼容性和稳定性。
|
||||
|
||||
### 为什么使用 kfuncs?
|
||||
|
||||
1. **扩展功能:** kfuncs 允许执行标准 eBPF 辅助函数无法完成的操作。
|
||||
2. **自定义:** 定义针对特定用例的逻辑,增强 eBPF 程序的灵活性。
|
||||
3. **安全性和稳定性:** 通过将 kfuncs 封装在内核模块中,避免对核心内核的直接修改,保持系统完整性。
|
||||
2. **定制化:** 定义针对特定用例量身定制的逻辑,增强 eBPF 程序的灵活性。
|
||||
3. **安全与稳定:** 通过将 kfuncs 封装在内核模块中,避免直接修改核心内核,保持系统完整性。
|
||||
|
||||
### kfuncs 在 eBPF 中的作用
|
||||
### kfuncs 在 eBPF 中的角色
|
||||
|
||||
kfuncs 作为 eBPF 程序与更深层内核功能之间的桥梁。它们允许 eBPF 程序执行更复杂的操作,方法是暴露现有内核函数或引入专门为 eBPF 交互设计的新封装。这种集成促进了更深入的内核交互,同时确保 eBPF 程序保持安全和可维护。
|
||||
kfuncs 作为 eBPF 程序与更深层次内核功能之间的桥梁。它们允许 eBPF 程序执行更复杂的操作,通过暴露现有内核函数或引入专为 eBPF 交互设计的新包装函数。这种集成在确保 eBPF 程序保持安全和可维护的同时,促进了更深入的内核交互。
|
||||
|
||||
需要注意的是,Linux 内核已经包含了大量的 kfuncs。这些内置的 kfuncs 覆盖了广泛的功能,使大多数开发者无需定义新的 kfuncs 就能完成任务。然而,在现有 kfuncs 无法满足特定需求的情况下,定义自定义 kfuncs 就变得必要。本教程将演示如何定义新的 kfuncs 以填补任何空白,确保你的 eBPF 程序能够利用你所需的确切功能。eBPF 还可以扩展到用户空间。在用户空间 eBPF 运行时 [bpftime](https://github.com/eunomia-bpf/bpftime) 中,我们还在实现 ufuncs,它们类似于 kfuncs,但用于扩展用户空间应用程序。
|
||||
需要注意的是,Linux 内核已经包含了大量的 kfuncs。这些内置的 kfuncs 覆盖了广泛的功能,大多数开发者无需定义新的 kfuncs 就能完成任务。然而,在现有 kfuncs 无法满足特定需求的情况下,定义自定义 kfuncs 就变得必要。本教程将演示如何定义新的 kfuncs 以填补任何空白,确保你的 eBPF 程序能够利用你所需的确切功能。eBPF 也可以扩展到用户空间。在用户空间 eBPF 运行时 [bpftime](https://github.com/eunomia-bpf/bpftime) 中,我们也在实现 ufuncs,它们类似于 kfuncs,但扩展了用户空间应用程序。
|
||||
|
||||
## kfuncs 的概述及其演变
|
||||
## kfuncs 及其演变概述
|
||||
|
||||
要理解 kfuncs 的重要性,必须了解它们与 eBPF 辅助函数之间的演变关系。
|
||||
要理解 kfuncs 的重要性,必须了解它们与 eBPF 辅助函数的演变关系。
|
||||
|
||||

|
||||

|
||||
|
||||
**关键要点:**
|
||||
|
||||
- **辅助函数的稳定性:** eBPF 辅助函数基本保持稳定,新增内容较少。
|
||||
- **kfuncs 的快速增长:** kfuncs 的采用和创建显著增加,表明社区对通过 kfuncs 扩展内核交互的兴趣。
|
||||
- **向更深层内核集成的转变:** 自 2023 年以来,新用例主要利用 kfuncs 影响内核行为,标志着通过 eBPF 进行更深层内核集成的趋势。
|
||||
- **辅助函数的稳定性:** eBPF 辅助函数保持了高度的稳定性,新增内容较少。
|
||||
- **kfuncs 的快速增长:** kfuncs 的采用和创建显著增加,表明社区有兴趣通过 kfuncs 扩展内核交互。
|
||||
- **向更深层次内核集成的转变:** 自 2023 年以来,新的用例主要利用 kfuncs 影响内核行为,显示出通过 kfuncs 实现更深层次内核集成的趋势。
|
||||
|
||||
这一趋势凸显了社区通过 kfuncs 更深入地与内核集成,推动 eBPF 能力的边界的动力。
|
||||
这一趋势凸显了社区通过 kfuncs 更深入地与内核集成,推动 eBPF 能力边界的决心。
|
||||
|
||||
## 定义你自己的 kfunc:逐步指南
|
||||
## 定义你自己的 kfunc:分步指南
|
||||
|
||||
要利用 kfuncs 的强大功能,你需要在内核模块中定义它们。此过程确保你的自定义函数能够安全地暴露给 eBPF 程序,而无需更改核心内核。
|
||||
为了利用 kfuncs 的强大功能,你需要在内核模块中定义它们。这个过程确保你的自定义函数能够安全地暴露给 eBPF 程序,而无需修改核心内核。
|
||||
|
||||
### 编写内核模块
|
||||
|
||||
让我们从创建一个定义 kfunc 的简单内核模块开始。这个 kfunc 将执行一个基本的算术操作,作为理解机制的基础。
|
||||
让我们从创建一个简单的内核模块开始,该模块定义一个 `strstr` kfunc。这个 kfunc 将执行子字符串搜索操作,作为理解机制的基础。
|
||||
|
||||
#### **文件:`hello.c`**
|
||||
|
||||
```c
|
||||
#include <linux/init.h> // 模块初始化的宏
|
||||
#include <linux/module.h> // 加载模块的核心头文件
|
||||
#include <linux/kernel.h> // 内核日志宏
|
||||
#include <linux/init.h> // 模块初始化宏
|
||||
#include <linux/module.h> // 加载模块的核心头文件
|
||||
#include <linux/kernel.h> // 内核日志宏
|
||||
#include <linux/bpf.h>
|
||||
#include <linux/btf.h>
|
||||
#include <linux/btf_ids.h>
|
||||
|
||||
// 声明 kfunc
|
||||
__bpf_kfunc u64 bpf_kfunc_call_test(u32 a, u64 b, u32 c, u64 d);
|
||||
/* 声明 kfunc 原型 */
|
||||
__bpf_kfunc int bpf_strstr(const char *str, u32 str__sz, const char *substr, u32 substr__sz);
|
||||
|
||||
/* 定义 kfunc 函数 */
|
||||
/* 开始 kfunc 定义 */
|
||||
__bpf_kfunc_start_defs();
|
||||
|
||||
__bpf_kfunc u64 bpf_kfunc_call_test(u32 a, u64 b, u32 c, u64 d)
|
||||
/* 定义 bpf_strstr kfunc */
|
||||
__bpf_kfunc int bpf_strstr(const char *str, u32 str__sz, const char *substr, u32 substr__sz)
|
||||
{
|
||||
return a + b + c + d;
|
||||
// 边界情况:如果 substr 为空,返回 0(假设空字符串在开始处找到)
|
||||
if (substr__sz == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
// 边界情况:如果子字符串比主字符串长,则无法找到
|
||||
if (substr__sz > str__sz)
|
||||
{
|
||||
return -1; // 返回 -1 表示未找到
|
||||
}
|
||||
// 遍历主字符串,考虑大小限制
|
||||
for (size_t i = 0; i <= str__sz - substr__sz; i++)
|
||||
{
|
||||
size_t j = 0;
|
||||
// 将子字符串与当前主字符串位置进行比较
|
||||
while (j < substr__sz && str[i + j] == substr[j])
|
||||
{
|
||||
j++;
|
||||
}
|
||||
// 如果整个子字符串都匹配
|
||||
if (j == substr__sz)
|
||||
{
|
||||
return i; // 返回第一次匹配的索引
|
||||
}
|
||||
}
|
||||
// 如果未找到子字符串,返回 -1
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 结束 kfunc 定义 */
|
||||
__bpf_kfunc_end_defs();
|
||||
|
||||
// 定义 BTF kfunc ID 集合
|
||||
/* 定义 BTF kfuncs ID 集 */
|
||||
BTF_KFUNCS_START(bpf_kfunc_example_ids_set)
|
||||
BTF_ID_FLAGS(func, bpf_kfunc_call_test)
|
||||
BTF_ID_FLAGS(func, bpf_strstr)
|
||||
BTF_KFUNCS_END(bpf_kfunc_example_ids_set)
|
||||
|
||||
// 注册 kfunc ID 集合
|
||||
/* 注册 kfunc ID 集 */
|
||||
static const struct btf_kfunc_id_set bpf_kfunc_example_set = {
|
||||
.owner = THIS_MODULE,
|
||||
.set = &bpf_kfunc_example_ids_set,
|
||||
};
|
||||
|
||||
// 模块初始化
|
||||
/* 模块加载时执行的函数 */
|
||||
static int __init hello_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
printk(KERN_INFO "Hello, world!\n");
|
||||
// 注册 BTF kfunc ID 集合
|
||||
/* 注册 BPF_PROG_TYPE_KPROBE 的 BTF kfunc ID 集 */
|
||||
ret = register_btf_kfunc_id_set(BPF_PROG_TYPE_KPROBE, &bpf_kfunc_example_set);
|
||||
if (ret) {
|
||||
pr_err("bpf_kfunc_example: 注册 BTF kfunc ID 集合失败\n");
|
||||
if (ret)
|
||||
{
|
||||
pr_err("bpf_kfunc_example: 注册 BTF kfunc ID 集失败\n");
|
||||
return ret;
|
||||
}
|
||||
printk(KERN_INFO "bpf_kfunc_example: 模块加载成功\n");
|
||||
return 0; // 成功
|
||||
return 0; // 成功返回 0
|
||||
}
|
||||
|
||||
// 模块清理
|
||||
/* 模块卸载时执行的函数 */
|
||||
static void __exit hello_exit(void)
|
||||
{
|
||||
// 注销 BTF kfunc ID 集合(根据内核版本可选)
|
||||
// unregister_btf_kfunc_id_set(BPF_PROG_TYPE_KPROBE, &bpf_kfunc_example_set);
|
||||
printk(KERN_INFO "Goodbye, world!\n");
|
||||
/* 取消注册 BTF kfunc ID 集 */
|
||||
unregister_btf_kfunc_id_set(BPF_PROG_TYPE_KPROBE, &bpf_kfunc_example_set);
|
||||
printk(KERN_INFO "再见,世界!\n");
|
||||
}
|
||||
|
||||
// 定义模块的入口和出口
|
||||
/* 定义模块的初始化和退出点的宏 */
|
||||
module_init(hello_init);
|
||||
module_exit(hello_exit);
|
||||
|
||||
MODULE_LICENSE("GPL"); // 许可类型
|
||||
MODULE_LICENSE("GPL"); // 许可证类型(GPL)
|
||||
MODULE_AUTHOR("Your Name"); // 模块作者
|
||||
MODULE_DESCRIPTION("一个简单的模块"); // 模块描述
|
||||
MODULE_DESCRIPTION("一个简单的模块"); // 模块描述
|
||||
MODULE_VERSION("1.0"); // 模块版本
|
||||
```
|
||||
|
||||
**代码解释:**
|
||||
|
||||
- **声明 kfunc:** `__bpf_kfunc` 宏声明了一个 eBPF 程序可以调用的函数。在这里,`bpf_kfunc_call_test` 接受四个参数(`a`、`b`、`c`、`d`),并返回它们的和。
|
||||
- **声明 kfunc:** `__bpf_kfunc` 宏声明一个 eBPF 程序可以调用的函数。在这里,`bpf_strstr` 执行给定字符串中的子字符串搜索。
|
||||
|
||||
- **BTF 定义:** `__bpf_kfunc_start_defs` 和 `__bpf_kfunc_end_defs` 宏标志着 kfunc 定义的开始和结束。`BTF_KFUNCS_START` 及相关宏有助于将 kfuncs 注册到 BPF 类型格式(BTF)中。
|
||||
- **BTF 定义:** `__bpf_kfunc_start_defs` 和 `__bpf_kfunc_end_defs` 宏标示 kfunc 定义的开始和结束。`BTF_KFUNCS_START` 及相关宏帮助将 kfuncs 注册到 BPF 类型格式(BTF)。
|
||||
|
||||
- **模块初始化:** `hello_init` 函数注册 kfunc ID 集合,使 `bpf_kfunc_call_test` 对 `BPF_PROG_TYPE_KPROBE` 类型的 eBPF 程序可用。
|
||||
- **模块初始化:** `hello_init` 函数注册 kfunc ID 集,使 `bpf_strstr` 可用于 `BPF_PROG_TYPE_KPROBE` 类型的 eBPF 程序。
|
||||
|
||||
- **模块清理:** `hello_exit` 函数确保在模块移除时注销 kfunc ID 集合,保持系统的清洁。
|
||||
- **模块清理:** `hello_exit` 函数确保在模块移除时取消注册 kfunc ID 集,保持系统整洁。
|
||||
|
||||
#### **文件:`Makefile`**
|
||||
|
||||
@@ -151,41 +182,41 @@ obj-m += hello.o # hello.o 是目标
|
||||
KBUILD_CFLAGS += -g -O2
|
||||
|
||||
all:
|
||||
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
|
||||
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
|
||||
|
||||
clean:
|
||||
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
|
||||
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
|
||||
```
|
||||
|
||||
**Makefile 解释:**
|
||||
|
||||
- **目标定义:** `obj-m += hello.o` 指定 `hello.o` 是要构建的模块。
|
||||
|
||||
- **BTF 生成标志:** `KBUILD_CFLAGS += -g -O2` 启用调试信息和优化,以促进 BTF 生成。
|
||||
- **BTF 生成标志:** `KBUILD_CFLAGS += -g -O2` 启用调试信息和优化,便于 BTF 生成。
|
||||
|
||||
- **构建命令:**
|
||||
- **`all`:** 通过调用内核构建系统编译内核模块。
|
||||
- **`clean`:** 清理构建产物。
|
||||
- **`all`:** 通过调用内核构建系统编译内核模块。
|
||||
- **`clean`:** 清理构建产物。
|
||||
|
||||
**注意:** 提供的代码已在 **Linux 内核版本 6.11** 上进行了测试。如果你使用的是较早的版本,可能需要实现一些变通方法,例如引用 `compact.h`。
|
||||
**注意:** 提供的代码在 Linux 内核版本 **6.11** 上进行了测试。如果你使用的是较早的版本,可能需要实现一些变通方法,例如引用 `compact.h`。
|
||||
|
||||
### 编译内核模块
|
||||
|
||||
有了内核模块的源代码和 Makefile 后,按照以下步骤编译模块:
|
||||
在内核模块源代码和 Makefile 就位后,按照以下步骤编译模块:
|
||||
|
||||
1. **导航到模块目录:**
|
||||
|
||||
```bash
|
||||
cd /path/to/bpf-developer-tutorial/src/43-kfuncs/module/
|
||||
```
|
||||
```bash
|
||||
cd /path/to/bpf-developer-tutorial/src/43-kfuncs/module/
|
||||
```
|
||||
|
||||
2. **编译模块:**
|
||||
|
||||
```bash
|
||||
make
|
||||
```
|
||||
```bash
|
||||
make
|
||||
```
|
||||
|
||||
该命令将生成一个名为 `hello.ko` 的文件,即编译后的内核模块。
|
||||
该命令将生成一个名为 `hello.ko` 的文件,即编译后的内核模块。
|
||||
|
||||
### 加载内核模块
|
||||
|
||||
@@ -197,7 +228,7 @@ sudo insmod hello.ko
|
||||
|
||||
### 验证模块加载
|
||||
|
||||
加载模块后,通过检查内核日志来验证是否成功插入:
|
||||
加载模块后,通过检查内核日志验证其是否成功插入:
|
||||
|
||||
```bash
|
||||
dmesg | tail
|
||||
@@ -227,12 +258,12 @@ dmesg | tail
|
||||
**预期输出:**
|
||||
|
||||
```txt
|
||||
[ 1234.9876] Goodbye, world!
|
||||
[ 1234.9876] 再见,世界!
|
||||
```
|
||||
|
||||
## 处理编译错误
|
||||
|
||||
在编译过程中,你可能会遇到以下错误:
|
||||
在编译过程中,可能会遇到以下错误:
|
||||
|
||||
```txt
|
||||
Skipping BTF generation for /root/bpf-developer-tutorial/src/43-kfuncs/module/hello.ko due to unavailability of vmlinux
|
||||
@@ -242,31 +273,31 @@ Skipping BTF generation for /root/bpf-developer-tutorial/src/43-kfuncs/module/he
|
||||
|
||||
1. **安装 `dwarves` 包:**
|
||||
|
||||
`dwarves` 包提供了 BTF 生成所需的工具。
|
||||
`dwarves` 包提供了生成 BTF 所需的工具。
|
||||
|
||||
```sh
|
||||
sudo apt install dwarves
|
||||
```
|
||||
```sh
|
||||
sudo apt install dwarves
|
||||
```
|
||||
|
||||
2. **复制 `vmlinux` 文件:**
|
||||
|
||||
确保包含 BTF 信息的 `vmlinux` 文件在构建目录中可用。
|
||||
确保包含 BTF 信息的 `vmlinux` 文件在构建目录中可用。
|
||||
|
||||
```sh
|
||||
sudo cp /sys/kernel/btf/vmlinux /usr/lib/modules/$(uname -r)/build/
|
||||
```
|
||||
```sh
|
||||
sudo cp /sys/kernel/btf/vmlinux /usr/lib/modules/$(uname -r)/build/
|
||||
```
|
||||
|
||||
该命令将 `vmlinux` 文件复制到适当的构建目录中,确保 BTF 生成成功。
|
||||
该命令将 `vmlinux` 文件复制到适当的构建目录,确保成功生成 BTF。
|
||||
|
||||
本教程的完整代码可以在 GitHub 的链接 <https://github.com/eunomia-bpf/bpf-developer-tutorial/tree/main/src/43-kfuncs> 中找到。这个代码在 Linux 内核版本 6.11 上进行了测试,其他低版本可能需要一些修改,参考 `compact.h`。
|
||||
本教程的完整代码可在 [bpf-developer-tutorial 仓库](https://github.com/eunomia-bpf/bpf-developer-tutorial/tree/main/src/43-kfuncs) 的 GitHub 上找到。此代码在 Linux 内核版本 6.11 上进行了测试,对于较低版本,可能需要参考 `compact.h` 进行一些修改。
|
||||
|
||||
## 在 eBPF 程序中使用自定义 kfunc
|
||||
|
||||
有了定义自定义 kfunc 的内核模块后,下一步是创建一个利用该函数的 eBPF 程序。这种交互展示了 kfuncs 引入的增强功能。
|
||||
有了定义自定义 `strstr` kfunc 的内核模块后,下一步是创建一个利用此函数的 eBPF 程序。此交互展示了 kfuncs 引入的增强功能。
|
||||
|
||||
### 编写 eBPF 程序
|
||||
|
||||
创建一个 eBPF 程序,该程序附加到 `do_unlinkat` 内核函数,并使用自定义的 `bpf_kfunc_call_test` kfunc。
|
||||
创建一个附加到 `do_unlinkat` 内核函数并使用自定义 `bpf_strstr` kfunc 的 eBPF 程序。
|
||||
|
||||
#### **文件:`kfunc.c`**
|
||||
|
||||
@@ -278,49 +309,82 @@ Skipping BTF generation for /root/bpf-developer-tutorial/src/43-kfuncs/module/he
|
||||
#include <bpf/bpf_tracing.h>
|
||||
|
||||
typedef unsigned int u32;
|
||||
typedef unsigned long long u64;
|
||||
typedef int pid_t;
|
||||
typedef long long s64;
|
||||
|
||||
// 声明外部 kfunc
|
||||
extern u64 bpf_kfunc_call_test(u32 a, u64 b, u32 c, u64 d) __ksym;
|
||||
/* 声明外部 kfunc */
|
||||
extern int bpf_strstr(const char *str, u32 str__sz, const char *substr, u32 substr__sz) __ksym;
|
||||
|
||||
// 许可信息
|
||||
char LICENSE[] SEC("license") = "Dual BSD/GPL";
|
||||
|
||||
// 附加到 do_unlinkat 内核函数
|
||||
SEC("kprobe/do_unlinkat")
|
||||
int handle_kprobe(void *ctx)
|
||||
int handle_kprobe(struct pt_regs *ctx)
|
||||
{
|
||||
pid_t pid = bpf_get_current_pid_tgid() >> 32;
|
||||
u64 result = bpf_kfunc_call_test(1, 2, 3, 4);
|
||||
bpf_printk("BPF 触发了 do_unlinkat,PID: %d。结果: %lld\n", pid, result);
|
||||
char str[] = "Hello, world!";
|
||||
char substr[] = "wor";
|
||||
int result = bpf_strstr(str, sizeof(str) - 1, substr, sizeof(substr) - 1);
|
||||
if (result != -1)
|
||||
{
|
||||
bpf_printk("'%s' found in '%s' at index %d\n", substr, str, result);
|
||||
}
|
||||
bpf_printk("Hello, world! (pid: %d) bpf_strstr %d\n", pid, result);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
**eBPF 代码解释:**
|
||||
|
||||
- **外部 kfunc 声明:** `extern` 关键字声明了 `bpf_kfunc_call_test` 函数,使其在 eBPF 程序中可用。
|
||||
- **外部 kfunc 声明:** `extern` 关键字声明 `bpf_strstr` 函数,使其在 eBPF 程序中可用。
|
||||
|
||||
- **Kprobe 附加:** `SEC("kprobe/do_unlinkat")` 宏将 eBPF 程序附加到 `do_unlinkat` 内核函数。每当调用 `do_unlinkat` 时,`handle_kprobe` 函数就会执行。
|
||||
- **Kprobe 附加:** `SEC("kprobe/do_unlinkat")` 宏将 eBPF 程序附加到 `do_unlinkat` 内核函数。每次调用 `do_unlinkat` 时,`handle_kprobe` 函数都会执行。
|
||||
|
||||
- **使用 kfunc:** 在 `handle_kprobe` 中,eBPF 程序调用 `bpf_kfunc_call_test`,传入四个参数(`1, 2, 3, 4`)。结果,即这些数字的和,通过 `bpf_printk` 打印出来,显示 PID 和结果。
|
||||
- **使用 kfunc:** 在 `handle_kprobe` 中,eBPF 程序调用 `bpf_strstr`,传入四个参数:
|
||||
- `str`: 要搜索的主字符串。
|
||||
- `str__sz`: 主字符串的大小。
|
||||
- `substr`: 要搜索的子字符串。
|
||||
- `substr__sz`: 子字符串的大小。
|
||||
|
||||
结果(子字符串在主字符串中的首次出现索引,或 -1 表示未找到)然后通过 `bpf_printk` 打印,显示 PID 和结果。
|
||||
|
||||
**重要提示:** 由于验证器限制,直接在 eBPF 中实现类似 `strstr` 的函数具有挑战性,因为这限制了循环和复杂的内存访问。通过将 `strstr` 实现为 kfunc,我们绕过了这些限制,使得在 eBPF 程序中执行更复杂和高效的字符串操作成为可能。
|
||||
|
||||
### 编译 eBPF 程序
|
||||
|
||||
要编译 eBPF 程序,确保你已安装必要的工具,如 `clang` 和 `llvm`。以下是编译程序的方法:
|
||||
要编译 eBPF 程序,确保你已安装必要的工具,如 `clang` 和 `llvm`。以下是编译程序的步骤:
|
||||
|
||||
1. **导航到 eBPF 程序目录:**
|
||||
|
||||
```bash
|
||||
cd /path/to/bpf-developer-tutorial/src/43-kfuncs/
|
||||
```
|
||||
```bash
|
||||
cd /path/to/bpf-developer-tutorial/src/43-kfuncs/
|
||||
```
|
||||
|
||||
2. **编译 eBPF 程序:**
|
||||
2. **为 eBPF 程序创建一个 `Makefile`:**
|
||||
|
||||
```bash
|
||||
make
|
||||
```
|
||||
```makefile
|
||||
# 文件:Makefile
|
||||
|
||||
CLANG ?= clang
|
||||
LLVM_STRIP ?= llvm-strip
|
||||
BPF_TARGET := bpf
|
||||
|
||||
CFLAGS := -O2 -g -target $(BPF_TARGET) -Wall -Werror -I/usr/include
|
||||
|
||||
all: kfunc.o
|
||||
|
||||
kfunc.o: kfunc.c
|
||||
$(CLANG) $(CFLAGS) -c $< -o $@
|
||||
|
||||
clean:
|
||||
rm -f kfunc.o
|
||||
```
|
||||
|
||||
3. **编译 eBPF 程序:**
|
||||
|
||||
```bash
|
||||
make
|
||||
```
|
||||
|
||||
该命令将生成一个名为 `kfunc.o` 的文件,即编译后的 eBPF 对象文件。
|
||||
|
||||
### 运行 eBPF 程序
|
||||
|
||||
@@ -329,50 +393,53 @@ int handle_kprobe(void *ctx)
|
||||
**示例输出:**
|
||||
|
||||
```bash
|
||||
$ sudo ./kfunc
|
||||
BPF 程序加载并成功附加。按 Ctrl-C 退出。
|
||||
node-9523 [004] ...21 7520.587718: bpf_trace_printk: BPF 触发了 do_unlinkat,PID: 9523。结果: 10
|
||||
# sudo ./load_kfunc
|
||||
BPF 程序已加载并成功附加。按 Ctrl-C 退出。
|
||||
```
|
||||
|
||||
cpptools-11242 [003] ...21 7859.613060: bpf_trace_printk: BPF 触发了 do_unlinkat,PID: 11235。结果: 10
|
||||
然后,当调用 `do_unlinkat` 函数时(例如,当文件被取消链接时),你可以检查内核日志:
|
||||
|
||||
^C
|
||||
cpptools-11242 [002] ...21 7865.831074: bpf_trace_printk: BPF 触发了 do_unlinkat,PID: 11235。结果: 10
|
||||
```bash
|
||||
dmesg | tail
|
||||
```
|
||||
|
||||
**预期输出:**
|
||||
|
||||
```txt
|
||||
[ 1234.5678] 'wor' found in 'Hello, world!' at index 7
|
||||
[ 1234.5679] Hello, world! (pid: 2075) bpf_strstr 7
|
||||
```
|
||||
|
||||
**输出解释:**
|
||||
|
||||
每当内核中调用 `do_unlinkat` 函数时,eBPF 程序都会打印一条消息,指示进程的 PID 和 kfunc 调用的结果。在此示例中,`1 + 2 + 3 + 4` 的和为 `10`,这在输出中得到了体现。
|
||||
每次内核调用 `do_unlinkat` 函数时,eBPF 程序都会打印一条消息,指示进程的 PID 以及 kfunc 调用的结果。在此示例中,子字符串 `"wor"` 在字符串 `"Hello, world!"` 的索引 `7` 处被找到。
|
||||
|
||||
## 总结与结论
|
||||
|
||||
在本教程中,我们深入探讨了通过定义和使用自定义内核函数(kfuncs)来扩展 eBPF 功能。以下是我们涵盖的内容回顾:
|
||||
在本教程中,我们深入探讨了通过定义和使用自定义内核函数(kfuncs)来扩展 eBPF 的能力。以下是我们涵盖的内容回顾:
|
||||
|
||||
- **理解 kfuncs:** 掌握了 kfuncs 的概念及其在增强 eBPF 标准辅助函数方面的作用。
|
||||
- **定义 kfuncs:** 创建了一个定义自定义 kfunc 的内核模块,确保其能够安全地暴露给 eBPF 程序,而无需更改核心内核。
|
||||
- **编写使用 kfuncs 的 eBPF 程序:** 开发了一个利用自定义 kfunc 执行特定操作的 eBPF 程序,展示了增强的功能。
|
||||
- **编译与执行:** 提供了逐步指南,编译、加载和运行内核模块及 eBPF 程序,确保你能在自己的系统上复现设置。
|
||||
- **错误处理:** 解决了潜在的编译问题,并提供了解决方案,以确保顺利的开发体验。
|
||||
- **理解 kfuncs:** 理解了 kfuncs 的概念及其在标准辅助函数之外增强 eBPF 的角色。
|
||||
- **定义 kfuncs:** 创建了一个内核模块,定义了自定义的 `strstr` kfunc,确保其能够安全地暴露给 eBPF 程序,而无需修改核心内核。
|
||||
- **编写包含 kfuncs 的 eBPF 程序:** 开发了一个利用自定义 kfunc 的 eBPF 程序,展示了增强的功能。
|
||||
- **编译与执行:** 提供了逐步指南,编译、加载并运行内核模块和 eBPF 程序,确保你可以在自己的系统上复制设置。
|
||||
- **错误处理:** 解决了潜在的编译问题,并提供了解决方案,确保顺利的开发体验。
|
||||
|
||||
**关键要点:**
|
||||
|
||||
- **克服辅助函数的限制:** kfuncs 填补了标准 eBPF 辅助函数留下的空白,提供了针对特定需求的扩展功能。
|
||||
- **维护系统稳定性:** 通过将 kfuncs 封装在内核模块中,确保在不进行侵入性内核更改的情况下保持系统稳定性。
|
||||
- **社区驱动的演变:** kfuncs 的快速增长和采用凸显了 eBPF 社区致力于通过内核级编程推动 eBPF 潜力的决心。
|
||||
- **利用现有 kfuncs:** 在定义新的 kfuncs 之前,探索内核提供的现有 kfuncs。它们涵盖了广泛的功能,除非绝对必要,否则无需创建自定义函数。
|
||||
- **克服辅助函数的限制:** kfuncs 弥合了标准 eBPF 辅助函数留下的空白,提供了针对特定需求的扩展功能。
|
||||
- **维护系统稳定性:** 通过将 kfuncs 封装在内核模块中,确保系统稳定性,而无需对内核进行侵入性更改。
|
||||
- **社区驱动的演变:** kfuncs 的快速增长和采用凸显了 eBPF 社区致力于通过 kfuncs 推动内核级编程可能性的决心。
|
||||
- **利用现有 kfuncs:** 在定义新的 kfuncs 之前,探索内核提供的现有 kfuncs。它们涵盖了广泛的功能,减少了除非绝对必要,否则无需创建自定义函数的需求。
|
||||
|
||||
**准备好进一步提升你的 eBPF 技能了吗?** [访问我们的教程仓库](https://github.com/eunomia-bpf/bpf-developer-tutorial)并[探索我们网站上的更多教程](https://eunomia.dev/tutorials/)。深入了解大量示例,深化你的理解,并为动态发展的 eBPF 世界做出贡献!
|
||||
**准备好进一步提升你的 eBPF 技能了吗?** [访问我们的教程仓库](https://github.com/eunomia-bpf/bpf-developer-tutorial)并[探索我们网站上的更多教程](https://eunomia.dev/tutorials/)。深入丰富的示例,深化你的理解,并为 eBPF 的动态世界做出贡献!
|
||||
|
||||
祝你 eBPF 编程愉快!
|
||||
祝你在 eBPF 的旅程中愉快!
|
||||
|
||||
## 参考资料
|
||||
|
||||
- [BPF 内核函数文档](https://docs.kernel.org/bpf/kfuncs.html)
|
||||
- [eBPF kfuncs 指南](https://docs.ebpf.io/linux/kfuncs/)
|
||||
|
||||
## 额外资源
|
||||
## 附加资源
|
||||
|
||||
如果你想了解更多关于 eBPF 的知识和实践,可以访问我们的开源教程代码仓库 <https://github.com/eunomia-bpf/bpf-developer-tutorial> 或者我们的网站 <https://eunomia.dev/tutorials/>,获取更多示例和完整代码。
|
||||
|
||||
## 结论
|
||||
|
||||
通过遵循本详尽的教程,你已经掌握了使用自定义 kfuncs 扩展 eBPF 功能的知识。无论你是旨在执行高级内核交互、克服辅助函数的限制,还是增强你的可观察性工具,kfuncs 都提供了你所需的灵活性和强大功能。继续尝试,保持好奇,并为不断发展的 eBPF 领域做出贡献!
|
||||
如果你想了解更多关于 eBPF 的知识和实践,可以访问我们的开源教程代码仓库 [bpf-developer-tutorial](https://github.com/eunomia-bpf/bpf-developer-tutorial) 或访问我们的网站 [eunomia.dev/tutorials](https://eunomia.dev/tutorials/) 以获取更多示例和完整代码。
|
||||
|
||||
Reference in New Issue
Block a user