49 KiB
P11:persistent map 采纳档位诚实化(A 包主机半)
2026-09-29。依据 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后、开数据连接前);serverServerSession::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按条目声明;MagmaArena.块与 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 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 §2 B、B0-CROSS-APP.md、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:帧头 magic0x464C474D+ 长度,小端;负载是原生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:设置 → 应用 → 应用管理 → → 省电策略 → 无限制;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(reservedApplyCore7 条,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);JavaPairBindFramesTest、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,HEAD3519a21b的 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 次补测与一个失控的采样子进程(roottr,占满一个核约 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 §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、serverT0Refusals,以及两侧的会话总计)。否则每个 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 → WRefuse{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=MGPCapBit1 << 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;serverrecvHandleFromUnixSocket(校验 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 的设备,vkCmdCopyBufferregion 0 → staging、vkCmdFillBufferregion 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。
- Espryt:导入时
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 只计算、不发射)。
- Espryt:
- 修法(
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 后复绿。
- 注册:单体用 ambient
- G1:符号增 0 减 0;
.texta5b933 → a5ba13(+224 字节),全部来自UniformManager::ResolveUniformBufferPayload(Magma 修复:单体属于 pull 构建,这里改的是真实行为,没有加守卫)。BufferState.cpp/Core.cpp按 pull 构建自己的参数预处理,新旧逐字相同。