文档概述
说明:
1.文章由移远通信技术股份有限公司提供
2.以下内容包含了个人理解,仅供参考,如有不合理处,请联系笔者修改

引言:标准系统启动与孵化问题的排查往往令人望而生畏——链条长、模块多、现象相似但根因各异。本文提供一套经过实战检验的排查方法论,通过「先定链、再定阶段、最后定代码点」的三步法,帮助开发者快速将复杂问题收敛到具体模块和失败点。无论您是面对 SA 起不来、appspawn 超时还是 sandbox 挂载异常,这套方法都能显著提升排查效率,避免在错误方向上浪费时间。

1. 说明与范围

摘要:本文针对标准系统启动与孵化问题的排查,提出一套高效的方法论:首先确定问题发生在哪条链路(能力链、孵化链或sandbox链),然后定位具体阶段(服务拉起、进程ready、超时等),最后聚焦到关键代码点。遵循“先定链、再定阶段、最后定代码点”的顺序,可以快速将复杂问题收敛到具体模块和失败点,避免在错误方向上浪费时间。

前五篇解决的是“系统怎么工作”,第六篇只解决一件事:

出了问题以后,应该先看哪条链,怎么把问题快速收敛到具体阶段、具体模块、具体失败点。

这篇不做模块原理复述,直接围绕三类高频问题展开:

  • SA 起不来
  • appspawn 回包超时
  • sandbox 挂载缺失 / 路径替换错误

如果只保留一句话,可以这样理解:

标准系统的启动和孵化问题,最容易误判的地方不是“代码写错了”,而是把“进程存在”误当成“系统 ready”,把下游症状误当成上游根因。第六篇的目标,就是把这类误判先剪掉。

0. 排查总览图

下图展示了“先定链、再定阶段、最后定代码点”的完整排查决策路径,帮助读者快速定位问题所在:

flowchart TD
    Start["开始排查"] --> Step1["第一步:定链"]
    
    Step1 --> Chain1["能力链<br>(SA 起不来)"]
    Step1 --> Chain2["孵化链<br>(appspawn 回包超时)"]
    Step1 --> Chain3["sandbox 链<br>(挂载缺失/变量替换错误)"]
    
    Chain1 --> Stage1_1["阶段:服务拉起"]
    Chain1 --> Stage1_2["阶段:进程 ready"]
    Chain1 --> Stage1_3["阶段:超时"]
    
    Chain2 --> Stage2_1["阶段:消息处理"]
    Chain2 --> Stage2_2["阶段:fork/child 执行"]
    Chain2 --> Stage2_3["阶段:回执/回包"]
    
    Chain3 --> Stage3_1["阶段:pre-unshare"]
    Chain3 --> Stage3_2["阶段:post-unshare"]
    Chain3 --> Stage3_3["阶段:mount/namespace"]
    
    Stage1_1 --> Code1_1["代码点:init service"]
    Stage1_1 --> Code1_2["代码点:samgr ready"]
    
    Stage1_2 --> Code1_3["代码点:safwk profile 解析"]
    Stage1_2 --> Code1_4["代码点:trust profile 过滤"]
    Stage1_2 --> Code1_5["代码点:so 装载/Start()"]
    
    Stage1_3 --> Code1_6["代码点:SendCheckLoadedMsg()"]
    Stage1_3 --> Code1_7["代码点:Publish()/AddSystemAbility()"]
    
    Stage2_1 --> Code2_1["代码点:ProcessSpawnReqMsg()"]
    Stage2_1 --> Code2_2["代码点:消息解码"]
    
    Stage2_2 --> Code2_3["代码点:WaitChildTimeout()"]
    Stage2_2 --> Code2_4["代码点:child hook/sandbox"]
    
    Stage2_3 --> Code2_5["代码点:ProcessChildResponse()"]
    Stage2_3 --> Code2_6["代码点:ProcessChildFdCheck()"]
    
    Stage3_1 --> Code3_1["代码点:StagedMountPreUnShare()"]
    Stage3_1 --> Code3_2["代码点:required.system-const"]
    
    Stage3_2 --> Code3_3["代码点:StagedMountPostUnshare()"]
    Stage3_2 --> Code3_4["代码点:SetSandboxPackageNameConfig()"]
    
    Stage3_3 --> Code3_5["代码点:MountSandboxConfigs()"]
    Stage3_3 --> Code3_6["代码点:ReplaceVariable()"]
    
    Code1_1 --> Conclusion1["结论:进程没起 → init"]
    Code1_2 --> Conclusion2["结论:samgr 不可用 → samgr ready"]
    Code1_3 --> Conclusion3["结论:profile 解析失败 → safwk"]
    Code1_4 --> Conclusion4["结论:SA 被过滤 → trust profile"]
    Code1_5 --> Conclusion5["结论:so/Start 失败 → safwk 内部"]
    Code1_6 --> Conclusion6["结论:超时 → SendCheckLoadedMsg()"]
    Code1_7 --> Conclusion7["结论:未注册 → Publish()"]
    
    Code2_1 --> Conclusion8["结论:消息未处理 → 连接/参数"]
    Code2_2 --> Conclusion9["结论:解码失败 → 协议"]
    Code2_3 --> Conclusion10["结论:child 超时 → sandbox/hook"]
    Code2_4 --> Conclusion11["结论:child crash → 早期初始化"]
    Code2_5 --> Conclusion12["结论:回执异常 → pipe/child 状态"]
    Code2_6 --> Conclusion13["结论:失败码 → child 显式错误"]
    
    Code3_1 --> Conclusion14["结论:pre-unshare 缺失 → 依赖组"]
    Code3_2 --> Conclusion15["结论:system-const 缺失 → 基础目录"]
    Code3_3 --> Conclusion16["结论:post-unshare 缺失 → 变量/权限"]
    Code3_4 --> Conclusion17["结论:package-name 未命中 → 条件"]
    Code3_5 --> Conclusion18["结论:mount 失败 → namespace/root"]
    Code3_6 --> Conclusion19["结论:变量未替换 → handler/二次替换"]
    
    Conclusion1 --> End["问题收敛到具体模块"]
    Conclusion2 --> End
    Conclusion3 --> End
    Conclusion4 --> End
    Conclusion5 --> End
    Conclusion6 --> End
    Conclusion7 --> End
    Conclusion8 --> End
    Conclusion9 --> End
    Conclusion10 --> End
    Conclusion11 --> End
    Conclusion12 --> End
    Conclusion13 --> End
    Conclusion14 --> End
    Conclusion15 --> End
    Conclusion16 --> End
    Conclusion17 --> End
    Conclusion18 --> End
    Conclusion19 --> End

    style Start fill:#f9f,stroke:#333,stroke-width:2px
    style Step1 fill:#bbf,stroke:#333,stroke-width:2px
    style Chain1 fill:#bfb,stroke:#333
    style Chain2 fill:#bfb,stroke:#333
    style Chain3 fill:#bfb,stroke:#333
    style End fill:#f96,stroke:#333,stroke-width:2px

使用说明:

  1. 先定链:根据现象选择左侧三条主链之一。
  2. 再定阶段:沿着选定链向下,确定问题发生在哪个阶段。
  3. 最后定代码点:找到对应阶段的关键函数,快速定位到具体失败点。
  4. 结论收敛:根据代码点得出具体结论,将问题范围压缩到最小。

此图将正文中的文字描述可视化,遵循“先定链、再定阶段、最后定代码点”的核心方法论,帮助读者建立清晰的排查思维框架。

2. 先建立统一判断框架

排查前先把下面这四个判断分开,按顺序执行:

flowchart TD
    Start["开始排查"] --> Step1["第一步:确认进程是否存在<br>(不等于 Ready)"]
    
    Step1 -->|进程不存在| ProcNotExist["进程不存在<br>→ 检查 init/service 配置"]
    Step1 -->|进程存在| Step2["第二步:确认进程是否 ready<br>(各子系统定义不同)"]
    
    Step2 -->|未 ready| NotReady["进程未 ready<br>→ 检查对应子系统 ready 标志"]
    Step2 -->|已 ready| Step3["第三步:定位问题发生在哪条链<br>(三选一)"]
    
    Step3 --> Chain1["能力链<br>SA 起不来"]
    Step3 --> Chain2["孵化链<br>appspawn 回包超时"]
    Step3 --> Chain3["sandbox 链<br>挂载缺失/变量替换错误"]
    
    Chain1 --> Step4_1["第四步:区分父进程还是子进程<br>→ 检查进程关系树"]
    Chain2 --> Step4_2["第四步:区分父进程还是子进程<br>→ 检查 appspawn parent/child"]
    Chain3 --> Step4_3["第四步:区分父进程还是子进程<br>→ 检查 sandbox 执行上下文"]
    
    Step4_1 --> Conclusion1["进入对应链详细排查"]
    Step4_2 --> Conclusion2["进入对应链详细排查"]
    Step4_3 --> Conclusion3["进入对应链详细排查"]
    
    style Start fill:#bbf,stroke:#333,stroke-width:2px
    style Step1 fill:#e1f5fe,stroke:#333
    style Step2 fill:#e1f5fe,stroke:#333
    style Step3 fill:#e1f5fe,stroke:#333
    style ProcNotExist fill:#ffebee,stroke:#333
    style NotReady fill:#ffebee,stroke:#333
    style Conclusion1 fill:#f3e5f5,stroke:#333
    style Conclusion2 fill:#f3e5f5,stroke:#333
    style Conclusion3 fill:#f3e5f5,stroke:#333

图2:统一判断框架流程图 - 展示了排查前必须明确的四个关键判断步骤及其决策路径。

2.1 第一步:确认进程是否存在(不等于 Ready)

使用 ps -ef | grep <进程名>cat /proc/<pid>/status 确认目标进程是否被 init 拉起。注意:进程存在 ≠ 系统 ready,这只是排查的起点。

2.2 第二步:确认进程是否 ready(各子系统定义不同)

各子系统有各自的 ready 标志:

  • samgr readybootevent.samgr.ready 且 binder 上能拿到代理
  • SA readyPublish() 成功且 samgr 完成 AddSystemAbility()
  • appspawn ready:socket 建立,STAGE_SERVER_PRELOAD 完成
  • sandbox ready:单次孵化中 mount/unshare/root 切换已完成

2.3 第三步:定位问题发生在哪条链(三选一)

根据现象快速定位到三条主链之一:

  1. 能力链:SA 起不来,服务能力不可用
  2. 孵化链:appspawn 回包超时,应用启动失败
  3. sandbox 链:挂载缺失或变量替换错误

2.4 第四步:区分父进程还是子进程

明确问题发生在哪个进程上下文:

  • 父进程问题:多在消息处理、状态机、超时控制
  • 子进程问题:多在初始化、hook、sandbox、业务入口
flowchart TD
    Start["开始排查"] --> Step1["第一步:确认进程是否存在<br>(不等于 Ready)"]
    
    Step1 -->|进程不存在| ProcNotExist["进程不存在<br>→ 检查 init/service 配置"]
    Step1 -->|进程存在| Step2["第二步:确认进程是否 ready<br>(各子系统定义不同)"]
    
    Step2 -->|未 ready| NotReady["进程未 ready<br>→ 检查对应子系统 ready 标志"]
    Step2 -->|已 ready| Step3["第三步:定位问题发生在哪条链<br>(三选一)"]
    
    Step3 --> Chain1["能力链<br>SA 起不来"]
    Step3 --> Chain2["孵化链<br>appspawn 回包超时"]
    Step3 --> Chain3["sandbox 链<br>挂载缺失/变量替换错误"]
    
    Chain1 --> Step4_1["第四步:区分父进程还是子进程<br>→ 检查进程关系树"]
    Chain2 --> Step4_2["第四步:区分父进程还是子进程<br>→ 检查 appspawn parent/child"]
    Chain3 --> Step4_3["第四步:区分父进程还是子进程<br>→ 检查 sandbox 执行上下文"]
    
    Step4_1 --> Conclusion1["进入对应链详细排查"]
    Step4_2 --> Conclusion2["进入对应链详细排查"]
    Step4_3 --> Conclusion3["进入对应链详细排查"]
    
    style Start fill:#bbf,stroke:#333,stroke-width:2px
    style Step1 fill:#e1f5fe,stroke:#333
    style Step2 fill:#e1f5fe,stroke:#333
    style Step3 fill:#e1f5fe,stroke:#333
    style ProcNotExist fill:#ffebee,stroke:#333
    style NotReady fill:#ffebee,stroke:#333
    style Conclusion1 fill:#f3e5f5,stroke:#333
    style Conclusion2 fill:#f3e5f5,stroke:#333
    style Conclusion3 fill:#f3e5f5,stroke:#333

图2:统一判断框架流程图 - 展示了排查前必须明确的四个关键判断步骤及其决策路径。

3. 快速分诊表

先用现象对问题做第一轮分类。

现象 优先怀疑链路 第一检查点
GetSystemAbility 拿不到对象 samgr / safwk / SA 先区分 samgr 未 ready 还是 SA 未 Publish()
LoadSystemAbility 长时间 pending 或超时 samgr 按需状态机 SendCheckLoadedMsg() 是否触发超时
SA 进程存在但能力不可见 safwk -> Publish -> samgr 看 so 是否装载、Publish() 是否走到
appspawn 有进程但客户端收不到成功回包 appspawn parent/child WaitChildTimeout()ProcessChildResponse()
child fork 成功但很快退出 sandbox / child hook / child entry 看 pipe 回执结果和 APPSPAWN_CHILD_CRASH
应用私有目录或系统目录挂载缺失 sandbox staged mount 先区分 pre-unshare 还是 post-unshare
路径里保留 <PackageName> 等占位符未替换 sandbox_cfgvar 看变量 handler 是否命中

4. 第一类问题:SA 起不来

sequenceDiagram
    participant Client as 客户端
    participant SAMgr as samgr
    participant SAFwk as safwk
    participant SAProc as SA进程
    participant Init as init

    Note over Client,SAProc: SA启动流程(常驻模式)
    Init->>SAProc: 拉起sa_main进程
    SAProc->>SAFwk: 解析profile
    SAFwk->>SAProc: 装载so库
    SAProc->>SAProc: 执行Start()
    SAProc->>SAMgr: Publish()注册
    SAMgr->>SAMgr: AddSystemAbility()
    SAMgr-->>Client: SA可用通知
    
    Note over Client,SAProc: SA启动流程(按需模式)
    Client->>SAMgr: LoadSystemAbility()
    SAMgr->>SAFwk: 触发加载
    SAFwk->>Init: 请求启动进程
    Init->>SAProc: 拉起进程
    SAProc->>SAFwk: 装载so并Start()
    SAFwk->>SAMgr: 通知加载完成
    SAMgr-->>Client: 回调成功

图1:SA启动流程时序图 - 展示了常驻SA和按需SA两种模式的完整启动链路,帮助理解各模块间的交互顺序。

这类问题表面现象通常是:

  • GetSystemAbility() 返回空;
  • LoadSystemAbility() 回调失败或超时;
  • 某个 SA 进程存在,但服务能力对外不可见;
  • 某个 profile 明明配了,能力仍然没有注册。

4.1 第一步:按正确顺序排查(避免盲目深挖)

这类问题不要一上来就盯 samgr

正确顺序应该是:

  1. init 是否把目标进程拉起来
  2. samgr 是否已 ready
  3. safwk 是否成功解析对应 profile
  4. trust profile 是否把目标 SA 过滤掉
  5. 对应 so 是否真的装载成功
  6. Start() 是否执行
  7. Publish() 是否执行
  8. samgr 是否完成 AddSystemAbility()

排查一定要按这个顺序,不要跳。

4.2 第二步:区分常驻与按需两种模式

SA 起不来,先问自己是下面哪一种。

4.2.1 常驻 SA

特征是:

  • init 直接拉起 sa_main /system/profile/<proc>.json
  • profile 里是 run-on-create

这类问题更偏:

  • init service 没起
  • profile 解析失败
  • so 装载失败
  • Publish() 没执行

4.2.2 按需 SA

特征是:

  • 调用方通过 LoadSystemAbility() 触发
  • samgr 状态机负责拉起

这类问题更偏:

  • samgr 状态机没有正确进入 load
  • StartingSystemProcess() 没成功
  • 目标进程起来了但未在超时窗口内 Publish()

4.3 第三步:聚焦关键代码断点(按需加载场景)

按需加载相关的关键代码断点非常集中。

第一组,看 load 是否真正进入调度:

  • SystemAbilityManager::LoadSystemAbility()
  • SystemAbilityManager::DoLoadSystemAbility()
  • SystemAbilityManager::StartingSystemProcess()

第二组,看超时是否发生:

  • SystemAbilityManager::SendCheckLoadedMsg()

这段代码里会明确打出:

  • loaded
  • handle for SA
  • load timeout

如果已经进入 SendCheckLoadedMsg() 的超时分支,说明问题已经不是“客户端没调到”,而是“目标 SA 没在窗口内注册完成”。

第三组,看成功回调是否真正发出:

  • SystemAbilityManager::NotifySystemAbilityLoaded()

如果这一步没走到,调用方拿不到成功就不是偶然。

4.4 第四步:排查高概率根因(按优先级)

4.4.1 samgr 自己还没 ready

表现:

  • safwk 进程存在,但始终卡在拿 samgr 代理前后
  • LoadSystemAbility() 提前失败或长时间无结果

判断原则:

  • 不要只看 bootevent.samgr.ready
  • 更可靠的是看 binder 上能不能真正拿到 samgr 代理

4.4.2 Profile 配错了

表现:

  • 进程启动了,但没装对应 SA
  • run-on-create 没生效
  • start-on-demand 或依赖字段不符合预期

重点检查:

  • process
  • saId
  • libPath
  • runOnCreate
  • startOnDemand
  • dependSa
  • dependTimeout

4.4.3 Trust Profile 把 SA 裁掉了

表现:

  • Profile 里有 SA
  • safwk 进程也起来了
  • 但某个 SA 始终不出现在后续装载流程里

这类问题很容易漏看,因为从表面看像“代码根本没执行到”,实际上是被 Trust Profile 先过滤掉了。

4.4.4 so 装载或 Start() 失败

表现:

  • 进程起来
  • samgr ready
  • 但就是没有 Publish()

这类问题本质已经不在 samgr,而在目标 safwk 进程内部。

4.4.5 Publish() 没走到

这是最容易误判的一类。

要记住:

  • 进程存在,不等于 SA ready
  • so 装载成功,不等于 SA ready
  • Start() 调过,不等于 SA ready

真正的 ready 判据只有一个:

  • Publish() 成功,且 samgr 完成 AddSystemAbility()

4.5 结论化判断

可以直接按下面的逻辑收敛:

  • 进程没起来:先回 init service 控制
  • 进程起来但 samgr 代理不可用:先回 samgr ready
  • 进程起来且 samgr 可用,但没注册:先回 Safwk profile / trust / so / Publish
  • LoadSystemAbility() 超时:先看 SendCheckLoadedMsg() 后面那段超时路径

5. 第二类问题:appspawn 回包超时

sequenceDiagram
    participant Client as 客户端
    participant AppSpawn as appspawn父进程
    participant Child as 子进程
    participant Sandbox as sandbox模块

    Client->>AppSpawn: 发送孵化请求
    AppSpawn->>AppSpawn: ProcessSpawnReqMsg()
    AppSpawn->>AppSpawn: 消息解码验证
    AppSpawn->>Child: fork()创建子进程
    
    Note over AppSpawn,Child: 关键超时点
    AppSpawn->>AppSpawn: WaitChildTimeout()开始计时
    
    Child->>Sandbox: 执行sandbox准备
    Sandbox->>Child: 挂载namespace
    Child->>Child: 执行早期hook
    Child->>AppSpawn: NotifyResToParent()回执
    
    AppSpawn->>AppSpawn: ProcessChildResponse()
    AppSpawn->>AppSpawn: ProcessChildFdCheck()
    
    alt 成功
        AppSpawn->>AppSpawn: AddSpawnedProcess()
        AppSpawn-->>Client: SendResponse(0, pid)
    else 超时
        AppSpawn->>Child: kill(SIGKILL)
        AppSpawn-->>Client: APPSPAWN_SPAWN_TIMEOUT
    else 失败码
        AppSpawn-->>Client: 返回child错误码
    end

图2:appspawn孵化流程时序图 - 展示了从请求到回包的完整流程,标明了三个关键失败点(超时、crash、失败码)。

这类问题表面现象通常是:

  • 客户端请求发出后没有成功回包;
  • appspawn 进程存在,但应用起不来;
  • child 进程可能存在,也可能立刻被杀;
  • 最终返回 APPSPAWN_SPAWN_TIMEOUT 或直接表现为启动超时。

5.1 正确的排查顺序

这类问题要沿着“消息 -> fork -> child 回执 -> 父进程回包”这条链查。

推荐顺序:

  1. 连接是否真正建立
  2. 消息是否解码成功
  3. ProcessSpawnReqMsg() 是否执行
  4. 是否成功进入 fork / clone
  5. child 是否通过 pipe 回执结果
  6. 父进程是否执行 ProcessChildResponse()
  7. 是否触发 WaitChildTimeout()

5.2 三个关键失败点

5.2.1 Child 直接 Crash

关键位置:

  • APPSPAWN_CHILD_CRASH

appspawn_service.c 里,如果连续失败次数超过阈值,甚至会触发:

  • Continuous failures in spawning the app, restart appspawn

这类问题通常说明:

  • Child 已经走到 fork 之后;
  • 但在很早期就崩了;
  • 根因大概率在 Child Hook、Sandbox 或业务入口早期初始化。

5.2.2 Child 回执超时

关键位置:

  • WaitChildTimeout()

这里的行为非常明确:

  • 记录 WaitChildTimeout
  • 必要时 DumpSpawnStack(pid)
  • kill(pid, SIGKILL)
  • 给客户端回 APPSPAWN_SPAWN_TIMEOUT

只要走到这个函数,问题就不是“客户端没发请求”,而是:

  • Child 没能在规定时间内把结果通过 pipe 回给父进程。

这类问题优先看:

  • Sandbox mount 卡住
  • Namespace / root 切换失败
  • child hook 卡死
  • 业务入口前初始化阻塞

5.2.3 child 返回了失败码

关键位置:

  • ProcessChildFdCheck()

这里会从 pipe 里读子进程写回的 result

如果 result != 0,父进程会直接:

  • 把这个错误码回给客户端
  • 删除本次 AppSpawningCtx

这类问题说明:

  • 父子通信本身是通的;
  • 只是 child 在初始化过程中显式失败。

这时应该顺着 child execute 阶段往下查,而不是盯 socket。

5.3 成功链长什么样?

理解成功链,反而更容易定位超时。

正常路径是:

  1. ProcessSpawnReqMsg()
  2. RunAppSpawnProcessMsg()
  3. child 执行早期 hook
  4. child 调 NotifyResToParent()
  5. 父进程 watcher 进入 ProcessChildResponse()
  6. ProcessChildFdCheck() 读到 result == 0
  7. AddSpawnedProcess()
  8. SendResponse(..., 0, pid)

如果最终没回成功,问题必然断在这条链中间。

5.4 高概率根因

5.4.1 消息阶段就失败

表现:

  • 没走到 fork
  • 很快失败返回

这类问题多半在:

  • 请求参数不合法
  • 权限或模式校验失败
  • Socket 连接上下文异常

5.4.2 child execute 阶段卡住

表现:

  • WaitChildTimeout()
  • child 进程短时间内还活着,随后被 kill

这类问题最常见,优先看:

  • sandbox mount
  • unshare()
  • pivot_root
  • 变量展开后的路径异常

5.4.3 连接被关闭

关键位置:

  • OnClose()

如果连接提前关闭,Appspawn 会清理不完整消息,并把该连接对应的孵化上下文一并收掉。

这类问题表面像“服务端没回包”,但根因可能是:

  • 客户端侧连接提前断开;
  • 半包、超时或异常关闭触发了服务端清理。

5.5 结论化判断

可以直接按下面逻辑收敛:

  • 没进入 ProcessSpawnReqMsg():先看消息和连接
  • 进入了但没 fork:先看参数与 pre-fork 检查
  • fork 了但 WaitChildTimeout():先看 child execute,尤其 sandbox
  • ProcessChildFdCheck() 收到非 0:先看 child 初始化里的显式错误返回

6. 第三类问题:sandbox 挂载缺失或变量替换错误

flowchart TD
    Start["开始sandbox准备"] --> CheckJSON["检查JSON命中"]
    CheckJSON --> PreUnshare{"pre-unshare阶段"}
    
    PreUnshare -->|是| PreMount["执行StagedMountPreUnShare()<br>挂载required.system-const"]
    PreUnshare -->|否| PostUnshare{"post-unshare阶段"}
    
    PreMount --> Unshare["执行unshare()<br>创建namespace"]
    Unshare --> RootCreate["SandboxRootFolderCreate()<br>创建根目录"]
    
    PostUnshare --> PostMount["执行StagedMountPostUnshare()<br>挂载app-variable/package-name"]
    PostMount --> VarCheck{"变量替换检查"}
    
    VarCheck -->|正常| Permission["检查权限位与gid<br>SpawnPrepareSandboxCfg()"]
    VarCheck -->|失败| VarError["变量替换失败<br>检查ReplaceVariable()"]
    
    Permission --> MountAll["MountSandboxConfigs()<br>执行所有挂载"]
    MountAll --> ChangeDir["ChangeCurrentDir()<br>切换工作目录"]
    ChangeDir --> Success["sandbox准备成功"]
    
    VarError --> Fail1["失败:变量未命中handler"]
    Permission --> Fail2["失败:权限位未命中"]
    MountAll --> Fail3["失败:mount/namespace错误"]
    
    Fail1 --> End["失败结束"]
    Fail2 --> End
    Fail3 --> End
    Success --> End
    
    style Start fill:#bbf,stroke:#333,stroke-width:2px
    style Success fill:#bfb,stroke:#333
    style Fail1 fill:#fbb,stroke:#333
    style Fail2 fill:#fbb,stroke:#333
    style Fail3 fill:#fbb,stroke:#333

图3:sandbox挂载决策流程图 - 展示了从JSON命中到最终挂载的完整决策路径,标明了各阶段可能失败的点。

这类问题通常表现为:

  • 进程起来了,但目录视图不对;
  • 某些系统目录或应用私有目录没挂进去;
  • 路径里残留 <PackageName><currentUserId> 这类占位符;
  • child 在 mount/root 切换阶段失败;
  • 某些权限明明配置了,但对应 gid 或挂载没生效。

6.1 先区分是哪一类缺失

6.1.1 pre-unshare 缺失

这类问题通常更偏:

  • required.system-const
  • 依赖组 staged mount

重点看:

  • StagedMountSystemConst()
  • StagedMountPreUnShare()

6.1.2 post-unshare 缺失

这类问题通常更偏:

  • app-variable
  • package-name
  • permission
  • 扩展配置

重点看:

  • StagedMountPostUnshare()
  • SetSandboxPackageNameConfig()
  • SetSandboxPermissionConfig()

先把这两类分开,定位速度会快很多。

6.2 先看 JSON 命中了没有

很多挂载问题不是执行失败,而是根本没命中对应 section。

先检查三件事:

  1. 当前进程到底选中了哪类 sandbox
  2. 对应 JSON 是否预加载成功
  3. 当前请求是否命中了 package-name/permission/spawn-flag 条件

特别是 permission 这一层,不是“配置里写了就会执行”,而是必须先命中权限位。

6.3 权限位和 gid 问题

如果现象是:

  • 路径没开出来;
  • gid 没补上;
  • 某些访问能力缺失;

优先看:

  • SpawnPrepareSandboxCfg()
  • AppendPermissionGid()
  • AppendPackageNameGids()
  • UpdateMsgFlagsWithPermission()

这段链路决定的是:

  • permission 是否命中
  • gid 是否追加到 TLV_DAC_INFO
  • 某些权限是否反向改写消息 flag

也就是说,很多“挂载为什么没出来”的问题,根因其实是上游 permission 位根本没命中。

6.4 变量替换错误

变量替换问题优先看:

  • ReplaceVariable()
  • GetSandboxRealVar()
  • AddVariableReplaceHandler()

需要特别注意三类情况。

6.4.1 变量根本没有命中 Handler

源码里这类情况会直接打出:

  • ReplaceVariable var 'xxx' no match variable

一旦看到这类日志,问题就非常明确:

  • 不是 Mount 逻辑错;
  • 而是占位符没有被任何变量处理器接住。

6.4.2 变量是二次替换场景

在依赖组路径场景下,如果第一次替换后结果里仍然有 <...>,代码会做二次替换。

所以某些路径看起来“第一次替换已经对了”,但最终仍异常时,要检查是不是 depNode 场景。

6.4.3 <lib><param:...> 是特殊分支

这两个不是走普通变量 handler 表。

  • <param:...> 走参数替换逻辑
  • <lib> 直接替换成 APPSPAWN_LIB_NAME

这类变量如果看错分支,会误以为普通 handler 没注册。

6.5 mount / namespace 失败

这类问题的关键执行链在:

  • MountSandboxConfigs()

主顺序非常固定:

  1. InitSandboxContext()
  2. StagedMountPreUnShare()
  3. unshare(context->sandboxNsFlags)
  4. SandboxRootFolderCreate()
  5. StagedMountPostUnshare()
  6. ChangeCurrentDir()

因此:

  • unshare() 失败,问题在 namespace flag 或前置环境
  • SandboxRootFolderCreate() 失败,问题在根目录骨架或 mount 语义
  • ChangeCurrentDir() 失败,问题已接近 pivot_root / root 切换

6.6 最容易忽略的上游根因

很多 sandbox 问题最后落在 mount 失败,但根因不一定在 sandbox 自身。

优先排除:

  • 基础分区还没准备好
  • 关键目录本身不存在
  • appspawn preload 没完成
  • 当前请求使用了错误的 sandbox 类型
  • permission 位和 package-name 条件未命中

如果这些上游条件不成立,后面的 mount 失败只是表象。

7. 统一的排查策略

flowchart TD
    Start["发现问题"] --> Step1["第一步:定链"]
    
    Step1 --> SAChain["能力链<br>SA起不来"]
    Step1 --> AppChain["孵化链<br>appspawn超时"]
    Step1 --> SandboxChain["sandbox链<br>挂载/变量错误"]
    
    SAChain --> SA_Step1["检查进程存在"]
    SA_Step1 --> SA_Step2["检查samgr ready"]
    SA_Step2 --> SA_Step3["检查safwk profile"]
    SA_Step3 --> SA_Step4["检查trust profile"]
    SA_Step4 --> SA_Step5["检查so装载/Start()"]
    SA_Step5 --> SA_Step6["检查Publish()"]
    
    AppChain --> App_Step1["检查连接/消息"]
    App_Step1 --> App_Step2["检查fork执行"]
    App_Step2 --> App_Step3["检查child回执"]
    App_Step3 --> App_Step4["检查超时清理"]
    
    SandboxChain --> SB_Step1["区分pre/post-unshare"]
    SB_Step1 --> SB_Step2["检查JSON命中"]
    SB_Step2 --> SB_Step3["检查权限位/gid"]
    SB_Step3 --> SB_Step4["检查变量替换"]
    SB_Step4 --> SB_Step5["检查mount/namespace"]
    
    SA_Step6 --> SA_Conclusion["结论:具体代码点"]
    App_Step4 --> App_Conclusion["结论:具体代码点"]
    SB_Step5 --> SB_Conclusion["结论:具体代码点"]
    
    SA_Conclusion --> Final["问题收敛"]
    App_Conclusion --> Final
    SB_Conclusion --> Final
    
    style Start fill:#f9f,stroke:#333,stroke-width:2px
    style Step1 fill:#bbf,stroke:#333,stroke-width:2px
    style Final fill:#bfb,stroke:#333,stroke-width:2px

图7:统一排查策略执行流程图 - 将"先定链、再定阶段、最后定代码点"的方法论可视化,为三类问题提供清晰的排查路径。

如果现场信息有限,可以按下面的固定顺序排查。

7.1 先定链

先判断问题属于:

  • 能力链
  • 孵化链
  • sandbox 链

不要一开始就按模块名猜。

7.2 再定阶段

再判断断在哪个阶段:

  • service 没拉起
  • 进程拉起但未 ready
  • ready 之前超时
  • ready 之后功能异常

7.3 再定边界

再判断是哪个 handoff 断了:

  • init -> samgr
  • samgr -> safwk
  • safwk -> Publish
  • init -> appspawn
  • parent -> child
  • sandbox pre-fork -> child execute

只要 handoff 定准,问题范围会很快缩小。

7.4 最后定代码点

最终再落到具体关键函数:

  • SendCheckLoadedMsg()
  • NotifySystemAbilityLoaded()
  • ProcessSpawnReqMsg()
  • WaitChildTimeout()
  • ProcessChildResponse()
  • SpawnPrepareSandboxCfg()
  • MountSandboxConfigs()
  • ReplaceVariable()

这样排查,效率通常比“从日志里通篇搜关键字”高很多。

7.5 快速排查清单

以下是根据「先定链、再定阶段、最后定代码点」方法论整理的快速排查清单,方便读者按步骤勾选执行:

排查步骤 关键检查点 预期结果/日志关键词
第一步:定链
1. 判断问题属于哪条链 SA 起不来 → 能力链
appspawn 回包超时 → 孵化链
sandbox 挂载缺失/变量替换错误 → sandbox 链
根据现象匹配上表「3. 快速分诊表」
第二步:定阶段
2. 确认进程是否存在 ps -ef | grep <进程名>
cat /proc/<pid>/status
进程 PID 存在且状态正常
3. 确认进程是否 ready samgr readybootevent.samgr.ready 且 binder 可拿到代理
SA readyPublish() 成功且 samgr 完成 AddSystemAbility()
appspawn ready:socket 建立,STAGE_SERVER_PRELOAD 完成
sandbox ready:mount/unshare/root 切换完成
对应 ready 标志已就位
4. 定位具体阶段 服务拉起阶段
进程 ready 阶段
超时阶段
功能异常阶段
明确断在哪一阶段
第三步:定代码点(按链细分)
能力链(SA 起不来)
5. 检查 init service init 是否拉起目标进程
/system/profile/<proc>.json 是否存在且可解析
init 日志显示进程启动成功
6. 检查 samgr ready bootevent.samgr.ready
• binder 上能否拿到 samgr 代理
samgr 代理可用
7. 检查 safwk profile 解析 • profile 中 processsaIdlibPath 等字段正确
runOnCreate/startOnDemand 符合预期
profile 解析成功,无语法错误
8. 检查 trust profile 过滤 • trust profile 是否包含目标 SA
• 安全策略是否允许该 SA 启动
SA 未被 trust profile 过滤掉
9. 检查 so 装载与 Start() • so 文件存在且可装载
Start() 函数被调用且无异常
so 装载成功,Start() 执行
10. 检查 Publish() 与注册 Publish() 被调用
samgr 收到 AddSystemAbility()
日志出现 Publish success 或类似信息
11. 检查超时路径 SendCheckLoadedMsg() 是否触发
• 是否进入超时分支
超时日志:load timeout
孵化链(appspawn 回包超时)
12. 检查连接与消息 • socket 连接是否建立
• 消息是否解码成功
ProcessSpawnReqMsg() 是否执行
连接正常,消息解码成功
13. 检查 fork/child 执行 • 是否成功进入 fork/clone
• child 是否通过 pipe 回执结果
fork 成功,child 进程创建
14. 检查 child 回执 ProcessChildResponse() 是否执行
ProcessChildFdCheck() 读取的 result
result == 0 表示成功
15. 检查超时与清理 WaitChildTimeout() 是否触发
OnClose() 是否因连接关闭而清理
超时日志:WaitChildTimeout
sandbox 链(挂载缺失/变量替换错误)
16. 区分 pre/post-unshare pre-unsharerequired.system-const、依赖组 staged mount
post-unshareapp-variablepackage-namepermission
明确缺失发生在哪个阶段
17. 检查 JSON 命中 • 当前进程选中的 sandbox 类型
• JSON 是否预加载成功
• package-name/permission/spawn-flag 条件是否命中
JSON 命中对应 section
18. 检查权限位与 gid SpawnPrepareSandboxCfg()
AppendPermissionGid()
AppendPackageNameGids()
permission 位命中,gid 正确追加
19. 检查变量替换 ReplaceVariable() 是否命中 handler
• 检查二次替换场景(depNode)
• 特殊分支:<lib><param:...>
变量被正确替换,无 no match variable 日志
20. 检查 mount/namespace MountSandboxConfigs() 执行链
unshare()SandboxRootFolderCreate()ChangeCurrentDir()
mount 成功,namespace 切换正常
第四步:结论收敛
21. 根据代码点得出结论 • 参考「4.5 结论化判断」、「5.5 结论化判断」、「6.6 最容易忽略的上游根因」 问题收敛到具体模块和失败点
22. 验证修复 • 修改配置/代码后重新测试
• 确认 ready 标志、日志关键词正常
问题解决,系统功能恢复正常

使用说明:

  1. 按顺序勾选:从上到下逐项检查,完成一项勾选一项。
  2. 链式排查:先完成「第一步:定链」,再进入对应链的详细检查。
  3. 日志关键词:关注右侧列的加粗日志关键词,快速定位异常点。
  4. 结论收敛:完成检查后,根据发现的问题点参考对应章节的「结论化判断」得出最终结论。

    7.6 性能与边界条件排查

flowchart TD
    Start["发现性能问题"] --> IsOccasional{"是否偶发?<br>低负载时正常?"}
    
    IsOccasional -->|是| Performance["性能瓶颈"]
    IsOccasional -->|否| Functional["功能失败"]
    
    Performance --> IdentifyChain["定位热点链"]
    IdentifyChain --> Chain1["能力链慢"]
    IdentifyChain --> Chain2["孵化链慢"]
    IdentifyChain --> Chain3["sandbox链慢"]
    
    Chain1 --> CheckSamgr["检查samgr负载<br>- binder线程池<br>- SA注册队列<br>- 锁竞争"]
    Chain2 --> CheckAppspawn["检查appspawn队列<br>- 并发控制<br>- 预加载耗时<br>- 子进程回收"]
    Chain3 --> CheckSandbox["检查sandbox性能<br>- JSON解析缓存<br>- 变量替换表<br>- mount批量优化"]
    
    CheckSamgr --> Metrics1["监控指标:<br>• CPU使用率<br>• 队列深度<br>• 锁等待时间"]
    CheckAppspawn --> Metrics2["监控指标:<br>• 孵化延迟<br>• 队列长度<br>• 内存占用"]
    CheckSandbox --> Metrics3["监控指标:<br>• 解析耗时<br>• 替换耗时<br>• mount操作数"]
    
    Metrics1 --> Tuning1["调优建议:<br>• 调整线程数<br>• 优化锁粒度<br>• 增加缓存"]
    Metrics2 --> Tuning2["调优建议:<br>• 调整max_concurrent<br>• 预加载优化<br>• 及时回收"]
    Metrics3 --> Tuning3["调优建议:<br>• 缓存JSON<br>• 优化查找算法<br>• mount去重"]
    
    Tuning1 --> Verify["验证优化效果"]
    Tuning2 --> Verify
    Tuning3 --> Verify
    
    Functional --> RootCause["按功能失败流程排查<br>(参考前文)"]
    
    style Start fill:#bbf,stroke:#333,stroke-width:2px
    style Performance fill:#ffd,stroke:#333
    style Functional fill:#fbb,stroke:#333
    style Verify fill:#bfb,stroke:#333

图4:性能瓶颈排查流程图 - 展示了如何区分性能问题与功能问题,并提供针对三条链的性能检查点和调优建议。

前面的排查策略主要针对功能性失败,但在并发启动、资源紧张(如内存不足)、配置项极多等边界条件下,问题可能表现为性能瓶颈而非功能失败。本节补充这类场景的排查思路。

如何区分性能瓶颈与功能失败

特征 性能瓶颈(偶发性) 功能失败(必然性)
发生时机 高并发、高负载时出现,低负载时正常 任何条件下都复现
错误表现 超时、延迟、队列积压,但最终可能成功 直接失败(如返回错误码、进程 crash)
日志特征 大量超时日志、等待队列深度增加、资源不足警告 明确的错误码、断言失败、权限拒绝等
恢复方式 降低并发、增加资源后可缓解 必须修复代码或配置才能解决

关键判断原则:如果问题在单次、低负载测试中不出现,而在压力测试或批量启动时频繁发生,应优先怀疑性能瓶颈。

针对性能瓶颈的专用检查点

1. 检查 samgr 的负载与队列深度
  • 现象LoadSystemAbility() 调用延迟大,SendCheckLoadedMsg() 频繁超时。
  • 检查点
    • binder 线程池状态samgr 的 binder 线程是否被占满,是否有大量 pending 请求。
    • SA 注册队列SystemAbilityManager 内部是否有大量 SA 在排队等待 Publish()
    • 内存与锁竞争:检查 samgr 进程的 CPU 使用率、锁等待时间,特别是 AddSystemAbility() 等关键函数的锁粒度。
  • 日志关键词binder thread pool busypending requestload timeout(但目标 SA 最终能起来)。
2. 检查 appspawn 的孵化队列与并发控制
  • 现象:应用启动超时,但 appspawn 进程 CPU 很高,或 WaitChildTimeout() 频繁触发。
  • 检查点
    • 并发孵化数:检查 appspawn 配置的最大并发孵化数(如 max_concurrent 参数),是否被突破。
    • 消息队列深度ProcessSpawnReqMsg() 前是否有大量请求排队。
    • sandbox 预加载耗时:检查 STAGE_SERVER_PRELOAD 阶段是否因 JSON 解析、mount 表加载而变慢(尤其配置项极多时)。
    • 子进程回收延迟SIGCHLD 处理是否及时,僵尸进程是否堆积。
  • 日志关键词spawn queue fullWaitChildTimeout(但 child 最终能完成)、preload cost too long
3. 检查 sandbox 预加载与变量替换性能
  • 现象:sandbox 挂载阶段明显变慢,尤其当应用权限多、依赖组复杂时。
  • 检查点
    • JSON 解析缓存:sandbox 配置 JSON 是否每次孵化都重新解析,还是有缓存机制。
    • 变量替换表大小ReplaceVariable() 的 handler 表是否过大,查找是否退化为线性扫描。
    • mount 操作批量优化MountSandboxConfigs() 是否对相同源路径做了去重,避免重复 mount。
    • 依赖组展开性能:当配置中 depend-on 链路过长时,递归展开是否导致指数级耗时。
  • 日志关键词ReplaceVariable slowmount too many entriesdepend-on chain too deep

边界条件排查建议

  1. 并发启动场景

    • 重点监控 samgr 的 binder 线程池、appspawn 的孵化队列。
    • 考虑是否需调整 max_concurrentload_thread_count 等参数。
    • 检查是否有 SA 或 sandbox 配置在并发时产生锁竞争。
  2. 资源紧张场景(内存不足)

    • samgrsafwk 可能因内存不足而无法装载 so、分配 binder 对象。
    • appspawn 的 child 可能在 fork 后因内存不足而 crash(表现像 APPSPAWN_CHILD_CRASH)。
    • sandbox 的 mount 可能因内存不足而失败,尤其在使用 tmpfsoverlayfs 时。
  3. 配置项极多场景

    • profile 中 SA 依赖关系复杂,导致 safwk 解析变慢。
    • sandbox JSON 文件巨大(几十KB以上),解析和匹配耗时增加。
    • 变量替换表庞大,ReplaceVariable() 查找性能下降。

排查步骤(性能专项)

  1. 确认是否性能问题:在低负载下单独测试同一功能,若正常则指向性能瓶颈。
  2. 定位热点链:用「先定链、再定阶段」方法,确定是能力链、孵化链还是 sandbox 链变慢。
  3. 检查专用检查点
    • 能力链慢 → 查 samgr 负载、SA 注册队列。
    • 孵化链慢 → 查 appspawn 队列深度、sandbox 预加载。
    • sandbox 链慢 → 查 JSON 解析、变量替换、mount 批量优化。
  4. 监控与调优
    • 增加相关日志级别(如 DEBUG 级性能日志)。
    • 使用 perftrace 工具抓取热点函数。
    • 考虑缓存、预加载、参数调优等优化手段。

总结

性能问题往往在边界条件下暴露,其排查思路与功能失败不同:要先确认是否偶发,再定位热点链,最后针对该链的专用检查点深入分析。不要一看到超时就只查功能逻辑,可能只是资源不足或配置过载。

8. 常见误判

8.1 只看 ps,不看 ready

这是最常见的误判。

在这套系统里:

  • 进程起来,不代表注册完成
  • 进程起来,不代表 preload 完成
  • 进程起来,不代表 child 已可交付

8.2 看到超时,就只盯超时点

超时往往只是最后表现,不是根因。

例如:

  • LoadSystemAbility 超时,根因可能是 trust profile 裁掉 SA
  • appspawn 超时,根因可能是 sandbox 变量替换失败

8.3 看到 mount 失败,就只盯 mount

mount 失败可能只是上游条件不成立的结果。

例如:

  • sandbox 类型选错
  • permission 位没命中
  • 基础目录还不存在

8. 关键流程可视化总结

8.1 核心排查流程图

flowchart TD
    Problem["发现问题"] --> Step1["第一步:定链"]
    
    Step1 --> SAChain["能力链<br>SA起不来"]
    Step1 --> AppChain["孵化链<br>appspawn超时"]
    Step1 --> SandboxChain["sandbox链<br>挂载/变量错误"]
    
    SAChain --> SA_Step1["检查进程存在"]
    SA_Step1 --> SA_Step2["检查samgr ready"]
    SA_Step2 --> SA_Step3["检查safwk profile"]
    SA_Step3 --> SA_Step4["检查trust profile"]
    SA_Step4 --> SA_Step5["检查so装载/Start()"]
    SA_Step5 --> SA_Step6["检查Publish()"]
    
    AppChain --> App_Step1["检查连接/消息"]
    App_Step1 --> App_Step2["检查fork执行"]
    App_Step2 --> App_Step3["检查child回执"]
    App_Step3 --> App_Step4["检查超时清理"]
    
    SandboxChain --> SB_Step1["区分pre/post-unshare"]
    SB_Step1 --> SB_Step2["检查JSON命中"]
    SB_Step2 --> SB_Step3["检查权限位/gid"]
    SB_Step3 --> SB_Step4["检查变量替换"]
    SB_Step4 --> SB_Step5["检查mount/namespace"]
    
    SA_Step6 --> SA_Conclusion["结论:具体代码点"]
    App_Step4 --> App_Conclusion["结论:具体代码点"]
    SB_Step5 --> SB_Conclusion["结论:具体代码点"]
    
    SA_Conclusion --> Final["问题收敛"]
    App_Conclusion --> Final
    SB_Conclusion --> Final
    
    style Problem fill:#f9f,stroke:#333,stroke-width:2px
    style Step1 fill:#bbf,stroke:#333,stroke-width:2px
    style Final fill:#bfb,stroke:#333,stroke-width:2px

图5:核心排查流程图 - 将"先定链、再定阶段、最后定代码点"的方法论可视化,为三类问题提供清晰的排查路径。

8.2 系统模块交互架构图

graph TB
    subgraph "客户端层"
        Client[客户端应用]
    end
    
    subgraph "系统服务层"
        SAMgr[samgr<br/>系统能力管理器]
        SAFwk[safwk<br/>系统能力框架]
        AppSpawn[appspawn<br/>应用孵化器]
    end
    
    subgraph "进程管理层"
        Init[init<br/>进程管理]
        SAProc[SA进程]
        AppProc[应用进程]
    end
    
    subgraph "环境隔离层"
        Sandbox[sandbox<br/>沙箱环境]
    end
    
    Client -->|1. Get/LoadSystemAbility| SAMgr
    Client -->|2. 孵化请求| AppSpawn
    
    SAMgr -->|3. 状态机管理| SAFwk
    SAFwk -->|4. 解析profile| Init
    SAFwk -->|5. 装载so/Start| SAProc
    SAProc -->|6. Publish注册| SAMgr
    
    AppSpawn -->|7. fork子进程| AppProc
    AppSpawn -->|8. sandbox准备| Sandbox
    Sandbox -->|9. 环境隔离| AppProc
    
    Init -->|10. 拉起进程| SAProc
    Init -->|11. 拉起进程| AppSpawn
    
    style Client fill:#e1f5fe
    style SAMgr fill:#f3e5f5
    style SAFwk fill:#e8f5e8
    style AppSpawn fill:#fff3e0
    style Init fill:#fce4ec
    style SAProc fill:#e8f5e8
    style AppProc fill:#fff3e0
    style Sandbox fill:#e0f2f1

图6:系统模块交互架构图 - 展示了标准系统中各核心模块的交互关系,帮助理解问题发生的上下文环境。

9. 术语与核心要点回顾

graph TB
    subgraph "核心概念"
        Methodology["排查方法论<br>先定链 → 再定阶段 → 最后定代码点"]
        ReadyState["Ready状态<br>进程存在 ≠ 系统ready"]
        Handoff["Handoff交接点<br>模块间关键交接"]
    end
    
    subgraph "三条主链"
        CapabilityChain["能力链<br>SA起不来"]
        SpawnChain["孵化链<br>appspawn超时"]
        SandboxChain["sandbox链<br>挂载/变量错误"]
    end
    
    subgraph "关键模块"
        SAMgr["samgr<br/>系统能力管理器"]
        SAFwk["safwk<br/>系统能力框架"]
        AppSpawn["appspawn<br/>应用孵化器"]
        Sandbox["sandbox<br/>沙箱环境"]
        Init["init<br/>进程管理"]
    end
    
    subgraph "常见误判"
        Misjudge1["只看ps不看ready"]
        Misjudge2["超时当根因"]
        Misjudge3["下游当上游"]
        Misjudge4["忽略trust profile"]
    end
    
    Methodology --> CapabilityChain
    Methodology --> SpawnChain
    Methodology --> SandboxChain
    
    ReadyState --> SAMgr
    ReadyState --> SAFwk
    ReadyState --> AppSpawn
    ReadyState --> Sandbox
    
    Handoff --> Init
    Handoff --> SAMgr
    Handoff --> SAFwk
    Handoff --> AppSpawn
    
    CapabilityChain --> SAMgr
    CapabilityChain --> SAFwk
    SpawnChain --> AppSpawn
    SandboxChain --> Sandbox
    
    Misjudge1 -.-> ReadyState
    Misjudge2 -.-> Methodology
    Misjudge3 -.-> Handoff
    Misjudge4 -.-> SAFwk
    
    style Methodology fill:#e1f5fe
    style ReadyState fill:#f3e5f5
    style Handoff fill:#e8f5e8
    style CapabilityChain fill:#fff3e0
    style SpawnChain fill:#e0f2f1
    style SandboxChain fill:#fce4ec
    style Misjudge1 fill:#ffebee
    style Misjudge2 fill:#ffebee
    style Misjudge3 fill:#ffebee
    style Misjudge4 fill:#ffebee

图8:核心术语与概念关系图 - 展示了排查方法论、三条主链、关键模块和常见误判之间的关联关系,帮助读者建立整体认知框架。

9.1 核心术语表

术语 简明定义
SA (System Ability) 系统能力,HarmonyOS 中可被远程调用的服务单元。
samgr (System Ability Manager) 系统能力管理器,负责 SA 的注册、发现、加载与生命周期管理。
safwk (System Ability Framework) 系统能力框架,负责解析 profile、装载 so、执行 SA 的 Start()Publish()
appspawn 应用孵化器,负责接收客户端请求、fork 子进程、管理 sandbox 环境并回包。
sandbox 沙箱,在子进程执行前为其准备隔离的挂载、namespace、权限等运行环境。
ready 状态 各子系统对外可用的标志:
samgr ready:binder context 与工作线程已建立,可拿到代理。
SA readyPublish() 完成且 samgr 已登记。
appspawn ready:socket 已建立,事件循环运行,STAGE_SERVER_PRELOAD 完成。
sandbox ready:单次孵化中 mount/unshare/root 切换已完成。
profile 描述 SA 或 sandbox 配置的 JSON 文件,决定进程如何拉起、依赖哪些 SA、何时启动等。
trust profile 安全策略配置文件,可过滤掉不符合条件的 SA,导致 SA 虽配却不可用。
handoff 链路上模块间的交接点(如 init → samgrsamgr → safwk),是定位断点的关键。

9.2 排障心法与常见误区

最关键的排障心法:

  1. 先定链,再定阶段,最后定代码点——不要一上来就扎进代码。先判断问题是能力链、孵化链还是 sandbox 链;再确定断在服务拉起、进程 ready、超时等哪个阶段;最后才聚焦到具体函数。
  2. 进程存在 ≠ 系统 ready——这是最易误判的点。ps 看到进程只说明 init 拉过它,不说明其初始化完成、注册成功或可对外服务。
  3. 超时是结果,不是根因——LoadSystemAbility 超时可能是 trust profile 过滤导致;appspawn 超时可能是 sandbox 变量替换失败。要沿着超时点向前追溯。
  4. 上游条件不成立,下游表现只是表象——例如 mount 失败可能是因为 permission 位未命中或基础目录不存在;SA 不出现可能是因为 profile 配错或 trust profile 过滤。
  5. 父子进程问题必须分开——尤其是 appspawn 相关,父进程问题多在消息、socket、超时控制;子进程问题多在 hook、sandbox、mount、业务入口。

最容易踩坑的误区:

  • **只看 ps,不看 ready**:误以为进程在就万事大吉,忽略 samgrsafwkappspawnsandbox 各自的 ready 标志。
  • 把下游症状当上游根因:例如看到 mount 失败就只查 mount 逻辑,没检查 permission 是否命中、JSON 是否加载。
  • 链判断错误:把能力链问题当成孵化链去查,或在 appspawn 超时时只盯 socket 而忽略 child execute。
  • 忽略 trust profile:SA 配了却不见,往往是被 trust profile 静默裁掉,表面像“代码没执行到”。
  • 变量替换想当然:认为 <PackageName> 等占位符会自动替换,没检查 handler 是否注册、是否为二次替换或特殊分支(<lib><param:...>)。

9. 建议的阅读顺序

如果是排障视角,建议按下面顺序看源码。

9.1 SA 起不来

  1. foundation/systemabilitymgr/samgr/services/samgr/native/source/system_ability_manager.cpp
  2. foundation/systemabilitymgr/safwk/services/safwk/src/local_ability_manager.cpp
  3. foundation/systemabilitymgr/safwk/services/safwk/src/system_ability.cpp
  4. foundation/systemabilitymgr/samgr/services/common/src/parse_util.cpp

9.2 appspawn 回包超时

  1. base/startup/appspawn/standard/appspawn_service.c
  2. base/startup/appspawn/common/appspawn_server.c
  3. base/startup/appspawn/standard/appspawn_msgmgr.c

9.3 sandbox 挂载异常

  1. base/startup/appspawn/modules/sandbox/modern/sandbox_manager.c
  2. base/startup/appspawn/modules/sandbox/modern/appspawn_sandbox.c
  3. base/startup/appspawn/modules/sandbox/modern/sandbox_cfgvar.c
  4. base/startup/appspawn/modules/sandbox/modern/sandbox_load.c
  5. base/startup/appspawn/modules/sandbox/appspawn_permission.c

10. 最后一句

第六篇最核心的结论只有一个:

先判断“哪条链断了”,再判断“哪个 handoff 断了”,最后才去看“哪一行代码错了”。

只要这个顺序不反,标准系统这几条启动和孵化链虽然长,但问题通常都能被比较快地压缩到一个很小的范围。

Logo

社区规范:仅讨论OpenHarmony相关问题。

更多推荐