AI 技术博客
Android全栈9 分钟阅读7214

【全栈第9课】AIDL HAL 编写(vendor 实现)— 阶段二重头戏

Android 全栈工程师进阶教程 第09课。AIDL HAL 编写(vendor 实现)— 阶段二重头戏。基于公开 AOSP 知识整理,含原理、可编译示例、常见问题定位。

> 本文是《Android 全栈工程师进阶教程》系列第 09 课。完整 26 课见 GitHub 仓库

【全栈第9课】AIDL HAL 编写(vendor 实现)— 阶段二重头戏


0. 这节课你将做出什么

从零写一个完整可编译的 hello AIDL HAL:7 个文件,覆盖一个真实 HAL 该有的全部要素——接口、实现、注册、开机自启、VINTF 声明、编译脚本。 学完你能回答:一个 HAL 由哪些文件组成?HAL 怎么注册到系统、怎么被 init 拉起、怎么被 framework 找到? 这是框架工程师真正跨进 BSP 的标志性技能。

1. 背景:为什么 HAL 要这么多文件

L1-L6 我们写的服务跑在 system_server(Java)。但直接和硬件打交道的代码必须在 vendor 分区(Treble 规定,见 L7),用 C++ 写,跑在独立的 vendor 进程。这就是 HAL。

一个 HAL "活起来"需要满足(对照 L7 三件事 + L2 集成三条件):

  1. 有接口(AIDL 定义,framework 和 HAL 共同遵守的契约)
  2. 有实现(C++,真正干活)
  3. 能注册(addService 上架,供 framework 找)
  4. 开机自启(init.rc 拉起)
  5. 被 VINTF 认可(vintf_fragment 声明,见 L7)
  6. 能编译(Android.bp)

所以文件多,但每个文件对应一个明确职责。

2. 全局图:hello HAL 的七要素

                        ┌─ IHelloHal.aidl ── 接口契约(@VintfStability)
                        │       │ aidl 工具生成 BnHelloHal(Stub)/BpHelloHal(Proxy)
   framework ──调用──▶  │       ▼
                        ├─ HelloHal.h/.cpp ── 实现(extends BnHelloHal)
   init ──拉起──▶       ├─ main.cpp ── AServiceManager_addService 注册 + joinThreadPool
                        ├─ hello-hal-default.rc ── service ... class hal(init 自启)
   VINTF ──校验──▶      ├─ hello-hal.xml ── vintf_fragment 声明(L7)
                        └─ Android.bp ── aidl_interface(ndk后端) + cc_binary(vendor)

3. 某真实机型 真实源码印证(照搬 vibrator HAL)

最经典的 AIDL HAL 样板是 vibrator:hardware/interfaces/vibrator/aidl/default/

// main.cpp(我们照抄这个注册模式):
auto vib = ndk::SharedRefBase::make<Vibrator>();
binder_status_t status = AServiceManager_addService(
        vib->asBinder().get(), Vibrator::makeServiceName("default").c_str());
ABinderProcess_joinThreadPool();
// vibrator-default.rc(我们照抄这个 init 模式):
service vendor.vibrator-default /vendor/bin/hw/android.hardware.vibrator-service.example
    class hal           ← 关键:归到 hal 类,init 在 hal 阶段统一拉起
    user nobody

4. 前置准备

  • 依赖 L1(AIDL/Binder)、L7(VINTF)。
  • 环境:AOSP 编译(aidl_interface + cc_binary)。

5. 动手:七个文件逐一写

文件 1:接口 aidl/vendor/example/hello/IHelloHal.aidl

package vendor.example.demo;

@VintfStability    // ← 标记此接口可跨 system/vendor 边界稳定使用(HAL 必须)
interface IHelloHal {
    String sayHello(in String name);   // in = 输入参数(AIDL 方向标记)
    int add(in int a, in int b);
}

> @VintfStability 是 HAL 接口的标志:它要求接口稳定(不能随便改),因为 system 和 vendor 是分开升级的,接口得是双方的稳定契约。注意:它必须配合 Android.bp 里 stability: "vintf",否则编译报错(本课实测踩到,见踩坑)。

文件 2+3:实现 default/HelloHal.h / HelloHal.cpp

// HelloHal.h
#pragma once
#include <aidl/vendor/example/hello/BnHelloHal.h>   // aidl 生成的服务端骨架
namespace aidl::vendor::example::hello {
class HelloHal : public BnHelloHal {                 // 继承 Bn(Stub)
public:
    ::ndk::ScopedAStatus sayHello(const std::string& name, std::string* _aidl_return) override;
    ::ndk::ScopedAStatus add(int32_t a, int32_t b, int32_t* _aidl_return) override;
};
}
// HelloHal.cpp
::ndk::ScopedAStatus HelloHal::sayHello(const std::string& name, std::string* _aidl_return) {
    *_aidl_return = "Hello from HAL, " + name;
    return ::ndk::ScopedAStatus::ok();   // ndk 后端用 ScopedAStatus(不是 cpp 后端的 Status)
}
::ndk::ScopedAStatus HelloHal::add(int32_t a, int32_t b, int32_t* _aidl_return) {
    *_aidl_return = a + b;
    return ::ndk::ScopedAStatus::ok();
}

> 注意:HAL 用 ndk 后端,返回类型是 ndk::ScopedAStatus(对比 L1 cpp 后端的 Status)。返回值同样是出参 _aidl_return

文件 4:注册 default/main.cpp(照搬 vibrator)

int main() {
    ABinderProcess_setThreadPoolMaxThreadCount(0);
    auto hal = ndk::SharedRefBase::make<HelloHal>();        // 创建实例
    const std::string name = std::string(HelloHal::descriptor) + "/default";  // 服务名=接口descriptor+/default
    binder_status_t status = AServiceManager_addService(hal->asBinder().get(), name.c_str());  // 上架
    CHECK_EQ(status, STATUS_OK);
    ABinderProcess_joinThreadPool();   // 阻塞等调用(同 L1)
    return EXIT_FAILURE;  // 正常不会到这
}

> 服务名 vendor.example.demo.IHelloHal/default —— 和 L7 vintf manifest 里的 <fqname>IHelloHal/default</fqname> + <name> 对应。framework 用同一个名字 waitForService 找它(L10)。

文件 5:init.rc default/hello-hal-default.rc

service vendor.hello-hal-default /vendor/bin/hw/vendor.example.demo-service.default
    class hal       ← 归到 hal 类,init 在 hal 启动阶段拉起(见 L21 开机流程)
    user nobody
    group nobody

文件 6:vintf_fragment default/hello-hal.xml(L7 学的)

<manifest version="1.0" type="device">
    <hal format="aidl">
        <name>vendor.example.demo</name>
        <version>1</version>
        <fqname>IHelloHal/default</fqname>
    </hal>
</manifest>

文件 7:Android.bp

aidl_interface {
    name: "vendor.example.demo",
    vendor_available: true,         // vendor 侧可用
    srcs: ["aidl/vendor/example/hello/IHelloHal.aidl"],
    stability: "vintf",             // ★ 必须!配合 @VintfStability
    backend: { cpp: { enabled: false }, ndk: { enabled: true } },  // HAL 用 ndk 后端
}
cc_binary {
    name: "vendor.example.demo-service.default",
    relative_install_path: "hw",    // 装到 /vendor/bin/hw/
    vendor: true,                   // vendor 分区
    init_rc: ["default/hello-hal-default.rc"],       // 关联 init.rc
    vintf_fragments: ["default/hello-hal.xml"],      // 关联 vintf 声明
    srcs: ["default/HelloHal.cpp", "default/main.cpp"],
    shared_libs: ["libbase", "libbinder_ndk", "vendor.example.demo-V1-ndk"],  // 链接生成的 ndk 接口库
}

> 链接库 vendor.example.demo-V1-ndk 命名规则:<aidl_interface名>-V<版本>-ndk,aidl_interface 自动生成。

6. 看底层:aidl_interface 生成了什么

aidl --lang=ndk --structured --stability vintf 会生成:

  • IHelloHal.h / BnHelloHal.h(服务端骨架)/ BpHelloHal.h(客户端代理)/ IHelloHal.cpp
  • 还会生成 vendor.example.demo-V1-ndk 这个库供链接

HelloHal 继承 BnHelloHal,onTransact 由 Bn 处理,你只写业务方法——和 L1 一个套路,只是后端从 cpp 换成 ndk。

7. 编译 & 运行(验证状态如实)

mm    # 编出 vendor.example.demo-service.default 可执行文件
# 刷机后,init 自动拉起(class hal):
adb shell lshal | grep hello           # 看 HAL 起没起、状态
adb shell ps -A | grep hello-service   # HAL 进程在不在
adb logcat | grep -i HelloHal

预期:

HelloHal: registered as vendor.example.demo.IHelloHal/default
lshal: vendor.example.demo.IHelloHal/default  (起来了)

> 验证状态(诚实):aidl --lang=ndk --structured --stability vintf 生成 ndk 后端通过(RC=0)。完整 mm 编 + 刷机起 HAL 需 AOSP 环境/真机,未实跑。

8. 踩坑提醒(本课真踩到的)

  1. @VintfStability 必须配 stability: "vintf":只写注解不配 Android.bp,aidl 报 Must compile @VintfStability type w/ aidl_interface 'stability: "vintf"'。本课实测踩到。
  2. HAL 用 ndk 后端不是 cpp:backend: { cpp:{enabled:false}, ndk:{enabled:true} };返回类型 ScopedAStatus 不是 Status
  3. 服务名要三处一致:main.cpp 的 addService 名、vintf xml 的 fqname、framework 找它的名(L10),都得是 vendor.example.demo.IHelloHal/default
  4. 装错分区:vendor: true + relative_install_path: "hw" 才进 /vendor/bin/hw/;漏了 init 找不到可执行文件。
  5. vintf_fragments 漏配:Android.bp 不写 vintf_fragments,HAL 声明不生效,VINTF 校验/framework 找不到(L7)。

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

9.1 常见问题清单

  1. 编译报 @VintfStability
  2. HAL 进程没起(lshal N/A 或不显示)
  3. framework getService/waitForService 拿不到 HAL
  4. HAL 起来了但调用崩(SELinux/参数)
  5. 开机 VINTF 校验失败

9.2 分析与定位

① 编译 @VintfStability 错 — Android.bp 没配 stability: "vintf" → 加上。

② HAL 进程没起

adb shell lshal | grep hello          # 不显示=vintf 没声明;N/A=声明了进程没起
adb shell ps -A | grep hello-service  # 进程在吗
adb logcat | grep -iE "hello-hal|init.*hello|avc.*hello"
  • 不显示 → vintf_fragments 没配(查 Android.bp,L7)
  • N/A(声明了没起)→ init 没拉起(查 .rc:class/路径)或 SELinux 拦(查 avc,L11)或可执行文件没装对分区

③ framework 拿不到 HAL(L10 会用)

adb shell lshal | grep hello   # HAL 自己起了吗(先确认②)
# 起了但拿不到 → 服务名不一致 / framework 侧 sepolicy 没权限访问

④ HAL 起来调用崩

adb shell ls /data/tombstones/        # native crash?
adb logcat | grep -iE "avc.*hello|tombstone"  # SELinux 拦?

⑤ VINTF 校验失败 — 见 L7。比对 vendor manifest(含你的 vintf_fragment)和 framework matrix。

9.3 定位决策树

HAL 出问题
├─ 编译 @VintfStability 错 → Android.bp 加 stability:"vintf"
├─ 进程没起 → lshal grep
│    ├─ 不显示 → vintf_fragments 没配(Android.bp,L7)
│    └─ N/A → init .rc(class/路径)/ 装错分区 / SELinux(L11)
├─ framework 拿不到 → 先确认 HAL 起了 → 服务名一致?framework sepolicy?
├─ 调用崩 → tombstone + avc
└─ VINTF 校验失败 → 比对 manifest/matrix(L7)

10. 小结

  1. 一个 HAL 七要素:接口(@VintfStability)+ 实现(extends Bn)+ main(addService)+ init.rc(class hal)+ vintf_fragment + Android.bp。
  2. HAL 用 ndk 后端(libbinder_ndk,ScopedAStatus),装 vendor 分区。
  3. @VintfStability 必配 stability:"vintf"(实测踩坑)。
  4. 服务名三处一致:main / vintf xml / framework 查找。
  5. 排查口诀:编译错配 vintf;没起用 lshal(不显示=vintf没配,N/A=init/sepolicy);拿不到查服务名+sepolicy。

下节预告

L10:HAL 注册 + framework 接入 —— 让 framework 侧真正拿到这个 HAL 并跨进程调用,打通 framework→HAL。

评论