Files
MobileGL/docs/Disaggregated/notes/p65/README.md
T
swung0x48 43b5cfa94e [Docs] (Disaggregated): the four index documents keep only summaries, and every phase's detail lives in its own notes/<phase>/README.md
- ROADMAP, MEASUREMENTS and ARCHITECTURE are rewritten as summaries (333 KB of index text -> 84 KB).
  ROADMAP's phase table is one line per phase plus a link; MEASUREMENTS keeps headline numbers under
  its old §1-§13 numbering (plus §14-§16 for P6.5, P7 and P12); ARCHITECTURE keeps every decided rule
  under the same §1-§17 / appendix numbering, so the `ARCHITECTURE.md §N` references in code still
  land, and §17 becomes an index of the phase landing shapes.
- CURRENT_STAGE_PROGRESS now describes the current stage (the P12 on-screen-server subset) and absorbs
  CURRENT_STAGE_STATUS, which is removed. The P7 status tables, the P6 closing snapshot and the P6.5
  first-wave snapshot move to notes/p7, notes/p6 and notes/p65.
- Each phase gets notes/<phase>/README.md: a short summary, then the verbatim material that left
  the index documents (its ROADMAP row, its MEASUREMENTS section with subsection numbers kept, its
  ARCHITECTURE §17 part, milestones) and a file index. P8-P11 and P13 get their planned scope;
  the full open-question list moves to notes/OPEN-QUESTIONS.md.
- The phase reports that sat at the top level move into their phase folders with unchanged names:
  P5D-INPROC-PERFORMANCE, P5E-RUNAHEAD, P5F-WIRE-COMPLETENESS, P6-SPAWN-PLAN, P6-CONTRACT-DRAFT,
  P6-ENDSTATE-REVIEW. Their relative links, CONTRACT-P6.md's links to them and one tool docstring
  are updated; notes/README.md maps every old path and section number to its new place.
- 71 files under notes/recovered/ that were byte-identical copies of phase-folder files are removed.
- Corrections made while condensing: the server process reads Transport as Spawn (CONTRACT-P6 §3.1
  amended the old "child forces monolith" rule); both backends now publish kCapRunAheadApply;
  MOBILEGL_IPC_RESPAWN is a named refusal and MOBILEGL_IPC_IDLE_EXIT_S is not parsed; the Redmi
  pin row is in tools/device_bench/pin_device.sh.

check_doc_citations over docs/Disaggregated/*.md: 0 problems. No relative link is newly broken.
2026-09-24 22:21:09 +00:00

188 lines
32 KiB
Markdown
Raw 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.
# P6.5 — 传输栈两轴化 + 跨机(第一波已落地,未宣布收官)
> 本页是该阶段的**阶段汇总**:阶段表行、实测与落地形状的完整文本(2026-09-24 从上层索引移入,原文照录)都在这里;上层只留摘要——[`ROADMAP.md`](../../ROADMAP.md)、[`MEASUREMENTS.md`](../../MEASUREMENTS.md)、[`ARCHITECTURE.md`](../../ARCHITECTURE.md)。文中 `file:line` 与"今天""当前"按各段写成时的头理解;文中的 `MEASUREMENTS.md §N` / `ARCHITECTURE.md §17.x` 编号保留,对照表见 [`../README.md`](../README.md)。
## 摘要
- 终局改判(2026-09-22):client 与 server 可在不同机器、不同 OS / 架构上经 TCP 连接;同机 `spawn` 保留。传输栈拆成两根独立可选、握手协商、可混搭的轴——控制面 `ITransport`(`fork` / `unix:<path>` / `tcp://host:port`)× 数据面 `ILink`(`ShmLink` / `StreamLink`)。设计摘要见 [`ARCHITECTURE.md`](../../ARCHITECTURE.md) §11.9,契约 `MobileGL/MG_Remote/CONTRACT-P65.md`。
- 第一波(`feat/disaggregated@fe28bdb5`,2026-09-22):全 TCP 控制 + StreamLink 数据,WSL client ↔ Redmi server。包 lk2 → wf(wire 定宽 + `wireFingerprint` + `Refuse`)→ ct(tcp 端点 + keepalive + 令牌)→ sl(`StreamLink`)→ lf(日志前送)→ sv(supervisor + 前台 Service)→ t65。
- 验收:主机 unit 2399、integration-tcp loopback 102/102、**Redmi device-102 102/102 全 run-ahead ARMED**、代表性 retrace OpenRA 1.0;G1 0/0/0/0;真实远端 kill 133 ms、真实 Wi-Fi 中断 5053 ms 闩住 device loss。四切片代码审查无硬缺陷 + 两修复。
- **残余(CI / 后续)**:完整 39 例 split 子集 device golden 矩阵与链路必测数(逐帧 `kWaitReply` / RTT、`SEG_STAGE` 字节/帧、`PRESENT_CREDIT` 1/2/3、loopback tcp-vs-spawn 逐线程 CPU)。第二波(同机 TCP 控制 + 共享段数据、nd `auto` / sd)未开工。
- 证据总索引 [`evidence-index.md`](evidence-index.md)。
## 阶段表行(原 `ROADMAP.md`)
- **阶段**:**P6.5** 传输栈两轴化 + 跨机
- **状态**:**第一波已落地**(`feat/disaggregated@fe28bdb5`,2026-09-22):全 TCP 控制 + StreamLink 数据,WSL client ↔ Redmi server。四切片代码审查无硬缺陷 + 两修复(`AcceptPair` 重试 `EINTR`/`ECONNABORTED`、supervisor 自设 `MOBILEGL_IPC_ROLE=server`,均 `MG_Remote` 门内 G1 不动,见 [`code-review-findings.md`](code-review-findings.md));固定制品(两端 stamp `p65-acceptfix-20260922`)再验收:unit 2399、integration-tcp loopback 102、**device-102 102 全 ARMED**、代表性 retrace OpenRA 1.0 全 ARMED。**残余(CI/后续)**:完整 39 例 device golden 矩阵 + 必测数。设计见本表下方「立即的下一项」节
- **落地什么 / 范围**:包:**wf** wire 定宽与布局摘要——`wireFingerprint` = `PipeFields.def` 派生的逐成员 name / `offsetof` / `sizeof` 摘要 + 有序目录摘要 + 记录头布局 + 两个 blob codec 版本 + `kOpCount` + 字节序 / 指针宽度,**保留** `sizeof(MGPCaps)` / `sizeof(DynamicBackendParameters)`(wire 事实)、删 `sizeof(GLFunctionsTable)`;`buildFingerprint` 只在 `Dial == Fork` 下比较;`DynamicBackendParameters` / `MGPCaps` / `RenderStateParameters` 整块 blob 的定宽化(两端是不同编译器产的不同二进制,`long` / padding / 位域差异都是静默读错状态);`Refuse{code, detail, 对端值}` 控制消息,握手路径零 abort → **ct** 控制面端点种类(`fork` 继承 fd / `unix:<path>` / `tcp://host:port`;`DialMode{No, Fork, Connect}` 与 `MGPipeShouldDial()` 按契约 §3 补齐;TCP keepalive 失败**作为传输层报告的挂断**进 dl 的闩,不是 apply 超时)→ **nd** 数据面协商(`LinkTerms{dataPlane, delivery, wireForm, maxReplyBytes, cmdWindowBytes, stageWindowBytes}` 入 Hello / Welcome,四个尺寸由 **server** 陈述;`dataPlane ∈ {SharedSegments, Stream}`;`auto` 的判据是**段实际交付成功**而不是地址判断,回落必须具名入日志与 stats;链路工厂在会话建立时选定一对,之后不变——缝是 setup-time 的,契约 §8.2)→ **sd** 共享段交付与控制面解耦(`ShmLink` 自带 AF_UNIX aux 汇合名、在 `Welcome` 里公布,`SCM_RIGHTS` 只走它;于是**控制面 TCP + 数据面共享段**在同机是合法配对;`ITransport` 收窄为纯控制帧,描述符传递归数据面)→ **sl** `StreamLink`(分块封帧;watermark 变消息且**规则逐字保留**——late-never-early、`>=` 不是 `==`、倒退是 Fatal;`appliedSeq` 修正案 + flush-on-idle;reply 变自带 seq 的消息,本链路上删 slot 池 / 取模 / stamp 自检;`SEG_STAGE` 变发送窗口,三个 cursor 要么完整复活要么不用、不留中间态;program archive 与 `draw_vbo` range 尾分片;`GetCaps` 两个 blobref 变消息;每方向每进程一条专用读线程)→ **t65** 车道。上层纯度:`SessionProducer` / `SessionConsumer` / `ServerLoop` / `PipeWireEncoder`、`ClientSession.cpp` 的 `kSegEvent` 挂载与 `ResourceTracker.h` 的 writeback 解析都只经 `ILink`。设计 `ARCHITECTURE.md` §11.9
- **验收门 / 证据**:门:inproc / spawn 车道结果数与逐线程 CPU 逐字不变(`ILink::ReserveRecord` 虚调用代价**实测**,`RingProducer::Reserve` 失去内联是具体风险);纯度 grep——`ShmLink` / `StreamLink` / `RingControl` / `ReplySlotPool` / `EventRingConsumer` / `m_shm.` 只出现在 `Transport/` 与其测试,上层零链路种类分支;**2×2 矩阵车道**(控制面 {unix, tcp} × 数据面 {shm, stream},loopback)名集合与 spawn 一致且同数绿,每条目记 `control=… data=…` 的 arm 证明(ID-124 的形状);跨机臂一条真 trace 逐像素;wf 三条 red-once(临时树里同宽成员互换 → 摘要变而 stamp 不变;`wireMajor = 99` → 两端具名拒绝、server exit 0、client 不回落 monolith;仅 stamp 不同的对端在 `Dial != Fork` 下连接成功——正控制);**必测数**:逐帧 `kWaitReply` 次数(`ResourceCreate` / `SetTextureParams` / 纹理半边 `ResourceSubData` 都在此列,TCP 上每次即一个 RTT)、`SEG_STAGE` 字节/帧对链路带宽、`PRESENT_CREDIT` 1..3 配对、混搭(tcp + shm)对(unix + shm)的逐线程 CPU 差
## 终局改判(原 `ROADMAP.md` 页首注记)
> **终局(2026-09-22 改判)**:client 与 server 可以在**不同机器、不同 OS / 架构**上,经 **TCP** 连接;同机的 `spawn`(AF_UNIX + 共享段)保留为本机形态。传输栈拆成**两根独立可选的轴**——控制面(`ITransport`:`fork` 继承 fd / `unix:<path>` / `tcp://host:port`)与数据面(`ILink`:共享段 `ShmLink` / 字节流 `StreamLink`)——由握手协商、可以混搭(同机可以控制面走 TCP、数据面走共享段以降开销),上层只见 `ITransport` + `ILink`,**不得按链路种类分支**。此前 [`P6-ENDSTATE-REVIEW.md`](../p6/P6-ENDSTATE-REVIEW.md) 假设的 AVF pVM / `AF_VSOCK` 终局撤回(该文顶部有更正)。这一改判把 **P6.5 从条件项变成 IPC 跑道的关键路径**,把 wire 定宽 + 布局摘要从 P7 / hs 的"以后"变成 P6.5 的第一个包,把 Ph(含配对 / 认证)变成 P12 非 loopback 监听的前提;设计见 `ARCHITECTURE.md` §11.9。
## 第一波验收快照(原 `CURRENT_STAGE_PROGRESS.md`,2026-09-22)
实现、代码审查与主要正确性门已完成;**已落地并推送 `feat/disaggregated@fe28bdb5`(2026-09-22)**(原
在 `codex/p65-all-tcp` 的 WIP + 本次审查修复 + 收尾文档一并合入)。完整 39 例 device golden 矩阵与全套
链路必测数按用户「本地代表性、CI 兜底」留 CI/后续,**尚未宣布阶段收官**。
- TCP 控制、ShmLink/StreamLink 数据面、布局指纹、portable program archive v2、令牌/Refuse、
设备 supervisor/前台服务、日志前送与有序关闭均已接线。
- 初始 caps 到达前误锁为 lockstep 的启动竞态已修复。READY7 主机 TCP **102/102**、
Redmi TCP **102/102**,零 skip;两臂逐例确认 **run-ahead ARMED**,无 fallback/demotion。
保留车道 spawn **102/102**、inproc **186/186**,零 skip。
- **代码审查(四并行切片:数据面 StreamLink / wire 定宽 / 控制面握手)无硬正确性缺陷**;两处修复已落地——
`SocketTransport::AcceptPair` 重试 `EINTR`/`ECONNABORTED`(Medium,防常驻 server 因 accept 瞬态 `return 73` 整体退出)、
`mobilegl_server_main` 自设 `MOBILEGL_IPC_ROLE=server`(Low,不再盲信 launcher);两处均在 `MG_Remote/`
(`MOBILEGL_BUILD_DISAGGREGATED` 门内),G1 monolith `.text` 构造性不受影响。其余为跟进项(`MobileGLServerService.onDestroy`
子进程泄漏→P12、`gen_pipe.py` 裸 `char` 符号性普查无实活风险、测试缺口等),见
[`code-review-findings.md`](code-review-findings.md)。
- **修后固定制品再验收**(WSL client 与设备 APK 同 build stamp `p65-acceptfix-20260922`,`REQUIRE_SAME_BUILD=1` 真实通过):
主机 full unit **2399/2399**、P6.5 子集 **147/147**、integration-tcp loopback **102/102**、
**device-102 102/102,全 102 条 client 日志 `run-ahead ARMED`**、代表性 device retrace
OpenRA SSIM **1.0** / startup **0.999999511** / in-world **0.999995438**,三条全 **ARMED**。
- G1 符号 **0/0/0/0**,`.text` 逐字一致;真实远端 kill **133 ms**、真实 Wi-Fi 中断 **5053 ms** 闩住 device loss。
- 原有单角色日志读取的 CI 缺陷已与上游修复合并;模拟器掉线分类另有回归验证。
- **残余(CI/后续)**:正式 retrace 的完整 **39 项** split 子集 device 矩阵(DirectGLES 现有阈值、OpenRA 1.0;
`ci:false` 的 rd12 单列)与链路必测数(逐帧 `kWaitReply`/RTT、`SEG_STAGE` 字节/帧、`PRESENT_CREDIT` 1/2/3、
loopback tcp-vs-spawn 逐线程 CPU)。`minecraft-1.21.11-main-menu` 的 Redmi 32B UBO 对齐 golden 伪差为
ci-false(设备实图 SSIM 0.998729),不作本波红。跑器 `tools/trace_replay/run_tcp_matrix.py`(支持进度超时、
进程组清理、实际 run-ahead 证明与可核验 checkpoint)已入库。
契约见 [`CONTRACT-P65.md`](../../../../MobileGL/MG_Remote/CONTRACT-P65.md),
制品身份与证据总索引见 [`evidence-index.md`](evidence-index.md);
具体结果与未决项见 [`validation-status.md`](validation-status.md)、
[`wire-and-validation.md`](wire-and-validation.md)、
[`performance.md`](performance.md)。以下保留 P6 收官时的原始进度快照。
## 第一波设计与操作手册(原 `ROADMAP.md`「立即的下一项」节)
> 状态:**已实现并落地推送 `feat/disaggregated@fe28bdb5`(2026-09-22),代码审查完成(无硬缺陷 + 两修复),固定制品代表性再验收通过。** 主机 unit 2399、integration-tcp loopback 102、Redmi **device-102 102 全 ARMED**、代表性 retrace OpenRA 1.0 全 ARMED;G1 与真实断线门通过。完整 39 例 device golden 矩阵与余下链路必测数按「本地代表性、CI 兜底」留 CI/后续,**尚未宣布阶段收官**;逐项见 [`CURRENT_STAGE_PROGRESS.md`](../../CURRENT_STAGE_PROGRESS.md) 与 [`notes/p65/`](.)。目标形态:x86_64 的 WSL 进程作为 client,Redmi `2f7cbe2e`(aarch64,Adreno)上的 server 进程作为 server,两者在同一局域网内经 **TCP** 连接;`integration` 全部用例与 CI retrace 的 split 子集在这条链路上跑通并对上 golden。本节是 P6.5 行的第一波,只做「控制面 TCP + 数据面 stream」这一对;同机的「TCP 控制 + 共享段数据」混搭(nd `auto` / sd)是第二波,本节不做。
### 1 设计时的树上基线(实现前快照)
以下表格保留本波开工前的缺口定位;当前实现与契约更正见 `MG_Remote/CONTRACT-P65.md` 和阶段进度,不以历史行号描述今天的树。
| 已有 | 出处 | 对本波的意义 |
|---|---|---|
| 控制面 `SocketTransport`:`Listen` / `AcceptPair` / `ConnectTo`,**汇合点是一个名字**,两条连接按序接受(第一条控制、第二条 aux);`auxFd = -1` 已是「这条链不传 fd」的诚实答案 | `SocketTransport.h:70-91` | TCP 只是第三种名字(`tcp://host:port`);两条连接的规矩原样用:第一条控制,**第二条从 aux 变成数据流** |
| 帧:`[u32 'MGLF'][u32 len][payload]`,`FrameReader` 是真正的字节流重组器 | `Framing.h` | 数据连接复用同一重组器,只换 magic |
| server 主程序:endpoint 来自 `argv[1]` / `MOBILEGL_IPC_ENDPOINT`;`Listen` → `AcceptPair` → 建 backend → `Accept`(Welcome + 七个描述符)→ 控制泵,**对端 EOF 即退出**;`MOBILEGL_IPC_DIAL=no` 反递归 | `ServerMain.cpp`(门 8 的收尾修改已落入该文件,本表不引行号) | 单会话进程形状保留为 **child**;设备侧要加一个 **supervisor** 循环 |
| client:`StartOverSocket(transport)` 已把「socket 怎么来的」与握手分开;spawn 路径 = 造 `@mgl-<pid>-<this>` 名 → `LaunchServer` → `ConnectTo` | `ClientSession.cpp:693-724` | `Dial = Connect` 只是**跳过 `LaunchServer`**、把名字换成 `tcp://` |
| 握手后 client 收 **7 个 fd**(4 段 + 3 个门铃)再 `AdoptFromDescriptors` | `ClientSession.cpp:832-897` | stream 上这一段整体不存在:段是本地私有的,门铃是本地条件变量 |
| **数据面没有缝**:`ILink` 只有声明,树里用到它的只有 `LinkProgress`(`Ring.h:97`);`ShmLink` 不存在;两端 session 直接持有 `SessionSegments`(`SessionRings.h:130`);`ServerSession.h:93` 自己写着「`lk` 把门铃挪到 `ILink` 后面并删掉这些访问器」——**没做** | `Ring.h:108`(`RingControl` 重组已做)、`ILink.h`、`StreamLink.h` | 重审 §5.1 那「约一周」的 lk 主体仍然欠着,是本波**第一个包**,没有它 `StreamLink` 无处可插 |
| wire 寻址:`MGPBlobRef{Offset, Size, Seg}` 只允许 `kSegStage`,解析集中在 `ResolveOrFatal` | `PipeWireCodec.cpp:1421`(拒绝其它段:`:496`、`:1401`) | **两端各建一个同尺寸的本地 stage 窗口、偏移一致,codec 一行不改** |
| reply:`ReplySlotHeader{Seq, Status, Size}` 16 字节、`seq % slotCount`(8 槽) | `ReplySlot.h` | 头已经是消息形状;stream 上删掉取模与 stamp 自检 |
| event:四种 kind、定宽头 | `EventRing.h` | 已是 wire 形状,原样装帧 |
| 指纹:`CapsAbiFingerprint()` = 三个 `sizeof` + 协议版本 + git stamp | `protocol.fbs` `table Hello` 的注释 | x86_64 ↔ aarch64 两份二进制:**必须换成布局摘要**,否则要么永远拒绝(stamp 不同)要么静默读错 |
| 日志按角色分文件;server 角色由启动方设的 `MOBILEGL_IPC_ROLE=server` 决定;`LogLine` 已在 `CtrlMsg` 里、**零生产者** | `ServerSpawn.cpp:116`、`ClientSession.cpp:1948` | 34 个读 `<base>.server.log` 的消费方在跨机时读不到 → 日志前送(§3.5) |
| 车道:`mgl_itest_register_split_arms` 一个宏两条臂 {Split, Spawn},环境差只有 `MOBILEGL_TRANSPORT=spawn` + `MOBILEGL_IPC_SERVER_PATH` | `MG_IntegrationTest/CMakeLists.txt:1985-2010` | 加第三条臂 `Tcp`;名集合门(`scripts/ci/spawn_lane_parity.py`)与 arm 证明(`scripts/ci/junit_tally.py:27`)各加一臂 |
| retrace:桌面经 `MOBILEGL_TRANSPORT` / `MOBILEGL_IPC_SERVER_PATH` 环境(`run_trace_case.cmake:47-52`);设备经 `--env K=V;…` intent extra,server 路径从 `nativeLibraryDir` 解析(`TraceReplayActivity.java:374-386`);**桌面与设备共用同一套 golden**(`trace_cases.json`),驱动差异由 SSIM 阈值吸收 | — | 跨机 retrace 的 golden 就是现有的,不另造 |
| 设备已能从 app 进程 exec `libMobileGLServer.so`(spike A、`spawn-acceptance` 臂) | `apk.yml:477-487` | supervisor 就是这个可执行文件加一个 `--serve` |
| manifest **没有 `INTERNET` 权限** | `android-plugin/app/src/main/AndroidManifest.xml` | server 要监听 TCP,trace flavour 必须加 |
### 2 拓扑:谁监听、谁拨号、为什么
server 在手机上**监听**,client 从 WSL **拨出**。理由不是偏好:WSL2 默认 NAT,出站到局域网直通、入站要 portproxy;手机侧无防火墙。`Role × Dial` 两轴(契约 §3)因此在本波是 `Role=Client, Dial=Connect` / `Role=Server, Dial=No`。备用形态:`adb forward tcp:40613 tcp:40613` 把 server 绑在手机 loopback、client 连 WSL 内 adb 的 loopback——**要求 adb 跑在 WSL 里**(adb-over-Wi-Fi:`adb connect <phone>:5555`),因为 Windows 侧 adb 绑的 127.0.0.1 WSL2 够不着;路由器开了 AP 隔离时用它。
### 3 机制
**3.1 端点与握手(包 ct)**。`SocketTransport::Listen/ConnectTo` 识别 `tcp://host:port`(其余仍是 AF_UNIX 路径);两条 TCP 连接按序:控制、数据。socket 选项:`TCP_NODELAY`(批处理由我们的 publish 点决定,不交给 Nagle)、`SO_KEEPALIVE` + `TCP_KEEPIDLE=2s / KEEPINTVL=1s / KEEPCNT=3` + `TCP_USER_TIMEOUT=5000ms`——Wi-Fi 掉线不产生 FIN,这是唯一能把「半开」变成挂断的机制;它以 `POLLHUP`/错误进入 `Doorbell::Dead()`,走 dl 的闩,**是传输层报告的挂断,不是 apply 超时**(开放问题 23)。`Hello` 追加 `linkTerms{dataPlane=Stream, wireForm, maxReplyBytes, cmdWindowBytes, stageWindowBytes}` 与 `token`;`Welcome` 回填 server 陈述的四个尺寸;新增 `Refuse{code, detail, 对端值}`(`CtrlMsg` 只追加)。`buildFingerprint`(git stamp)比较策略:`Dial=Fork` 不匹配 → Refuse(抓陈旧 `MOBILEGL_IPC_SERVER_PATH`,不变);`Dial=Connect` 不匹配 → **警告**,`MOBILEGL_IPC_REQUIRE_SAME_BUILD=1` 时 → Refuse(车道设它:APK 与 WSL 构建必须同一 commit)。令牌:`MOBILEGL_IPC_TOKEN` 两端一致;server 无令牌时**只允许绑 loopback**,错令牌 → Refuse 并记日志。
**3.2 wire 定宽与布局摘要(包 wf)**。`wireFingerprint` = `gen_pipe.py` 从 `PipeFields.def` 生成的逐成员 `{name, offsetof, sizeof}` 表的摘要 + 有序目录摘要(opcode / payload 名 / flags / WaitClass)+ 记录头布局 + `MGPipeRenderStateSpans` 的 offsetof 表 + 两个 blob codec 版本 + `kOpCount` + `MOBILEGL_ABI_VERSION` + 字节序 + 指针宽度 + `sizeof(MGPCaps)` / `sizeof(DynamicBackendParameters)`;**删** `sizeof(GLFunctionsTable)`。x86_64 ↔ aarch64 都是 LP64、小端、Itanium ABI,布局大概率一致,但**由摘要断言、不由这句话断言**;已知的真实差异是 **`char` 的符号性**(aarch64 默认无符号)——wf 普查 payload 与 blob 里裸 `char` 当数用的站点。`DynamicBackendParameters` 定宽重写(从 P7 挪来)与 `RenderStateParameters` 1168 字节整块的成员表在此包。三条 red-once 见 P6.5 行。
**3.3 缝的主体(包 lk2,零行为变化)**。`SessionSegments` + `RingProducer/Consumer` + `ReplySlotPool` + `EventRingProducer/Consumer` + 两个门铃搬到 `ShmLink : ILink` 后面;`SessionProducer` / `SessionConsumer` / `ServerLoop` / `PipeWireEncoder` / `ClientSession.cpp` 的 `kSegEvent` 挂载 / `ResourceTracker.h` 的 writeback 解析都改收 `ILink*`;`ServerSession.h:93` 说要删的访问器删掉。门:inproc / spawn 车道结果数与逐线程 CPU **逐字不变**(`ILink::ReserveRecord` 的虚调用在 ~850–3550 次/帧上实测;若不在噪声里,答案是把 reserve/commit 做成 link 自有 arena 上的非虚函数);纯度 grep 门上线。
**3.4 `StreamLink`(包 sl)——被消息复制的镜像段**。设计原则一句话:**两端各自持有一份同尺寸的私有段,链路只负责把字节搬到对面同一偏移;codec、watermark 规则、apply 线程、编码器全部不变。**
- 段:握手协商后两端各 `malloc` 四块:cmd 环(`cmdWindowBytes`)、stage 窗口(`stageWindowBytes`)、event 环、reply 邮箱。尺寸相同 ⇒ 偏移相同 ⇒ `MGPBlobRef` 与 `ResolveOrFatal` 原样工作。
- 数据连接帧:`[u32 'MGLD'][u32 len][u8 kind][u8×3][u64 a][u64 b][payload]`。C→S:`Cmd{ringOffset, bytes}`(记录字节逐字、写到对面环的同一偏移)、`Stage{offset, bytes}`、`ClientProgress{cmdHead, submittedSeq, eventTail}`。S→C:`Progress{appliedSeq, retiredSeq, completedFrameSerial, presentAckSerial, cmdAppliedTail, eventRingFull}`、`Reply{seq, status, bytes}`(头即 `ReplySlotHeader`)、`Event{ringOffset, bytes}`。
- client 侧:编码器照旧写本地 cmd 环与 stage 窗口;`Flush()` 把 `[上次已送, cmdHead)` 作为一帧 `Cmd` 发出(环回绕用两段 iov 一次 `sendmsg`),**之前先发本次新 stage 的 `Stage` 帧**——顺序由编码器「先 stage 后 reserve」保证;`LinkCapabilities.PublishIsDelivery=false`,所以每个等待点先 `Flush`(这个位就是为此设的)。C→S 背压:`WaitForCmdSpace` 不变(读 `Progress` 里的 `cmdAppliedTail`),`send()` 阻塞是第二层,没有无界缓冲。
- server 侧:`mgl-srv-io` 读线程把 `Cmd`/`Stage` 拷到镜像段同偏移,再推进本地 `cmdHead`/`submittedSeq` 并敲 apply 的本地门铃;**apply 线程一行不改**。
- `Progress` S→C 的发出时机(对 `Ring.h` 「`appliedSeq` 不得懒发布」的**修正案**,规则改成「最多晚一次 flush,绝不早」):(a) apply 线程**即将 park 前**(flush-on-idle,解决「帧内最后一条 `kWaitReply` 记录两端互等」);(b) 每次 `PostReply` 之后;(c) present ack / `completedFrameSerial` 变化时;(d) 每 64 条已 apply 记录或 1 ms 取先到者。接收侧对每个 watermark 做 `AdvanceMonotonic`,**倒退是 `Fatal{ProtocolCorruption}`**。
- reply:`kWaitReply` 下同一时刻最多一条未决,client 侧是「单槽 + 条件变量 + seq 核对」;本链路上 slot 池、取模、stamp 自检**删除**(`ShmLink` 保留)。
- event:server 的 `EventRingProducer` 写本地环,io 线程把新增字节装 `Event` 帧;client 读线程写进 client 本地环同偏移,`EventRingConsumer` 不变;`Drained()` 发 `ClientProgress{eventTail}`;server producer 在 `head − 对端 tail > capacity` 时**阻塞**(P5e 的规则原样,`eventRingFull` 经 `Progress` 回传)。
- 门铃:`ConsumerBell()` / `ProducerBell()` 是本地 `CondVarDoorbell`,由各自的读线程敲;读线程见 EOF / 错误置 `Dead()`。每进程数据连接**一条**专用读线程(写在调用线程上),控制连接的读法不变。
- 帧上限:`Cmd`/`Stage` 单帧 ≤ `kMaxFramePayloadSize`(64 MiB);一条记录本来 ≤ 4 MiB。未分片的 program archive / `draw_vbo` range 尾在 stream 上和今天一样受 `stageWindowBytes` 约束,超出仍是 `Fatal{RingOverrun}`——**不是本波阻塞项**,记在开放问题 11。
- 不做的:`kSegShadow` / `kSegAdopt`(P8 / P11 的段)在 stream 上具名拒绝;`GetCaps` 的 blobref 若仍走 stage 之外 → 具名拒绝,本波不发明第三条载体。
**3.5 日志前送(包 lf)**。server 进程的日志 sink 在 `MOBILEGL_IPC_ROLE=server` 且有控制传输且 `MOBILEGL_IPC_LOG_FORWARD=1`(`Dial=Connect` 默认开)时,每行**同时**写本地 `<base>.server.log` 并作为一帧 `LogLine`(已在 `CtrlMsg` 里)经控制连接发出;client 收到后写入**自己的** `<base>.server.log`。于是 34 个消费方(`PipeStatsWindow` 的计数器窗口、四个 arming 场景、拒绝普查)**一字不改**。顺序:读者在 `MarkLaneLog` / `ReadLaneLogSince` 之前调一个导出的 `MGPipeSyncPeerLog()`(一次控制往返:`LogFlush` → server 冲刷并 ack),远端不存在时它是 no-op;导出符号一个,`-fvisibility=hidden` 下显式 `default`。若 lf 滑期,退路是这 34 处在 tcp 臂上**按名 SKIP**(清单文件,像 `dualblock-expected-fatals.txt`),不许静默通过。stats 行一帧一条,前送代价可忽略。
**3.6 设备侧 supervisor 与启动(包 sv)**。`mobilegl_server_main tcp://0.0.0.0:40613 --serve [--max-sessions 1]`:listen → 循环 { `AcceptPair`(控制 + 数据)→ `fork()` → child 关掉 listen fd、用 `SocketTransport(streamFd, dataFd)` 走今天的单会话路径到 `_exit`;parent 关掉已接受的 fd、非阻塞 `waitpid`、继续 accept }。每会话一进程 ⇒ `eglTerminate` 的进程级拆机被隔离(D9 原样);同时只服务 1 个会话,第二个连接 → `Refuse{Busy}`。启动:trace flavour 新增前台 `MobileGLServerService`(`android:process=":mglsrv"`),`am start-foreground-service` 带 `--es listen`、`--es token`、`--es env "K=V;…"`(复用 `--env` 的 `K=V;` 语法),它从 `nativeLibraryDir` exec `libMobileGLServer.so`(spike A 的路径)、给 child 设 `MOBILEGL_IPC_ROLE=server`、持 partial wake lock;`am force-stop` 整包停掉。后端由 client 的 `Hello.backendType` 请求、supervisor 的 env 可钉死,不一致 → Refuse。EGL 在 child 里是 pbuffer / surfaceless(P6 边界;真窗口归 P12)。manifest 加 `INTERNET`。
**3.7 谓词与测试的拓扑感知**。client 进程:`Transport=Spawn`(拓扑:两进程)、`Dial=Connect`、不 fork;`MGPipeBackendIsLocal()=false`。`CountOwnChildren()` 一类断言改为按 Dial 分支:Connect 下断言**本地零子进程** + `Welcome::serverPid` 非零 + 日志 `control=tcp`。S2(kill server)在跨机臂上经 `MGITEST_PEER_KILL_CMD` 模板(`adb -s <serial> shell run-as top.mobilegl.plugin.trace kill -9 {pid}`)真杀——**不**允许用协作式「请你退出」代替,那测的不是挂断。
### 4 包与顺序
`lk2`(缝主体,零行为)→ `wf`(定宽 + 摘要 + Refuse)→ `ct`(tcp 端点 + keepalive + 令牌 + Connect 拨号)→ `sl`(`StreamLink`,先在 **WSL loopback** 上对着 spawn 车道打平)→ `lf`(日志前送)→ `sv`(supervisor + Service + adb 脚本)→ `t65`(tcp 臂、跨机臂、门、数)。每包 red-once(R-16)。第二波(不在本节):`nd` 的 `auto` 与 `sd` 的 aux 交付(同机 TCP 控制 + 共享段数据)、P11 的 stream 具名拒绝、Windows client。
### 5 出口门(本波)
1. G1 pull 0/0/0/0、`.text` 不变(全部新代码在 `MOBILEGL_BUILD_DISAGGREGATED` 下)。
2. lk2 后 inproc / spawn 车道结果数与逐线程 CPU 逐字不变(实测,不论证)。
3. **`integration-tcp`(WSL loopback,server 以 `--serve tcp://127.0.0.1:40613` 本地起)**:名集合 == spawn(parity 脚本加臂)、同数绿;每条目 arm 证明 `control=tcp data=stream server=<host:port> pid=N`(`junit_tally --require-tcp-ran`)。
4. **`integration-tcp-device`(server 在 Redmi)**:同一名集合;首轮**记录**、不设阻塞门,每一条与 loopback 臂的差异必须具名(GPU 能力 / P7 的 Magma 分歧 / 时限);第二轮起转硬门。
5. **retrace**:CI split 子集(`trace_cases.json` 里未 `"split": false` 的)从 WSL 对着手机跑,DirectGLES 的 SSIM ≥ 现有阈值、OpenRA 为 1.0(E2 的锚);DirectVulkan 记录不设门(真机分歧是 P7 的)。
6. red-once:wf 三条(P6.5 行);ct——错令牌 → Refuse、`wireMajor` 不匹配 → 两端具名拒绝、`adb shell svc wifi disable` → 10 s 内闩住(而不是 120 s `BarrierTimeout`);sl——`Progress` 倒退 → `Fatal{ProtocolCorruption}`、越窗的 `Stage` 帧 → 拒绝、去掉 flush-on-idle 后「帧内最后一条 `kWaitReply`」用例必须挂住并被超时抓到;lf——关掉前送后四个 arming 场景必须**按名红**;sv——第二个并发连接 → `Refuse{Busy}`。
7. **必测数(记录)**:逐帧 `kWaitReply` 次数与 RTT 分布(OpenRA、MC in-world 各一);`SEG_STAGE` 字节/帧对链路实测吞吐(车道自己量一次 64 MiB `Stage` 突发);`PRESENT_CREDIT` 1 / 2 / 3 的帧率;loopback tcp+stream 对 spawn(unix+shm)的逐线程 CPU 差。
### 6 两边怎么配(第一波操作手册)
**手机(server)**
```bash
# 1. 同一 commit 构建 trace APK(分离构建)并安装
./gradlew :app:assembleTraceDebug -Pmobilegl.buildDisaggregated=ON && adb install -r app/build/outputs/apk/trace/debug/*.apk
# 2. 手机 IP
adb shell ip -f inet addr show wlan0
# 3. 起 server(前台 Service → supervisor 监听)
adb shell am start-foreground-service -n top.mobilegl.plugin.trace/top.mobilegl.plugin.MobileGLServerService \
--es listen tcp://0.0.0.0:40613 --es token devtoken-0123456789abcdef \
--es env "MOBILEGL_BACKEND_TYPE=DirectGLES;MOBILEGL_PIPE_STATS=1;MOBILEGL_PIPE_STATS_PERIOD=1;MOBILEGL_LOG_FILE_PATH=/data/data/top.mobilegl.plugin.trace/files/mgl.log"
adb logcat -s MobileGL | grep -m1 'listening on tcp://'
# 停:adb shell am force-stop top.mobilegl.plugin.trace
```
**WSL(client)**,在分离构建目录(今天是 `~/lk-split`):
```bash
export MOBILEGL_TRANSPORT=spawn # 拓扑:两进程;不 fork,因为下面给了远端
export MOBILEGL_IPC_CONTROL=tcp://<phone-ip>:40613
export MOBILEGL_IPC_DATA=stream
export MOBILEGL_IPC_TOKEN=devtoken-0123456789abcdef # ≥16 字节(PH-7 (3)):更短的令牌让 server 以 72 退出而不是监听
export MOBILEGL_IPC_REQUIRE_SAME_BUILD=1 # APK 与本地构建必须同 commit
ctest -L integration-tcp-device -j 1 --output-on-failure # -j 1:设备同时只服务一个会话
# retrace(同一环境):
(cd build-retrace/tools/trace_replay && ctest -V -R '^MobileGLTraceReplay\.OpenRA\.DirectGLES$')
```
日志:client 侧 `<base>.client.log` 与前送来的 `<base>.server.log` 都在 WSL;设备本地副本用 `adb shell run-as top.mobilegl.plugin.trace cat files/mgl.server.log`。
**备用(AP 隔离 / 没有局域网直通)**:adb 跑在 WSL 里——Windows 上一次 `adb tcpip 5555`,WSL 里 `adb connect <phone-ip>:5555 && adb forward tcp:40613 tcp:40613`;手机侧 `--es listen tcp://127.0.0.1:40613`(loopback,不需令牌),WSL 侧 `MOBILEGL_IPC_CONTROL=tcp://127.0.0.1:40613`。
### 7 风险与未决(本波)
1. **`kWaitReply` × RTT**:`ResourceCreate` / `SetTextureParams` / 纹理 `ResourceSubData` 每次一个 RTT(Wi-Fi 2–5 ms);MC 加载区块的创建突发会可感知地慢。本波**只量**(门 7),减少这一列是 P9 / P10 的活。
2. **带宽**:`SEG_STAGE` 字节/帧对 Wi-Fi;单次 128 MiB 上传 = 秒级停顿(开放问题 19、20)。
3. **Wi-Fi AP 隔离**:用 §6 的 adb forward 备用。
4. **DirectVulkan 真机分歧**(P7)会在门 5 出现:记录,不当本波的红。
5. **`MOBILEGL_IPC_ROLE`**:supervisor 必须替 child 设它,否则 child 的日志会当 client 写(`Log.cpp` 从这个 env 解析角色)。
6. **ctest 并行 vs 一设备一会话**:`-j 1` 或 supervisor `--max-sessions N`(每会话一进程,N 个 EGL 上下文同时存在,Adreno 上未验证)。
7. **同 commit**:两端二进制不同是常态,指纹只保证 wire 布局;`REQUIRE_SAME_BUILD` 是车道纪律,不是协议要求。
## 里程碑记录(原 `ROADMAP.md`)
- **终局改判(2026-09-22)**:AVF pVM / `AF_VSOCK` 终局撤回,改为 TCP 跨机跨平台 + 控制面 × 数据面两轴传输栈;P6.5 上关键路径,Ph 前置于 P12 的非 loopback 监听;P6 核出三项漏项(server 进程 `PipeStats` 结构性零——收尾中已修、184 棘轮门、hs 的三行契约未落地)与 `CURRENT_STAGE_PROGRESS.md` 里一条错误的 S6 陈述(已改);class-C 已归零到两个(`SetSwapInterval`、`DeleteTransformFeedback`)。
## 本目录
| 文件 | 内容 |
|---|---|
| [`code-review-findings.md`](code-review-findings.md) | P6.5 第一波 代码审查(4 切片,2026-09-22) |
| [`data-link-implementation.md`](data-link-implementation.md) | P6.5 数据面实现记录 |
| [`evidence-index.md`](evidence-index.md) | P6.5 第一波证据索引 |
| [`mainmenu-trace-difference.md`](mainmenu-trace-difference.md) | P6.5:1.21.11 主菜单 GLES 像素差异诊断 |
| [`peer-loss-probe.md`](peer-loss-probe.md) | P6.5 真实 peer-loss 探针 |
| [`performance.md`](performance.md) | P6.5 主机逐线程 CPU 前后记录 |
| [`validation-status.md`](validation-status.md) | P6.5 TCP 车道与设备验收记录 |
| [`window-1b-p7w5.md`](window-1b-p7w5.md) | 窗口 1b · p7w5 DirectGLES × TCP 配速矩阵(中断报告) |
| [`window-1b-p7w6.md`](window-1b-p7w6.md) | 窗口 1b:p7w6 DirectGLES × TCP(2026-09-23) |
| [`wire-and-validation.md`](wire-and-validation.md) | P6.5 wire、计量与主机验证记录 |