【全栈第10课】HAL 注册 + framework 接入
Android 全栈工程师进阶教程 第10课。HAL 注册 + framework 接入。基于公开 AOSP 知识整理,含原理、可编译示例、常见问题定位。
> 本文是《Android 全栈工程师进阶教程》系列第 10 课。完整 26 课见 GitHub 仓库。
【全栈第10课】HAL 注册 + framework 接入
0. 这节课你将做出什么
写一个 client,从 framework 侧拿到 L9 的 hello HAL 并跨进程调用,打通 framework→HAL 这一环。
学完你能回答:framework 怎么找到并调用一个 vendor HAL?为什么用 waitForService 而不是 getService?真实产线里 framework 调 HAL 为什么还要再封一层?
1. 背景:HAL 自己起来了,但谁调它
L9 我们让 HAL 进程起来、注册到 servicemanager 了。但 HAL 是被动的——得有人调它才有意义。调它的通常是 framework(system 侧),framework 拿到 HAL 数据再上报给 App。
这一环就是 Treble 架构里 system 跨界调 vendor 的关键路径。
2. 全局图:framework 调 HAL
framework 进程(system 侧)
│ ① AServiceManager_waitForService("vendor.example.demo.IHelloHal/default")
│ 拿到 BinderProxy(BpHelloHal)
│ ② IHelloHal::fromBinder(binder) 转成接口代理
▼ ③ hal->sayHello("framework", &reply)
╞══════ Binder 驱动(跨 system→vendor)══════▶ HelloHal(vendor 进程,L9)
│ ::sayHello 实现
◀══════════════════════════════════════════════ 返回
和 L1 客户端调服务端是一个模式,只是这次跨的是 system→vendor 边界。
3. 某真实机型 真实源码印证(照搬 vibrator 消费方)
frameworks/native/services/vibratorservice/VibratorHalController.cpp:62:
std::shared_ptr<IVibrator> hal = IVibrator::fromBinder(
ndk::SpAIBinder(AServiceManager_waitForService(serviceName.c_str())));
这就是 framework 调 ndk AIDL HAL 的标准三步:waitForService → SpAIBinder 包装 → fromBinder 转接口。
4. 前置准备
- 依赖 L9(hello HAL 已写好、能注册)。
5. 动手:写 framework 侧 client
hello_hal_client.cpp(逐段):
#include <android/binder_manager.h>
#include <aidl/vendor/example/hello/IHelloHal.h>
using aidl::vendor::example::hello::IHelloHal;
int main() {
// 服务名:和 L9 HAL 注册的、vintf 声明的完全一致
const std::string name = std::string(IHelloHal::descriptor) + "/default";
// ① 等待并拿到 HAL 的 binder(framework 侧拿的是 BpHelloHal 代理)
ndk::SpAIBinder binder(AServiceManager_waitForService(name.c_str()));
if (binder.get() == nullptr) {
LOG(ERROR) << "拿不到 HAL,HAL 起了吗?vintf 声明了吗?";
return 1;
}
// ② fromBinder 转成接口代理
std::shared_ptr<IHelloHal> hal = IHelloHal::fromBinder(binder);
if (hal == nullptr) { LOG(ERROR) << "fromBinder 失败"; return 1; }
// ③ 跨进程调 HAL(framework 进程 → vendor HAL 进程)
std::string reply;
ndk::ScopedAStatus st = hal->sayHello("framework", &reply);
if (st.isOk()) LOG(INFO) << "HAL sayHello 返回: " << reply;
int32_t sum = 0;
hal->add(7, 8, &sum);
LOG(INFO) << "HAL add(7,8) = " << sum;
return 0;
}
逐步讲为什么:
IHelloHal::descriptor + "/default":服务名必须和 L9 注册时一致,否则找不到。waitForServicevsgetService:waitForService会阻塞等 HAL 起来(HAL 进程可能比你晚起);getService不等,拿不到就返回 null。framework 调 HAL 一般用waitForService更稳。fromBinder:把裸 binder 转成强类型接口代理,之后就能像本地对象一样调。
Android.bp:
cc_binary {
name: "hello_hal_client",
srcs: ["hello_hal_client.cpp"],
shared_libs: ["libbase", "libbinder_ndk", "vendor.example.demo-V1-ndk"], // 链接 L9 的 ndk 接口库
}
6. 看底层:真实产线为什么还要封一层 HalController
本课 client 是直接调。但真实 framework 调 HAL(如 vibrator)会再封一层 XxxHalController,因为要处理:
- HAL 重启:HAL 进程崩了又起,缓存的 binder 失效,要重新
waitForService(配合linkToDeath,L1 问题2)。 - 降级:HAL 不存在/调用失败时的兜底。
- 缓存:避免每次都 waitForService。
所以你在 framework 源码里看到的是
HalController/HalWrapper,本课的直接调是简化版,帮你理解核心。
7. 编译 & 运行(验证状态如实)
mm # 编出 hello_hal_client
# L9 HAL 起来后运行:
adb shell /vendor/bin/hello_hal_client # 或 push 到能跑的位置
adb logcat | grep -iE "HelloHal|hello_hal_client"
预期:
hello_hal_client: HAL sayHello 返回: Hello from HAL, framework
hello_hal_client: HAL add(7,8) = 15
HelloHal: (vendor 进程收到调用)
> 验证状态(诚实):代码为 framework 调 ndk HAL 标准写法(照搬 VibratorHalController);完整 mm + 真机跑通需 AOSP/真机,未实跑。
8. 踩坑提醒
- 服务名不一致:client 找的名和 L9 注册的名差一个字 → waitForService 永远阻塞或返回 null。
- waitForService 永久阻塞:HAL 根本没起(L9 没起来),
waitForService会一直等。要先确认 HAL 起了(lshal)。 - system 调 vendor 的 sepolicy:framework(system 域)调 vendor HAL 需要 sepolicy 允许
hwbinder_call/ 访问 hal_xxx_service。漏了 avc denied。 - 链接库版本:
vendor.example.demo-V1-ndk的 V1 要和接口版本一致。 - VNDK/可见性:system 进程链接 vendor 接口库有可见性限制,产线要走正规的 stable AIDL 可见性配置。
9. 常见问题分析与定位(framework→HAL 实战)
9.1 常见问题清单
- waitForService 永久卡住
- fromBinder 返回 null
- 调用返回错误状态(ScopedAStatus 非 ok)
- avc denied(system 调 vendor 被拒)
- HAL 调一半进程崩,client 后续调用失败
9.2 分析与定位
① waitForService 卡住 — HAL 没起
adb shell lshal | grep hello # HAL 起了吗(没起先解决 L9)
adb shell ps -A | grep hello-service
- HAL 没起 → 回 L9 排查(init/sepolicy/vintf);HAL 起了还卡 → 服务名不一致。
② fromBinder 返回 null — 拿到的 binder 不是这个接口类型
- 原因:服务名对但注册的是别的接口;或接口版本不匹配。
- 定位:
lshal -i看 HAL 实际暴露的接口和版本。
③ 调用返回非 ok 状态
- 原因:HAL 实现里返回了错误,或参数/权限问题。
- 定位:看 HAL 侧 logcat(HelloHal 的日志),看它为什么返回错误。
④ avc denied — system 调 vendor 没权限
adb logcat | grep -iE "avc.*hello|hwbinder"
- 修复:给 framework 的 domain 加
allow <domain> hal_hello_service:service_manager find;+binder_call(L11)。
⑤ HAL 崩后调用失败 — 缓存的 binder 失效
- 修复:client 注册
linkToDeath,HAL 死了重新 waitForService(这就是产线要 HalController 的原因)。
9.3 定位决策树
framework→HAL 出问题
├─ waitForService 卡 → lshal HAL 起没起 → 没起回 L9;起了查服务名一致
├─ fromBinder null → lshal -i 看接口/版本是否匹配
├─ 调用非 ok → HAL 侧 logcat 看为什么返回错误
├─ avc denied → 给 framework domain 加 hal_hello_service find + binder_call(L11)
└─ HAL 崩后失败 → linkToDeath 重连(产线用 HalController)
10. 小结
- framework 调 HAL 三步:
waitForService→SpAIBinder包装 →fromBinder转接口。 - waitForService 会阻塞等 HAL 起来,比 getService 稳。
- 服务名三处一致(L9 注册 / vintf 声明 / 本课查找)。
- 真实产线再封 HalController:处理 HAL 重启/降级/缓存。
- 排查口诀:卡住先 lshal 看 HAL 起没;拿不到查服务名;denied 加 sepolicy(L11)。
下节预告
L11:HAL 的 SELinux + VINTF 声明 —— 让 L9 的 HAL 在真机上真正起来(HAL 进程的 SELinux 域怎么配)。