Statics, reverse channel, Magma cross-role sites, EGL control plane, registry rekey, build/gate mechanics, the 15 BARRIER_PULLED fields' carrier rulings, and the dual-block mechanism's design recon.
19 KiB
f0-egl — P5f §2.3 EGL / surface 控制面普查(只读)
基线
feat/disaggregated @ 8b68b92c(代码头25fba0d5)。路径在MobileGL/下。 本文件只读普查,不改任何代码。对应 P5c 审计 G4 行(docs/Disaggregated/ROADMAP.md:71), 原计划记给 P6(MobileGL/MG_Remote/CONTRACT-P5C.md:537),P5f 计划 §2.3 (docs/Disaggregated/P5F-WIRE-COMPLETENESS.md:181-186)将其收回 P5f 的包fc。
1. 单槽邮箱机制(现状)
机制本体。 十二个 forwarder 共享同一条过线通道:
ServerLoop.h:220:using ControlWork = MobileGLResult (*)(void* user);—— 一个裸函数指针 加一个void*。注释(ServerLoop.h:217-219、ServerLoop.cpp:784-787)说明为何不是std::function:析构路径不得分配(ID-8)。ServerLoop.cpp:788-793:每个 forwarder 用同一个无捕获 trampoline+[](void* user) { return static_cast<Args*>(user)->Run(); }和指向调用者栈上Args的 指针调用RunOnApplyThread。ServerLoop.cpp:645-707(RunOnApplyThread):m_callerMutex串行化多个投递者(:653); 在m_controlMutex临界区内发布{m_controlWork, m_controlUser, m_controlPending}四元组, 影子位m_controlPosted最后发(:685-694);发布后再敲门铃(:702-704,先发布后敲门是 Doorbell 的硬规则);然后阻塞在m_controlDone.wait(:705),返回m_controlResult(:706)。ServerLoop.cpp:533-568(PumpControlRequest):apply 线程在 drain 批之间取走请求、 执行work(user)(:560)、回填结果并 notify(:561-567)。- 泵点:
ApplyThreadMain主循环 :397-398 与退出前 :453;park 谓词ready(:391-395)把ControlIsPending()列为唤醒条件之一。 - 重入臂:
RunOnApplyThread:651——apply 线程自己投递就内联执行(拆构路径~BackendObject_DirectGLES→ReleaseEGLResources走这里)。 - 无线程时的行为:backend 活着而线程不在 →
Fatal{ApplyThreadNotRunning}(:670-676); backend 已不在 → 返回MOBILEGL_ERR_NOT_INITIALIZED(:678-683)。
邮箱字段在 ServerLoop.h:367-389:m_callerMutex / m_controlMutex /
m_controlPosted(一位影子)/ m_controlDone / m_controlWork / m_controlUser /
m_controlResult / m_controlPending / m_controlFinished。
今天为何能工作:两个角色同进程、同地址空间。函数指针是双方都能执行的代码地址;
&args、major/minor、&handle 指向的栈帧由阻塞握手(:705 的 wait)保证在整个执行期间存活;
单槽够用是因为"verb barrier 同一时刻只留一个 client 线程可运行"(ServerLoop.h:367-368
注释),加上 m_callerMutex 兜底。跨进程后这三样全部失效:代码地址、栈地址、以及"阻塞等待
同进程另一个线程"本身。
P5f 处置建议:邮箱机制在 inproc 下整体保留(它是 apply 线程归属语义的一部分),但在其上
叠一层"帧化"接缝:每个 forwarder 的参数先收敛成一个纯值结构(见 §4 草案),inproc 臂把该
结构放在栈上传指针(现状等价),spawn 臂把同一结构编码进 SurfaceOp 帧。这样 fc 包不动
线程模型,只动载荷的形状。
2. 十二个 forwarder 逐个点名
声明在 ServerLoop.h:459-475,定义在 ServerLoop.cpp:802-1033。下表"载荷"一列标出
值(可直接序列化)/ 栈上指针(指向调用者存储)/ 句柄值(client 进程的 EGL 句柄,
跨进程无意义)。
| # | forwarder(定义行) | 签名 | 语义 | 载荷分类 |
|---|---|---|---|---|
| 1 | ServerInitializeEGLDisplay(ServerLoop.cpp:802-816) |
Bool (EGLDisplay dpy, EGLint* major, EGLint* minor) |
初始化 server 后端的 EGL display,回写主/次版本 | dpy 句柄值;major/minor 是指向 client 栈的 out 指针(:805-806) |
| 2 | ServerCreateEGLWindowSurface(:818-834) |
Bool (EGLSurface surface, const WindowHandle& handle) |
建窗口 surface;成功后 ForgetCurrentTuple()(:829,N-3) |
surface 句柄值;&handle 是指向 client 存储的指针(:821/:832),且 WindowHandle::Handle 本身是 void*(BackendObject.h:561-566) |
| 3 | ServerResizeEGLWindowSurface(:836-850) |
Bool (EGLSurface, Uint32 w, Uint32 h) |
重建 swapchain | 全为值。最干净的一个 |
| 4 | ServerCreateEGLPbufferSurface(:852-870) |
Bool (EGLSurface, EGLint w, EGLint h) |
建 pbuffer;成功后 ForgetCurrentTuple()(:865) |
全为值 |
| 5 | ServerMakeEGLCurrent(:872-918) |
Bool (EGLDisplay, EGLSurface draw, EGLSurface read, EGLContext ctx) |
经 ApplyMakeCurrent(:269-316)三分支:NativeBind / RepeatNoOp / ClientRelease(ID-54);真绑定后重发布 caps 快照(:899-913,R-12) |
四个全是句柄值 |
| 6 | ServerSwapEGLBuffers(:920-933) |
Bool (EGLDisplay, EGLSurface draw) |
直接在 server 上 present | 句柄值。无生产调用者(见 §5 发现 F8) |
| 7 | ServerSetEGLSwapInterval(:935-946) |
void (Int interval) |
设置交换间隔;返回值被丢弃(:945) | 值 |
| 8 | ServerReleaseEGLSurface(:948-965) |
void (EGLSurface) |
释放 surface;ForgetCurrentTupleIfItNames(:960,N-3) |
句柄值 |
| 9 | ServerReleaseEGLResources(:967-989) |
void () |
按契约阻塞(:967-973 注释:之后 MobileGL::Destroy() 立即走);ForgetCurrentTuple()(:984) |
无参数 |
| 10 | ServerInitCapabilities(:991-1007) |
Bool () |
跑 server 端 InitCapabilities 并 PublishCapsSnapshot()(:999-1002) |
无参数 |
| 11 | ServerInitWindowSurface(:1009-1020) |
Bool () |
server 端 InitWindowSurface |
无参数。无任何调用者(见 §5 发现 F8) |
| 12 | ServerSetWindowHandle(:1022-1033) |
void (const WindowHandle& handle) |
把窗口句柄交给 server 后端 | &handle 是指向 client 存储的指针(:1024/:1031),内含 void* |
参数里的函数指针:每一条都是同一个 trampoline(:790-791),它承载的唯一信息是"调哪个
Args::Run",即操作的身份。参数里的栈上 void*:&args 永远存在(:792),其内部再
含 client 栈指针的是 #1(major/minor)、#2 与 #12(handle)。
今天为何能工作:同进程;阻塞握手(RunOnApplyThread:705)使调用者栈帧在整个 RPC 期间
存活,所以 out 指针与引用参数安全。EGL 句柄值两边通用是因为同一个驱动在同一进程里认它们。
P5f 处置建议(共性):
- 函数指针 →
SurfaceOp.kind枚举(操作身份显式化)。 - out 指针 → 帧的 reply 字段(#1 已被
SurfaceReply.eglMajor/eglMinor覆盖)。 WindowHandle→windowKind+nativeToken+width/height值字段;Handle指针在 Android 上是ANativeWindow*(P6-SPAWN-PLAN.md:38),子进程无意义,真窗口到达是 P12, P5f 只在 server 侧落具名拒绝Fatal{UnmigratedSurface, "AndroidNativeWindow@P12"}(P6-CONTRACT-DRAFT.md:43已预写这个 Fatal 名)。- EGL 句柄值 → client 铸造的 token(
DisplayToken/SurfaceToken/ContextToken,P6-CONTRACT-DRAFT.md:41已裁定:client 铸造、从 1 起稠密、session 内不复用,server 持 token → native 映射,永远不见 client 的 EGL 值)。protocol.fbs:189-190的display/surface: ulong已经就是 token 的宽度。
3. client 侧调用面(BackendObject_Remote.cpp)
九个 EGL 虚函数的路由(:196-310),核心纪律是"先转发,再跑基类"(:198-201 注释):
InitializeEGLDisplay(:203-207)→ forwarder #1。CreateEGLWindowSurface(:209-225)→ 先 #12(:214,"唯一告诉 server 窗口是哪扇的途径"), 再 #2(:215),随后DrainPublishedEvents()(:223)——surface-changed 事件走 SEG_EVENT。ResizeEGLWindowSurface(:227-234)→ #3 + drain(:232)。CreateEGLPbufferSurface(:236-243)→ #4 + drain(:241)。MakeEGLCurrent(:245-276)→ #5,成功后PumpControlPlane()+ drain + 刷新格式能力 (:265-274,codex 12:把 R-12 的"第二次到达即失效"闭合在造成它的那次 make-current 上)。SwapEGLBuffers(:278-287)→ 不转发:present 走记录(class-BPresentemitter), 注释明确 #6 "在 P5 没有调用者,这是刻意的"(:284-285)。SetEGLSwapInterval(:289-297)→ #7。覆写的理由:基类会调 class-C 的SetSwapInterval槽而 Fatal(:291-295 注释)。ReleaseEGLSurface(:299-302)→ #8。ReleaseEGLResources(:304-310)→ #9,按契约阻塞。- 另有
InitCapabilities(:106-142)调 #10(:125);InitWindowSurface(:144-149)是 纯 client no-op,不调 #11。
每个 forwarder 调用前的 fence:WaitForApplyBeforeEglForwarder(:189-193),对除
#6 外的每一次转发先 WaitForApplyToCatchUp。理由(:176-187 注释、CONTRACT-P5E §2.5
CONTRACT-P5E.md:177-180):邮箱在 drain 批之间被泵,run-ahead 下控制请求会插进两条
在飞记录之间。client 入口侧:九个虚函数由 MG_Impl/EGLImpl/EGLImpl.cpp 的 EGL 入口调
(如 eglMakeCurrent → :284、eglSwapBuffers → :178、eglSwapInterval → :448、eglTerminate 路径
→ :328)。
P5f 处置建议:这个 fence 在 spawn 下仍然是必要的——socket 控制通道与 SEG_CMD 是两条
通道,控制帧一样可以超过在飞记录。fc 包的帧化必须保留"发帧前先等 appliedSeq 追上
LastPublishedSeq"这一步;它不属于 wire 格式而属于发送纪律,应写进 fc 的契约行。
4. schema 对照与 wire 帧设计草案
4.1 现有 schema(Protocol/protocol.fbs,已定但无人使用)
SurfaceOpKind(:166-175):None / InitializeDisplay / CreateWindowSurface / CreatePbufferSurface / ResizeWindowSurface / ReleaseSurface / MakeCurrent / ReleaseCurrent, 共 7 个有效枚举。WindowKind(:177-184):None / AndroidNativeWindow / X11 / Win32Hwnd / Surfaceless / Pbuffer。SurfaceOp(:186-196):seq, kind, display, surface, windowKind, nativeToken, width, height, swapInterval。nativeToken注释自述"Android transfers the window out of band"。SurfaceReply(:198-204):seq, ok, eglMajor, eglMinor, defaultFb。- 信封:
CtrlMsgunion(:273-284)已含SurfaceOp/SurfaceReply;CtrlEnvelope(:286-288)。
4.2 映射表:12 forwarder → op
| forwarder | 映射 | 缺的枚举 / 字段 |
|---|---|---|
| #1 InitializeEGLDisplay | InitializeDisplay;reply 的 eglMajor/eglMinor 正是 major/minor out 参数的线形 |
无 |
| #2 CreateEGLWindowSurface | CreateWindowSurface + windowKind/nativeToken/width/height |
无(P12 前 server 具名拒绝 AndroidNativeWindow) |
| #3 ResizeEGLWindowSurface | ResizeWindowSurface + width/height |
无 |
| #4 CreateEGLPbufferSurface | CreatePbufferSurface + width/height |
无 |
| #5 MakeEGLCurrent | MakeCurrent;其 ClientRelease 臂(ClassifyEglMakeCurrent,:257-267)落 ReleaseCurrent |
SurfaceOp 缺 readSurface 与 context 两个字段:MakeCurrent 的语义单元是 (dpy, draw, read, ctx) 四元组,schema 只有一个 surface 且没有 context 字段 |
| #6 SwapEGLBuffers | 不配帧:present 走记录(BackendObject_Remote.cpp:278-287) |
— |
| #7 SetEGLSwapInterval | 用 swapInterval 字段 |
缺 SetSwapInterval 枚举 |
| #8 ReleaseEGLSurface | ReleaseSurface |
无 |
| #9 ReleaseEGLResources | 无参数,阻塞 | 缺 ReleaseResources 枚举 |
| #10 InitCapabilities | 不配 SurfaceOp:应答物是 CapsSnapshot 帧(server 已在 :999-1002 发布;P6-CONTRACT-DRAFT.md:88-89 同此裁定) |
—(但 client 需要一个"等下一帧 CapsSnapshot"的同步点,见 4.4) |
| #11 InitWindowSurface | 不配帧:client 侧 no-op(:144-149),无调用者 | — |
| #12 SetWindowHandle | 用 windowKind/nativeToken/width/height |
缺 SetWindowHandle 枚举 |
缺的三个枚举与 P6-SPAWN-PLAN.md:76 的预测("预期缺 SetSwapInterval/ReleaseResources/
SetWindowHandle")逐字吻合,本普查确认。9 个需映射的 forwarder = 7 个现有枚举 + 3 个新增
枚举 - 1 个复用(ReleaseCurrent 由 #5 的释放臂复用),与 P6-CONTRACT-DRAFT.md:87-90 的
"the remaining nine map onto the seven plus §1's additions" 一致。追加枚举必须 append-only
(schema 自述纪律在 :268-272 的 union 注释与 CapsSnapshot 的 deprecated 先例 :115-127)。
另外发现 schema 缺的字段(P6 计划未预写):
SurfaceOp缺readSurface(MakeCurrent 需要 draw≠read 的场景)。SurfaceOp缺contexttoken 字段(MakeCurrent/ReleaseCurrent 都要指名 ctx)。WindowKind与WindowBackend(BackendObject.h:551-559:Android/X11/MetalLayer/Win32) 不互洽:WindowKind缺MetalLayer,多Surfaceless/Pbuffer(那两个不是窗口后端)。 需要一张显式映射表,不能按整数值直译。
4.3 帧设计草案(逐 forwarder)
公共规则:seq 由 client 单调铸造,SurfaceReply.seq 回声;携带 ok=false 等价今天的
Bool false 或 void 丢弃结果;server 侧答不出来等价 ServerBackendOrNull() 为 null
(:798)时的 MOBILEGL_ERR_NOT_INITIALIZED。token 化按 P6-CONTRACT-DRAFT.md:41。
| op 帧 | 载荷 | reply | 阻塞性 |
|---|---|---|---|
InitializeDisplay |
display(token) |
ok + eglMajor/eglMinor |
阻塞(今天返回 Bool) |
CreateWindowSurface |
surface(token) + windowKind + nativeToken + width/height |
ok + defaultFb(见 4.4 的路线冲突) |
阻塞。可并入 #12(见下) |
ResizeWindowSurface |
surface + width/height |
ok + defaultFb |
阻塞 |
CreatePbufferSurface |
surface + width/height(windowKind=Pbuffer) |
ok |
阻塞 |
MakeCurrent |
display + surface(draw) + 新字段 readSurface + 新字段 context |
ok |
阻塞 |
ReleaseCurrent |
display(其余 token 为 0 合法,P6-CONTRACT-DRAFT.md:41 已许) |
ok |
阻塞(今天经同一个 Bool 通道) |
SetSwapInterval(新枚举) |
surface + swapInterval |
可无 reply | 可改 fire-and-forget:今天虽经阻塞邮箱但结果被丢弃(:945) |
ReleaseSurface |
surface |
可无 reply | 可 fire-and-forget(:964 丢结果);但须保序 |
ReleaseResources(新枚举) |
无 | ok |
必须阻塞:ServerLoop.cpp:967-973 的契约,Destroy() 在它返回后立即走 |
SetWindowHandle(新枚举) |
windowKind + nativeToken + width/height |
可无 reply | 可 fire-and-forget,但必须在后续 CreateWindowSurface 之前生效——同一有序通道即保证 |
两个可合并项(供 fc 包定夺,本普查只摆出):① CreateWindowSurface 的 schema 字段已自带
windowKind/nativeToken/width/height,#12 可以并进 #2,SetWindowHandle 枚举只在"先给窗口
后建 surface"的时序必须拆开时才需要——今天的代码就是拆开的(:214-215),保守做法是两者都
落;② ReleaseCurrent 也可用 MakeCurrent + 全 0 token 表达,现有枚举分开更好,保留。
4.4 三个与 schema 相邻的副作用,必须在 fc 的契约里点名
- caps 重发布:#5 的真绑定臂(:899-913)和 #10(:999-1002)都会让 server 主动推一帧
CapsSnapshot。跨进程后这是 server 自发帧;client 今天的吸收点(RPC 返回后PumpControlPlane(),:266-272 与 :130)天然对应"reply 之后泵控制通道"。schema 无需加 字段,但 fc 应写明"MakeCurrent reply 到达后、下一帧 CapsSnapshot 到达前"的语义窗口。 SurfaceReply.defaultFb与 SEG_EVENT 是重复路线:P5c 已把默认 FBO 形状做成 surface-changed 事件,client 在 create/resize reply 后DrainPublishedEvents()(:223/:232/:241)。defaultFb字段(:154-160, :203)若启用是第二载体。建议 fc 二选一 并写明,不要两个都活(CapsSnapshot的 deprecated 字段教训,:101-127,就是两种拼法 并存的代价)。- server 死亡的具名化:今天的
Fatal{ApplyThreadNotRunning}(:670-676)在 spawn 下 对应FatalCode::ServerCrashed(:240 已分配,P6-CONTRACT-DRAFT.md:44已点名 P6 是 第一个能产生它的阶段)。fc 的 client 侧发送路径要把"发不出去/等不到 reply"接到这个码上。
5. 其他发现
- F8:两个 forwarder 没有生产调用者。
ServerSwapEGLBuffers只有单测调用 (MG_Test/Wire/ServerLoopTest.cpp:1549、:1568);ServerInitWindowSurface全树无调用者 (client 的InitWindowSurface是纯 no-op,BackendObject_Remote.cpp:144-149)。 处置建议:fc 不为它们配帧(§4.2 已按此映射);ServerLoop.h:469/:474的声明与 实现在 spawn 世界只剩 inproc 测试价值,fc 可具名删除或标注 inproc-only。 - F9:文档行号漂移。
ROADMAP.md:71(P5c 审计 G4 行)引ServerLoop.cpp:543-598, 当前 HEAD 的邮箱是:533-568(PumpControlRequest)+:645-707(RunOnApplyThread);CONTRACT-P5C.md:537把控制面记给 P6,P5f 计划 §2.3(P5F-WIRE-COMPLETENESS.md:181-186) 已改记 P5f 的包fc(P5F-WIRE-COMPLETENESS.md:252)。处置建议:fc 落地时顺手刷新 ROADMAP G4 行的行号与去向。 - F10:
ServerLoop.h:436与ServerLoop.cpp:968引的EGLImpl.cpp行号(:284/:326) 与当前MG_Impl/EGLImpl/EGLImpl.cpp基本相符(:284 是 eglMakeCurrent;ReleaseEGLResources 实际在 :328)。仅为记录,不构成缺陷。
6. 结论汇总
- 十二个 forwarder 的过线载荷里,真正"不可序列化"的只有两类:trampoline 函数指针
(操作身份 →
SurfaceOp.kind)和三处 client 栈/堆指针(#1 的 major/minor → reply 字段; #2/#12 的WindowHandle*→ windowKind+nativeToken 值字段,Android 的ANativeWindow*归 P12,P5f 只落具名拒绝)。其余参数全是值或 client 句柄值(→ token)。 - schema 缺口清点确认:缺 3 个枚举(SetSwapInterval / ReleaseResources / SetWindowHandle,与 P6-SPAWN-PLAN 的预测逐字一致)+ 缺 2 个字段(MakeCurrent 的 readSurface 与 context token)+ WindowKind/WindowBackend 需要显式映射表。
- #6、#10、#11 不配 SurfaceOp 帧:present 走记录、InitCapabilities 走 CapsSnapshot、 InitWindowSurface 无调用者。需要帧化的是 9 个 forwarder、10 种 op(含 ReleaseCurrent)。