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

32 KiB
Raw Blame History

P6.5 — 传输栈两轴化 + 跨机(第一波已落地,未宣布收官)

本页是该阶段的阶段汇总:阶段表行、实测与落地形状的完整文本(2026-09-24 从上层索引移入,原文照录)都在这里;上层只留摘要——ROADMAP.md、MEASUREMENTS.md、ARCHITECTURE.md。文中 file:line 与"今天""当前"按各段写成时的头理解;文中的 MEASUREMENTS.md §N / ARCHITECTURE.md §17.x 编号保留,对照表见 ../README.md。

摘要

  • 终局改判(2026-09-22):client 与 server 可在不同机器、不同 OS / 架构上经 TCP 连接;同机 spawn 保留。传输栈拆成两根独立可选、握手协商、可混搭的轴——控制面 ITransport(fork / unix:<path> / tcp://host:port)× 数据面 ILink(ShmLink / StreamLink)。设计摘要见 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。

阶段表行(原 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);固定制品(两端 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 假设的 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。
  • 修后固定制品再验收(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, 制品身份与证据总索引见 evidence-index.md; 具体结果与未决项见 validation-status.md、 wire-and-validation.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 与 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)

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

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 P6.5 第一波 代码审查(4 切片,2026-09-22)
data-link-implementation.md P6.5 数据面实现记录
evidence-index.md P6.5 第一波证据索引
mainmenu-trace-difference.md P6.5:1.21.11 主菜单 GLES 像素差异诊断
peer-loss-probe.md P6.5 真实 peer-loss 探针
performance.md P6.5 主机逐线程 CPU 前后记录
validation-status.md P6.5 TCP 车道与设备验收记录
window-1b-p7w5.md 窗口 1b · p7w5 DirectGLES × TCP 配速矩阵(中断报告)
window-1b-p7w6.md 窗口 1b:p7w6 DirectGLES × TCP(2026-09-23)
wire-and-validation.md P6.5 wire、计量与主机验证记录