AI 技术博客
Android全栈7 分钟阅读4821

【全栈第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 的标准三步:waitForServiceSpAIBinder 包装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 注册时一致,否则找不到。
  • waitForService vs getService: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. 踩坑提醒

  1. 服务名不一致:client 找的名和 L9 注册的名差一个字 → waitForService 永远阻塞或返回 null。
  2. waitForService 永久阻塞:HAL 根本没起(L9 没起来),waitForService 会一直等。要先确认 HAL 起了(lshal)。
  3. system 调 vendor 的 sepolicy:framework(system 域)调 vendor HAL 需要 sepolicy 允许 hwbinder_call / 访问 hal_xxx_service。漏了 avc denied。
  4. 链接库版本:vendor.example.demo-V1-ndk 的 V1 要和接口版本一致。
  5. VNDK/可见性:system 进程链接 vendor 接口库有可见性限制,产线要走正规的 stable AIDL 可见性配置。

9. 常见问题分析与定位(framework→HAL 实战)

9.1 常见问题清单

  1. waitForService 永久卡住
  2. fromBinder 返回 null
  3. 调用返回错误状态(ScopedAStatus 非 ok)
  4. avc denied(system 调 vendor 被拒)
  5. 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. 小结

  1. framework 调 HAL 三步:waitForServiceSpAIBinder 包装 → fromBinder 转接口。
  2. waitForService 会阻塞等 HAL 起来,比 getService 稳。
  3. 服务名三处一致(L9 注册 / vintf 声明 / 本课查找)。
  4. 真实产线再封 HalController:处理 HAL 重启/降级/缓存。
  5. 排查口诀:卡住先 lshal 看 HAL 起没;拿不到查服务名;denied 加 sepolicy(L11)。

下节预告

L11:HAL 的 SELinux + VINTF 声明 —— 让 L9 的 HAL 在真机上真正起来(HAL 进程的 SELinux 域怎么配)。

评论