mirror of
https://github.com/eunomia-bpf/bpf-developer-tutorial.git
synced 2026-11-01 13:15:01 +08:00
docs: normalize punctuation in tutorials 50 through 54
This commit is contained in:
@@ -12,7 +12,7 @@
|
||||
|
||||
### 传统方案:两个程序配合哈希表
|
||||
|
||||
最常见的做法是使用两个独立的 BPF 程序——一个附加到函数进入(fentry),一个附加到函数返回(fexit)。进入程序把时间戳存入一个以线程 ID 为键的哈希表;返回程序查找时间戳、计算延迟、删除条目:
|
||||
最常见的做法是使用两个独立的 BPF 程序,一个附加到函数进入(fentry),一个附加到函数返回(fexit)。进入程序把时间戳存入一个以线程 ID 为键的哈希表;返回程序查找时间戳、计算延迟、删除条目:
|
||||
|
||||
```
|
||||
进入程序: 返回程序:
|
||||
@@ -27,15 +27,15 @@
|
||||
|
||||
- **哈希表开销**:每次函数调用都需要在进入时插入、在返回时查找并删除。对于像 `vfs_read` 这样的高频函数,开销会累积。
|
||||
- **状态泄漏**:如果线程在进入后、返回前被杀死(比如 `kill -9`),哈希表条目永远不会被删除,造成内存泄漏。
|
||||
- **没有内在关联**:两个程序完全独立,唯一的联系是外部哈希表——没有任何保证它们追踪的是同一次函数调用。
|
||||
- **没有内在关联**:两个程序完全独立,唯一的联系是外部哈希表,没有任何保证它们追踪的是同一次函数调用。
|
||||
|
||||
### kprobe/kretprobe:相同模式,更高开销
|
||||
|
||||
kprobe 机制也是两个程序的结构,同样需要外部哈希表来关联。此外,kprobe 通过软件断点机制工作(用中断指令替换第一条指令),开销比 fentry 更大——后者使用内核的 ftrace 基础设施和 JIT 优化的调用序列。
|
||||
kprobe 机制也是两个程序的结构,同样需要外部哈希表来关联。此外,kprobe 通过软件断点机制工作(用中断指令替换第一条指令),开销比 fentry 更大,后者使用内核的 ftrace 基础设施和 JIT 优化的调用序列。
|
||||
|
||||
### 用户态采样:统计性的,不是精确的
|
||||
|
||||
像 `perf record` 这样的工具周期性采样堆栈,可以构建时间花在哪里的统计概貌。然而,采样无法测量单次函数调用的延迟。如果你需要捕获尾部延迟事件——偶尔出现的 100ms 读操作导致超时——统计采样可能完全错过它们。
|
||||
像 `perf record` 这样的工具周期性采样堆栈,可以构建时间花在哪里的统计概貌。然而,采样无法测量单次函数调用的延迟。如果你需要捕获尾部延迟事件(偶尔出现的 100ms 读操作导致超时),统计采样可能完全错过它们。
|
||||
|
||||
## fsession 的解决方案
|
||||
|
||||
@@ -70,7 +70,7 @@ __u64 *started = bpf_session_cookie(ctx);
|
||||
*started = bpf_ktime_get_ns();
|
||||
```
|
||||
|
||||
cookie 从进入到返回一直存在,之后自动清理——不可能泄漏。
|
||||
cookie 从进入到返回一直存在,之后自动清理,不可能泄漏。
|
||||
|
||||
### 返回阶段可以访问函数参数
|
||||
|
||||
@@ -167,7 +167,7 @@ BPF 程序中的 `const volatile` 变量有特殊语义。它们被放在 `.roda
|
||||
|
||||
`stats` 是 `.bss` 段的全局变量,程序运行后用户空间可以直接读取。
|
||||
|
||||
ring buffer(`events`)大小是 256 KB——足够容纳数千个事件才会溢出。
|
||||
ring buffer(`events`)大小是 256 KB,足够容纳数千个事件才会溢出。
|
||||
|
||||
```c
|
||||
/*
|
||||
@@ -179,7 +179,7 @@ extern bool bpf_session_is_return(void *ctx) __ksym;
|
||||
extern __u64 *bpf_session_cookie(void *ctx) __ksym;
|
||||
```
|
||||
|
||||
这是 **kfunc** 声明——导出给 BPF 程序调用的内核函数。`__ksym` 属性告诉验证器在加载时从运行中的内核解析这些符号,而不是期望它们在 BPF 对象中定义。
|
||||
这是 **kfunc** 声明,导出给 BPF 程序调用的内核函数。`__ksym` 属性告诉验证器在加载时从运行中的内核解析这些符号,而不是期望它们在 BPF 对象中定义。
|
||||
|
||||
```c
|
||||
SEC("fsession/vfs_read")
|
||||
@@ -223,9 +223,9 @@ TGID 在 `bpf_get_current_pid_tgid()` 返回值的高 32 位;低 32 位是线
|
||||
return 0;
|
||||
```
|
||||
|
||||
**返回阶段**:如果 cookie 是 0,说明进入阶段已经过滤了这次调用——直接返回。否则计算延迟并更新聚合计数器。`__sync_fetch_and_add` 提供原子更新,因为多个 CPU 可能同时执行这个程序。
|
||||
**返回阶段**:如果 cookie 是 0,说明进入阶段已经过滤了这次调用,直接返回。否则计算延迟并更新聚合计数器。`__sync_fetch_and_add` 提供原子更新,因为多个 CPU 可能同时执行这个程序。
|
||||
|
||||
如果延迟低于阈值,到此为止——调用被计数但不产生事件。这让 ring buffer 只关注慢调用。
|
||||
如果延迟低于阈值,到此为止,调用被计数但不产生事件。这让 ring buffer 只关注慢调用。
|
||||
|
||||
```c
|
||||
__sync_fetch_and_add(&stats.slow, 1);
|
||||
@@ -248,7 +248,7 @@ TGID 在 `bpf_get_current_pid_tgid()` 返回值的高 32 位;低 32 位是线
|
||||
|
||||
对于慢调用,递增 `slow` 计数器并尝试在 ring buffer 中预留空间。如果预留失败(缓冲区满),递增 `dropped` 让用户知道有事件丢失。成功后填入各字段并提交。
|
||||
|
||||
注意 `count`(请求的字节数)可以直接访问——不需要在进入时存储。这就是 fsession 的优势:函数参数在返回阶段仍然可用。
|
||||
注意 `count`(请求的字节数)可以直接访问,不需要在进入时存储。这就是 fsession 的优势:函数参数在返回阶段仍然可用。
|
||||
|
||||
### 用户空间加载器
|
||||
|
||||
@@ -384,7 +384,7 @@ sudo make test
|
||||
- **直方图**:用 `BPF_MAP_TYPE_ARRAY` 的延迟直方图替代逐事件上报
|
||||
- **cgroup 过滤**:添加 cgroup ID 检查实现容器感知的追踪
|
||||
|
||||
`dropped` 计数器仍然是 ring buffer 压力的信号——如果它在增长,要么增大缓冲区大小,要么提高阈值减少事件量。
|
||||
`dropped` 计数器仍然是 ring buffer 压力的信号。如果它在增长,要么增大缓冲区大小,要么提高阈值减少事件量。
|
||||
|
||||
## 总结
|
||||
|
||||
@@ -392,7 +392,7 @@ sudo make test
|
||||
|
||||
1. **单个程序**:一个 BPF 程序处理进入和返回,用 `bpf_session_is_return()` 区分阶段
|
||||
2. **内置关联**:8 字节的 session cookie 替代外部哈希表在两个阶段间传递数据
|
||||
3. **无状态泄漏**:内核管理 cookie 生命周期——不需要清理,不可能泄漏
|
||||
3. **无状态泄漏**:内核管理 cookie 生命周期,不需要清理,不可能泄漏
|
||||
4. **返回时可访问参数**:函数参数在返回阶段仍然可用,无需显式存储
|
||||
|
||||
模式很简单:检查 `bpf_session_is_return()`,用 `bpf_session_cookie()` 管理调用级状态,直接访问参数和返回值。这个模式适用于任何需要关联函数进入和退出的场景。
|
||||
|
||||
Reference in New Issue
Block a user