OH 标准系统启动结构梳理(六):启动与孵化问题定位手册
文档概述
说明:
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
使用说明:
- 先定链:根据现象选择左侧三条主链之一。
- 再定阶段:沿着选定链向下,确定问题发生在哪个阶段。
- 最后定代码点:找到对应阶段的关键函数,快速定位到具体失败点。
- 结论收敛:根据代码点得出具体结论,将问题范围压缩到最小。
此图将正文中的文字描述可视化,遵循“先定链、再定阶段、最后定代码点”的核心方法论,帮助读者建立清晰的排查思维框架。
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 ready:
bootevent.samgr.ready且 binder 上能拿到代理 - SA ready:
Publish()成功且samgr完成AddSystemAbility() - appspawn ready:socket 建立,
STAGE_SERVER_PRELOAD完成 - sandbox ready:单次孵化中 mount/unshare/root 切换已完成
2.3 第三步:定位问题发生在哪条链(三选一)
根据现象快速定位到三条主链之一:
- 能力链:SA 起不来,服务能力不可用
- 孵化链:appspawn 回包超时,应用启动失败
- 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。
正确顺序应该是:
init是否把目标进程拉起来samgr是否已 readysafwk是否成功解析对应 profile- trust profile 是否把目标 SA 过滤掉
- 对应 so 是否真的装载成功
Start()是否执行Publish()是否执行samgr是否完成AddSystemAbility()
排查一定要按这个顺序,不要跳。
4.2 第二步:区分常驻与按需两种模式
SA 起不来,先问自己是下面哪一种。
4.2.1 常驻 SA
特征是:
- 由
init直接拉起sa_main /system/profile/<proc>.json - profile 里是
run-on-create
这类问题更偏:
initservice 没起- profile 解析失败
- so 装载失败
Publish()没执行
4.2.2 按需 SA
特征是:
- 调用方通过
LoadSystemAbility()触发 samgr状态机负责拉起
这类问题更偏:
samgr状态机没有正确进入 loadStartingSystemProcess()没成功- 目标进程起来了但未在超时窗口内
Publish()
4.3 第三步:聚焦关键代码断点(按需加载场景)
按需加载相关的关键代码断点非常集中。
第一组,看 load 是否真正进入调度:
SystemAbilityManager::LoadSystemAbility()SystemAbilityManager::DoLoadSystemAbility()SystemAbilityManager::StartingSystemProcess()
第二组,看超时是否发生:
SystemAbilityManager::SendCheckLoadedMsg()
这段代码里会明确打出:
loadedhandle for SAload 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或依赖字段不符合预期
重点检查:
processsaIdlibPathrunOnCreatestartOnDemanddependSadependTimeout
4.4.3 Trust Profile 把 SA 裁掉了
表现:
- Profile 里有 SA
- safwk 进程也起来了
- 但某个 SA 始终不出现在后续装载流程里
这类问题很容易漏看,因为从表面看像“代码根本没执行到”,实际上是被 Trust Profile 先过滤掉了。
4.4.4 so 装载或 Start() 失败
表现:
- 进程起来
samgrready- 但就是没有
Publish()
这类问题本质已经不在 samgr,而在目标 safwk 进程内部。
4.4.5 Publish() 没走到
这是最容易误判的一类。
要记住:
- 进程存在,不等于 SA ready
- so 装载成功,不等于 SA ready
Start()调过,不等于 SA ready
真正的 ready 判据只有一个:
Publish()成功,且samgr完成AddSystemAbility()
4.5 结论化判断
可以直接按下面的逻辑收敛:
- 进程没起来:先回
initservice 控制 - 进程起来但
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 回执 -> 父进程回包”这条链查。
推荐顺序:
- 连接是否真正建立
- 消息是否解码成功
ProcessSpawnReqMsg()是否执行- 是否成功进入 fork / clone
- child 是否通过 pipe 回执结果
- 父进程是否执行
ProcessChildResponse() - 是否触发
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 成功链长什么样?
理解成功链,反而更容易定位超时。
正常路径是:
ProcessSpawnReqMsg()RunAppSpawnProcessMsg()- child 执行早期 hook
- child 调
NotifyResToParent() - 父进程 watcher 进入
ProcessChildResponse() ProcessChildFdCheck()读到result == 0AddSpawnedProcess()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-variablepackage-namepermission- 扩展配置
重点看:
StagedMountPostUnshare()SetSandboxPackageNameConfig()SetSandboxPermissionConfig()
先把这两类分开,定位速度会快很多。
6.2 先看 JSON 命中了没有
很多挂载问题不是执行失败,而是根本没命中对应 section。
先检查三件事:
- 当前进程到底选中了哪类 sandbox
- 对应 JSON 是否预加载成功
- 当前请求是否命中了 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()
主顺序非常固定:
InitSandboxContext()StagedMountPreUnShare()unshare(context->sandboxNsFlags)SandboxRootFolderCreate()StagedMountPostUnshare()ChangeCurrentDir()
因此:
unshare()失败,问题在 namespace flag 或前置环境SandboxRootFolderCreate()失败,问题在根目录骨架或 mount 语义ChangeCurrentDir()失败,问题已接近pivot_root/ root 切换
6.6 最容易忽略的上游根因
很多 sandbox 问题最后落在 mount 失败,但根因不一定在 sandbox 自身。
优先排除:
- 基础分区还没准备好
- 关键目录本身不存在
appspawnpreload 没完成- 当前请求使用了错误的 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 -> samgrsamgr -> safwksafwk -> Publishinit -> appspawnparent -> childsandbox 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 ready:bootevent.samgr.ready 且 binder 可拿到代理• SA ready: Publish() 成功且 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 中 process、saId、libPath 等字段正确• 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-unshare:required.system-const、依赖组 staged mount• post-unshare: app-variable、package-name、permission |
明确缺失发生在哪个阶段 |
| 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 标志、日志关键词正常 |
问题解决,系统功能恢复正常 |
使用说明:
- 按顺序勾选:从上到下逐项检查,完成一项勾选一项。
- 链式排查:先完成「第一步:定链」,再进入对应链的详细检查。
- 日志关键词:关注右侧列的加粗日志关键词,快速定位异常点。
- 结论收敛:完成检查后,根据发现的问题点参考对应章节的「结论化判断」得出最终结论。
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 线程池状态:
- 日志关键词:
binder thread pool busy、pending request、load timeout(但目标 SA 最终能起来)。
2. 检查 appspawn 的孵化队列与并发控制
- 现象:应用启动超时,但
appspawn进程 CPU 很高,或WaitChildTimeout()频繁触发。 - 检查点:
- 并发孵化数:检查
appspawn配置的最大并发孵化数(如max_concurrent参数),是否被突破。 - 消息队列深度:
ProcessSpawnReqMsg()前是否有大量请求排队。 - sandbox 预加载耗时:检查
STAGE_SERVER_PRELOAD阶段是否因 JSON 解析、mount 表加载而变慢(尤其配置项极多时)。 - 子进程回收延迟:
SIGCHLD处理是否及时,僵尸进程是否堆积。
- 并发孵化数:检查
- 日志关键词:
spawn queue full、WaitChildTimeout(但 child 最终能完成)、preload cost too long。
3. 检查 sandbox 预加载与变量替换性能
- 现象:sandbox 挂载阶段明显变慢,尤其当应用权限多、依赖组复杂时。
- 检查点:
- JSON 解析缓存:sandbox 配置 JSON 是否每次孵化都重新解析,还是有缓存机制。
- 变量替换表大小:
ReplaceVariable()的 handler 表是否过大,查找是否退化为线性扫描。 - mount 操作批量优化:
MountSandboxConfigs()是否对相同源路径做了去重,避免重复 mount。 - 依赖组展开性能:当配置中
depend-on链路过长时,递归展开是否导致指数级耗时。
- 日志关键词:
ReplaceVariable slow、mount too many entries、depend-on chain too deep。
边界条件排查建议
-
并发启动场景:
- 重点监控
samgr的 binder 线程池、appspawn的孵化队列。 - 考虑是否需调整
max_concurrent、load_thread_count等参数。 - 检查是否有 SA 或 sandbox 配置在并发时产生锁竞争。
- 重点监控
-
资源紧张场景(内存不足):
samgr或safwk可能因内存不足而无法装载 so、分配 binder 对象。appspawn的 child 可能在 fork 后因内存不足而 crash(表现像APPSPAWN_CHILD_CRASH)。- sandbox 的 mount 可能因内存不足而失败,尤其在使用
tmpfs或overlayfs时。
-
配置项极多场景:
- profile 中 SA 依赖关系复杂,导致
safwk解析变慢。 - sandbox JSON 文件巨大(几十KB以上),解析和匹配耗时增加。
- 变量替换表庞大,
ReplaceVariable()查找性能下降。
- profile 中 SA 依赖关系复杂,导致
排查步骤(性能专项)
- 确认是否性能问题:在低负载下单独测试同一功能,若正常则指向性能瓶颈。
- 定位热点链:用「先定链、再定阶段」方法,确定是能力链、孵化链还是 sandbox 链变慢。
- 检查专用检查点:
- 能力链慢 → 查
samgr负载、SA 注册队列。 - 孵化链慢 → 查
appspawn队列深度、sandbox 预加载。 - sandbox 链慢 → 查 JSON 解析、变量替换、mount 批量优化。
- 能力链慢 → 查
- 监控与调优:
- 增加相关日志级别(如
DEBUG级性能日志)。 - 使用
perf或trace工具抓取热点函数。 - 考虑缓存、预加载、参数调优等优化手段。
- 增加相关日志级别(如
总结
性能问题往往在边界条件下暴露,其排查思路与功能失败不同:要先确认是否偶发,再定位热点链,最后针对该链的专用检查点深入分析。不要一看到超时就只查功能逻辑,可能只是资源不足或配置过载。
8. 常见误判
8.1 只看 ps,不看 ready
这是最常见的误判。
在这套系统里:
- 进程起来,不代表注册完成
- 进程起来,不代表 preload 完成
- 进程起来,不代表 child 已可交付
8.2 看到超时,就只盯超时点
超时往往只是最后表现,不是根因。
例如:
LoadSystemAbility超时,根因可能是 trust profile 裁掉 SAappspawn超时,根因可能是 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 ready: Publish() 完成且 samgr 已登记。• appspawn ready:socket 已建立,事件循环运行, STAGE_SERVER_PRELOAD 完成。• sandbox ready:单次孵化中 mount/unshare/root 切换已完成。 |
| profile | 描述 SA 或 sandbox 配置的 JSON 文件,决定进程如何拉起、依赖哪些 SA、何时启动等。 |
| trust profile | 安全策略配置文件,可过滤掉不符合条件的 SA,导致 SA 虽配却不可用。 |
| handoff | 链路上模块间的交接点(如 init → samgr、samgr → safwk),是定位断点的关键。 |
9.2 排障心法与常见误区
最关键的排障心法:
- 先定链,再定阶段,最后定代码点——不要一上来就扎进代码。先判断问题是能力链、孵化链还是 sandbox 链;再确定断在服务拉起、进程 ready、超时等哪个阶段;最后才聚焦到具体函数。
- 进程存在 ≠ 系统 ready——这是最易误判的点。
ps看到进程只说明init拉过它,不说明其初始化完成、注册成功或可对外服务。 - 超时是结果,不是根因——
LoadSystemAbility超时可能是 trust profile 过滤导致;appspawn超时可能是 sandbox 变量替换失败。要沿着超时点向前追溯。 - 上游条件不成立,下游表现只是表象——例如 mount 失败可能是因为 permission 位未命中或基础目录不存在;SA 不出现可能是因为 profile 配错或 trust profile 过滤。
- 父子进程问题必须分开——尤其是
appspawn相关,父进程问题多在消息、socket、超时控制;子进程问题多在 hook、sandbox、mount、业务入口。
最容易踩坑的误区:
- **只看
ps,不看ready**:误以为进程在就万事大吉,忽略samgr、safwk、appspawn、sandbox各自的 ready 标志。 - 把下游症状当上游根因:例如看到 mount 失败就只查 mount 逻辑,没检查 permission 是否命中、JSON 是否加载。
- 链判断错误:把能力链问题当成孵化链去查,或在
appspawn超时时只盯 socket 而忽略 child execute。 - 忽略 trust profile:SA 配了却不见,往往是被 trust profile 静默裁掉,表面像“代码没执行到”。
- 变量替换想当然:认为
<PackageName>等占位符会自动替换,没检查 handler 是否注册、是否为二次替换或特殊分支(<lib>、<param:...>)。
9. 建议的阅读顺序
如果是排障视角,建议按下面顺序看源码。
9.1 SA 起不来
foundation/systemabilitymgr/samgr/services/samgr/native/source/system_ability_manager.cppfoundation/systemabilitymgr/safwk/services/safwk/src/local_ability_manager.cppfoundation/systemabilitymgr/safwk/services/safwk/src/system_ability.cppfoundation/systemabilitymgr/samgr/services/common/src/parse_util.cpp
9.2 appspawn 回包超时
base/startup/appspawn/standard/appspawn_service.cbase/startup/appspawn/common/appspawn_server.cbase/startup/appspawn/standard/appspawn_msgmgr.c
9.3 sandbox 挂载异常
base/startup/appspawn/modules/sandbox/modern/sandbox_manager.cbase/startup/appspawn/modules/sandbox/modern/appspawn_sandbox.cbase/startup/appspawn/modules/sandbox/modern/sandbox_cfgvar.cbase/startup/appspawn/modules/sandbox/modern/sandbox_load.cbase/startup/appspawn/modules/sandbox/appspawn_permission.c
10. 最后一句
第六篇最核心的结论只有一个:
先判断“哪条链断了”,再判断“哪个 handoff 断了”,最后才去看“哪一行代码错了”。
只要这个顺序不反,标准系统这几条启动和孵化链虽然长,但问题通常都能被比较快地压缩到一个很小的范围。
更多推荐

所有评论(0)