Files
dpdk-blog/products/unos-method.md
T
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

12 KiB
Executable File
Raw Blame History

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):

// 自己写:

// 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 小时一个模块。