按理说之前两篇讲 HIDL HAL 的应该都写完了,但我还是打算再补充一篇,专门介绍 HAL 的发展历史以及一些经典写法,可以作为后期大家做适配时的参考。
前言:架构演进图鉴
上古时代(Android 7.0 之前):纯直连时代(Legacy HAL)
- 方式:
hw_module_t+.h头文件 + JNI dlopen - 结果:性能极高,但系统脆弱,极易崩溃,无分区隔离
过渡时代(Android 8.0 - 10):Treble 改革(HIDL)
- 方式:
.hal文件 + 伪绑定(包裹老.so)或纯绑定进程 - 结果:实现了跨进程隔离,保住了 SystemServer 的命,且厂商无需立刻重写老代码
现代与未来(Android 11 至今):大一统时代(Stable AIDL)
- 方式:
.aidl文件 + 纯绑定进程(强制剥离所有老旧的hw_module_t) - 结果:Google 宣布废弃 HIDL,全面回归 AIDL。所有新写的 HAL 必须是基于 AIDL 的独立守护进程(纯绑定)
前两篇文章主要围绕过渡时代的写法展开,AIDL 我放在了另外一篇。所以这篇主要围绕纯直连时代(Legacy HAL)和伪绑定这两种实现方式来聊。
一、纯直连时代(Legacy HAL)
现在先假设我们不知道什么是 HAL。假如驱动调好了一个传感器,让你用 App 去访问,你大概率会说:"你把 so 库和 .h 发给我"。这其实非常符合我们的开发习惯——通过 JNI 的方式去访问 .so 库,直接简单粗暴。
但假设驱动老哥经常修改,你是不是每次都得更新 .so 库,还要自己打包 App?而且万一这个驱动挂了,你自己的 App 也会出问题,因为大家同生同死,测试第一时间就会给你提 bug。
你看,问题来了。我们来思考一下:
第一个问题:假如驱动不稳定,我们每次都要修改,那能否提供一个简单的方式?可能你会说".h 写好就不改了,不就行了吗"。但这里有个问题——Android 升级时,Framework(/system)会更新,但 Vendor(/vendor 里的直通 .so)不变。如果你直接用一个传统的 C++ .h 文件来通信,一旦新版 Android 换了编译器版本,或者改变了某个 std::string 的内部实现大小,或者类的虚函数表(VTable)顺序变了……虽然在同一个进程里,但因为内存布局(ABI)不匹配,会立刻段错误(Segfault)崩溃。
你这个时候肯定会说:NDK 不就是为了解决这个问题的吗?
NDK 是用来解决 C/C++ 稳定性的,那我们双方都用 NDK 编译,直接给个
.h头文件,ABI 不就稳定了吗?
现实是残酷的:NDK 只能保证 C 语言的 ABI 绝对稳定,但它永远无法保证 C++ 的 ABI 稳定。
举个例子:
class ICameraDevice {
public:
virtual void takePicture(std::string fileName) = 0;
};
-
Vendor(底层驱动)编译时:假设你当时用的是 Android 11 的 NDK(比如 Clang 10)编译出了
camera.so。在那个版本的libc++标准库里,std::string的内部实现可能是:一个指针(8 字节)+ 一个长度(8 字节)= 占据 16 字节内存。 -
Framework(系统层)升级了:两年后,手机 OTA 升级到了 Android 13。Google 更新了系统里的 NDK 编译器(比如 Clang 14)。为了优化性能,新版
libc++引入了 SSO(短字符串优化),导致std::string内部增加了一个联合体,变成占据 24 字节内存。 -
调用瞬间崩溃:Android 13 的
system_server拿着新版的.h,创建了一个 24 字节的std::string,然后通过指针调用旧版camera.so里的takePicture。旧版代码依然以为传过来的是 16 字节的对象,内存一读取,瞬间越界错位,整个系统进程直接段错误(Segmentation Fault)崩溃,车机无限重启! (当然这种情况一般不会出现,我们肯定在升级前会进行测试的)
只要你的接口里暴露了纯 C++ 的对象(哪怕是用 NDK 编译的),在跨越不同 Android 系统大版本时,还是会存在不稳定的情况。
但这并不是说 NDK 就不行了,而是说 NDK 的作用并不是来干这个的(后续我会出一篇关于 NDK 的介绍)。这里我们再讨论一下:如果纯直连模式下,C++ 的 .h 会出问题,那应该怎么办?
1.1 纯 C 语言方案:hardware.h
其实 C 语言的 ABI 是稳定的,如果只用纯 C 语言写头文件(.h)就可以办到。好,这个时候我们就完成了一套稳定的 HAL 直连开发。
在上古时代里,Android 就是严格按照这个思路在跑。Google 提供了一个极其核心的纯 C 语言头文件,叫 hardware.h。所有的硬件厂商都要去实现里面的两个纯 C 结构体:hw_module_t 和 hw_device_t。
// 纯 C 语言结构体,只包含基本数据类型和函数指针
struct camera_device {
struct hw_device_t common; // 父类(C 语言的伪继承)
// 纯 C 的函数指针
int (*take_picture)(struct camera_device* dev);
void (*set_parameters)(struct camera_device* dev, const char* params);
};
1.2 该写到哪里?
用户态有两个地方可以写:一个是 native 层,一个是 HAL 层。那我们如何判断一个 C/C++ 代码应该写在哪一层呢?
-
自定义 Native Service (系统本地服务)
-
阵营:属于 Android 框架层(Framework)的“亲儿子”。
-
居住地:通常编译输出到
/system/bin/或/system_ext/bin/。 -
大管家:向
servicemanager注册(应用层用的也是这个管家)。 -
通信机制:标准的 Binder IPC(通常用 AIDL 编写)。
-
职责:负责全局性的、与具体底层硬件无关的纯软件逻辑或系统级策略。
-
-
自定义 HAL Service (硬件抽象层服务)
-
阵营:属于底层硬件供应商(Vendor/ODM)的“驻扎代表”。
-
居住地:必须编译输出到
/vendor/bin/hw/。 -
大管家:向
hwservicemanager注册(专门为硬件服务设立的管家)。 -
通信机制:HwBinder(早期 HIDL)或 Stable AIDL。
-
职责:直接和 Linux Kernel 里的设备驱动打交道,负责特定硬件设备的控制和数据读写。
-
服务需要直接操作底层的 /dev/xxx 设备节点吗?
-
如果是(强硬件相关) 选择 HAL Service。
-
场景:你们公司开发了一款带特殊跑马灯的智能硬件、一个定制的串口通信模块、或者外接了一个特殊的温湿度传感器。你的代码需要通过
open(),ioctl(),read(),write()去操作驱动节点。 -
原因:在现代 Android 的 SELinux 策略中,
/system分区里的进程是被严禁直接访问大多数/dev下的底层硬件节点的。只有/vendor下的 HAL 进程才有权限(在配置了对应的 sepolicy 后)去摸这些底层驱动。
-
逻辑是纯软件算法、系统策略,还是跨硬件通用的?
-
如果是(纯软件/系统级) 选择 Native Service。
-
场景:你需要写一个后台常驻进程,监控系统的 CPU 温度并在过高时杀掉部分后台应用;或者你需要集成一个你们公司自研的、非常耗费 CPU 的音视频滤镜算法库(纯 C++ 运算,不依赖特定 DSP 芯片)。
-
原因:这种逻辑和具体的芯片无关,换个主板它依然能跑。把它放在 System 分区,可以随着 Android 系统的升级(OTA)一起更新,而不需要厂商重新提供底层 BSP。
-
1.3 hw_get_module 的底层原理以及调用流程详细解释
如果你去看过以前关于hal的文章,以及B站的一些视频教程,他们大概率都是讲的hw_device_t,hw_module_t,hw_module_methods_t 这几个概念。那如何理解这些概念呢?我先贴一下他们具体代码在哪里
hardware\libhardware\include\hardware\hardware.h
struct hw_module_t;
struct hw_module_methods_t;
struct hw_device_t;
/**
* Every hardware module must have a data structure named HAL_MODULE_INFO_SYM
* and the fields of this data structure must begin with hw_module_t
* followed by module specific information.
*/
typedef struct hw_module_t {
...
/** Identifier of module */
const char *id;
/** Name of this module */
const char *name;
/** Author/owner/implementor of the module */
const char *author;
/** Modules methods */
struct hw_module_methods_t* methods;
} hw_module_t;
typedef struct hw_module_methods_t {
/** Open a specific device */
int (*open)(const struct hw_module_t* module, const char* id,
struct hw_device_t** device);
} hw_module_methods_t;
/**
* Every device data structure must begin with hw_device_t
* followed by module specific public methods and attributes.
*/
typedef struct hw_device_t {
/** tag must be initialized to HARDWARE_DEVICE_TAG */
uint32_t tag;
uint32_t version;
/** reference to the module this device belongs to */
struct hw_module_t* module;
/** Close this device */
int (*close)(struct hw_device_t* device);
} hw_device_t;
/**
* Get the module info associated with a module by id.
*
* @return: 0 == success, <0 == error and *module == NULL
*/
int hw_get_module(const char *id, const struct hw_module_t **module);
/**
* Get the module info associated with a module instance by class 'class_id'
* and instance 'inst'.
*
* Some modules types necessitate multiple instances. For example audio supports
* multiple concurrent interfaces and thus 'audio' is the module class
* and 'primary' or 'a2dp' are module interfaces. This implies that the files
* providing these modules would be named audio.primary.<variant>.so and
* audio.a2dp.<variant>.so
*
* @return: 0 == success, <0 == error and *module == NULL
*/
int hw_get_module_by_class(const char *class_id, const char *inst,
const struct hw_module_t **module);
- hw_get_module:传入目标HAL的id
- 根据id,拼装路径,寻找so文件
- 使用dlopen加载打开对应的so文件
- 使用dlsym加载符号表中的 HAL_MODULE_INFO_SYM,对应的xx_hw_module_t变量,转换成hw_module_t指针返
- 根据获取的hw_module_t指针,获取hw_module_methods_t,通过open函数的调用,获取hw_device_t
- 使用hw_device_t指针调用自定义的功能函数 (自定义的函数都是写在 hw_device_t 中)
- 最后调用hw_device_t的close关闭
提示:在纯直连模式下,根本不需要 .hal 或 .aidl 文件
为什么不需要?因为 .hal(HIDL)和 .aidl(AIDL)的本质是 IDL(接口定义语言,Interface Definition Language)。它们诞生的唯一目的,就是为了让 hidl-gen 或 aidl 工具去生成那堆繁琐的、用于跨进程(IPC)序列化/反序列化的 Proxy 和 Stub 代码(也就是那个作为"翻译官"的 .so 或 .jar)。
纯直连怎么写?在纯直连(直接 dlopen)的世界里,因为大家都在同一个进程的内存空间里聊天,根本不需要打包数据。开发者只需要写最普通的 C/C++ 头文件(.h)就可以完成接口定义了!
当年写传统 HAL 的时候,压根就没有 hidl 这个词,大家全靠 #include <hardware/hardware.h>(这里面定义了 hw_module_t)来打天下。
1.4 完整实战:Custom Timer HAL
自定义Legacy HAL 之前,我们做一些知识填充,先来看当年上层 Java 框架(比如在 SystemServer 进程里)想点亮 LED,底层会走 Android 提供的一个神级 C 语言函数:hw_get_module()。
这个函数的底层原理极其简单直接:
- 去
/vendor/lib/hw/找名字叫led_module.default.so的文件 - 调用 Linux 的
dlopen()把这个.so加载到当前进程内存里 - 调用
dlsym(),去这个.so里寻找一个叫HAL_MODULE_INFO_SYM的符号地址 - 找到后,强转成
hw_module_t - 调用
module->methods->open()拿到hw_device_t - 最后调用
device->set_light()去点亮灯
现在我们来设计一套完整的 Legacy HAL。整体调用链路如下:
- SystemServer 启动时,注册 Binder 服务 XXXService
- App 通过 XXXManager 获取到 XXXService 代理端对象
- App 通过代理端对象,发起远程调用访问
- XXXService 通过 JNI 加载 HAL so 库,调用 HAL so 库中操作硬件的函数
- HAL so 库中操作硬件的函数通过
open/ioctl等 Linux 系统调用访问到驱动程序
1.4.1 HAL 层实现
头文件:custom_timer_hal.h
路径:
vendor/custom/hardware/interfaces/legacy/customtimer/hal/custom_timer_hal.h
#ifndef ANDROID_CUSTOM_TIMER_HAL_H
#define ANDROID_CUSTOM_TIMER_HAL_H
#include <hardware/hardware.h>
#include <stdint.h>
__BEGIN_DECLS
// 模块 ID,JNI 就是靠这个名字来找 .so 的
#define CUSTOM_TIMER_HARDWARE_MODULE_ID "custom_timer"
// 定义回调函数的函数指针类型
typedef void (*timer_callback_t)(int connected_time_sec, void* user_data);
// 定义我们的设备结构体,必须以 hw_device_t 开头!
struct custom_timer_device_t {
struct hw_device_t common; // 继承的基类
// 面向对象思想:函数指针就是设备的方法
int (*start_timer)(struct custom_timer_device_t* dev);
int (*stop_timer)(struct custom_timer_device_t* dev);
int (*get_current_time)(struct custom_timer_device_t* dev);
// 注册上层的回调函数
void (*set_callback)(struct custom_timer_device_t* dev, timer_callback_t cb, void* user_data);
// timer 驱动读写接口
int (*write_to_hello)(struct custom_timer_device_t* dev, const char* data, int len);
int (*read_from_hello)(struct custom_timer_device_t* dev, char* buf, int len);
};
__END_DECLS
#endif // ANDROID_CUSTOM_TIMER_HAL_H
实现文件:custom_timer_hal.cpp
路径:
vendor/custom/hardware/interfaces/legacy/customtimer/hal/custom_timer_hal.cpp
#include "custom_timer_hal.h"
#include <fcntl.h> // O_RDWR
#include <log/log.h>
#include <stdlib.h>
#include <thread>
#include <atomic>
#include <unistd.h>
#define LOG_TAG "CustomTimerHAL"
// --- 设备的内部状态 ---
struct custom_timer_context_t {
custom_timer_device_t device; // 必须是第一个成员,方便强转
std::atomic<bool> is_running;
std::atomic<int> connected_time;
std::thread* timer_thread;
timer_callback_t callback;
void* user_data;
int hello_fd; // /dev/timer 的文件描述符
};
// --- 后台工作线程(模拟硬件中断)---
static void timer_thread_loop(custom_timer_context_t* ctx) {
while (ctx->is_running) {
sleep(2); // 模拟每 2 秒触发一次
if (!ctx->is_running) break;
ctx->connected_time += 2;
ALOGI("HAL 层:定时器触发,当前时间 %d 秒", (int)ctx->connected_time);
// 向上层(JNI)触发回调
if (ctx->callback != nullptr) {
ctx->callback(ctx->connected_time, ctx->user_data);
}
}
}
// --- 设备方法的具体实现 ---
static int hal_start_timer(struct custom_timer_device_t* dev) {
auto* ctx = reinterpret_cast<custom_timer_context_t*>(dev);
if (ctx->is_running) return 0;
ctx->is_running = true;
ctx->connected_time = 0;
ctx->timer_thread = new std::thread(timer_thread_loop, ctx);
ALOGI("HAL 层:定时器已启动");
return 0;
}
static int hal_stop_timer(struct custom_timer_device_t* dev) {
auto* ctx = reinterpret_cast<custom_timer_context_t*>(dev);
if (!ctx->is_running) return 0;
ctx->is_running = false;
if (ctx->timer_thread) {
ctx->timer_thread->join();
delete ctx->timer_thread;
ctx->timer_thread = nullptr;
}
ALOGI("HAL 层:定时器已停止");
return 0;
}
static int hal_get_current_time(struct custom_timer_device_t* dev) {
auto* ctx = reinterpret_cast<custom_timer_context_t*>(dev);
return ctx->connected_time;
}
static void hal_set_callback(struct custom_timer_device_t* dev, timer_callback_t cb, void* user_data) {
auto* ctx = reinterpret_cast<custom_timer_context_t*>(dev);
ctx->callback = cb;
ctx->user_data = user_data;
}
static int hal_write_to_hello(struct custom_timer_device_t* dev, const char* data, int len) {
auto* ctx = reinterpret_cast<custom_timer_context_t*>(dev);
if (ctx->hello_fd < 0) {
ALOGE("HAL 层:/dev/timer 未打开,无法写入");
return -1;
}
// 限制写入长度,与内核驱动 kernel_buf[1024] 一致
int write_len = len < 1024 ? len : 1024;
int ret = write(ctx->hello_fd, data, write_len);
ALOGI("HAL 层:写入 /dev/timer %d 字节,返回值 %d", write_len, ret);
return ret;
}
static int hal_read_from_hello(struct custom_timer_device_t* dev, char* buf, int len) {
auto* ctx = reinterpret_cast<custom_timer_context_t*>(dev);
if (ctx->hello_fd < 0) {
ALOGE("HAL 层:/dev/timer 未打开,无法读取");
return -1;
}
// 限制读取长度,与内核驱动 kernel_buf[1024] 一致
int read_len = len < 1024 ? len : 1024;
int ret = read(ctx->hello_fd, buf, read_len);
ALOGI("HAL 层:从 /dev/timer 读取 %d 字节,返回值 %d", read_len, ret);
return ret;
}
static int hal_close(struct hw_device_t* device) {
auto* ctx = reinterpret_cast<custom_timer_context_t*>(device);
hal_stop_timer(&ctx->device);
if (ctx->hello_fd >= 0) {
close(ctx->hello_fd);
}
free(ctx);
return 0;
}
// --- 模块的 Open 函数 ---
static int timer_device_open(const struct hw_module_t* module, const char* id, struct hw_device_t** device) {
auto* ctx = (custom_timer_context_t*)malloc(sizeof(custom_timer_context_t));
memset(ctx, 0, sizeof(custom_timer_context_t));
// 打开 /dev/timer 设备节点
ctx->hello_fd = open("/dev/timer", O_RDWR);
if (ctx->hello_fd < 0) {
ALOGE("HAL 层:无法打开 /dev/timer (errno=%d),timer 功能仍可用", errno);
} else {
ALOGI("HAL 层:/dev/timer 打开成功,fd=%d", ctx->hello_fd);
}
// 填充继承自 hw_device_t 的信息
ctx->device.common.tag = HARDWARE_DEVICE_TAG;
ctx->device.common.version = 1;
ctx->device.common.module = (hw_module_t*)module;
ctx->device.common.close = hal_close;
// 绑定函数指针
ctx->device.start_timer = hal_start_timer;
ctx->device.stop_timer = hal_stop_timer;
ctx->device.get_current_time = hal_get_current_time;
ctx->device.set_callback = hal_set_callback;
ctx->device.write_to_hello = hal_write_to_hello;
ctx->device.read_from_hello = hal_read_from_hello;
*device = (struct hw_device_t*)&ctx->device;
ALOGI("HAL 层:设备 Open 成功");
return 0;
}
static struct hw_module_methods_t timer_module_methods = {
.open = timer_device_open,
};
// --- 唯一暴露的全局符号!Framework 就是找它! ---
struct hw_module_t HAL_MODULE_INFO_SYM = {
.tag = HARDWARE_MODULE_TAG,
.version_major = 1,
.version_minor = 0,
.id = CUSTOM_TIMER_HARDWARE_MODULE_ID,
.name = "Custom Timer Legacy HAL",
.author = "Andy",
.methods = &timer_module_methods,
};
1.4.2 编译配置(HAL 层 Android.bp)
HAL 层代码写完之后,必须通过 Android.bp 把它编译成动态库,并安装到 /vendor/lib64/hw/ 目录下,才能被 hw_get_module() 找到。
路径:
vendor/custom/hardware/interfaces/legacy/customtimer/Android.bp
// 1. 编译 HAL 层 (纯 C++)
// 必须命名为 "模块ID.default" 才能被 hw_get_module 找到!
cc_library_shared {
name: "custom_timer.default",
relative_install_path: "hw", // 强制安装到 /vendor/lib64/hw/ 目录下
vendor: true,
srcs: [
"hal/custom_timer_hal.cpp",
],
shared_libs: [
"liblog",
"libcutils",
],
header_libs: [
"libhardware_headers", // 依赖 hardware.h
],
}
这里有三个关键点需要特别说明:
- 命名规范
custom_timer.default:hw_get_module()在底层寻找.so时,拼接规则是模块ID + "." + ro.hardware 属性值 + ".so"。ro.hardware在大多数设备上默认是default,所以库必须命名为custom_timer.default.so,否则dlopen会找不到。 relative_install_path: "hw":强制把.so安装到/vendor/lib64/hw/子目录下,而不是默认的/vendor/lib64/。这是hw_get_module()的搜索路径约定。vendor: true:标记为 vendor 模块,编译产物会进vendor分区。Legacy HAL 必须放在 vendor 分区,否则 SELinux 会拒绝 system 进程加载它。
编译命令:
mmm vendor/custom/hardware/interfaces/legacy/customtimer
编译完成后,产物路径为 /vendor/lib64/hw/custom_timer.default.so,后续 JNI 层就是通过 hw_get_module("custom_timer") 来加载它的。
1.4.3 JNI 层实现
路径:
services/core/jni/com_android_server_customtimer_CustomTimerService.cpp
#define LOG_TAG "CustomTimerServiceJNI"
#include <jni.h>
#include <log/log.h>
#include <hardware/hardware.h>
#include <nativehelper/JNIHelp.h>
#include "custom_timer_hal.h"
namespace android {
// 全局变量保存 JVM 和 Service 层的引用
static JavaVM* g_jvm = nullptr;
static jobject g_service_obj = nullptr;
static jmethodID g_on_tick_method = nullptr;
static custom_timer_device_t* g_timer_dev = nullptr;
// HAL 层的 C 回调函数 — 运行在 HAL 后台线程
static void on_hal_timer_tick(int connected_time_sec, void* /* user_data */) {
if (!g_jvm || !g_service_obj || !g_on_tick_method) return;
JNIEnv* env;
bool attached = false;
if (g_jvm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) {
g_jvm->AttachCurrentThread(&env, nullptr);
attached = true;
}
// 调用 Java 层 CustomTimerService.onTimerTick
env->CallStaticVoidMethod(
env->GetObjectClass(g_service_obj),
g_on_tick_method,
connected_time_sec);
if (attached) {
g_jvm->DetachCurrentThread();
}
}
// ==================== Native 方法实现 ====================
static jboolean nativeInit(JNIEnv* env, jclass /* clazz */, jobject service) {
hw_module_t* module;
// 1. 加载 legacy HAL 模块
int err = hw_get_module(CUSTOM_TIMER_HARDWARE_MODULE_ID,
(hw_module_t const**)&module);
if (err != 0) {
ALOGE("JNI: HAL module not found! err=%d", err);
return JNI_FALSE;
}
// 2. 打开设备
hw_device_t* device;
err = module->methods->open(module, CUSTOM_TIMER_HARDWARE_MODULE_ID, &device);
if (err != 0) return JNI_FALSE;
g_timer_dev = reinterpret_cast<custom_timer_device_t*>(device);
// 3. 保存 JVM 和 Service 对象引用,用于回调
env->GetJavaVM(&g_jvm);
g_service_obj = env->NewGlobalRef(service);
jclass service_class = env->GetObjectClass(service);
g_on_tick_method = env->GetStaticMethodID(service_class, "onTimerTick", "(I)V");
// 4. 注册 HAL 回调
g_timer_dev->set_callback(g_timer_dev, on_hal_timer_tick, nullptr);
ALOGI("JNI: Service HAL init ok");
return JNI_TRUE;
}
static void nativeStart(JNIEnv* /* env */, jclass /* clazz */) {
if (g_timer_dev) g_timer_dev->start_timer(g_timer_dev);
}
static void nativeStop(JNIEnv* /* env */, jclass /* clazz */) {
if (g_timer_dev) g_timer_dev->stop_timer(g_timer_dev);
}
static jint nativeGetCurrentTime(JNIEnv* /* env */, jclass /* clazz */) {
return g_timer_dev ? g_timer_dev->get_current_time(g_timer_dev) : 0;
}
static jint nativeWriteHello(JNIEnv* env, jclass /* clazz */, jstring data) {
if (!g_timer_dev) return -1;
const char* str = env->GetStringUTFChars(data, nullptr);
jsize len = env->GetStringUTFLength(data);
int ret = g_timer_dev->write_to_hello(g_timer_dev, str, len);
env->ReleaseStringUTFChars(data, str);
ALOGI("JNI: write /dev/timer %d bytes, ret=%d", len, ret);
return ret;
}
static jstring nativeReadHello(JNIEnv* env, jclass /* clazz */) {
if (!g_timer_dev) return nullptr;
char buf[1024] = {0};
int len = g_timer_dev->read_from_hello(g_timer_dev, buf, sizeof(buf) - 1);
if (len <= 0) return nullptr;
buf[len] = '\0';
ALOGI("JNI: read /dev/timer %d bytes", len);
return env->NewStringUTF(buf);
}
// ==================== JNI 方法表 ====================
static const JNINativeMethod gMethods[] = {
{"nativeInit", "(Lcom/android/server/customtimer/CustomTimerService;)Z",
(void*)nativeInit},
{"nativeStart", "()V", (void*)nativeStart},
{"nativeStop", "()V", (void*)nativeStop},
{"nativeGetCurrentTime","()I", (void*)nativeGetCurrentTime},
{"nativeWriteHello", "(Ljava/lang/String;)I", (void*)nativeWriteHello},
{"nativeReadHello", "()Ljava/lang/String;", (void*)nativeReadHello},
};
int register_android_server_customtimer_CustomTimerService(JNIEnv* env) {
return jniRegisterNativeMethods(env,
"com/android/server/customtimer/CustomTimerService",
gMethods, NELEM(gMethods));
}
} // namespace android
JNI 注册需要在 onload.cpp 中添加声明和调用:
// services/core/jni/onload.cpp
// 声明
int register_android_server_customtimer_CustomTimerService(JNIEnv*);
// 在 JNI_OnLoad 中注册
extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
{
// ... 其他注册
register_android_server_customtimer_CustomTimerService(env);
// ... 其他注册
}
1.4.4 Framework 层实现(AIDL + Manager + 系统注册)
回调接口 AIDL:ICustomTimerCallback.aidl
package android.customtimer;
/**
* 客户端回调接口 — 对应 HAL 层的 timer_callback_t。
* oneway 确保 HAL 推送线程不被对端阻塞。
* @hide
*/
oneway interface ICustomTimerCallback {
void onTimerTick(int connectedTimeSec);
}
服务接口 AIDL:ICustomTimerService.aidl
package android.customtimer;
import android.customtimer.ICustomTimerCallback;
/**
* Framework 侧 CustomTimer 服务 AIDL 接口 — 映射 legacy HAL custom_timer_hal.h 全部方法。
* @hide
*/
interface ICustomTimerService {
// 定时器操作
int startTimer();
int stopTimer();
int getCurrentTime();
// hello 驱动读写
int writeToHello(String data);
String readFromHello();
// 回调订阅
int subscribe(ICustomTimerCallback callback);
int unsubscribe(ICustomTimerCallback callback);
}
Manager 类:CustomTimerManager.java
package android.customtimer;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
import android.content.Context;
import android.os.ServiceManager.ServiceNotFoundException;
import java.util.concurrent.Executor;
/**
* CustomTimerManager — 提供给 App 层访问 /dev/timer 驱动和定时器功能的入口。
*
* <p>使用方式:
* <pre>
* CustomTimerManager mgr = new CustomTimerManager(context);
* mgr.startTimer();
* mgr.writeToHello("hello");
* String result = mgr.readFromHello();
* mgr.stopTimer();
* </pre>
*
* @hide
*/
public final class CustomTimerManager {
private static final String TAG = "CustomTimerManager";
private ICustomTimerService sService;
private final Context mContext;
/**
* @hide
*/
public CustomTimerManager(Context context) throws ServiceNotFoundException {
mContext = context;
sService = ICustomTimerService.Stub.asInterface(
ServiceManager.getServiceOrThrow(Context.CUSTOM_TIMER_SERVICE));
}
// ================================================================
// 定时器操作
// ================================================================
/**
* 启动定时器,开始计时并周期性回调 onTimerTick。
* @hide
*/
public int startTimer() {
try {
return sService.startTimer();
} catch (RemoteException e) {
e.printStackTrace();
} catch (Throwable t) {
Log.e(TAG, "startTimer error", t);
}
return -1;
}
/**
* 停止定时器。
* @hide
*/
public int stopTimer() {
try {
return sService.stopTimer();
} catch (RemoteException e) {
e.printStackTrace();
} catch (Throwable t) {
Log.e(TAG, "stopTimer error", t);
}
return -1;
}
/**
* 获取当前定时器已连接的秒数。
* @hide
*/
public int getCurrentTime() {
try {
return sService.getCurrentTime();
} catch (RemoteException e) {
e.printStackTrace();
} catch (Throwable t) {
Log.e(TAG, "getCurrentTime error", t);
}
return 0;
}
// ================================================================
// hello 驱动读写
// ================================================================
/**
* 写入数据到 /dev/timer 内核驱动。
* @hide
*/
public int writeToHello(String data) {
try {
return sService.writeToHello(data);
} catch (RemoteException e) {
e.printStackTrace();
} catch (Throwable t) {
Log.e(TAG, "writeToHello error", t);
}
return -1;
}
/**
* 从 /dev/timer 内核驱动读取数据。
* @hide
*/
public String readFromHello() {
try {
return sService.readFromHello();
} catch (RemoteException e) {
e.printStackTrace();
} catch (Throwable t) {
Log.e(TAG, "readFromHello error", t);
}
return "";
}
// ================================================================
// 回调订阅
// ================================================================
/**
* 订阅定时器回调。
* @hide
*/
public int subscribe(@NonNull ICustomTimerCallback callback) {
try {
return sService.subscribe(callback);
} catch (RemoteException e) {
e.printStackTrace();
}
return -1;
}
/**
* 订阅定时器回调(带 Executor)。
* @hide
*/
public int subscribe(@NonNull ICustomTimerCallback callback, @NonNull Executor executor) {
return subscribe(callback);
}
/**
* 取消订阅定时器回调。
* @hide
*/
public int unsubscribe(@NonNull ICustomTimerCallback callback) {
try {
return sService.unsubscribe(callback);
} catch (RemoteException e) {
e.printStackTrace();
}
return -1;
}
/**
* 取消订阅定时器回调(带 Executor)。
* @hide
*/
public int unsubscribe(@NonNull ICustomTimerCallback callback, @NonNull Executor executor) {
return unsubscribe(callback);
}
}
系统注册:Context.java 补丁
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3718,6 +3718,9 @@ public abstract class Context {
//@hide: STATS_COMPANION_SERVICE,
COMPANION_DEVICE_SERVICE,
CROSS_PROFILE_APPS_SERVICE,
+ CUSTOM_TIMER_SERVICE,
//@hide: SYSTEM_UPDATE_SERVICE,
@@ -5697,6 +5700,28 @@ public abstract class Context {
public static final String DISPLAY_HASH_SERVICE = "display_hash";
+ /**
+ * @see #getSystemService(String)
+ * @hide
+ */
+ public static final String CUSTOM_TIMER_SERVICE = "custom_timer";
服务注册:SystemServiceRegistry.java 补丁
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -233,7 +233,9 @@ import com.android.internal.net.INetworkWatchlistManager;
import com.android.internal.os.IDropBoxManagerService;
import com.android.internal.policy.PhoneLayoutInflater;
import com.android.internal.util.Preconditions;
-
+import android.customtimer.CustomTimerManager;
import java.util.Map;
@@ -1470,6 +1472,33 @@ public final class SystemServiceRegistry {
return new DisplayHashManager();
}});
+ registerService(Context.CUSTOM_TIMER_SERVICE, CustomTimerManager.class,
+ new CachedServiceFetcher<CustomTimerManager>() {
+ @Override
+ public CustomTimerManager createService(ContextImpl ctx)
+ throws ServiceNotFoundException {
+ return new CustomTimerManager(ctx.getOuterContext());
+ }
+ });
1.4.5 编译配置(Framework 层)
services/core/jni/Android.bp 补丁
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -39,6 +39,7 @@ cc_library_static {
"com_android_server_am_BatteryStatsService.cpp",
"com_android_server_biometrics_SurfaceToNativeHandleConverter.cpp",
"com_android_server_ConsumerIrService.cpp",
+ "com_android_server_customtimer_CustomTimerService.cpp",
"com_android_server_devicepolicy_CryptoTestHelper.cpp",
@@ -85,6 +86,7 @@ cc_library_static {
"system/memory/libmeminfo/include",
"vendor/rockchip/hardware/interfaces",
"hardware/rockchip/libhardware_rockchip/include/",
+ "vendor/custom/hardware/interfaces/legacy/customtimer/hal",
补充说明:Android Framework 中 JNI 是如何参与编译的
SystemServer.java中会执行System.loadLibrary("android_servers"):// com/android/server/SystemServer.java private void run() { System.loadLibrary("android_servers"); }而
libandroid_servers这个动态库的构建链路是:// base/services/Android.bp cc_library_shared { name: "libandroid_servers", defaults: ["libservices.core-libs"], whole_static_libs: ["libservices.core"], } // base/services/core/jni/Android.bp cc_library_static { name: "libservices.core", defaults: ["libservices.core-libs"], // ... 所有 com_android_server_*.cpp 都在这里 }也就是说,我们写的
com_android_server_customtimer_CustomTimerService.cpp最终会被打包进libandroid_servers.so,由 SystemServer 在启动时加载。
1.4.6 SELinux 配置
路径:
vendor/custom/hardware/interfaces/legacy/customtimer/sepolicy/
device.te — 定义节点类型:
# 1. 定义节点类型 customtimer 字符设备类型定义
type custom_timer_dev_t, dev_type;
file_contexts — 绑定文件上下文:
# 2. 绑定文件上下文(File Contexts):告诉系统哪个路径对应哪个类型。
# /dev/timer 设备节点标签绑定
/dev/timer u:object_r:custom_timer_dev_t:s0
hal_customtimer.te — 赋予目标进程访问权限:
# 3. 赋予目标进程访问权限:允许 system_server(JNI 静态链接 HAL)访问 /dev/timer
allow system_server custom_timer_dev_t:chr_file { getattr read write open ioctl };
注意:Legacy HAL 兼容路径,不需要 manifest。
1.4.7 编译与验证
make update-api -j32
mmm vendor/custom/hardware/interfaces/legacy/customtimer
1.4.8 完整调用链闭环
写入调用链:
App
↓ CustomTimerManager.writeToHello("nihao")
AIDL (ICustomTimerService)
↓ Binder IPC
CustomTimerService (SystemService)
↓ JNI (nativeWriteHello)
com_android_server_...Service.cpp
↓ hw_get_module → write_to_hello
custom_timer.default.so (HAL)
↓ write(fd, data, len)
/dev/timer → timer_driver.ko → kernel_buf
回调调用链:
timer_driver.ko → kernel_buf
↑ read(fd, buf, len)
HAL → JNI callback → onTimerTick
↑ fan-out to all subscribers
AIDL clients → ICustomTimerCallback.onTimerTick()
↑
App
即:app.writeToHello("abc") → JNI → HAL.write_to_hello() → write(fd) → /dev/timer → kernel_buf
1.4.9 踩坑记录:/dev/timer 权限问题
更新后大概率会遇到 /dev/timer 节点无法访问的问题,日志如下:
HAL 层:无法打开 /dev/timer (errno=13),timer 功能仍可用
HAL 层:设备 Open 成功
errno=13 是 EACCES,权限不够。先排查是 SELinux 还是 Unix 权限问题:
adb shell ls -laZ /dev/timer
adb logcat -d | grep -i "avc.*timer\|avc.*custom_timer"
根因:内核驱动用 device_create(..., "timer") 创建节点时,默认权限是 crw------- root root,system_server(uid 1000 system)无权访问。SELinux 策略本身没问题(标签已生效、无 AVC denied)。
解决:在 device/xxxx/common/rootdir/ueventd.xxxx.rc 中添加:
# for custom timer HAL (timer driver)
/dev/timer 0666 system system
重启后 ueventd 会把 /dev/timer 设成 0666 system:system,HAL 就能打开了。
1.5 为什么现在不能新写 Legacy HAL 了?
现代 Android 开发中,这条路已经被 Google 彻底封死了!
虽然从纯 C++ 编程的角度来说完全可以,但在 Android 系统的运行规则下,如果你现在新开发一个自定义硬件,试图用这种纯直连(Legacy HAL)的方式让 Framework 去调用,你会撞上两堵墙:
第一堵墙:SELinux 的"同进程隔离"(SP-HAL 禁令)
在 Project Treble 之后,Google 引入了 SP-HAL(Same-Process HAL)的概念。
只有被 Google 官方加入白名单的极少数对性能极其敏感的底层库(比如 Vulkan 驱动、OpenGL 渲染、Graphics Mapper 内存分配),才被允许作为 SP-HAL 被上层框架直接 dlopen。
如果你写了一个自定义的 vendor.custom.xxx.so,由于它放在 vendor 分区,普通的 system_server 或 App 进程如果试图去 dlopen 它,SELinux 策略会立刻拦截并报 avc: denied 致命错误。系统严禁跨分区加载未经许可的动态库。
第二堵墙:VINTF 兼容性矩阵的封杀
Android 强制要求所有的硬件服务必须在 VINTF(manifest.xml)中声明。而现代的 VINTF 校验工具(VTS 测试),全面拒绝非 Binder 化的自定义 HAL 注册。如果你不用 HIDL 或 AIDL 写成独立进程,你的设备连 Google 的兼容性测试都过不了。
其他的同样还有些问题:
移植性太差了
如果你只写了一个纯 C 的 .h 文件,一旦由于安全要求,你需要把这个驱动挪到一个独立的进程里跑(比如指纹识别必须在独立的安全进程),你怎么办?你只能手写几千行的 JNI、Binder 序列化(Parcel)、跨进程 Proxy 代码! 这会把开发者逼疯。
HIDL/ AIDL 的降维打击:你只要写一份 .hal / .aidl,编译器自动帮你把本地调用的 Wrapper 和跨进程的 Binder Proxy 全都生成好。你需要跨进程,它就走 Proxy;你需要直通,它就退化成纯指针调用。一份代码,通吃两种架构。
代码不好写
以前可没有 AI 可以帮你写,都是开发者纯手写。让底层工程师用纯 C 语言(满屏幕的 malloc / free、函数指针、没有对象、没有智能指针)去写成百上千行的底层逻辑,那是极其反人类的。
统一测试标准(VTS)
如果大家都写 .h,那就成了群魔乱舞,Google 没法写自动化测试。有了统一的 .hal(或 .aidl),Google 就可以写出一套统一的 VTS(Vendor Test Suite)测试用例。无论你是直通还是绑定,你都得通过这套契约的严刑拷打,才能拿到 Android 兼容性认证(CTS/VTS)。
HIDL 和 AIDL
所以最后出来了 HIDL 和 AIDL。
.hal 的作用:.hal 是一种平台无关的接口描述语言。通过 hidl-gen 翻译后,它会生成一套对 ABI 极其严格、绝对内存对齐的 C++ 代码(比如强制使用 hidl_string 或固定大小的结构体)。它就像一个契约,保证了不管编译器怎么变,同一个进程里的新系统和老驱动依然能完美接头。
Google 的初衷是让 Framework 层的代码做到真正的"目中无硬件"。在调用方(如 system_server 或 SurfaceFlinger)的代码里,获取硬件服务的代码永远只有这一句:
auto service = IMyInterface::getService();
Framework 根本不想知道,也不需要知道底层的硬件到底是在同一个进程里(直通),还是在另一个进程里(绑定)。今天,某家芯片厂商觉得自己的显示驱动很稳,通过 VINTF .xml 把它配成了直通模式,以追求极致性能。明天,另一家芯片厂商觉得自己的驱动总崩溃,为了不拉着 SurfaceFlinger 一起死,他在 .xml 里把它改成了绑定模式。因为它们都遵循同一个 .hal 契约,所以上面的 Framework 框架层一行代码都不用改!
1.6 那现在的 Android 还有纯直通吗?
有的!虽然 99% 的 HAL 都被强制要求包裹成 Binder 服务了,但在极少数对性能要求变态到极致的领域,Google 依然网开一面,允许纯直通模式的存在。
最典型的代表就是 Graphics Mapper HAL(图形渲染的内存分配)。如果屏幕渲染每秒 60 帧或 120 帧,每一帧的图像数据都要在 System 和 Vendor 之间通过 Binder 拷贝一次,那手机早就卡死了。所以对于这种特殊的 HAL,Android Framework 依然是直接 dlopen 加载厂商的 .so 库到自己的进程里运行。
二、伪绑定模式(Passthrough in Binderized)
2.1 Project Treble 的妥协
为了解决升级难和系统崩溃的问题,Google 推出了 Project Treble,立下死规矩:System 分区和 Vendor 分区必须严格隔离,必须走 Binder 跨进程通信。
但这个时候,芯片厂商懵了:"我们累积了十年的底层驱动代码,全都是基于直接调用的 .so 库写的,现在你要我们全改成 Binder 守护进程?这得改到猴年马月!" , "大哥,我这几千个 C 语言的 hw_module_t 都在这呢,重写成 C++ 的 HIDL 还要调 Binder,要出人命的!"
Google 看了下不行,大家适配起来困难,那么给你们搞个Wrapper吧,也就是通过伪绑定模式进行解决,具体是怎么解决的呢?答案就在 :defaultPassthroughServiceImplementation(套壳工具)里面。
那它到底偷偷帮你干了什么事?
帮你自动调用了 hw_get_module()!
它把你以前写的那个基于 hw_module_t 的老 .so 库 dlopen 加载进来,把里面的函数指针提取出来,然后包裹在一个生成的 C++ HIDL 对象里,最后注册成了 Binder 服务!
如果你是在维护老代码,或者你的公司/平台有一套祖传的直通模式 .so 库,为了不破坏原有的库结构,你就可以:保留 -impl 编译出库,然后写一个只有 main 函数的 service.cpp 把它动态包裹起来拉起。
伪直通模式的核心标志是:业务逻辑被编译成 cc_library_shared(动态库 .so),并且二进制入口程序没有在编译时链接它,而是运行时用 dlopen 加载。
2.2 套壳机制实现
Android.bp:
// 第一部分:真正干活的业务逻辑被编译成动态库 (.so)
cc_library_shared {
// 【核心命名规范】:名字必须以 -impl 结尾!(这是 defaultPassthrough 寻找的线索)
name: "vendor.custom.hello_hidl@1.0-impl",
vendor: true,
relative_install_path: "hw",
srcs: [
"Hello.cpp", // 业务逻辑都在这里
"HelloCallback.cpp",
],
shared_libs: [
"liblog",
"libutils",
"libhidlbase",
"vendor.custom.hello_hidl@1.0",
],
}
// 第二部分:空壳服务包工头
cc_binary {
name: "vendor.custom.hello_hidl@1.0-service",
defaults: ["hidl_defaults"],
vendor: true,
relative_install_path: "hw",
init_rc: ["vendor.custom.hello_hidl@1.0-service.rc"],
// 【核心差异点 1】:它的 srcs 里只有 main 函数文件,绝对不包含 Hello.cpp!
srcs: [
"service.cpp",
],
shared_libs: [
"liblog",
"libutils",
"libhidlbase",
"vendor.custom.hello_hidl@1.0",
// 注意:这里绝对不能写 "vendor.custom.hello_hidl@1.0-impl"!
// 因为它是运行时通过 dlopen 动态寻找的,而不是编译时静态链接的。
],
}
在**Hello.cpp:**新增入口
// ---- HIDL_FETCH — passthrough 入口 ----
V1_0::IHello* HIDL_FETCH_IHello(const char* /*name*/) {
return new Hello();
}
service.cpp:
// 伪直通模式的 service.cpp
#include <hidl/LegacySupport.h>
// 注意:这里没有 include "Hello.h"!也没有 new Hello()!
using vendor::custom::hello_hidl::V1_0::IHello;
int main() {
// 【核心差异点 2】:一行代码搞定全部。
// 这行代码会在底层:
// 1. 去 /vendor/lib64/hw/ 找 vendor.custom.hello_hidl@1.0-impl.so
// 2. 调用 dlopen() 把 .so 加载进当前进程。
// 3. 注册服务。
// 4. 调用 configureRpcThreadpool 和 joinRpcThreadpool 陷入死循环。
return android::hardware::defaultPassthroughServiceImplementation<IHello>();
}
defaultPassthroughServiceImplementation 的内部实现如下:
template<class Interface>
__attribute__((warn_unused_result))
status_t defaultPassthroughServiceImplementation(std::string name, size_t maxThreads = 1) {
// 1. 配置 Binder 线程池
configureRpcThreadpool(maxThreads, true);
// 2. 核心操作:内部会调用 dlopen 去加载旧版的 .so,并包装成 Binder 服务
status_t result = registerPassthroughServiceImplementation<Interface>(name);
if (result != OK) {
return result;
}
// 3. 阻塞监听:进入死循环,等待 Framework 的跨进程调用!
joinRpcThreadpool();
return 0; // 除非进程被强杀,否则永远执行不到这里
}
2.3 VINTF 机制(Manifest vs Matrix)
在 VINTF 机制中,有两个最核心的 XML 文件概念:
- Device Manifest(设备清单):相当于 Vendor 分区的"简历"。它向系统声明:"我这个硬件板子,提供了
hello_hidl@1.0这个服务"。 - Compatibility Matrix(兼容性矩阵):相当于 System/Product 分区的"招聘要求"。它声明:"我这个系统,必须要求底层提供
hello_hidl@1.0这个服务,否则我可能会罢工(开机失败或 OTA 报错)"。
Manifest 示例:
<manifest version="1.0" type="device">
<hal format="hidl">
<name>vendor.custom.hello_hidl</name>
<transport>hwbinder</transport>
<version>1.0</version>
<interface>
<name>IHello</name>
<instance>default</instance>
</interface>
</hal>
</manifest>
一套完整的 HAL 服务通常包含以下产物:
.../android.hardware.automotive.vehicle@2.0-service.xml(VINTF 注册清单).../android.hardware.automotive.vehicle@2.0-service.rc(开机自启脚本).../android.hardware.automotive.vehicle@2.0-service(独立的服务守护进程!).../android.hardware.automotive.vehicle@2.0.so(必须的 HIDL 跨进程通信协议库)
2.4 SELinux 配置要点
只要你的设备处于 enforcing 模式,init 进程去拉起你的二进制文件时,会因为没有分配独立的 domain(安全域)而被直接拦截,进程直接死亡(你在 logcat 搜 avc: denied 就能看到)。
你需要为它编写对应的 .te 文件(比如定义 type vendor_hello_hidl, domain; 等等),并将二进制文件与上下文绑定。
最后用一张表把三种 HAL 模式做个对比收尾:
| 模式 | 运行机制 | 进程状态 | 通信方式 | 优点 | 缺点 |
|---|---|---|---|---|---|
| 纯直通(Same-Process) | Framework 进程通过 dlopen() 直接加载 vendor 分区下的 HAL 动态库 | HAL 代码运行在调用者(Framework 进程)的同一个内存空间里 | 直接的函数调用(C++ Virtual Function Call) | 性能极高,几乎没有额外的通信开销(Zero-overhead) | 严重耦合。HAL 层代码崩溃会导致整个 Framework 进程崩溃,手机重启;可能遇到库版本冲突 |
| 伪绑定(Passthrough in Binderized) | 独立 Daemon 进程自己 dlopen 了旧的 .so,对外披上 Binder 外衣 | 对象被创建在独立的 HAL Daemon 进程里 | system_server 调用 getService() 拿到跨进程的 Binder Proxy,每次调用走 /dev/hwbinder | 兼容老代码,符合 Google 跨进程隔离规范 | 存在 IPC 通信开销 |
| 纯绑定(Binderized) | HAL 层作为独立守护进程运行,cc_binary 在开机时由 init.rc 拉起 | HAL 代码拥有自己独立的进程空间和 PID | 通过 HwBinder(IPC)进行跨进程通信 | 极致隔离。HAL 进程挂了,Framework 进程收到 RemoteException 可尝试重启,不会导致系统崩溃;真正实现框架与硬件分离,方便 OTA 独立升级 | 存在 IPC 通信开销(上下文切换、数据序列化与反序列化、内存拷贝),对延迟敏感的模块有轻微影响 |
关于编译产物的角色分工:
vendor.custom.hello_hidl@1.0-impl(动态库),其实就是可以直接用于直通模式的核心逻辑。vendor.custom.hello_hidl@1.0-service(可执行文件),则是为了将这个逻辑包裹起来,使其成为一个独立进程,从而实现绑定模式。
defaultPassthroughServiceImplementation 这种"伪绑定",其实是 Google 为了强推架构解耦,给那些来不及重写驱动的硬件厂商提供的一颗"语法糖"和"过渡方案"——它承担了跨进程的开销,但在内部延续了老代码的生命。
总结
回到开篇的三句话定论,这篇主要带你走了一遍 Legacy HAL 的完整实战,以及伪绑定模式的套壳机制。核心要点:
- Legacy HAL 的本质是用纯 C 的
hw_module_t/hw_device_t保证 ABI 稳定,但这条路已被 SELinux SP-HAL 禁令和 VINTF 兼容性矩阵封死。 - 伪绑定是 Project Treble 的妥协产物——保留老
.so不动,用defaultPassthroughServiceImplementation套一层 Binder 外衣,既不逼厂商重写祖传代码,又满足跨进程隔离的规范。 - HIDL/AIDL 取代纯 C 的原因:版本控制(
sizeof(struct)噩梦)、移植性(一份代码通吃直通/绑定)、可写性(告别满屏malloc/free)、统一测试标准(VTS)。 - 架构选型:强硬件相关选 HAL Service,纯软件/系统级选 Native Service,复杂场景两者配合(如 CarService 分层设计)。
下一篇我们会聊 AIDL HAL 的写法,把现代与未来这一块也补齐。
好了,以上就是关于整个基于HIDL 的 自定义HAL 的开发范式了,接下来我想和你讨论几个问题:
三、加餐-浅谈概念以及补全
3.1 三种 Binder:binder / hwbinder / vndbinder
binder、hwbinder 和 vndbinder 的存在
不管是 AOSP 原生的 hardware/interfaces,还是你自定义在 vendor/ 下的接口,决定它注册在哪个 Binder 里的根本原因并不是它的代码放在哪个目录,而是你使用了哪种技术(HIDL 还是 AIDL),以及你的服务究竟要跟谁通信。
| Binder 设备 | Service Manager | 服务对象 | 典型场景 |
|---|---|---|---|
/dev/binder | servicemanager | 传统的 Android 框架服务、App 应用进程,以及现代的 AIDL HAL | 自定义 Native Service(纯软件系统服务)、现代 Vendor HAL(AIDL) |
/dev/hwbinder | hwservicemanager | 专属 HIDL 时代的产物,用于打通 System 进程与 Vendor 进程 | 原生/自定义 HAL(HIDL),如 hello_hidl |
/dev/vndbinder | vndservicemanager | 专用于 Vendor 进程与 Vendor 进程之间的 AIDL 通信 | 纯 Vendor 内部服务(如"音频算法 Daemon"与"功放控制 Daemon"之间的通信) |
场景映射总结:
- 前两篇说的到 HIDL 写法,它注册在
hwservicemanager中,走hwbinder。 - 但是如果你想写一个新的纯软件的自定义后台功能,放在 system 里,它注册在
servicemanager中,走binder。 - 下篇使用最新的 AIDL HAL 技术,它也会注册在
servicemanager中,走binder。
从 Android 10/11 开始,Google 宣布废弃 HIDL,全面拥抱 Stable AIDL 来写 HAL。用 AIDL 写了一个给 Framework 调用的底层硬件接口,它会向
servicemanager注册,并走/dev/binder。系统通过强大的 SELinux 策略来保证 System 进程跨界调用 Vendor 进程时的安全性。
3.2 Android 分区体系与挂载时机
启动流程与挂载时机
- BootLoader:引导程序,只负责加载内核镜像和提供最基础的内存环境,未挂载。
- Kernel:内核启动,内核启动后初始化驱动,挂载虚拟文件系统 VFS,并启动第 0 号进程(swapper/idle)和第 1 号进程(init)。
- Init First Stage:挂载核心只读分区(RO 分区)。此时 SELinux 策略正在从 vendor 和 system 组装,必须尽早挂载以获取底层硬件的描述文件。
- Init Second Stage:挂载读写分区(如 data)。此时 SELinux 已经处于 enforcing 模式,开始解析
init.rc并拉起硬件抽象层(HAL)服务。 - Android Framework:Zygote 进程 fork 出 SystemServer,启动所有 Java 层服务,用户界面准备就绪。
分区职责一览
| 分区 | 归属 | 放什么 | 边界限制 |
|---|---|---|---|
| system | Google AOSP 核心团队 | Android Framework(system_server)、原生系统应用 | 纯软件层,严禁直接操作硬件。想点亮屏幕或读取车速,必须通过 Binder 呼叫底层服务 |
| vendor | 核心芯片厂商 | 芯片级 BSP、核心硬件的 HAL 服务(图形渲染、基础音频、核心总线驱动) | 拥有直接读写底层硬件节点的最高权限。但在 Treble 架构下,不允许直接链接 system 分区里的私有 .so 库 |
| odm | 硬件主板制造商(基于芯片二次开发核心板件的方案商) | 针对特定主板的定制外设驱动或配置(如不同批次的摄像头 sensor、特殊蓝牙模块配置) | 芯片厂只管核心芯片能跑通;板厂负责外围器件。两者代码解耦 |
| oem | 最终面向消费者的品牌方(如某家新能源汽车主机厂) | 极具品牌特征的定制化内容(专属开机动画、预装 App、定制主题资源) | 权限较低,通常不涉及底层硬件的驱动逻辑 |
| data | 系统的实际使用者 | 所有 App 的沙盒数据、OTA 升级下载的临时包等 | 系统启动后唯一一个默认完全可读写的核心分区 |
关于 Data 分区读写的"潜规则"
"所有数据只能往 Data 分区写",大方向是绝对正确的!因为 system 和 vendor 挂载时就是 Read-Only(RO)的。但即使在可读写的 /data 分区,Android 也划分了严格的"租界":
- App 的数据:只能写在
/data/data/<包名>/下(沙盒机制)。 - System 服务的数据:通常写在
/data/system/或/data/misc/下(比如系统的各种 XML 配置文件、Wi-Fi 密码)。 - Vendor 服务的数据:SELinux 强制规定,Vendor 进程绝不能随意乱写
/data,它通常只能把自己的配置或日志写到专属于它的/data/vendor/目录下。