From 676266d4b49895e4777e9b629570d4efe0af059a Mon Sep 17 00:00:00 2001 From: LinuxDev9002 Date: Sat, 18 Jul 2026 23:28:18 -0700 Subject: [PATCH] Use standard Markdown in the egress tutorial --- src/53-egress-pacer/README.md | 16 ++++------------ src/53-egress-pacer/README.zh.md | 16 ++++------------ 2 files changed, 8 insertions(+), 24 deletions(-) diff --git a/src/53-egress-pacer/README.md b/src/53-egress-pacer/README.md index baf8762..c5c8ff7 100644 --- a/src/53-egress-pacer/README.md +++ b/src/53-egress-pacer/README.md @@ -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 - ```c /* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ #ifndef __EGRESS_PACER_H @@ -37,7 +36,6 @@ struct pacer_stats { #endif /* __EGRESS_PACER_H */ ``` - 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. - ```c /* SPDX-License-Identifier: GPL-2.0 */ #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 */ ``` - 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. - ```c // SPDX-License-Identifier: GPL-2.0 #include "vmlinux.h" @@ -292,7 +287,6 @@ struct Qdisc_ops pacer = { .id = "bpf_pacer", }; ``` - #### 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. - ```c // SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) #include @@ -634,7 +627,6 @@ cleanup: return err != 0; } ``` - #### 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 -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 cd src/53-egress-pacer @@ -654,13 +646,13 @@ make clean 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 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 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. -> If you'd like to dive deeper into eBPF, check out our tutorial repository at or visit our website at . +> If you'd like to dive deeper into eBPF, check out our tutorial repository at . ## References diff --git a/src/53-egress-pacer/README.zh.md b/src/53-egress-pacer/README.zh.md index bd3e43e..9a1a130 100644 --- a/src/53-egress-pacer/README.zh.md +++ b/src/53-egress-pacer/README.zh.md @@ -20,7 +20,6 @@ Linux 6.16 让 BPF 可以再向前一步:`struct_ops` 程序能够直接实现 ### 让 BPF 和用户态共享统计信息 - ```c /* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */ #ifndef __EGRESS_PACER_H @@ -37,7 +36,6 @@ struct pacer_stats { #endif /* __EGRESS_PACER_H */ ``` - BPF 程序会在报文经过队列时更新这六个计数器。`enqueued` 和 `dequeued` 记录正常的入队与出队;`policy_dropped` 记录队列已满或节点分配失败造成的拒绝;`cleanup_dropped` 记录 reset 时仍留在队列里的报文。最后两个字段分别保存已经发送的字节数和本次运行观察到的最大队长。 @@ -47,7 +45,6 @@ loader 移除 qdisc 后,会从 `skel->bss->stats` 读取同一个结构。把 仓库中较早生成的头文件缺少部分 BPF qdisc 定义。下面这个本地兼容头文件补齐所需声明,不会修改仓库全局集成的头文件。 - ```c /* SPDX-License-Identifier: GPL-2.0 */ #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 */ ``` - 这个文件本身不做限速。它只是把真正的 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 回调、报文队列、限速时钟和统计计数。 - ```c // SPDX-License-Identifier: GPL-2.0 #include "vmlinux.h" @@ -292,7 +287,6 @@ struct Qdisc_ops pacer = { .id = "bpf_pacer", }; ``` - #### 接管一个 skb @@ -335,7 +329,6 @@ next_departure_ns = eligible_ns + interval_ns 用户态加载器负责检查参数、注册 BPF qdisc、把它安装到一个接口,等待指定时间后再将其移除。 - ```c // SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) #include @@ -634,7 +627,6 @@ cleanup: return err != 0; } ``` - #### 把实验限制在一个接口内 @@ -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 cd src/53-egress-pacer @@ -654,13 +646,13 @@ make clean 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 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 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` 则处理移除时仍留在队列中的报文。真正可以复用的是这些机制。如果要加入公平性、并发状态、动态策略、持久化和更强的故障恢复,就需要设计一个更完整的调度器。 -> 如果你想继续深入学习 eBPF,可以查看我们的 [教程仓库](https://github.com/eunomia-bpf/bpf-developer-tutorial),或访问 [eunomia 教程网站](https://eunomia.dev/tutorials/)。 +> 如果你想继续深入学习 eBPF,可以查看我们的[教程仓库](https://github.com/eunomia-bpf/bpf-developer-tutorial)。 ## 参考资料