mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-09 19:13:23 +09:00
355 lines
49 KiB
Markdown
355 lines
49 KiB
Markdown
# P11:persistent map 采纳档位诚实化(A 包主机半)
|
||
|
||
2026-09-29。依据 [`PLAN-P11.md`](../notes/p11/PLAN-P11.md) §2 的 A1、A2 与 C 的文档修正。档位名按 `design/07`:T0 = client 分配 `AHardwareBuffer`、server 导入;T1 = server 导出 opaque fd;T2 = 拒绝,client 保留 shadow 并推送。本文只记实现所需的行为与门;A3 / A4(设备)与 B(T0)不在本文。
|
||
|
||
## 范围
|
||
|
||
档位在握手时定下(§1);断言 arena 落在哪一档、三臂登记(§2);文档漂移(§3)。
|
||
|
||
## 1. 档位由知道数据面的一侧在握手时定下(A1)
|
||
|
||
### 线上格式
|
||
|
||
- 无变化。LinkTerms、`RefuseCode`、控制协议修订号(3)都不动:每一侧只判自己的旋钮,不需要把档位带过线。
|
||
|
||
### 规则(`Transport/AdoptTier.cpp` `SettleAdoptTierAtHandshake`)
|
||
|
||
| 数据面 | 侧 | `MOBILEGL_IPC_ADOPT_TIER=0/1` |
|
||
|---|---|---|
|
||
| Stream(tcp) | client / server | 一行 W `Refuse{AdoptTierOnStream, "T<n>"}`,本会话 T2;从不 Fatal |
|
||
| SharedSegments(inproc、fork / unix spawn) | client | `Fatal{UnimplementedAdoptTier, "T<n>"}`,握手期、任何记录之前 |
|
||
| SharedSegments | server | 一行 W(档位由 client 定),T2 |
|
||
|
||
- **B2 起,SharedSegments 两行被 B2 的档位表取代**(0 = 问 T0、1 = 具名拒绝后 T2,都不再 Fatal),见 B2 节。
|
||
|
||
- 调用点:client `StartOverTransportPair`(Welcome 校验后)与 `StartOverSocket`(link terms 定下 `stream` 后、开数据连接前);server `ServerSession::Accept`(后端检查后、Welcome 前)。
|
||
- 使用处 `Client::AdoptTierIsEmulate`(`PipeApply.cpp`、`PipeWireCodec.cpp` 仍问它):旋钮 2 → true;本进程已有握手定档 → true,不再打印;没有握手定过档的调用者 → 仍 `Fatal{UnimplementedAdoptTier}`("no handshake settled")。旋钮 > 2(解析器不收)→ Fatal。
|
||
- `ServerSpawn` 从它拉起的子进程环境剔除 `MOBILEGL_IPC_*`(五个 server 自有旋钮除外),所以 fork / unix spawn 的 server 看不到 client 的旋钮;tcp 的 server 读自己的环境。
|
||
|
||
### 普查
|
||
|
||
新的死亡走 `SessionFail`:abort 站点仍 79,family 词仍 45(复用 `UnimplementedAdoptTier`);refusal 词 3 → 4,`AdoptTierOnStream` 进 `fatal_census.py` 的 `LOCAL_REFUSAL_WORDS`(本地拒绝,不成为 Refuse 帧)。
|
||
|
||
### 门
|
||
|
||
- 单元:`AdoptTierStream.*`(真 tcp 会话:client 0/1 → 会话起、一行拒绝、`map_persistent` 得 DECLINED、之后仍 apply;进程内显示 server 0 → 每会话一行、不死);`RemoteClientControls.AdoptTier{Zero,One}OverSharedSegmentsDiesAtTheHandshakeBeforeAnyMapPersistent`(inproc 真握手,子进程在 `Start` 返回前死、具名);`SplitBufferSet.OnlyAdoptTierTwoIsImplemented`(函数级矩阵);`PipeWireCodecTest.AdoptTier{Zero,One}AfterAStreamHandshakeIsADeclineOnTheWirePath` 与 `AnUnsettledAdoptTierStillDiesByNameOnTheWirePath`。
|
||
- 集成:`{DirectGLES,DirectVulkan}.Tcp.AdoptTier0.LargeArenaAdoptionScenario.*`(`MOBILEGL_IPC_ADOPT_TIER=0`;`integration-tcp` / `integration-magma-tcp`),`AStreamSessionRefusesTheAdoptTierOnceByName` 读 client 日志恰一行,其余用例绿。tcp 专属:共享段上同一旋钮在握手期死(`spawn_lane_parity.py` 的 `MAGMA_TCP_ONLY`)。
|
||
- red-once:删 Stream 分支 → 两后端 tcp 条目 client 握手期 `Fatal{UnimplementedAdoptTier}`、无拒绝行,单元 `AdoptTierStream` 两例、codec 两例、`SplitBufferSet` 一例红;把检查挪回使用处 → inproc 会话起来、`map_persistent` 发出后才在 apply 线程死(子进程写出 `S`),spawn 臂则静默跑 T2(server 环境被剔除)。
|
||
|
||
## 2. arena 落在哪一档(A2)
|
||
|
||
- `LargeArenaAdoptionScenario.TheArenaLandsInTheTierItsLaneDeclares`:`PersistentMapPeek` 读 arena 的 `IsBackendPersistentMapped`,与车道的 `MGITEST_PERSISTENT_MAP_ARM` 比;问两次(NULL 定义后;一帧绘制 + SSBO 绑定 + dispatch 后)。未声明的车道跳过。
|
||
- 声明:monolith = adopted(`DirectGLES.ResourceSubsystemOn/Off.`、新 `DirectVulkan.ArenaArm.`,都钉 monolith);split = emulated(Espryt 三臂经 `SplitLogPaths` 按条目声明;Magma `Arena.` 块与 Buffers 车道)。
|
||
- 登记:`mgl_itest_register_split_arms("LargeArenaAdoptionScenario.*")`(Espryt 进门、Magma 进信息层);Magma 进门:`DirectVulkan.<arm>.Arena.`(band 口味,六例)与 Buffers 车道(加六例,`test.yml` 的 require-green 名单同步)。
|
||
- red-once:monolith 车道加 `MOBILEGL_DISABLE_LARGE_BUFFER_ADOPTION=1` → 三条红;split 车道改声明 adopted → 两后端 8 条红。删 `PipeApply.cpp` 的 R-6 拒绝**不会**让 Espryt inproc 采纳(实测仍 emulated):split 下 client 的采纳门经线路走,codec 不调 applier 就答 DECLINED;applier 只在 server 角色自己发射时被问到(只有 Magma 的 `EnsureGpuResidentStorage`),而 Magma 的线路表 `MapPersistent` 恒返回空。
|
||
|
||
## 3. 文档漂移(C)
|
||
|
||
- `Config.h`:大 buffer 采纳下的 SubData 自 `0ee3384b` 起经 resident-subdata GPU 有序落地,不是"plain memcpy"。
|
||
- `MG_Backend/Init.cpp`:DirectVulkan 在非 monolith 下注册线路资源表(`VkBufferManager::RegisterWireResourceOps`)并声明 bit 7。
|
||
- 档位命名按 `design/07`:`PersistentMapTracker.h` 注释已改。`protocol.fbs` 的 `Adopt = 6 // server-owned adopted store` 注释**没改**:`protocol_revision_pin.py` 对整文件取 sha256,改注释要 bump 修订号。
|
||
- `CONTRACT-P6.md`、`design/06`、`design/07` 的"stream 上具名拒绝"现为真,已指向本节代码。
|
||
|
||
## 不变量
|
||
|
||
- G1:新代码全在 `MOBILEGL_BUILD_DISAGGREGATED` 下(`MG_Remote`)。
|
||
- 线上格式与控制协议修订号不变(3)。(A 包;PAIR 包把修订号升到 4,见下节;B2 升到 5。)
|
||
|
||
## PAIR:socket client 的两条连接按身份配对(B0 F2)
|
||
|
||
依据 [`B0-CROSS-APP.md`](../notes/p11/B0-CROSS-APP.md) F2:server 按到达顺序配对(`AcceptPair`,"先到是控制、后到是 aux"),设备上 server app 的就绪探测(一次空 `connect()` + `close()`)成了下一个真 client 的控制连接,会话读到探测的套接字后退出(`control peer closed before sending a first frame`),client `no Welcome … (rc=6)`;两个 client 同时连也会互换半边。
|
||
|
||
### 线上格式
|
||
|
||
- `protocol.fbs` 追加 `table PairBind { nonce: [ubyte]; aux: bool; }` 与 `CtrlMsg` 末尾的 `PairBind`。**控制协议修订号 3 → 4**(`mg_protocol_base.h`,`protocol_revision_pins.json` 已钉),`wireFingerprint` 随之变。
|
||
- `SocketTransport::ConnectTo`(`unix:` 与 fork spawn 两条 client 路径都走它;`tcp://` 上只有测试用):CSPRNG 取 16 字节 nonce,控制、aux 两条连接各以一帧 `PairBind{nonce, aux}` 开头;控制连接上 Hello 在它之后。编解码在 `Transport/PairBind.h`。
|
||
- TCP 产品路径不变:`ConnectControl` 单连接,数据连接凭 server 发的 `Welcome.dataNonce` 绑定(`DataBind`,PH-7 (4)),`TcpSupervisor` 的预认证门本来就丢探测。
|
||
|
||
### 规则(`Server/PairAcceptor.{h,cpp}`,取代已删的 `SocketTransport::AcceptPair`)
|
||
|
||
| 连接的首帧 / 状态 | 处置 |
|
||
|---|---|
|
||
| 一字节未发就关(就绪探测) | 关闭,D 级日志 |
|
||
| `PairBind`,另一角色同 nonce 已在等 | 成对交出(控制作流、aux 作描述符通道) |
|
||
| `PairBind`,同 nonce 同角色已在等 | `Refuse{Authentication}`,关新来者 |
|
||
| `Hello`(修订号 < 4 的 client) | 按 `RunSession` 的顺序问:token → 线上格式;旧 client 得 `Refuse{WireFingerprint}`,都过的仍 `Refuse{MalformedHello}`;从不配对 |
|
||
| `DataBind` / 其它 | `Refuse{Authentication}` "data connection names no live session" / `Refuse{MalformedHello}` + 形状细节 |
|
||
| 自 accept 起 2000 ms 内未发 `PairBind`,或伙伴未到 | 控制与未识别者 `Refuse{Authentication}` 后关;aux 直接关(其 client 在那里只收描述符);W 日志 |
|
||
| 同时未配对的超过 16 条 | 丢最老的未识别者(没有则最老者),`Refuse{Busy}` |
|
||
|
||
- 从不读过 `PairBind` 一个字节(`FirstFrameAssembler`):控制连接后面的 Hello 归会话读,aux 后面是 `SCM_RIGHTS`。
|
||
- 有状态:A-control、B-control、B-aux、A-aux 时先交出 B,A-control 留给下一次 `Accept`。`Accept(timeoutMs)` 超时返回 `TIMEOUT`,未配对的留着。
|
||
- 调用点:`UnixInProcessSupervisor`(成员)与 `mobilegl_server_main` 的 unix 分支。`--serve` 的会话子进程 `CloseInForkedChild`(只 `close`,不 `shutdown`);单会话形状配对后即析构,其余等待者得 `Refuse{Busy}`。`RefuseBusy` 不变(Hello 仍在套接字里)。
|
||
|
||
### 版本互通
|
||
|
||
| | 修订 3 server | 修订 4 server |
|
||
|---|---|---|
|
||
| 修订 3 client | — | `PairAcceptor` 代答 `Refuse{WireFingerprint}`,从不配对 |
|
||
| 修订 4 client | 旧 server 按顺序配对后首帧是 `PairBind` → `Refuse{MalformedHello}` "first control frame is not a verifiable Hello";旧会话退出若抢在 client 发 Hello 之前,client 只见发送失败 | 正常 |
|
||
|
||
### 门
|
||
|
||
- 单元 `Endpoints/PairAcceptorTest.*/{unix,tcp}`(8 例):(1) 探测后紧跟 client;(2) 四条交错;(3) 孤儿 aux 过预算被关、下一个 client 成对;(4) Hello 先到的旧 client 具名拒绝。每例用 `ServesExactly` 证身份(控制双向各一帧 + 该 client 的 aux 字节是 server aux 上的头几个字节)。
|
||
- 端到端 `ServerSpawnTest.AStrayConnectionAheadOfTheClientDoesNotTakeItsSession`:单会话 unix server,探测之后真 client 握手成功、server 退出 0。
|
||
- red-once:`PairAcceptor::Accept` 换回到达顺序配对(仍读掉首帧,只差配对规则)→ 单元 8/8 红((1) 控制连接是已关的探测;(2) aux 读到的不是本 client 的字节),端到端握手 rc=6(与设备上 `no Welcome … (rc=6)` 同);对照 `StartsAServerProcessAndHandshakesAcrossIt` 仍绿。拷回原文件、`cmp` 字节相同后复绿。
|
||
|
||
### 不变量
|
||
|
||
- G1:新代码全在 `MG_Remote`(`MOBILEGL_BUILD_DISAGGREGATED` 下)。
|
||
- 未改:`TcpSupervisor`、预认证门、`DataBind`、会话读 Hello 的路径。
|
||
|
||
## B1:同机外部 client 经 app_process helper 与令牌 broker 走共享内存
|
||
|
||
依据 [`PLAN-P11.md`](../notes/p11/PLAN-P11.md) §2 B、[`B0-CROSS-APP.md`](../notes/p11/B0-CROSS-APP.md)、[`HSPIKE.md`](../notes/p11/HSPIKE.md)(ID-P11-13)。消费者:同一台手机上从另一个 app(Termux、adb shell)启动、没有 Java `Context` 的原生 GL 程序。证据根 `~/w7/notes/p11/evidence/b1/`(下文的 `evidence/b1/…`、`m-probe*`、`freeze-*` 都在它下面),报告 `~/w7/notes/p11/b1-report.md`。
|
||
|
||
### 链路
|
||
|
||
| 步 | 谁 | 做什么 |
|
||
|---|---|---|
|
||
| 1 | 用户 | `MOBILEGL_IPC_TOKEN=<令牌> CLASSPATH=<server APK> app_process / top.mobilegl.plugin.ExternalClientHelper [--server <包名>] [--timeout-ms <n>] [--keep-affinity] -- <程序> [参数…]`(server 界面给出可复制的整行:APK 路径取 `getApplicationInfo().sourceDir`,不靠 `pm path`——包可见性会过滤 app uid 的查询) |
|
||
| 2 | helper(`app_process`,client 自己的 uid) | 令牌取自 `MOBILEGL_IPC_TOKEN`,否则取 `MOBILEGL_IPC_TOKEN_FILE` 指的文件;**从不取 argv**(`--token` 具名拒绝)。`--server` 缺省为 `BuildConfig.APPLICATION_ID`(加载它的那个 APK 的包名) |
|
||
| 3 | helper | 经隐藏的 `IActivityManager.broadcastIntentWithFeature`(null caller,参数按类型填,末位 int = user id)发显式广播:组件 `<包名>/top.mobilegl.plugin.ExternalClientBroker`,action `top.mobilegl.plugin.EXTERNAL_CLIENT_CONNECT`,`FLAG_RECEIVER_FOREGROUND \| FLAG_INCLUDE_STOPPED_PACKAGES`,extra `mobilegl_broker` = Bundle{`callback`: helper 自建的 Binder,`token`,`version`=2} |
|
||
| 4 | broker(导出的 receiver,两个口味都有,跑在 `:mglsrv`) | 按下表逐条检查;通过则连两次 server 的 unix 端点(`@name` 或路径;相对路径按 `filesDir` 解析)。server 只听 `tcp://` 时,`MobileGLServerService` 在旁边多起一个 supervisor,听私有抽象名 `@<包名>.broker.<8 位 hex>`(同一二进制、同一环境与令牌),broker 连它。连上后**立即写 PairBind 对**(ID-P11-12:谁开连接谁出示;新 nonce,控制 `aux=false`、辅助 `aux=true`;字节见下「交接时配对」) |
|
||
| 5 | broker → helper | **每个请求都回话**,单向 `transact`:`FIRST_CALL_TRANSACTION` = int version(2)+ 控制 PFD + 辅助 PFD + long applyCore(在跑的 supervisor 启动时宣布的保留核,0 = 无)+ int flags(bit 0 = 已在交接时配对);`FIRST_CALL_TRANSACTION+1` = String 拒绝名 + String 说明 |
|
||
| 6 | helper | 两个 fd 清 `FD_CLOEXEC`,`execve` 程序(applyCore ≠ 0 且没给 `--keep-affinity` 时经 `/system/bin/taskset <在线 & ~applyCore>`);环境 = 原环境 − `CLASSPATH` + `MOBILEGL_TRANSPORT=spawn`、`MOBILEGL_IPC_CONTROL=fd:<控制>,<辅助>`、`MOBILEGL_IPC_DATA=shm`、`MOBILEGL_IPC_TOKEN=<令牌>`,flags bit 0 时再加 `MOBILEGL_IPC_FD_PAIRED=1` |
|
||
| 7 | client(`fd:` 端点) | 见下;之后与 `unix:` 完全相同:Hello(dial = Connect)、Welcome、7 个 fd 经 `SCM_RIGHTS`、SharedSegments |
|
||
|
||
### `fd:<控制>,<辅助>` 端点(`ClientSession::StartSpawned`,`SocketTransport::AdoptConnectedPair`)
|
||
|
||
- 文本严格:两个十进制数、一个逗号,别的都不收;`MOBILEGL_IPC_DATA` 只收 `auto` / `shm`。违者 `Refuse{ProtocolMismatch}` 具名、`MOBILEGL_ERR_UNSUPPORTED`。
|
||
- 两个描述符必须互不相同、在本进程打开、是 `AF_UNIX` + `SOCK_STREAM`、已连接(`getpeername`);否则 `Refuse{ProtocolMismatch}` 说明哪一条,`MOBILEGL_ERR_INVALID_ARGUMENT`,**一个字节不写**。TCP 套接字被拒(没有描述符通道,用 `tcp://`)。
|
||
- 收养后两个 fd 设 `FD_CLOEXEC`(程序自己的子进程不继承)。
|
||
- `MOBILEGL_IPC_FD_PAIRED=1`(helper 按 broker 的 flags 设):PairBind 已由 broker 在交接时出示,本端点**不再写**(`SocketTransport.cpp:492`),直接 Hello。没有这个标记时本端点自己出示一对(CSPRNG 16 字节 nonce,控制 `aux=false`、辅助 `aux=true`,然后 Hello——与 `ConnectTo` 同),供裸连接交来的 fd(测试、旧 broker)。
|
||
- 自己出示时发送失败(server 已拒绝并关掉这一对):两个 fd 留给调用方,client 读控制连接上待读的拒绝帧并具名报出(如 `peer Refuse{Authentication} no PairBind within the pairing budget`),不只是一个发送错误。
|
||
- 一对 fd 只够一个会话:会话结束(或同一进程再起会话)时 fd 已关,第二次收养具名失败。
|
||
|
||
### 交接时配对与等 Hello 的时限(ID-P11-12)
|
||
|
||
- broker 连上两条连接后立即写 PairBind 对(`PairBindFrames.java`:帧头 magic `0x464C474D` + 长度,小端;负载是原生 `EncodePairBind` 的输出,16 字节 nonce 在最后)。两个前缀是编码器的字节;`ServerSpawnTest.TheBrokersPairBindTemplatesAreTheEncoders` 从 Java 源文件读出前缀、对 32 个随机 nonce 与 `AppendFrame(EncodePairBind(…))` 逐字节比较,`protocol.fbs` 一改就红。`PairAcceptor` 的 2000 ms 配对预算(`PairAcceptor.h:63`)因此在 broker 手里满足,不再等程序跑到第一个 EGL 调用。
|
||
- **配对之后等 Hello:没有时限,靠 EOF。** 之前是 10 s(`RunSession` 的 `helloWaitMs`,`ServerMain.cpp:385` 的读);现在 unix 端点的 `--serve` 会话子进程与单会话形状都用 `kPairedUnixHelloWaitMs = kWaitForever`(`ServerMain.cpp:136`,调用点 `:1537`、`:1562`)。理由:配对时身份已定(nonce),unix 端点只有本用户 / 本 app 能连(Android 上 SELinux,路径上 0600;开发机 Linux 上一个 `@abstract` 名同一 network namespace 内谁都能连——这是产品设备上的论证),而一个先加载资源的程序要多久没有上界;程序退出或关掉描述符 → EOF → `control peer closed before sending a first frame`,会话子进程干净退出。代价:程序活着但迟迟不 Hello 时,它占着唯一的会话槽(与一个正在渲染的会话相同):别的 client 在配对后 2 s 内说 Hello 时得 `Refuse{Busy}`;更慢的那个只见到 Hello 发送失败——`RefuseBusy` 写完 Busy 就关了连接,而 Hello 发送失败的路径不读待读的拒绝帧(`AdoptConnectedPair` 那条路径会读);未修,见 B1 报告附录。TCP 保留预认证时限(`PreAuthKnobs`);进程内显示 server(`:mglwin`,`ServerMain.cpp:1395`)保留 10 s(它停止时要等会话线程)。
|
||
|
||
### 令牌
|
||
|
||
- broker 比对的是**正在运行的 server 自己的** `MOBILEGL_IPC_TOKEN`(启动 server 时界面上的 Auth token),常量时间比较。
|
||
- server 的令牌不足 16 字节(含空)→ `server-no-token`:不接同机外部 client。unix 端点对同 app client 仍然不要令牌(行为不变)。
|
||
- helper 把令牌放进被 exec 程序的环境,Hello 也带它:server 配了令牌时 `AuthenticatePeerToken` 在 unix 会话上照样核对(`Refuse{Authentication}`)。
|
||
- 令牌只经环境、令牌文件、Binder 事务传;不进任何进程的 argv。
|
||
|
||
### 拒绝
|
||
|
||
| 名 | 条件 | helper 退出码 |
|
||
|---|---|---|
|
||
| `bad-request` | 没有 `mobilegl_broker` Bundle / callback(只能记日志)或 version ≠ 2 | 64 |
|
||
| `token-missing` | 未出示令牌 | 65 |
|
||
| `token-wrong` | 令牌不符;不连、不给 fd | 66 |
|
||
| `server-no-token` | server 令牌 < 16 字节 | 67 |
|
||
| `server-not-running` | `:mglsrv` 里没有在跑的 server(广播可以把 `:mglsrv` 进程拉起来——server app 被强停后也一样,设备实测——但 Android 12+ 不许后台 receiver 起前台服务,所以答"先在 MobileGL 的 server 界面启动 server",不挂起)。只认离屏服务;`:mglwin` 显示 server 在跑时也答它 | 68 |
|
||
| `broker-busy` | 另一个请求正在 broker 里连接,或 server 的监听 3000 ms 内没接受两条连接 | 69 |
|
||
| `connect-failed` | server 在跑但它的端点拒绝连接,或 PairBind 写不进去 | 70 |
|
||
| (helper 本地)无应答 | `--timeout-ms`(缺省 10000)内 broker 没回话:包没装,或系统没把停止的 app 拉起来(自启动管控) | 71 |
|
||
| (helper 本地)广播发不出 | 反射拿不到 `IActivityManager` / 调用抛异常(见"依赖 ROM") | 72 |
|
||
| (helper 本地)exec 失败 | | 73 |
|
||
| 用法错 | 无程序、未知选项、`--token` | 2 |
|
||
|
||
### apply 线程的亲和 / 自旋(`Server/ApplyThreadPolicy.h`)
|
||
|
||
`ServerSession::Accept` 记下 Hello 的 dial mode 与数据面;`ServerLoop` 的 apply 线程按它们解析 `MOBILEGL_IPC_SERVER_AFFINITY=auto`:
|
||
|
||
| peer | 数据面 | `auto` |
|
||
|---|---|---|
|
||
| in-process(inproc,dial No) | — | 全部大核、配置的自旋(不变) |
|
||
| forked(同 app fork spawn,dial Fork) | 共享段 | 同上(不变) |
|
||
| connected(dial Connect:`tcp://` / `unix:` / `fd:`) | stream | 同上(不变:io 线程喂 apply 线程,B0 实测 apply 7.9 ms/帧 vs shm 26.8) |
|
||
| connected | 共享段 | 一个核,`ReservedApplyCore(big, prime, online)`(`ApplyThreadPolicy.h:120`):≥ 2 个 prime 核(峰值主频)→ 最低编号的 prime 核;只有 1 个 prime 核 → 它旁边最低编号的其他大核;只有 1 个 prime 核且无其他大核 → 不设亲和;无非对称(全是大核或读不到拓扑)→ 不设亲和。**从不把唯一的 prime 核给自旋的 apply 线程**。自旋 `kConnectedPeerSpinUs` = 50 µs(`MOBILEGL_IPC_SPIN_US` 显式设了则用它) |
|
||
|
||
- **一个来源**:保留核只由原生规则算。supervisor 启动时(`ServerMain.cpp:1485`)按本机拓扑与 `MOBILEGL_IPC_SERVER_AFFINITY`(显式掩码 / `off` → 0)算出 `ReservedApplyCoreForThisProcess`(`ServerLoop.cpp:210`),在 stdout 打印 `MG_Remote server: apply core reserved for dialled-in shared-segment clients: 0x40 (<规则>)`;`MobileGLServerService` 逐行读 supervisor 输出(`ServerEnvironment.parseReservedApplyCore`),broker 把它放进答复;helper 经 `/system/bin/taskset <在线 & ~applyCore>` exec 程序(亲和跨 exec 继承);`--keep-affinity` 不设。Java 不再重算规则。代价:整个程序少用一个核。
|
||
- 显式掩码或 `off` 对所有 peer 生效(与以前相同);`MOBILEGL_IPC_SPIN_US` 设了就用设的值。
|
||
- 启动日志 `mgl-srv-apply started … RESOLVED mask 0x…, spin N us; peer <in-process|forked|connected> on <shared segments|stream>, policy <规则>`,规则名写明是哪一种(最低 prime / 唯一 prime 旁的大核 / 唯一 prime 留给 client 而不设亲和 / 无非对称)。
|
||
- P12 的进程内显示 server(`:mglwin`)的 client 也是 dial Connect:它们走 stream 时不变,走 unix 共享段时得到本规则。
|
||
|
||
选择依据(红米,rd12 稳态 fps,helper 路线,默认落位、定频;`evidence/b1/m-policy.tables.md`):
|
||
|
||
| 候选(apply 掩码, 自旋) | a shm Espryt | a tcp Espryt | b shm Espryt | b tcp Espryt | a shm Magma | a tcp Magma |
|
||
|---|---|---|---|---|---|---|
|
||
| 旧 auto(0xc0, 50) | 21.5 | — | 62.5 | — | 22.1 | — |
|
||
| 最高大核(0x80, 50) | 26.7 | — | **6.5** | — | — | — |
|
||
| 最低大核(0x40, 50)= q1 | 26.5 | 51.1 | **119.9** | 40.3 | 21.9 | 20.3 |
|
||
| 0x40, 不自旋 | 16.9 | — | 73.6 | — | 22.5 | — |
|
||
| 不设亲和, 50 | 21.7 | — | 64.9 | — | 23.3 | — |
|
||
| 不设亲和, 不自旋 | 20.2 | — | 12.8 | — | 20.7 | — |
|
||
| 非大核(0x3f, 50)= q3 | 49.0 | 27.0 | 50.3 | 29.2 | 18.4 | 13.9 |
|
||
|
||
- 前台 app 的 GL 线程被调度器放在**最高**大核(cpu7,44 次采样中 43 次):把 apply 线程钉在 cpu7 就与它同核(6.5 fps);钉在 cpu6 得到 B0 手工定核的布局(120)。
|
||
- 不自旋(0)在每一格都输:保留 50 µs。
|
||
- adb shell 的 client 被放在 cpu6(与 apply 线程同核):q1 下路线 a 的 shm 26.5 < tcp 51.1。q3(apply 放非大核)能让 shm ≥ tcp,是因为它**同样**把 tcp 会话的 apply 线程放到慢核(tcp 51 → 27),会拖慢所有同机 TCP client(FCL loopback、`:mglwin`),所以不用;改为数据面分流(stream 不变)+ helper 让程序避开保留核。
|
||
|
||
### 保活
|
||
|
||
- server 仍是前台服务(`dataSync`,带 partial wake lock);broker 从不启动它。前台服务本身挡不住 HyperOS 的冻结(greeze / SmartPower,B0 F1)。
|
||
- **用户必须设**:server app 与 client 所在 app 的"省电策略"都选"**无限制**"(HyperOS:设置 → 应用 → 应用管理 → <app> → 省电策略 → 无限制;powerkeeper 记为 `bgControl=noRestrict`,并进 deviceidle 白名单),再在 server 界面设 Auth token(≥ 16 字节)、以离屏模式启动。
|
||
- 设了(红米,`evidence/b1/freeze-{a,b}`):路线 a、b 各熄屏 180 s,在飞会话(rd12 + 200 s 保持)**存活**(正常结束,0 个 Fatal),熄屏期间每 45 s 新起的 3 个路线 a 会话都通过(ssim 1.0);两 app 所有进程的 cgroup.freeze 采样 0 / 103 与 0 / 143,无 `do_freezer_trap`;系统日志 `PolicyMaker: uid=… pkg=<server> reason=NO_RESTRICT_APP`。
|
||
- 不设(缺省"智能限制后台运行")时具名失败的样子:client app 在前、server app 在后,greeze 对 server uid `setUidState allow=false`、停用其 wake lock,路线 b 的会话 `no Welcome from the spawned server within 5000 ms (rc=7)`(`m-probe2-defaultbattery`,屏亮时就发生,非必现);熄屏约 7 s 后冻结整个 server uid,在飞会话 120 s 后 `Fatal{BarrierTimeout}`(B0 F1、HSPIKE §7)。
|
||
- 已知:这台机上旧包 `top.mobilegl.plugin.trace` 的"省电策略"页不显示选项(原因未查),门改用新装的 `top.mobilegl.plugin.b1.trace`(像用户新装一样)。
|
||
|
||
### 依赖 ROM
|
||
|
||
- 直接跑的 `app_process` 不是 zygote fork 出来的,不受 app 的隐藏 API 黑名单约束,`IActivityManager.broadcastIntentWithFeature` 才能反射调用(HyperOS / Android 16 上 `hiddenapi` 日志 0 条,shell 与 app uid 都通)。若某 ROM 对它也强制执行,helper 以退出码 72 具名失败;没有替代(`am broadcast` 带不了 Binder)。
|
||
- HyperOS 的冻结(greeze / SmartPower)不看前台服务:见"保活"。
|
||
|
||
### 线上格式
|
||
|
||
无变化(控制协议修订 4,PAIR)。`fd:` 只是 client 端点的一种写法;broker 协议是 Android Binder 上的 Bundle / Parcel,不属于 MobileGL 线上格式。
|
||
|
||
### 门
|
||
|
||
- 单元:`ApplyThreadPolicy.*`(8 例:每个 peer × 数据面的 `auto`、最低大核与非连续大核集、对称 / 单大核 / 读不到拓扑、显式掩码 / `off` / 显式自旋、认不出的文本);`ServerSpawnTest.AnFdPairTheBrokerConnectedBringsASharedSegmentSessionUp`(真 server 进程,两次裸连接交给 `fd:`,会话起、fd 为 CLOEXEC、server 退出 0)、`…AnAdoptedFdPairPresentsOnePairBindPairBeforeAnythingElse`(在 server 侧读:每条连接恰一帧 PairBind,同 nonce,aux 标志正确)、`…AnFdEndpointThatIsNotAConnectedUnixPairIsRefusedByNameAndWritesNothing`(管道、同一 fd 两次、未打开、未连接、八种坏文本、`stream`,都具名拒绝且一字节不写);Java 镜像 `ServerEnvironmentTest`(`reservedApplyCore` 7 条,`test_android_lifecycle.py`)。
|
||
- red-once:`AdoptConnectedPair` 不写 PairBind → 端到端例红,client 日志 `peer Refuse{MalformedHello} the control connection's first frame is a Hello, not a PairBind (control revision 4 pairs by nonce)`,PairBind 例红(控制连接上无帧);保留核取最高大核(设备否决的规则)→ 3 例红;策略不看数据面 → stream 例红。每次拷回原文件、`cmp` 相同后复绿。
|
||
- 跟进(交接时配对、唯一 prime 核、单一来源):`ServerSpawnTest.TheBrokersPairBindTemplatesAreTheEncoders`(Java 前缀 = 编码器字节,32 个随机 nonce × 两个角色)、`…APairTheBrokerPresentedAtHandoffWaitsForAHelloThatComesSecondsLater`(broker 的字节在交接时写出,Hello 5 s 后才来,会话起)、`…APairPresentedOnlyWhenTheProgramStartsIsRefusedByNameOnceThePairingBudgetIsGone`(旧流程:3 s 后才出示 → client 日志 `peer Refuse{Authentication} no PairBind within the pairing budget`);`ApplyThreadPolicy.*` 10 例(加唯一 prime 核旁有 / 无其他大核、无非对称、`ReservedApplyCoreForConfig`);Java `PairBindFramesTest`、`ServerEnvironmentTest`(宣布行解析 5 条)。red-once:交接步骤拿掉(今天的 client 侧配对)→ 交接例红,`Refuse{Authentication} no PairBind within the pairing budget`;Hello 时限改回有限的 3 s → 红,`Refuse{Authentication} no authenticated first frame within the pre-auth deadline`;client 不看标记、再出示一对 → 红,`Refuse{MalformedHello} first control frame is not a verifiable Hello`;保留核不看 prime 集 → 唯一 prime 的两例红;Java 前缀改一个字节 → 模板例红。每次拷回、`cmp` 相同后复绿(`~/w7/logs/p11/b1/redonce4.out`)。
|
||
- 设备门(红米 `2f7cbe2e`,HEAD `3519a21b` 的 APK,两 app "无限制",默认环境:不手工定核、无解冻守护;CPU 定频并回读只为可重复,`evidence/b1/m-gate.summary.md`):16 个 ssim 格全过(rd12 0.99988 / 0.999884,openra 1.0);shm(helper)对 tcp 的稳态 fps 中位数:
|
||
|
||
| 路线 | 后端 | rd12 shm / tcp | openra shm / tcp |
|
||
|---|---|---|---|
|
||
| a(shell uid) | Espryt | 119.2 / 53.1(2.25×) | 927.6 / 427.5(2.17×) |
|
||
| a | Magma | 36.8 / 20.2(1.82×) | 281.2 / 278.6(1.01×,8 次) |
|
||
| b(另一 app,在前) | Espryt | 119.9 / 39.0(3.07×) | 923.4 / 377.4(2.45×) |
|
||
| b | Magma | 31.5 / 20.2(1.56×) | **285.3 / 286.6(1.00×,8 次,差 0.5%)** |
|
||
|
||
15 / 16 格 shm ≥ tcp;Magma openra 路线 b 按合并中位数差 0.5%(逐次配对 shm 赢 6 / 8,区间完全重叠):该负载两个数据面都受 server 端限制,shm 不增益。拒绝(token-wrong a/b、token-missing、server-not-running a/b 与强停后、server-no-token)都按名、退出码对、无会话。同 app spawn+shm / inproc 的 rd12 p50 与 A4 相同(策略对 Forked / InProcess 不变,日志 `mask 0xc0`)。
|
||
|
||
- 重定基后的设备复测(`df826f42` 的 APK,两 app "无限制",默认环境;`evidence/b1/m-pass2.summary.md`、`m-openra3.summary.md`):rd12 八格 ssim 全过;shm / tcp 中位数(3 次交错)路线 a Espryt 119.3 / 53.2、Magma 36.4 / 20.3,路线 b Espryt 118.7 / 39.8、Magma 32.5 / 20.4;22 个 shm 运行全部"交接时配对"。Magma openra 在干净设备上重测:路线 a 296.4 / 284.4、路线 b 292.7 / 281.6(都 1.04×)——首轮门那一格的 5 次补测与一个失控的采样子进程(root `tr`,占满一个核约 65 分钟)同时跑过,不作数。慢启动:helper exec 一个先睡 10 s 的包装再 exec retrace,broker 交接后 10.2 s 才 Hello,两后端会话都起、ssim 过。plugin 口味(`.b1p`,界面上 Generate token + Start server):helper 路线 openra ssim 1.0、rd12 0.99988,HyperOS 省电策略页显示四个选项。
|
||
|
||
### 不变量
|
||
|
||
- G1:原生改动全在 `MG_Remote`(`MOBILEGL_BUILD_DISAGGREGATED` 下);pull 构建符号增 0 减 0。
|
||
- 线上格式与控制协议修订号不变(4)。
|
||
|
||
## B2:T0——client 的 AHardwareBuffer 作 persistent map 的存储
|
||
|
||
依据 [`PLAN-P11.md`](../notes/p11/PLAN-P11.md) §2 B、`design/07`。证据根 `~/w7/notes/p11/evidence/b2/`,报告 `~/w7/notes/p11/b2-report.md`。T0 只换**存储**:采纳门(≥ 16 MiB 的 NULL 定义、persistent 写映射等,与 monolith 同一组门)问到的那个 store 由 client 分配的 AHB 承载,server 导入它;GPU 有序的写(`glBufferSubData` 进已采纳 store = resident subdata)仍走 SEG_STAGE——**T0 不减少 MC 26.3 每帧的 stage 字节**:红米实测 SEG_STAGE 1071 → 1052 KiB/帧(只少了 T2 的 persistent-map 推送 19.4 KiB/帧),省的是 client shadow 与 server 暂存副本(内存)。monolith 不变。
|
||
|
||
### 档位(取代 §1 表里 SharedSegments 的 client 行)
|
||
|
||
| 数据面 | `MOBILEGL_IPC_ADOPT_TIER` | client | server |
|
||
|---|---|---|---|
|
||
| SharedSegments | **未设(缺省,2026-09-29 起)** | Hello 问 `0x80`(`kAdoptAskT0Default`:T0,回退安静)。有 `kCapAdoptT0` → T0(I 行同 0);没有 → **只 MGLOG_D**,本会话 T2,计入 `T0Fallbacks` | 授予同 0(`T0 granted` I 行);不授予 → **只 MGLOG_D**(POST 结果行同样降为 D),计入 `T0Refusals`;会话总计行降为 D |
|
||
| Stream | 未设 | Hello 问 2;**只 MGLOG_D**(不是 A1 的 W 行),T2,计入 `T0Fallbacks` | server 自己的旋钮未设时同样只 D |
|
||
| 任意 | 2 | 今天的 T2,逐字节不变;Hello 的 `adoptTier` = 2 | 不变 |
|
||
| Stream | 0 / 1 | A1 不变:一行 W `Refuse{AdoptTierOnStream, "T<n>"}`,T2 | A1 不变 |
|
||
| SharedSegments | 1 | 一行 W `Refuse{AdoptTierClosed, "T1"}`,问 2,T2 | — |
|
||
| SharedSegments | 0 | Hello 问 T0。第一次 `map_persistent` 时档 = min(旋钮, caps, 数据面):有 `kCapAdoptT0` → T0(I 行);没有 → 一行 W `Refuse{AdoptT0Unavailable, "no kCapAdoptT0"}`,本会话 T2 | 第一次原生 bind 时定(`ServerSession::SettleAdoptT0AtBind`,`ServerSession.cpp:1076`):允许开关 → 平台有 AHB → 后端登记了导入 op → POST(进程内缓存)。授予:I `T0 granted`,post-bind 的 caps 快照带 `kCapAdoptT0`;否则一行 W `Refuse{AdoptT0Unavailable, "disallowed" \| "no AHardwareBuffer" \| "POST"}`,不发布 |
|
||
| 任意 | 解析不了的值 | 一行 W(Config 忽略该值),按**未设**处理 | 同 |
|
||
|
||
- **缺省是 T0(用户 2026-09-29 决定)**:旋钮未设 = 能用就用 T0,不能用就安静回退 T2——stream、没有 AHB、POST 失败、server 不允许这四种都只打 MGLOG_D、没有 W / I 行,但照样计数(client `T0Fallbacks`、server `T0Refusals`,以及两侧的会话总计)。否则每个 tcp 会话和每个主机会话都会多一行回退。**显式 `=0` 与未设的区别只在日志**:`=0` 保留全部具名行(A1 的 `Refuse{AdoptTierOnStream}`、`Refuse{AdoptT0Unavailable, …}`),线上问 `0`;未设问 `0x80`,这样 server 才知道要安静(拒绝的原因只有 server 知道)。`=2` 仍是 T2,`MOBILEGL_IPC_ALLOW_ADOPT_T0` 不变。Config 行记为 `adopt-tier=unset(T0)`。
|
||
- **修订 5 的 schema 补丁**:`protocol.fbs` 里 `adoptTier` 的注释加上 `0x80`,修订 5 按 `protocol_revision_pin.py --write --force` 重钉(`4632750f…`)。依据是脚本自己写的「修订号提交本身的罕见补丁」这一例外:修订 5 只在这条未落地的分支里出现过,没有外部对端。e44d6ac7 之前的修订 5 server 收到 `0x80` 时按名 `Refuse{LinkTerms}`,不会静默误读。生成的头文件不变。
|
||
- **行为变化(A1 → B2)**:SharedSegments + 0 / 1 不再在握手期 `Fatal{UnimplementedAdoptTier}`。A1 的两个死亡用例改写为 `RemoteClientAdoptT0.*` 的具名回退用例。从不 Fatal:T0 不可用 → T2 + 具名行。
|
||
- server 允许开关 `MOBILEGL_IPC_ALLOW_ADOPT_T0`(缺省 1;0 = 从不授予),进 `ServerSpawn` 的 server 自有名单(环境剔除后仍在),Config 行 `allow-t0=`。
|
||
- 门(缺省 T0):`RemoteClientAdoptT0.AnUnsetKnobFallsBackToT2QuietlyAndCountsIt`(主机无 AHB 的共享段)、`AdoptTierStream.AnUnsetKnobRunsT2OnAStreamQuietlyAndCountsTheFallback`(真 tcp);未设 → T2,没有 W / I 回退行,计数为 1。red-once:把未设路径的三处 D 改成 W → 两例都红。`KnobTwoIsTodaysT2WithNoT0Line` 显式设 2。`SessionHandshakeTest` 的问值表加上 `0x80`(合法且安静)和 `0x81`(`Refuse{LinkTerms}`)。
|
||
- 单个 store 的拒绝(server 已授予,这一条没导入)→ 该 store DECLINED = T2,client W 行,会话继续:Offer 未到(E `Refuse{AdoptT0NoStore, "record N"}`)、client 分配失败(Offer flags 0)、hop 读不出、后端导入失败;client 建不了 hop → W `Refuse{AdoptT0NoStore, "hop"}`、不发记录。
|
||
- refusal 词 4 → 7(`AdoptTierClosed`、`AdoptT0Unavailable`、`AdoptT0NoStore` 进 `LOCAL_REFUSAL_WORDS`);abort 站点仍 79、family 仍 45。
|
||
|
||
### 线上格式(控制协议修订 4 → 5)
|
||
|
||
- `LinkTerms` 末尾追加 `adoptTier: ubyte = 2`:Hello 里是 client 的问,Welcome 里 server 回显。server 收到既非 2、也非(共享段上的)0 的问 → `Refuse{LinkTerms}`;client 见回显 ≠ 问 → `Refuse{LinkTerms}`。
|
||
- 修订号 5(`mg_protocol_base.h`,`protocol_revision_pins.json` 第 5 行 `f83f4c5b…`);`wireFingerprint` 含修订号,修订 4 的对端得 `Refuse{WireFingerprint}`,从不配对。
|
||
- `kCapAdoptT0` = `MGPCapBit` 1 << 11(`MGPipeTypes.h`),只在 post-bind 的 `CapsSnapshot.callMask` 里(POST 在第一次原生 bind 之后才能跑;client 在 make-current 返回前采用 post-bind caps)。
|
||
- `map_persistent` 记录与回复不变:OK = 已导入、client 采纳 AHB 映射;DECLINED = 该 store T2;ERROR → `Fatal{ReplyError}`;OK 而 client 没给 AHB → `Fatal{UnexpectedMapAccept}`。
|
||
- **Offer**(aux 套接字,`SCM_RIGHTS`,`Transport/AdoptT0.h:36`):32 字节旁带 {magic `'MGT0'` = `0x3054474D`,version 1,seq = 该 `map_persistent` 记录序号,size,flags bit 0 = HasBuffer} + 一个 fd = **hop**:client 建的 socketpair 一端,client 已在另一端 `AHardwareBuffer_sendHandleToUnixSocket`;server `recvHandleFromUnixSocket`(校验 BLOB 宽 = size)。hop 的理由:AHB 自带的 fd 集不定长,aux 上一条消息只带一个 fd。
|
||
|
||
### 顺序、线程、为什么不死锁
|
||
|
||
- client(app 的 GL 线程,`ClientSession::AdoptPersistentT0`,`ClientSession.cpp:2739`):分配 AHB(BLOB,CPU_READ/WRITE_OFTEN \| GPU_DATA_BUFFER)、lock(整个 store 生命期持有)、用 shadow 播种、建 hop → 编码 `map_persistent` → pre-publish 钩子在 `EncodeRecord` 与 `PublishAndNotify` 之间把 Offer 排上 aux → 发布 → 阻塞等回复。
|
||
- server:**apply 线程**(`mgl-srv-apply`)在应用该记录时取 Offer:codec 的 `MapPersistent` 分支先问 `WireVerbSink::OnMapPersistent`(`PipeWireCodec.cpp:1834`)→ `ServerSession::AdoptStoreT0`(`:1118`)→ `AdoptInbox::Take(seq, 2000 ms)`。
|
||
- 不死锁:(1) fd 在记录可见之前已进内核队列;(2) 握手后只有 apply 线程读 server 的 aux;(3) 每会话至多一条 T0 `map_persistent` 在飞(client 阻塞等回复),aux 队列里至多一个有效 Offer,排不满;(4) 等待有界(`kOfferWaitMs` = 2000)→ 具名 DECLINED,不挂。晚到 / 乱序:按 seq 暂存(`AdoptInbox`);seq 小于当前的旧 Offer 关闭并计数;畸形的跳过并计数。
|
||
|
||
### POST(`kCapAdoptT0` 的前提)
|
||
|
||
持续持锁形状:分配 64 KiB AHB,**整个测试期间 CPU lock 不放**,导入,GPU 读 CPU 写的图案、GPU 写一段,经持有的指针读回比较。**两轮**:
|
||
- 第一轮读的是导入**之前**写进去的图案。导入会顺手清 CPU 缓存,所以只有这一轮的 POST 在「导入之后经持有指针的写 GPU 永远看不见」的设备上照样通过——而 T0 的真实流量全是导入之后的写。
|
||
- 第二轮(steady,2026-09-30 补):导入且 GPU 用过之后,CPU 才经持有的指针写 region 1、把 region 4(第一轮的 GPU 写)读进缓存;再一次 GPU 工作把 region 1 拷出、改写 region 4,两个方向都比对。两轮都全对才 PASSED。
|
||
- 实测:Mali-G1-Ultra(天玑,`3B159D009VZ00000`)第一轮全对、第二轮 Magma 读到 0/1024 词、写回 16/1024(import 内存 non-coherent),Espryt 48/1024、160/1024 → FAILED,T0 回退 T2。改之前该机 T0 被授予,`create-indirect-in-world-align1024` × Magma 的 inproc / spawn 丢掉全部 Flywheel 实例物体(0.867 / 0.848),同 trace 的 T2 0.99998;改之后两臂 0.999984。红米(Adreno 830)两后端两轮全对(Magma coherent),T0 照旧授予。
|
||
- Espryt(`Ops_H_SelfTestExternal`,第二轮 `SelfTestExternalSteady`):一次性 ES 3.1 pbuffer 上下文(不碰 Espryt 的绑定缓存),`glBufferStorageExternalEXT` + 持久 coherent 映射,映射读一遍,compute shader 把 region 0 拷到输出、在 word 4096 起写 `0x6C0FFEE0 ^ i`;第二轮另一个 compute program 拷 word 1024 起、在 word 4096 起写 `0x3D5EA11E ^ i`。
|
||
- Magma(`VkBufferManager::SelfTestWireImport`,`VkBufferManager.cpp:768`):renderer 的设备,`vkCmdCopyBuffer` region 0 → staging、`vkCmdFillBuffer` region 4,栅栏等 2 s;第二轮同一命令池重录:region 1 → staging、region 4 填 `0x3D5EA11E`。
|
||
- 结果一行 I `T0 POST (<后端>, sustained-lock pattern) PASSED|FAILED - …`,两轮的计数都在这一行。红米(Adreno 830)两后端 PASSED;Mali-G1-Ultra 两后端 FAILED(缺省旋钮下这行降为 D,见上表)。
|
||
|
||
### server 端存储
|
||
|
||
- Espryt:`Ops_H_ImportExternal` → `ImportAhbAsBuffer`(`eglGetNativeClientBufferANDROID` + `glBufferStorageExternalEXT`,flags READ \| WRITE \| PERSISTENT \| COHERENT \| DYNAMIC_STORAGE,持久 coherent 映射);**先导入、再退役旧存储**(导入失败则 T2 存储原样);孪生体 = adopted 形状(`InstallT0Store`:immutable、persistentMapped、无 pendingRanges、无 hostBytes → 不进 buffer pool、不走 R-11 暂存)。上下文丢失后下一次 ensure 在新上下文重导入。
|
||
- Magma:`VkBufferManager::ImportWireBuffer`(`:714`)经 `VK_ANDROID_external_memory_android_hardware_buffer`(Android 线路设备上有就启用,连同 `VK_EXT_queue_family_foreign`):要求 host-visible,优先 coherent,dedicated 导入;`VkBufferObject` 外部模式;`stagedCoverage` = 整个 store(`:742`)。
|
||
|
||
暂存副本(R-11 的 server shadow)读者审计——T0 孪生体没有暂存副本:
|
||
|
||
| 读者 | T0 下 |
|
||
|---|---|
|
||
| CPU 回读 `Ops_H_Readback`(Espryt `Managers.cpp:2540`) | persistentMapped 分支:`DrainResidentWritesNow` + `glFinish`,不回字节 |
|
||
| CPU 回读 `ReadbackWireBuffer`(Magma `:560`) | imported:只 `WaitForWireBufferHostAccess`,不回字节 |
|
||
| P9 W1 pack-buffer 落地(`PipeApplier.cpp:648` → `Ops_H_SubData`) | imported 分支排进 `pendingResidentWrites`(`Managers.cpp:2378`,GPU 有序);B2 前 adopted 的提前返回会丢这批像素 |
|
||
| GPU 写跟踪 | client 自有(`m_gpuWritePending`);XFB 捕获回读跳过 persistentMapped(`DirectGLES.cpp:1806`) |
|
||
| R-11 排水(`FlushPendingRangesFrom`、`MGL_SERVER_STAGED_REQUIRE_PENDING`) | 不可达:T0 孪生体无 pendingRanges |
|
||
| handle 臂的 CPU 读者(间接命令、primitive restart 替换、multi-draw 索引重定基:`DirectGLES.cpp:486`、`:9224`、`:9541-9542`、`:9695-9696`、`MultiDraw.cpp:202`) | `SplitHostBytes`(`Managers.h:1121`)→ 导入的 coherent 映射,同 monolith 读 adopted 映射 |
|
||
| Magma `WriteWireBuffer`(`:329`) | 不变:store 忙 → staged copy(GPU 有序),闲 → host 写进导入内存 |
|
||
|
||
### 生命周期
|
||
|
||
- client:从采纳到 respecify / destroy(`WireTables.cpp:214`、`:907` → `ReleaseT0Store`)或会话结束(`ResetT0`)持有 AHB 与 lock;之后 unlock + release。
|
||
- server 在自己的栅栏之后放手,不依赖 link watermark,不需要 `completedFrameSerial` 的生产者:
|
||
- Espryt:导入时 `AcquireImported`;`glDeleteBuffers` 之后 `RetireT0Import` 插栅栏,栅栏 signal 后才 release(`SweepT0Retired`:每次导入、ensure、以及两个同步点在延迟释放排水之后的 `ProcessDeferredT0Retires`);无上下文时的 destroy 进延迟列表并留第二个引用,排水删 id 后再退役;上下文丢失 → 全部释放。`ProcessDeferredBufferReleases` 逐字不动(G5)。
|
||
- Magma:respecify / destroy 一律 `DeferWireRelease(T0ReleaseSerial(lastUse))`,按 serial 回收、从不"立即";外部 `VkBufferObject::Destroy` 释放 buffer / memory 后 `ReleaseImported`。
|
||
|
||
### CPU 读
|
||
|
||
client `BufferObject::SyncGpuWrites` 的 T0 分支:发 `ResourceReadback` 往返("完成、无字节"),清标记,读持有的映射;server 先落地排队的 resident 写、等 GPU(Espryt `glFinish`,Magma host-access 等待)再答。
|
||
|
||
### 计数与日志
|
||
|
||
client 与 server 每会话:导入 store 数、字节、拒绝数(I `T0 store {…} adopted - N bytes` / `T0 imported store {…} - N bytes (record R); session total S stores / B bytes`;会话结束 I `T0 session totals - …`)。
|
||
|
||
### 门
|
||
|
||
主机(`~/w7/notes/p11/evidence/b2/redonce-host*`;每次拷回原文件、`cmp` 相同、touch 后复绿):
|
||
|
||
| 门 | 用例 | red-once(变异 → 红) |
|
||
|---|---|---|
|
||
| 旋钮 2 = 今天的 T2 | `RemoteClientAdoptT0.KnobTwoIsTodaysT2WithNoT0Line`;全门禁缺省旋钮全绿 | Hello 不看旋钮一律问 0 → 红(client `Refuse{LinkTerms}` 回显 ≠ 问) |
|
||
| 旋钮 0、主机无 AHB → T2 + 具名行 | `RemoteClientAdoptT0.{KnobZero…,NoAhardwareBuffer…}`、`DirectGLES.{Split,Spawn}.AdoptTier0.*`、`DirectVulkan.{Split,Spawn,Tcp}.AdoptTier0.*`(`ASessionThatCannotRunT0FallsBackToT2OnceByName` 读两行) | 恢复 A1 的握手 Fatal → 单元 2 例 + 集成例红(`e869d18c` 起 Espryt split / spawn 各条目的 ENVIRONMENT 自带 `MOBILEGL_ITEST_REQUIRE_GPU=1`,预检 abort 是失败而不是被 ctest 算作通过的 SKIP;之前只有门禁的 integration-gpu 两遍能抓到);删 client 的具名行 → 单元 1 例 + 两臂集成例红 |
|
||
| 允许开关关 → T2 + 具名行 | `…TheServersAllowSwitchOffIsT2AndANamedLine`、`DirectGLES.Spawn.AdoptT0Disallowed.*` | 开关不进 `ServerSpawn` 白名单 → spawn 例红(server 说 "no AHardwareBuffer" 不是 "disallowed") |
|
||
| 修订不符 → 具名拒绝 | `SessionHandshakeTest.ARevisionFourPeerIsRefusedByNameAtRevisionFive`、`protocol_revision_pin.py` | 修订号留 4 → 编译期 `static_assert` 红;去掉它 → 运行期该例红(两指纹相等,Welcome 发出) |
|
||
| Stream + 0 → A1 行不变 | `{DirectGLES,DirectVulkan}.Tcp.AdoptTier0.*` | stream 上也问 0 → 19 / 21 例在 bring-up abort(握手不成,client 随后 `Fatal{CapsBeforeFirstSnapshot}`) |
|
||
| 授予 + 存储落地 | `…AGrantedStoreLandsWithTheClientsBytes`(替身平台:memfd 当 AHB) | 不发布 `kCapAdoptT0` → 3 例红 |
|
||
| Offer 按 seq 配对 | `Channels/AdoptInboxTest.*`(6 例 × inprocess / socket) | 按到达顺序配对 → 4 例红 |
|
||
| Offer 晚到 / 不到 | `…AnOfferThatArrivesAfterItsRecordStillLands`(晚 300 ms)、`…AnOfferThatNeverArrivesIsANamedDeclineNotAHang`、`AdoptInboxTest.AnOfferThatNeverComesTimesOutWithinItsBound` | 等待不设界 → 3 例红(2 例超时 90 s) |
|
||
|
||
设备(红米 `2f7cbe2e`,Adreno 830;Android 集成测试静态 peek 载具 inproc 臂,每个用例一个进程,同主机 ctest;`evidence/b2/itest/`):对照载具旋钮 0 两后端 arena 落 T0(`arena_arm = adopted`,18 次导入),旋钮 2 落 T2(`emulated`),11 例(8 过、3 条车道专属跳过)全绿。
|
||
|
||
| 变异(只改一次性源码拷贝) | 载具 / 用例 | Espryt | Magma |
|
||
|---|---|---|---|
|
||
| M1 resident 写就地落(client 把 T0 store 的 SubData 直接写进 AHB) | `ADrawQueuedBeforeASubDataKeepsItsOwnBytes` | 红 | 红 |
|
||
| M2 respecify 时立即释放导入 | `ARespecifyWithADrawQueuedKeepsThatDrawsStore` | **绿**:与"GL 导入自持内存引用、早放我们的 AHB 引用不释放页"一致,未直接验证;扩展不保证这一引用,栅栏后释放保留 | 红(先排队的绘制读到被释放的存储) |
|
||
| M3 不发布 `kCapAdoptT0` | `TheArenaLandsInTheTierItsLaneDeclares` | 红(emulated) | 红 |
|
||
| M4 CPU 读不做回读往返 | `ReadbackSeesTheLatestCpuWrite`、`GpuWriteIntoTheArenaIsReadBack` | 红 | 红 |
|
||
|
||
brief 点名的 `SubDataAfterAnInFlightDrawReachesTheNextDraw`(M1)与 `RespecifiedVertexArenaKeepsVaoBindings`(M2)在变异下**仍绿**:两者在每次写 / respecify 前都 `glReadPixels`,GPU 已退役,就地写与早释放都看不出来;所以加了上表两个"两次绘制之间不回读"的用例(主机各车道全绿)。
|
||
|
||
MC 26.3(同 app,spawn+shm 与 inproc × 两后端 × 旋钮 0 / 2)ssim 全过、旋钮 0 时 8 个 store(441.5 MiB)全部 T0;create-instancing 1 个 16 MiB store T0、ssim 过;B1 外部路线 b(helper)旋钮 0 同样导入。内存与帧时间见报告。
|
||
|
||
### 不变量
|
||
|
||
- G1:pull 构建符号增 0 减 0,`.text` 不变;原生改动在 `MG_Remote` 与两后端的线路路径(`MOBILEGL_BUILD_DISAGGREGATED` 下);Espryt 的 T0 导入只在 `__ANDROID__`。
|
||
- G5:`ProcessDeferredBufferReleases` 等 P3a / P4a 名单上的函数逐字不动(T0 退役放在它之后的 `ProcessDeferredT0Retires`)。
|
||
- 线上格式:控制协议修订 5(`LinkTerms.adoptTier`);Offer 走 aux 描述符通道,不是新帧。
|
||
|
||
## B2 追加:删除仍绑定在索引绑定点上的 buffer
|
||
|
||
- **缺陷(B2 之前就有,`b9b14c47` 同样复现)**:`BufferState::MarkBufferObjectForDeletion` 解绑索引绑定点,但不动绑定点代次(`NoteBindPointChanged`)。dirty 位 15 / 16 / 17(`Tracker.h`)只看代次,所以窗口不重发,server 一直指着死 buffer 的句柄;槽位以新代次被复用后,下一次遍历窗口的 draw / dispatch 就去要旧代次。
|
||
- Espryt:`Fatal{ProtocolCorruption, "BackendSlotTable.Generation"}`(`Managers.cpp:3507`),整个会话死掉。
|
||
- Magma:不 Fatal,句柄解析不到 wire buffer,声明了该点的 draw / dispatch 被具名拒绝后丢掉(`Magma wire decline [ComputeUniformBufferBinding]`)。
|
||
- 触发面:storage / atomic 在 Espryt 的每次 draw(storage 窗口整窗遍历)和声明该点的程序;uniform 在 compute(整窗遍历)和声明该点的程序,且只在程序没换时才触发,因为 uniform 窗口的 shutter 混入了程序身份;transform feedback 目前没有消费者(位 17 只计算、不发射)。
|
||
- **修法(`4aec5621`)**:删除时解绑的每个索引目标都调用 `NoteBindPointChanged`;transform-feedback 对象切换(`RestoreBoundTransformFeedbackState`,写绑定点的第四处)也调用。都在 `MOBILEGL_PIPE_PUSH` 下:代次计数器本身只存在于 push 构建。其余写入口(`BindBuffer{Base,Range}_State`、`SetNamedTransformFeedbackBinding`)原本就会动代次。
|
||
- **Magma 单体(`7784d6bb`,新用例暴露的旧崩溃)**:程序声明的 uniform block 所在点为空时,`ResolveUniformBufferPayload` 解引用空指针。现在改给一个清零的块,与线路臂一致。
|
||
- **门**:`DeletedBoundBufferScenario` 覆盖 storage / uniform / atomic counter / transform feedback 四个目标。
|
||
- 注册:单体用 ambient `DirectGLES.` / `DirectVulkan.`;Espryt 用 `Split` / `Spawn` / `Tcp`(`mgl_itest_register_split_arms`);Magma 用 `DirectVulkan.<arm>.DeletedBound.`;另有同进程两例的回归 `…LargeArenaAdoptionScenario.GpuWriteThenTheTierCaseInOneProcess`,两后端 × 三臂。
|
||
- red-once(去掉修法):前三个目标在 Espryt 三臂和 inproc 都 abort,报上面那条 Fatal;在 Magma 三臂和 inproc 都失败,dispatch 被丢;同进程对在 Espryt 三臂都 abort。transform feedback 在两后端都保持绿(没有消费者);单体在两后端都保持绿(没有窗口)。
|
||
- red-once(去掉 Magma 空 UBO 分支):`DirectVulkan.DeletedBoundBufferScenario.AUniformBuffer…` 单体 SegFault。
|
||
- 每次拷回原文件、`cmp` 相同、touch 后复绿。
|
||
- **G1**:符号增 0 减 0;`.text` a5b933 → a5ba13(+224 字节),全部来自 `UniformManager::ResolveUniformBufferPayload`(Magma 修复:单体属于 pull 构建,这里改的是真实行为,没有加守卫)。`BufferState.cpp` / `Core.cpp` 按 pull 构建自己的参数预处理,新旧逐字相同。
|