Android 自定义 HAL 开发篇之 HIDL 篇——从入门到实战(下)

22 阅读34分钟

按理说之前两篇讲 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_thw_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);
  1. hw_get_module:传入目标HAL的id
  2. 根据id,拼装路径,寻找so文件
  3. 使用dlopen加载打开对应的so文件
  4. 使用dlsym加载符号表中的 HAL_MODULE_INFO_SYM,对应的xx_hw_module_t变量,转换成hw_module_t指针返
  5. 根据获取的hw_module_t指针,获取hw_module_methods_t,通过open函数的调用,获取hw_device_t
  6. 使用hw_device_t指针调用自定义的功能函数 (自定义的函数都是写在 hw_device_t 中)
  7. 最后调用hw_device_t的close关闭

提示:在纯直连模式下,根本不需要 .hal 或 .aidl 文件

为什么不需要?因为 .hal(HIDL)和 .aidl(AIDL)的本质是 IDL(接口定义语言,Interface Definition Language)。它们诞生的唯一目的,就是为了让 hidl-genaidl 工具去生成那堆繁琐的、用于跨进程(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()

这个函数的底层原理极其简单直接:

  1. /vendor/lib/hw/ 找名字叫 led_module.default.so 的文件
  2. 调用 Linux 的 dlopen() 把这个 .so 加载到当前进程内存里
  3. 调用 dlsym(),去这个 .so 里寻找一个叫 HAL_MODULE_INFO_SYM 的符号地址
  4. 找到后,强转成 hw_module_t
  5. 调用 module->methods->open() 拿到 hw_device_t
  6. 最后调用 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.defaulthw_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=13EACCES,权限不够。先排查是 SELinux 还是 Unix 权限问题:

adb shell ls -laZ /dev/timer
adb logcat -d | grep -i "avc.*timer\|avc.*custom_timer"

根因:内核驱动用 device_create(..., "timer") 创建节点时,默认权限是 crw------- root rootsystem_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_serverSurfaceFlinger)的代码里,获取硬件服务的代码永远只有这一句:

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 的完整实战,以及伪绑定模式的套壳机制。核心要点:

  1. Legacy HAL 的本质是用纯 C 的 hw_module_t / hw_device_t 保证 ABI 稳定,但这条路已被 SELinux SP-HAL 禁令和 VINTF 兼容性矩阵封死。
  2. 伪绑定是 Project Treble 的妥协产物——保留老 .so 不动,用 defaultPassthroughServiceImplementation 套一层 Binder 外衣,既不逼厂商重写祖传代码,又满足跨进程隔离的规范。
  3. HIDL/AIDL 取代纯 C 的原因:版本控制(sizeof(struct) 噩梦)、移植性(一份代码通吃直通/绑定)、可写性(告别满屏 malloc/free)、统一测试标准(VTS)。
  4. 架构选型:强硬件相关选 HAL Service,纯软件/系统级选 Native Service,复杂场景两者配合(如 CarService 分层设计)。

下一篇我们会聊 AIDL HAL 的写法,把现代与未来这一块也补齐。

好了,以上就是关于整个基于HIDL 的 自定义HAL 的开发范式了,接下来我想和你讨论几个问题:

三、加餐-浅谈概念以及补全

3.1 三种 Binder:binder / hwbinder / vndbinder

binderhwbindervndbinder 的存在

不管是 AOSP 原生的 hardware/interfaces,还是你自定义在 vendor/ 下的接口,决定它注册在哪个 Binder 里的根本原因并不是它的代码放在哪个目录,而是你使用了哪种技术(HIDL 还是 AIDL),以及你的服务究竟要跟谁通信。

Binder 设备Service Manager服务对象典型场景
/dev/binderservicemanager传统的 Android 框架服务、App 应用进程,以及现代的 AIDL HAL自定义 Native Service(纯软件系统服务)、现代 Vendor HAL(AIDL)
/dev/hwbinderhwservicemanager专属 HIDL 时代的产物,用于打通 System 进程与 Vendor 进程原生/自定义 HAL(HIDL),如 hello_hidl
/dev/vndbindervndservicemanager专用于 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 分区体系与挂载时机

启动流程与挂载时机

  1. BootLoader:引导程序,只负责加载内核镜像和提供最基础的内存环境,未挂载。
  2. Kernel:内核启动,内核启动后初始化驱动,挂载虚拟文件系统 VFS,并启动第 0 号进程(swapper/idle)和第 1 号进程(init)。
  3. Init First Stage:挂载核心只读分区(RO 分区)。此时 SELinux 策略正在从 vendor 和 system 组装,必须尽早挂载以获取底层硬件的描述文件。
  4. Init Second Stage:挂载读写分区(如 data)。此时 SELinux 已经处于 enforcing 模式,开始解析 init.rc 并拉起硬件抽象层(HAL)服务。
  5. Android Framework:Zygote 进程 fork 出 SystemServer,启动所有 Java 层服务,用户界面准备就绪。

分区职责一览

分区归属放什么边界限制
systemGoogle AOSP 核心团队Android Framework(system_server)、原生系统应用纯软件层,严禁直接操作硬件。想点亮屏幕或读取车速,必须通过 Binder 呼叫底层服务
vendor核心芯片厂商芯片级 BSP、核心硬件的 HAL 服务(图形渲染、基础音频、核心总线驱动)拥有直接读写底层硬件节点的最高权限。但在 Treble 架构下,不允许直接链接 system 分区里的私有 .so
odm硬件主板制造商(基于芯片二次开发核心板件的方案商)针对特定主板的定制外设驱动或配置(如不同批次的摄像头 sensor、特殊蓝牙模块配置)芯片厂只管核心芯片能跑通;板厂负责外围器件。两者代码解耦
oem最终面向消费者的品牌方(如某家新能源汽车主机厂)极具品牌特征的定制化内容(专属开机动画、预装 App、定制主题资源)权限较低,通常不涉及底层硬件的驱动逻辑
data系统的实际使用者所有 App 的沙盒数据、OTA 升级下载的临时包等系统启动后唯一一个默认完全可读写的核心分区

关于 Data 分区读写的"潜规则"

"所有数据只能往 Data 分区写",大方向是绝对正确的!因为 systemvendor 挂载时就是 Read-Only(RO)的。但即使在可读写的 /data 分区,Android 也划分了严格的"租界":

  • App 的数据:只能写在 /data/data/<包名>/ 下(沙盒机制)。
  • System 服务的数据:通常写在 /data/system//data/misc/ 下(比如系统的各种 XML 配置文件、Wi-Fi 密码)。
  • Vendor 服务的数据:SELinux 强制规定,Vendor 进程绝不能随意乱写 /data,它通常只能把自己的配置或日志写到专属于它的 /data/vendor/ 目录下。