【全栈第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 集成三条件):
- 有接口(AIDL 定义,framework 和 HAL 共同遵守的契约)
- 有实现(C++,真正干活)
- 能注册(addService 上架,供 framework 找)
- 开机自启(init.rc 拉起)
- 被 VINTF 认可(vintf_fragment 声明,见 L7)
- 能编译(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. 踩坑提醒(本课真踩到的)
@VintfStability必须配stability: "vintf":只写注解不配 Android.bp,aidl 报Must compile @VintfStability type w/ aidl_interface 'stability: "vintf"'。本课实测踩到。- HAL 用 ndk 后端不是 cpp:
backend: { cpp:{enabled:false}, ndk:{enabled:true} };返回类型ScopedAStatus不是Status。 - 服务名要三处一致:main.cpp 的 addService 名、vintf xml 的 fqname、framework 找它的名(L10),都得是
vendor.example.demo.IHelloHal/default。 - 装错分区:
vendor: true+relative_install_path: "hw"才进/vendor/bin/hw/;漏了 init 找不到可执行文件。 - vintf_fragments 漏配:Android.bp 不写 vintf_fragments,HAL 声明不生效,VINTF 校验/framework 找不到(L7)。
9. 常见问题分析与定位(HAL 实战)
9.1 常见问题清单
- 编译报
@VintfStability错 - HAL 进程没起(lshal N/A 或不显示)
- framework getService/waitForService 拿不到 HAL
- HAL 起来了但调用崩(SELinux/参数)
- 开机 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. 小结
- 一个 HAL 七要素:接口(@VintfStability)+ 实现(extends Bn)+ main(addService)+ init.rc(class hal)+ vintf_fragment + Android.bp。
- HAL 用 ndk 后端(libbinder_ndk,ScopedAStatus),装 vendor 分区。
@VintfStability必配stability:"vintf"(实测踩坑)。- 服务名三处一致:main / vintf xml / framework 查找。
- 排查口诀:编译错配 vintf;没起用 lshal(不显示=vintf没配,N/A=init/sepolicy);拿不到查服务名+sepolicy。
下节预告
L10:HAL 注册 + framework 接入 —— 让 framework 侧真正拿到这个 HAL 并跨进程调用,打通 framework→HAL。