Use standard Markdown in the egress tutorial

This commit is contained in:
LinuxDev9002
2026-07-18 23:28:18 -07:00
parent 5514e9f8ff
commit 676266d4b4
2 changed files with 8 additions and 24 deletions

View File

@@ -20,7 +20,6 @@ We will build the pacer one component at a time. The first component is the smal
### Sharing Statistics with User Space ### Sharing Statistics with User Space
<!-- BEGIN FULL SOURCE: src/53-egress-pacer/egress_pacer.h -->
```c ```c
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ /* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
#ifndef __EGRESS_PACER_H #ifndef __EGRESS_PACER_H
@@ -37,7 +36,6 @@ struct pacer_stats {
#endif /* __EGRESS_PACER_H */ #endif /* __EGRESS_PACER_H */
``` ```
<!-- END FULL SOURCE -->
The BPF program updates these six counters while packets move through the queue. `enqueued` and `dequeued` cover the normal path. `policy_dropped` counts packets rejected because the queue is full or a node cannot be allocated. `cleanup_dropped` counts packets still waiting when the qdisc is reset. The last two fields record the number of bytes sent and the largest queue depth seen during the run. The BPF program updates these six counters while packets move through the queue. `enqueued` and `dequeued` cover the normal path. `policy_dropped` counts packets rejected because the queue is full or a node cannot be allocated. `cleanup_dropped` counts packets still waiting when the qdisc is reset. The last two fields record the number of bytes sent and the largest queue depth seen during the run.
@@ -47,7 +45,6 @@ The loader reads the same structure from `skel->bss->stats` after it removes the
The repository's generated headers predate a few BPF qdisc definitions. This local compatibility header supplies those declarations without changing the vendored global headers. The repository's generated headers predate a few BPF qdisc definitions. This local compatibility header supplies those declarations without changing the vendored global headers.
<!-- BEGIN FULL SOURCE: src/53-egress-pacer/bpf_experimental.h -->
```c ```c
/* SPDX-License-Identifier: GPL-2.0 */ /* SPDX-License-Identifier: GPL-2.0 */
#ifndef __EGRESS_PACER_BPF_EXPERIMENTAL_H #ifndef __EGRESS_PACER_BPF_EXPERIMENTAL_H
@@ -82,7 +79,6 @@ bpf_list_pop_front(struct bpf_list_head *head) __ksym;
#endif /* __EGRESS_PACER_BPF_EXPERIMENTAL_H */ #endif /* __EGRESS_PACER_BPF_EXPERIMENTAL_H */
``` ```
<!-- END FULL SOURCE -->
This file does not implement pacing. It exposes the graph-object kfuncs used by the real qdisc: `bpf_obj_new` and `bpf_obj_drop` manage packet nodes, while `bpf_list_push_back` and `bpf_list_pop_front` manage the FIFO. The `__contains` BTF tag tells the verifier which node type belongs to the list head. This file does not implement pacing. It exposes the graph-object kfuncs used by the real qdisc: `bpf_obj_new` and `bpf_obj_drop` manage packet nodes, while `bpf_list_push_back` and `bpf_list_pop_front` manage the FIFO. The `__contains` BTF tag tells the verifier which node type belongs to the list head.
@@ -92,7 +88,6 @@ Keeping these declarations local matters because they are compatibility glue for
Now we can look at the kernel-mode program. It implements the qdisc operations, packet queue, pacing clock, and counters. Now we can look at the kernel-mode program. It implements the qdisc operations, packet queue, pacing clock, and counters.
<!-- BEGIN FULL SOURCE: src/53-egress-pacer/egress_pacer.bpf.c -->
```c ```c
// SPDX-License-Identifier: GPL-2.0 // SPDX-License-Identifier: GPL-2.0
#include "vmlinux.h" #include "vmlinux.h"
@@ -292,7 +287,6 @@ struct Qdisc_ops pacer = {
.id = "bpf_pacer", .id = "bpf_pacer",
}; };
``` ```
<!-- END FULL SOURCE -->
#### Taking Ownership of an skb #### Taking Ownership of an skb
@@ -335,7 +329,6 @@ Together, the BPF callbacks update the six counters we saw in the shared header:
The user-space loader validates the command line, registers the BPF qdisc, installs it on one interface, waits for the requested duration, and removes it. The user-space loader validates the command line, registers the BPF qdisc, installs it on one interface, waits for the requested duration, and removes it.
<!-- BEGIN FULL SOURCE: src/53-egress-pacer/egress_pacer.c -->
```c ```c
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) // SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
#include <errno.h> #include <errno.h>
@@ -634,7 +627,6 @@ cleanup:
return err != 0; return err != 0;
} }
``` ```
<!-- END FULL SOURCE -->
#### Keeping the Experiment on One Interface #### Keeping the Experiment on One Interface
@@ -646,7 +638,7 @@ After `READY`, the loader waits in 100 ms intervals until the duration expires o
## Compilation and execution ## Compilation and execution
Build the BPF object and loader with the lesson's [`Makefile`](./Makefile). Compilation does not need root: Build the BPF object and loader with the lesson's [`Makefile`](https://github.com/eunomia-bpf/bpf-developer-tutorial/blob/main/src/53-egress-pacer/Makefile). Compilation does not need root:
```bash ```bash
cd src/53-egress-pacer cd src/53-egress-pacer
@@ -654,13 +646,13 @@ make clean
make -j2 make -j2
``` ```
The safest first run uses the [integration fixture](./tests/test_egress_pacer.py). It creates and removes a disposable `epac_tx`/`epac_rx` veth pair, so the runtime step needs root: The safest first run uses the [integration fixture](https://github.com/eunomia-bpf/bpf-developer-tutorial/blob/main/src/53-egress-pacer/tests/test_egress_pacer.py). It creates and removes a disposable `epac_tx`/`epac_rx` veth pair, so the runtime step needs root:
```bash ```bash
sudo python3 tests/test_egress_pacer.py ./egress_pacer sudo python3 tests/test_egress_pacer.py ./egress_pacer
``` ```
The final binary was built cleanly on the host and run in the reused `bpf-benchmark` KVM guest. That guest used an x86_64 `7.0.0-rc2+` kernel built from commit `a03114efd0720dff230388f7e160e427e54ea31b`. The fixture produced: The example was tested on x86_64 with a `7.0.0-rc2+` kernel built from commit `a03114efd0720dff230388f7e160e427e54ea31b`. The fixture produced:
```console ```console
READY interface=epac_tx rate_kbps=64 queue_limit=8 duration=2 READY interface=epac_tx rate_kbps=64 queue_limit=8 duration=2
@@ -743,7 +735,7 @@ Deleting a qdisc changes traffic scheduling, so never run this recovery command
This lesson used a small FIFO to expose the full BPF qdisc lifecycle. `enqueue` takes ownership of an skb and assigns its departure time, the watchdog wakes `dequeue` without a busy loop, and `reset` accounts for packets still queued during removal. Those mechanics are the reusable part. Fairness, concurrent state, dynamic policy, persistence, and stronger recovery would require a larger scheduler. This lesson used a small FIFO to expose the full BPF qdisc lifecycle. `enqueue` takes ownership of an skb and assigns its departure time, the watchdog wakes `dequeue` without a busy loop, and `reset` accounts for packets still queued during removal. Those mechanics are the reusable part. Fairness, concurrent state, dynamic policy, persistence, and stronger recovery would require a larger scheduler.
> If you'd like to dive deeper into eBPF, check out our tutorial repository at <https://github.com/eunomia-bpf/bpf-developer-tutorial> or visit our website at <https://eunomia.dev/tutorials/>. > If you'd like to dive deeper into eBPF, check out our tutorial repository at <https://github.com/eunomia-bpf/bpf-developer-tutorial>.
## References ## References

View File

@@ -20,7 +20,6 @@ Linux 6.16 让 BPF 可以再向前一步:`struct_ops` 程序能够直接实现
### 让 BPF 和用户态共享统计信息 ### 让 BPF 和用户态共享统计信息
<!-- BEGIN FULL SOURCE: src/53-egress-pacer/egress_pacer.h -->
```c ```c
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ /* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
#ifndef __EGRESS_PACER_H #ifndef __EGRESS_PACER_H
@@ -37,7 +36,6 @@ struct pacer_stats {
#endif /* __EGRESS_PACER_H */ #endif /* __EGRESS_PACER_H */
``` ```
<!-- END FULL SOURCE -->
BPF 程序会在报文经过队列时更新这六个计数器。`enqueued` 和 `dequeued` 记录正常的入队与出队;`policy_dropped` 记录队列已满或节点分配失败造成的拒绝;`cleanup_dropped` 记录 reset 时仍留在队列里的报文。最后两个字段分别保存已经发送的字节数和本次运行观察到的最大队长。 BPF 程序会在报文经过队列时更新这六个计数器。`enqueued` 和 `dequeued` 记录正常的入队与出队;`policy_dropped` 记录队列已满或节点分配失败造成的拒绝;`cleanup_dropped` 记录 reset 时仍留在队列里的报文。最后两个字段分别保存已经发送的字节数和本次运行观察到的最大队长。
@@ -47,7 +45,6 @@ loader 移除 qdisc 后,会从 `skel->bss->stats` 读取同一个结构。把
仓库中较早生成的头文件缺少部分 BPF qdisc 定义。下面这个本地兼容头文件补齐所需声明,不会修改仓库全局集成的头文件。 仓库中较早生成的头文件缺少部分 BPF qdisc 定义。下面这个本地兼容头文件补齐所需声明,不会修改仓库全局集成的头文件。
<!-- BEGIN FULL SOURCE: src/53-egress-pacer/bpf_experimental.h -->
```c ```c
/* SPDX-License-Identifier: GPL-2.0 */ /* SPDX-License-Identifier: GPL-2.0 */
#ifndef __EGRESS_PACER_BPF_EXPERIMENTAL_H #ifndef __EGRESS_PACER_BPF_EXPERIMENTAL_H
@@ -82,7 +79,6 @@ bpf_list_pop_front(struct bpf_list_head *head) __ksym;
#endif /* __EGRESS_PACER_BPF_EXPERIMENTAL_H */ #endif /* __EGRESS_PACER_BPF_EXPERIMENTAL_H */
``` ```
<!-- END FULL SOURCE -->
这个文件本身不做限速。它只是把真正的 qdisc 会用到的 graph-object kfunc 暴露出来:`bpf_obj_new` 和 `bpf_obj_drop` 管理报文节点的生命周期,`bpf_list_push_back` 和 `bpf_list_pop_front` 管理 FIFO。`__contains` 生成的 BTF tag 则告诉 verifier,list head 中保存的是哪一种节点。 这个文件本身不做限速。它只是把真正的 qdisc 会用到的 graph-object kfunc 暴露出来:`bpf_obj_new` 和 `bpf_obj_drop` 管理报文节点的生命周期,`bpf_list_push_back` 和 `bpf_list_pop_front` 管理 FIFO。`__contains` 生成的 BTF tag 则告诉 verifier,list head 中保存的是哪一种节点。
@@ -92,7 +88,6 @@ bpf_list_pop_front(struct bpf_list_head *head) __ksym;
接下来进入内核态程序。它实现 qdisc 回调、报文队列、限速时钟和统计计数。 接下来进入内核态程序。它实现 qdisc 回调、报文队列、限速时钟和统计计数。
<!-- BEGIN FULL SOURCE: src/53-egress-pacer/egress_pacer.bpf.c -->
```c ```c
// SPDX-License-Identifier: GPL-2.0 // SPDX-License-Identifier: GPL-2.0
#include "vmlinux.h" #include "vmlinux.h"
@@ -292,7 +287,6 @@ struct Qdisc_ops pacer = {
.id = "bpf_pacer", .id = "bpf_pacer",
}; };
``` ```
<!-- END FULL SOURCE -->
#### 接管一个 skb #### 接管一个 skb
@@ -335,7 +329,6 @@ next_departure_ns = eligible_ns + interval_ns
用户态加载器负责检查参数、注册 BPF qdisc、把它安装到一个接口,等待指定时间后再将其移除。 用户态加载器负责检查参数、注册 BPF qdisc、把它安装到一个接口,等待指定时间后再将其移除。
<!-- BEGIN FULL SOURCE: src/53-egress-pacer/egress_pacer.c -->
```c ```c
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) // SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
#include <errno.h> #include <errno.h>
@@ -634,7 +627,6 @@ cleanup:
return err != 0; return err != 0;
} }
``` ```
<!-- END FULL SOURCE -->
#### 把实验限制在一个接口内 #### 把实验限制在一个接口内
@@ -646,7 +638,7 @@ cleanup:
## 编译和运行 ## 编译和运行
使用本课的 [`Makefile`](./Makefile) 编译 BPF 对象和加载器,这一步不需要 root: 使用本课的 [`Makefile`](https://github.com/eunomia-bpf/bpf-developer-tutorial/blob/main/src/53-egress-pacer/Makefile) 编译 BPF 对象和加载器,这一步不需要 root:
```bash ```bash
cd src/53-egress-pacer cd src/53-egress-pacer
@@ -654,13 +646,13 @@ make clean
make -j2 make -j2
``` ```
第一次运行最好使用[集成测试程序](./tests/test_egress_pacer.py)。它会自动创建并删除一次性的 `epac_tx`/`epac_rx` veth 对,实际加载 BPF 和操作 qdisc 时需要 root: 第一次运行最好使用[集成测试程序](https://github.com/eunomia-bpf/bpf-developer-tutorial/blob/main/src/53-egress-pacer/tests/test_egress_pacer.py)。它会自动创建并删除一次性的 `epac_tx`/`epac_rx` veth 对,实际加载 BPF 和操作 qdisc 时需要 root:
```bash ```bash
sudo python3 tests/test_egress_pacer.py ./egress_pacer sudo python3 tests/test_egress_pacer.py ./egress_pacer
``` ```
最终二进制先在宿主机上完成干净编译,再放入复用的 `bpf-benchmark` KVM 客户机运行。客户机为 x86_64,使用从 commit `a03114efd0720dff230388f7e160e427e54ea31b` 构建的 `7.0.0-rc2+` 内核。测试输出如下: 本课在 x86_64 环境中完成测试,使用从 commit `a03114efd0720dff230388f7e160e427e54ea31b` 构建的 `7.0.0-rc2+` 内核。测试输出如下:
```console ```console
READY interface=epac_tx rate_kbps=64 queue_limit=8 duration=2 READY interface=epac_tx rate_kbps=64 queue_limit=8 duration=2
@@ -743,7 +735,7 @@ sudo tc qdisc del dev IFACE root
这个小型 FIFO 展示了完整的 BPF qdisc 生命周期。`enqueue` 接管 skb 并计算发送时间,watchdog 在不忙等的情况下重新唤醒 `dequeue`,`reset` 则处理移除时仍留在队列中的报文。真正可以复用的是这些机制。如果要加入公平性、并发状态、动态策略、持久化和更强的故障恢复,就需要设计一个更完整的调度器。 这个小型 FIFO 展示了完整的 BPF qdisc 生命周期。`enqueue` 接管 skb 并计算发送时间,watchdog 在不忙等的情况下重新唤醒 `dequeue`,`reset` 则处理移除时仍留在队列中的报文。真正可以复用的是这些机制。如果要加入公平性、并发状态、动态策略、持久化和更强的故障恢复,就需要设计一个更完整的调度器。
> 如果你想继续深入学习 eBPF,可以查看我们的 [教程仓库](https://github.com/eunomia-bpf/bpf-developer-tutorial),或访问 [eunomia 教程网站](https://eunomia.dev/tutorials/)。 > 如果你想继续深入学习 eBPF,可以查看我们的[教程仓库](https://github.com/eunomia-bpf/bpf-developer-tutorial)。
## 参考资料 ## 参考资料