Files
newbie 41531c19c2 init: DPDK → DPU learning blog (VitePress)
- VitePress static site for rendering all markdown documents
- Auto-discovers .md files from dpdk-roadmap-v3 and dpdk-study-kb
- Features: full-text search, dark mode, responsive design
- Pages: roadmap, notes (Phase 0-3), product docs, knowledge base
2026-08-09 15:42:47 +00:00

360 lines
12 KiB
Markdown
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# uNOS 学习方法论 — 三层剥皮法
> **创建日期**: 2026-08-01
> **适用**: 系统编程学习(DPDK / 内核 / TCP 协议栈 / PMD 驱动)
> **关联**: `uNOS_REQUIREMENTS.md`、`uNOS_DESIGN_SPEC.md`、`lazy-puzzling-rocket.md`
---
## 一、核心公式
```
掌握 = 内核为什么慢 → DPDK 怎么解决的 → 我亲手写一遍 → 性能数据证明
────────── ────────────── ────────── ─────────────
Layer 1 Layer 2 Layer 3 Layer 4
(理解问题) (理解方案) (内化知识) (验证理解)
```
每一层不可跳过:
| 跳过 | 后果 |
|------|------|
| Layer 1 | 不知道为什么需要 DPDK,面试回答停留在"因为快" |
| Layer 2 | 只会调 API,不理解设计决策 |
| Layer 3 | "看懂了但不会写",面试一问细节就崩 |
| Layer 4 | "写出来了但不知道自己写对了没" |
---
## 二、四层详解
### Layer 1: 内核为什么慢(20 分钟,理解问题)
**做什么**:读内核源码,找到瓶颈的**精确位置**。
**方法**:
1. 先确定要研究的子系统(如:包分配、路由查找、TCP 状态机)
2. 在 Linux 内核源码中找到对应的 C 文件
3. 跟踪一个完整的数据路径(如:收包 → 分配 skb → 协议栈 → 应用)
4. 标注每一步的延迟来源:中断、锁、内存分配、cache miss、上下文切换
**产出标准**:能用一句话解释"内核做 X 要走多少步、每一步慢在哪"。
**示例**(mempool):
```
问题: 内核是怎么分配网络包的?
精确到源码:
收包: net/core/dev.c → netif_rx() → enqueue_to_backlog()
→ 每个包触发一次 ksoftirqd 调度
分配: include/linux/skbuff.h → alloc_skb()
→ kmem_cache_alloc() → slab allocator
→ 每次分配都要获取 per-cpu 锁
为什么慢:
1. 每包一次中断 → 每包一次上下文切换
2. slab allocator 有锁 → 多核竞争
3. kmem_cache 有冷热分离 → 热路径 miss 时有 cache miss
4. sk_buff 结构体 200+ 字节,跨多个 cacheline
```
---
### Layer 2: DPDK 怎么解决的(20 分钟,理解方案)
**做什么**:读 DPDK 源码,找到优化的**精确手段**。
**方法**:
1. 在 DPDK 源码中找到对应的库/驱动
2. 读头文件的 struct 定义和函数注释
3. 读实现文件的几个关键函数
4. 画出"数据路径图":一个包从进入到离开,经过哪些结构体、哪些函数
**产出标准**:能用一张图画出关键路径,标注每一级的延迟量级。
**示例**(mempool):
```
方案: rte_mempool
精确到源码:
lib/mempool/rte_mempool.h line 230 → struct rte_mempool
lib/mempool/rte_mempool.h line 90 → struct rte_mempool_cache
三级路径:
L1: mbuf 从 per-lcore cache 拿 → 零锁,L1 cache 常驻 (~5ns)
L2: cache 空了 → 从 ring 批量补 256 个 → CAS 一次 (~50ns 均摊)
L3: ring 也空了 → 从 hugepage 拿 → 很少触发 (~200ns)
为什么快:
1. 预分配 → 收发过程零分配,只从池里取
2. per-lcore cache → 无锁,无竞争
3. ring 批量操作 → 分摊 CAS 开销
4. mbuf 固定 128B,2 个 cacheline → prefetch 友好
```
---
### Layer 3: 亲手写一遍(60 分钟,内化知识)
**做什么**:关掉所有参考代码,自己从头写。
**方法**:
1. 读完 Layer 1-2 后,**立即关掉 DPDK 源码**
2. 打开编辑器,自己写
3. 编译 → crash → gdb backtrace → 读报错 → 修 → 继续
4. 跑通后用 gdb 单步跟踪关键路径:打印 struct 内容、看指针偏移
5. 用 `pahole` 看结构体 cache line 布局
**产出标准**:能在 gdb 里打印出关键结构体的内部状态,能解释为什么这么快。
**示例**(mempool):
```c
// 自己写:
// 1. 创建 mempool
struct rte_mempool *pool = rte_pktmbuf_pool_create(
"my_pool", 8191, 256, 0,
RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id()
);
// 2. 分配/释放循环,测性能
uint64_t start = rte_rdtsc();
for (int i = 0; i < 1000000; i++) {
struct rte_mbuf *m = rte_pktmbuf_alloc(pool);
rte_pktmbuf_free(m);
}
uint64_t cycles = (rte_rdtsc() - start) / 1000000;
printf("avg cycles per alloc+free: %lu\n", cycles);
// 3. gdb 验证
// (gdb) p *pool
// (gdb) p pool->cache[0] ← per-lcore cache 里有多少个 mbuf?
// (gdb) p pool->cache[0].len ← 当 len > 0 时 alloc 走 L1
// (gdb) p pool->cache[0].len ← 当 len = 0 时 alloc 走 L2 补货
```
**禁止事项**:
- ❌ 复制粘贴 DPDK examples 代码
- ❌ 先看答案再写
- ❌ crash 后直接问 AI 怎么修(先自己 gdb 至少 5 分钟)
---
### Layer 4: 性能数据证明(20 分钟,验证理解)
**做什么**:用数据证明你写的东西确实比内核快。
**方法**:
1. `perf stat` 对比内核路径 vs DPDK 路径
2. `perf record` + FlameGraph 生成火焰图
3. 填一张对比表
4. 写入 `docs/` 文档
**产出标准**:有一张表格,有数字,有火焰图链接。
**示例**(mempool):
```
│ 指标 │ 内核 (slab) │ DPDK (mempool) │
│ alloc+free 延迟 │ ~200ns │ ~15ns │
│ L1 cache miss │ ~8% │ ~0.5% │
│ 锁竞争 │ 有 (per-cpu) │ 无 (per-lcore) │
│ 火焰图 │ [链接] │ [链接] │
```
---
## 三、每阶段时间分配
```
总时间: 2 小时/阶段
Layer 1: 20 分钟 (读内核源码 + 理解问题)
Layer 2: 20 分钟 (读 DPDK 源码 + 理解方案)
Layer 3: 60 分钟 (手写 + gdb 跟踪)
Layer 4: 20 分钟 (perf 验证 + 写文档)
输入 (Layer 1-2): 30%
输出 (Layer 3-4): 70%
```
---
## 四、应用到 uNOS 每个 Phase
### Phase 1: L2 MAC 转发
| Layer | 内容 | 产出 |
|-------|------|------|
| 1 | 读 `net/core/dev.c` NAPI 收包路径 | 理解中断驱动的瓶颈在哪 |
| 2 | 读 DPDK PMD 轮询模式: `rte_eth_rx_burst` | 理解零中断轮询 |
| 3 | 手写 L2 转发循环,gdb 跟踪 mbuf 从口0到口1 | 能跑通,iPerf3 双向通 |
| 4 | `perf record` 火焰图 | CPU 100% 有用功,中断开销 0% |
### Phase 2: L3 路由转发
| Layer | 内容 | 产出 |
|-------|------|------|
| 1 | 读 `net/ipv4/fib_trie.c` 内核路由表 | 理解 FIB trie 的结构 |
| 2 | 读 `lib/librte_lpm/rte_lpm.c` DIR-24-8 算法 | 理解 LPM 的 2 级 Trie |
| 3 | 手写 LPM lookup + TTL 减一 + checksum | 跨网段 ping 通 |
| 4 | rdtsc 测量单次 lookup 延迟 | 内核 ~500ns vs DPDK ~200ns |
### Phase 3: ARP 协议
| Layer | 内容 | 产出 |
|-------|------|------|
| 1 | 读 `net/ipv4/arp.c` 内核 ARP 实现 | 理解 ARP 表的维护逻辑 |
| 2 | 读 `lib/ring/rte_ring.h` 无锁 CAS 原理 | 理解 ring 的 MP/MC 模式 |
| 3 | 手写 ARP 请求/应答 + pending 队列 | arping 能收到回复 |
| 4 | 连续 ARP 风暴,检查 mbuf 泄漏 | 内存无泄漏 |
### Phase 4: 控制面框架
| Layer | 内容 | 产出 |
|-------|------|------|
| 1 | 读内核 workqueue / tasklet 机制 | 理解异步任务调度 |
| 2 | 读 `rte_eal_remote_launch` + `rte_timer` | 理解 DPDK 的多核 + 定时器 |
| 3 | 手写 Pipeline 框架 + Interface Manager | 两机多 lcore 运行 |
| 4 | 控制面定时器无影响数据面延迟 | 数据面延迟不因定时器而抖动 |
### Phase 5: NAT + 连接跟踪
| Layer | 内容 | 产出 |
|-------|------|------|
| 1 | 读 `net/netfilter/nf_conntrack_core.c` | 理解内核 conntrack 的锁竞争 |
| 2 | 读 `lib/hash/rte_cuckoo_hash.c` Cuckoo hash | 理解 2-bucket 开放寻址 |
| 3 | 手写 SNAT/DNAT + rte_hash 连接表 | 内网 curl 外网能通 |
| 4 | 100K 连接查找延迟 + 哈希冲突率 | 查找 O(1),冲突率 < 5% |
### Phase 6: ACL + QoS
| Layer | 内容 | 产出 |
|-------|------|------|
| 1 | 读内核 `net/sched/` TC 调度框架 | 理解多队列调度的复杂度 |
| 2 | 读 `lib/acl/rte_acl.h` + `lib/sched/` | 理解硬件加速分类 + 调度 |
| 3 | 手写 5 元组 ACL + 8 队列 SP/WRR | 满载大包时小包延迟 < 2ms |
| 4 | iperf3 大包 + ping 小包 CDF 曲线 | 内核 p99 > 80ms vs uNOS < 2ms |
### Phase 7: IPv4/IPv6 + ICMP + Fragmentation
| Layer | 内容 | 产出 |
|-------|------|------|
| 1 | 读 `net/ipv4/ip_input.c` IP 收包路径 | 理解 IP 层在内核中的位置 |
| 2 | 读 RFC 791, RFC 792, RFC 815 | 理解协议规范本身 |
| 3 | 手写 IP 封装/解封 + ICMP Echo Reply | 能 ping 通你的栈 |
| 4 | 分包重组 2000 字节包 | 正确重组 |
### Phase 8: TCP 状态机
| Layer | 内容 | 产出 |
|-------|------|------|
| 1 | 读 `net/ipv4/tcp_input.c` tcp_rcv_state_process | 理解内核 11 状态 |
| 2 | 读 RFC 793(逐字读) | 画出自己的状态转换表 |
| 3 | 手写 11 状态 + scapy 逐状态触发 | gdb 单步跟踪每个转换 |
| 4 | scapy 100 万 TIME_WAIT → 时间轮 O(1) | 内核链表 O(n) vs 时间轮 O(1) |
### Phase 9: 重传引擎
| Layer | 内容 | 产出 |
|-------|------|------|
| 1 | 读 `net/ipv4/tcp_output.c` tcp_retransmit_skb | 理解内核重传逻辑 |
| 2 | 读 RFC 6298 (RTO), RFC 2018 (SACK) | 理解重传算法 |
| 3 | 手写 RTO + 快速重传 + SACK | tc netem 丢包 5% → 正确补偿 |
| 4 | rdtsc 测量 RTO 精度 | 误差 < 10ms |
### Phase 10: 拥塞控制
| Layer | 内容 | 产出 |
|-------|------|------|
| 1 | 读 `net/ipv4/tcp_cubic.c` 内核 CUBIC | 理解三次函数窗口增长 |
| 2 | 读 RFC 8312 + 2008 年 CUBIC 论文 | 理解设计决策 |
| 3 | 手写滑动窗口 + CUBIC | wireshark 验证窗口曲线 |
| 4 | 内核 TCP vs 自研 TCP 吞吐对比 | 1G: 940M vs 940M (持平) |
| | | CPU: 50% vs 8% (6x 优) |
### Phase 11: Socket API
| Layer | 内容 | 产出 |
|-------|------|------|
| 1 | 读 POSIX socket 语义 | 理解 API 设计哲学 |
| 2 | 读内核 `net/socket.c` socket 系统调用 | 理解 fd 到 sock 的映射 |
| 3 | 手写 tcp_socket/bind/listen/accept/send/recv | HTTP Server 跑在自研 TCP 上 |
| 4 | wrk 压测 HTTP QPS | 自研栈 ≥ 内核栈 QPS |
### Phase 12-17 (应用层/管理面/生产级增强)
每个 Phase 同样应用四层框架。Layer 1-2 以阅读对应 RFC / 工业标准实现为主。
---
## 五、方法论红黑榜
### ✅ 做这些
```
每个概念都按 1→2→3→4 顺序来,不跳层
Layer 3 时关掉所有参考,完全自己写
每次 crash 先 gdb 至少 5 分钟再搜答案
每层产出必须可验证(代码能跑 / 图表能看 / 数据能复现)
每天产出至少 50 行可运行的代码
```
### ❌ 不做这些
```
跳过 Layer 1 直接看 DPDK 文档 → 不知道为什么需要
跳过 Layer 3 只看不写 → "我看懂了"(其实没有)
Layer 3 复制粘贴 examples → 没有内化
不写文档 → 一个月后全忘了
反复讨论设计但不写代码 → 设计永远不会完美
不跑 perf 不生成数据 → 证明不了自己写的东西好
```
---
## 六、每日日志模板
```
日期: 2026-08-XX
阶段: Phase X - XXX
Layer 1 (20min):
读了: [内核文件/函数]
理解: [一句话瓶颈描述]
Layer 2 (20min):
读了: [DPDK 文件/函数]
理解: [一句话优化描述]
画了: [数据路径图]
Layer 3 (60min):
写了: [文件路径,代码行数]
Crash: [次数 + 最关键的 bug]
gdb 发现: [最关键的 insight]
Layer 4 (20min):
perf 数据: [关键对比数字]
火焰图: [链接]
文档: [docs/ 文件路径]
明天计划:
[下一阶段的 Layer 1 要读什么]
```
---
> **文档结束**
>
> 这个方法不是学知识的,是逼你写代码的。
> 系统编程不能靠"看懂",要靠"调通"。
> 每层 20/20/60/20 分钟,一天 2 小时一个模块。