91. 推荐学习顺序
第一次阅读不要直接跳到 HEFT 代码。建议按以下顺序:

图 1 推荐学习顺序(13 个能力检查点)
- 能解释 byte、十六进制、网络字节序和封装;
- 能手算 IPv4/IPv6 前缀,并解释最长前缀匹配;
- 能区分普通路由下一跳与 SRv6 segment list;
- 能从卫星位置推导 LOS、链路时隙、HELLO 邻接和本地 FIB;
- 能拿一个 SRH 逐步写出 active segment 和 IPv6 DA;
- 能解释控制面事实为何不等于内核已应用状态;
- 能区分 task_id、Color、SID 和 Run ID;
- 能从逻辑 Anycast SID 推导本节点的 primary、backup 和 provider 漂移;
- 能按 SPP 头定位 task/object/tile/epoch,并解释部分成功;
- 能逐步证明 RGB→Gray→Edge 经过真实本地算子;
- 能手算一次 HEFT,再比较 Exact 与 Resilient;
- 最后连接 BNN、Alarm、10% 损毁、handover、前端和证据;
- 掌握原理后再按照后端操作手册启动系统。
每完成一项,先用自己的话讲给别人听。能复述术语不等于理解;能根据一个新报文或新拓扑推导结果,才算掌握。
91.1 当前开发功能覆盖总表
本教材覆盖当前完整系统的原理主线与业务功能,但不会把每个命令行 flag 和历史 Issue 都塞进教学正文;实际命令归后端操作手册,历史演进归 Issue。可按下表核对是否漏项:
| 已开发能力 | 教材位置 | 学完后应能说明 |
|---|---|---|
| IPv4、IPv6、Ethernet、邻居和路由 | 第 1–6 章 | 一个普通包怎样逐跳交付 |
| Linux 原生 SRv6、SRH、End/End.X/DX4/DX6/DT6、uSID、SR Policy | 第 7–12 章 | 一串标准 SID 怎样执行而不依赖私有协议栈 |
| 200 卫星、50 飞机、50 船与两机架构 | 第 13、16 章 | 每个前端对象为何对应真实节点及怎样运动 |
| 圆轨道、ECI/ECEF、LOS、跨轨匹配、端侧仰角 | 第 16 章 | 物理链路通断怎样由规则自然产生 |
| 60 秒物理代次、SQLite 事务、AF_XDP 原子重载 | 第 16 章 | 300 节点怎样只看到完整的新链路矩阵 |
| veth、namespace、宿主 XDP 与 AF_XDP | 第 14–15 章 | frame、UMEM、四个 ring 和有向链路队列的所有权 |
| 限速、burst、传播时延、拥塞与三类 drop | 第 15 章 | token bucket 和各类丢包为何不能混算 |
| HELLO/ACK、四控制器、双向拓扑、端点与资源事实 | 第 16–22 章 | 物理变化怎样经真实观察形成协议图 |
| 节点本地普通 FIB、LFA、accepted/applied | 第 20–22 章 | 控制器为何不替节点安装 FIB |
| 统一 DAG、ETS3 REQUEST/READY/REJECTED | 第 23–24 章 | 普通传输、单算子和多算子为何共用生命周期 |
| task_id、入口本地 Color、eBPF classifier、策略表 | 第 25 章 | task 怎样在 Linux 入口选择自己的 SR Policy |
| Exact 与 Resilient 模式边界 | 第 26、45、74、78–79 章 | 两种模式分别由谁选择实例 |
| 七类真实算子与独立 namespace | 第 27–38 章 | Gray、Edge、两种 Mask、Mean、Blur、BNN 怎样实现 |
| BNN KEEP/DROP、模型摘要与 96% 稀疏结果语义 | 第 38 章 | 正常 DROP 为何不等于网络或算子错误 |
| 火情检测到地面 Alarm 闭环 | 第 38.1、86 章 | 新 Alarm task 怎样自然 REQUEST、READY、发送和接收 |
| 逻辑 Anycast SID、节点本地 primary/backup FIB | 第 40–47 章 | provider 怎样选择、撤销、切换和形成漂移 |
| SPP 64-byte 头、Tile、Morton、对象窗口和部分成功 | 第 48–54 章 | 100 个 Tile 完成 90 个为何是 90% 成功 |
| SPP 与有状态 Anycast 的理论张力 | 第 54 章 | 无网络丢包时窗口为何仍可能拆散 |
| RGB、Gray、Edge 三级自主降档 | 第 55 章 | 链路比例怎样触发本地真实算子 detour 而非伪造格式 |
| HEFT rank、EST/EFT、候选过滤和完整手算 | 第 57–67 章 | Exact 怎样联合选择具体实例 |
| HEFT primary/backup、动态重算、无解与耗时分层 | 第 68–76 章 | 何时替换策略、怎样如实记录无解和 CPU 竞争 |
| 10% 固定卫星损毁与 A→B 可靠性 | 第 16.13、80 章 | 故障集合为何独立于业务,分母为何不能筛选 |
| 飞机/船舶 handover | 第 16.10、81 章 | 几何接入怎样经 HELLO 和端点事实自然变化 |
| 控制面 EAGAIN 与 latest-only 背压 | 第 19、82 章 | 旧完整快照为何可被新代替代而不堆积 |
| ClickHouse、MinIO、Gitea、Run ID 与只读前端 | 第 83–86 章 | 观察失败为何不能反向停止后端 |
| 八类前端事实:SRv6、HEFT、重算、Anycast、SPP、BNN、Alarm、降档 | 第 86 章 | 每种画面应由哪类真实事件驱动 |
| 24/300 统一模型及 install/images/start/status/logs/stop/clean | 项目后端操作手册 | 怎样在同一入口安全运行和清理系统 |
因此,Anycast 和三级降档现在不仅“出现过”,而是分别拥有完整的第六编和第八编。若今后新增正式业务能力,应先在这张表增加学习位置和源码入口,防止功能只存在于 Issue 或前端卡片而没有进入教材。
92. 源码地图
源码会演进,文件名以当前 checkout 为准。阅读入口如下:
| 想理解的内容 | 首先阅读 |
|---|---|
| 不可违反的系统边界 | docs/simulation-compass.md |
| 当前研发原理摘要 | docs/technical-principles.md |
| 裸机操作 | docs/backend-operations.md |
| SPP 二进制头 | include/spp.h |
| SPP 实现与 Tile 坐标 | src/spp.c 及相关测试 |
| 图像算子算法 | src/gyr_tile_ops.c、src/bnn_fire_model.c 及相关头文件 |
| 算子 worker/AF_XDP 循环 | src/gray_af_xdp_operator.c、src/gyr_tile_pipeline.c 及 SPP runtime |
| BNN 检测与 Alarm 业务 | src/gray_af_xdp_operator.c、scripts/fire_alarm.py、scripts/endpoint_business.py |
| 节点本地 Anycast FIB | scripts/anycast_fib.py、scripts/sdn_node_agent.py |
| 三级降档与 SRH detour | src/spp_degradation.c、src/spp_srh_detour.c、src/host_link_fabric.c |
| HEFT 算法 | scripts/heft_dag.py |
| 任务 classifier | bpf/heft_task_classifier.bpf.c |
| Color map 操作 | src/heft_task_map.c 及节点启动脚本 |
| 节点 FIB 与任务状态机 | scripts/sdn_node_agent.py、scripts/local_heft_policy.py 和节点脚本 |
| 控制协议 | scripts/sdn_wire.py、四个 sdn_*_service.py 和节点 agent |
| 宿主 XDP 入口 | bpf/xdp_link_fabric.bpf.c、include/link_fabric.h |
| 宿主 AF_XDP fabric | src/host_link_fabric.c、scripts/setup_issue_76_physical_fabric.sh |
| 场景生成 | configs/issue255-physical-scene-300.json、scripts/physical_scene_provider.py、scripts/polar_topology_provider.py |
| 拓扑事务与时隙 | scripts/topology_slot_store.py、scripts/topology_scene_runtime.py、scripts/topology_slot_fabric.py |
| 拓扑到 fabric 激活 | scripts/topology_snapshot_to_fabric.py、scripts/continuous_polar_fabric.py |
| HELLO 邻居发现 | scripts/neighbor_hello_wire.py、scripts/neighbor_hello.py |
| 协议拓扑与本地 FIB | scripts/sdn_topology_module.py、scripts/sdn_node_agent.py |
| 前端真实事实投影 | src/frontend/src/App.tsx、src/frontend/src/business.ts 及前端测试 |
| 统一部署入口 | scripts/envsim_host.sh |
| 事件分析 | scripts/analyze_*.py、scripts/analyze_*.sh |
阅读代码时先找 wire contract(线上契约)和 state owner(状态所有者),再看循环和优化。不要从一个日志字符串反推整套架构。
93. 如何安全做只读学习
学习者在 .5 源码机上可以执行静态命令,不会启动仿真:
git status --short --branch
git log -3 --oneline
sed -n '1,240p' include/spp.h
python3 scripts/heft_dag.py --help
bash scripts/envsim_host.sh --help
bash scripts/envsim_host.sh doctor
doctor 只检查能力;install 默认也只输出计划,只有 install --apply 才改变依赖。不要在.5 启动 300 节点仿真。
在 .2 查看运行状态也应先使用操作手册中的 status 和 logs。不要随意执行全局docker prune、删除 network namespace、清空 bpffs 或修改宿主 sysctl,因为这些动作可能影响本项目以外的工作。
94. 实验一:手算 IPv6 前缀
给出:

图 2 六个只读动手实验
地址 A = fd42:3:14::100
地址 B = fd42:3:14::200
地址 C = fd42:3:15::100
前缀 P = fd42:3:14::/64
问题:A、B、C 谁属于 P?
方法:IPv6 每个 hextet 是 16 bit,/64 比较前四个 hextet:
A前四组 = fd42:0003:0014:0000
B前四组 = fd42:0003:0014:0000
C前四组 = fd42:0003:0015:0000
答案是 A、B 属于,C 不属于。这就是本项目用 fd42:3:<node>::/64 覆盖某节点全部具体算子 SID的原因。
95. 实验二:手推 SRH
假设概念执行顺序为:
S1 → S2 → S3
入口封装后:
- IPv6 DA 是 S1;
- SRH 保存足够信息以继续到 S2、S3;
- S1 执行 End 后,DA 变为 S2;
- S2 执行 End 后,DA 变为 S3;
- S3 执行最终 endpoint behavior。
练习:画出每一步的 Segments Left。它表示还有多少 segment 需要推进,而不是“数组里共有多少项”。抓包工具显示 segment list 的顺序可能与概念执行顺序相反,必须同时看 DA 和Segments Left。
96. 实验三:解码一个 SPP 头
在受控测试文件中取 64 byte 头,按偏移读取:
0..3 magic
7 payload format
10..11 payload length
12..19 task id
20..27 object id
28..35 tile id
52..55 object epoch
每读取多字节字段都先确认网络字节序。然后验证:
actual_frame_payload >= 64 + declared_payload_length
task_id != 0
object_id != 0
format与payload length相容
tile坐标在grid边界内
学习目标不是背偏移,而是形成“先验证边界,再解释字段”的解析习惯。
97. 实验四:手算 Color 路由
假设:
task_id = 70001
Color = 4466
dst = fd42:10:12c::2
推导:
- classifier 识别 70001 并查 map;
skb->mark=4466;ip rule选择 table10000+4466=14466;- table 14466 的目的
/128路由执行 SRv6 封装; - 后续节点不需要知道 Color。
若 map 没有 task key,先查为什么安装未完成;不要为了“让包走起来”把所有未知 task 都塞到某条默认正确策略。
98. 实验五:手算 HEFT
先完全遮住第 67 章答案,使用表格独立计算:
- 两个 stage 的平均计算时间;
- 四个候选组合的平均通信时间;
- upward rank;
- Gray 两个候选的 EST/EFT;
- Edge 两个候选含 sink 的完成时间;
- 最终 placement 和 makespan。
然后改变一个条件:把 G5 计算时间从 500 us 改成 100 us。重新计算,观察入口传输代价是否仍使 G2 获胜。这个练习能建立“局部快不等于端到端快”的直觉。
99. 实验六:区分真实失败与观察失败
构造下面四组证据并判断:
| 源端发送 | 算子处理 | 目的端接收 | 前端显示 | 结论 |
|---|---|---|---|---|
| 有 | 有 | 有 | 无 | 观察面失败,业务成功 |
| 有 | DROP | 无 | 正确显示 DROP | BNN 正常业务裁决 |
| 有 | 无 | 无 | 无 | 需继续定位数据面边界 |
| 无 | 无 | 无 | 无 | 先查业务是否自然产生,不应先改网络 |
这张表训练“缺少显示不等于缺少事实”。
100. 常见误区与纠正
100.1 “SRv6 就是 IPv6 源路由”
不完整。SRv6 不仅列路径,还把 SID 解释为网络程序行为,例如 End.X、End.DX6 和算子实例。
100.2 “控制器知道全网,所以应该直接写所有节点 FIB”
本项目明确禁止。控制器分发事实,节点根据自己已经应用的事实形成 FIB 和 SR Policy。
100.3 “Color 是画面颜色或业务优先级”
不是。Color 是入口本地把 task 导向专用策略表的短标识。
100.4 “Anycast 会把同一个包复制给多个容器”
普通 Anycast 选择一个 provider,不复制包。出现五个实例记录通常意味着不同包或不同时刻的FIB 选择发生了漂移。
100.5 “有一个 Tile 缺失,整条 task 失败”
不符合当前 SPP 部分成功语义。应按完成 Tile/预期 Tile 统计。
100.6 “HEFT 没有解就是 Bug”
不是。资源、算子或路径约束可能使问题真实不可行,应记录无解原因。
100.7 “HEFT 超过 100 ms 必须强行短路”
不是。先区分算法 CPU 时间、调度等待、快照传播和内核安装;观察阈值不是修改协议语义的命令。
100.8 “把超时加长就能提高可靠性”
加长可能只推迟错误并增加旧状态堆积。先证明第一个未闭环的组件边界。
100.9 “前端页面是系统真相”
前端是事实投影。真实来源是节点、内核、fabric、控制器和对象证据。
100.10 “24 节点是简化假模型”
24 与 300 使用相同镜像、PID 1、协议和启动逻辑,只改变规模和参数。
100.11 “10% 损毁后只统计仍有路的任务更合理”
这会改变实验分母。可以另报可行任务完成率,但不能替代损毁后的总业务成功率 B。
100.12 “SPP 和 Anycast 不能同时使用”
过于绝对。无状态算子自然兼容;有状态算子会承受 provider 漂移带来的窗口损失,当前将其按Tile 可靠性量化,更高层状态安置是后续理论问题。
101. 综合练习
101.1 基础题
- IPv6 地址 128 bit 共有多少 byte?
/64表示哪部分是前缀?- FIB 与 RIB 分别解决什么问题?
- SRH 中的 active segment 与 IPv6 DA 有什么关系?
- End.X 与 End.DX6 的作用有什么不同?
101.2 系统题
- 为什么宿主 XDP 只按 MAC redirect,而不自己计算 IPv6 路由?
- 为什么控制器的 accepted generation 不能直接当作节点 applied generation?
- 为什么 resource report 必须有 instance、sequence 和 lifetime?
- task_id、Color、Run ID 的作用域各是什么?
- Exact 策略中为什么使用具体算子 SID?
- Resilient 策略中为什么使用逻辑 Anycast SID?
101.3 SPP 题
- 两个包 object_id 相同、epoch 不同,能否进入同一缓存窗口?
- Tile Mean 为什么不能按“任意四个包”聚合?
- 100 个预期 Tile 到 95 个,Tile 成功率是多少?
- BNN DROP 与算子 ERROR 有什么区别?
101.4 HEFT 题
- upward rank 使用平均成本,最终 placement 也使用平均成本吗?
- EST 为何可能晚于所有前驱完成时间?
- 一个 stage 在 node-5 的 EFT 更小,但它到 sink 很远;出口选择应比较什么?
- primary 排除首边与具体实例后 backup 无解,正确记录是什么?
- Resilient provider 变化为什么不触发 HEFT?
101.5 诊断题
- 前端没有图但目的端有接收事件,应先修数据面还是观察面?
- 控制 socket 出现 EAGAIN 时,为什么不应无限累积旧拓扑快照?
- HEFT 墙钟耗时变大,至少还要观察哪三类数据?
- 一轮运行有业务 ENETUNREACH,为什么不能立即断言宿主网络安装失败?
- 观察器失败后,分析脚本应该怎样继续?
102. 综合练习答案
128÷8=16 byte。- 前 64 bit 是网络前缀,其余 64 bit 是接口标识部分;具体地址规划仍由项目定义。
- RIB 保存控制面候选路由知识,FIB 保存内核实际用于快速转发的结果。
- active segment 作为当前 IPv6 DA;端点行为推进后,DA 变为下一个 active segment。
- End.X 推进 SRH 并交指定下一跳;End.DX6 解封装并交付内层 IPv6。
- 保持 Linux 拥有路由语义,XDP 只做最小可证的 L2 分流,避免宿主暗中形成第二套路由器。
- accepted 仅表示消息被接收,节点还要通过单一 worker 验证并安装,成功后才是 applied。
- 用来区分进程重启、消息新旧和事实过期,防止旧实例“复活”。
- task_id 是端到端业务任务;Color 是某入口本地策略选择;Run ID 是一整轮实验和证据。
- Exact 已由 HEFT 选择具体实例,策略必须把选择落实成可执行地址。
- Resilient 把实例选择留给每个逻辑服务的当前 FIB。
- 不能,epoch 不同表示对象版本不同。
- 必须是同对象、同 epoch、同 2×2 空间 group 的四块。
95/100=95%。- DROP 是模型作出的正常负类裁决;ERROR 表示格式、模型或运行异常。
- 不使用;最终 placement 使用候选间具体计算和通信代价。
- 目标 processor 可能仍在执行此前放置的 stage,所以还要等待 resource available time。
- 比较 EFT 加该候选到 sink 的具体通信时间。
- 记录 primary 成功、backup no solution 及约束原因,不伪造 backup。
- Resilient 不运行 HEFT;它只更新逻辑服务 FIB,task SRH 稳定。
- 先查观察面查询、Run ID、权限和投影。
- 旧拓扑已被新代完整取代;积累历史会使节点永远追赶过期状态。
- 线程 CPU 时间、调度/run queue、输入候选规模;还应分开快照传播与内核安装。
- 业务路径在动态拓扑/损毁下可以自然不可达,需先区分宿主能力、控制面、FIB 和任务结果。
- 捕获该观察器状态并继续其他领域分析,最后统一汇总,不能让
set -e提前退出。