3. HAL Service 与 Manager:服务治理的核心骨架

HAL Service 和 Service Manager,说白了就是 Android 硬件服务的「注册中心」和「服务节点」。我刚开始接触 HAL 时,总觉得这层抽象有点多余——直接调驱动不香吗?直到我在项目中遇到多进程并发访问摄像头,才明白没有这套治理机制,系统早就乱成一锅粥了。

3.1 HAL Service 的生命周期

一个 HAL Service 从生到死,大致经历四个阶段。我习惯把它比作一个「后台精灵」——启动、注册、服务、消亡。

阶段 触发时机 关键动作
创建 系统启动 / 动态加载 分配资源、初始化硬件
注册 ServiceManager.addService() 向 SM 注册服务名与 Binder 引用
运行 客户端请求到达 处理 IPC 调用、操作硬件
销毁 进程退出 / 异常崩溃 释放硬件、清理 Binder 资源

嗯,这里要注意:HAL Service 通常是常驻进程。一旦注册成功,它不会主动退出。我曾经在调试一个音频 HAL 时,发现服务莫名其妙消失了,查了半天原来是 selinux 策略把进程给杀了——这种坑,遇过一次就长记性了。

3.2 Service Manager 的角色

Service Manager(简称 SM)是整个 Binder 世界的「电话总机」。它不干具体活,但所有人都得通过它找到对方。

SM 的核心职责:

  • 名字服务:维护服务名 → Binder 引用的映射表
  • 查询路由:客户端通过服务名获取 Binder 代理
  • 生命周期监控:检测服务进程是否存活
  • 权限校验:检查调用方是否有权访问服务

你想想看,如果没有 SM,每个客户端都得知道服务端的进程号和 Binder 节点——这在嵌入式设备上简直是灾难。SM 把这一切简化成了「你给我名字,我给你句柄」。

3.3 HAL Service 注册与获取流程

这个流程我画过不下十次。每次给新人讲,我都会在白板上画这张图:

HAL Service 注册与获取流程 客户端进程 Service Manager HAL Service ① addService("camera", binder) 存储服务名→Binder ② getService("camera") 返回 Binder 代理 ③ 返回 BinderProxy 持有 BinderProxy ④ 直接 IPC 调用(不再经过 SM) 硬件设备

流程其实就四步:

  1. HAL Service 启动,调用 ServiceManager.addService() 把自己注册进去
  2. 客户端发起查询,调用 ServiceManager.getService()
  3. SM 返回 Binder 代理,客户端拿到一个轻量级的代理对象
  4. 双方直接通信,后续所有调用绕过 SM,走 Binder 驱动

避坑指南:我曾经在项目里遇到一个诡异问题——客户端 getService 总是返回 null。排查了半天,发现是 HAL Service 注册时用的服务名和客户端查询时的大小写不一致。SM 的服务名匹配是严格区分大小写的,这个细节坑过不少人。

3.4 Binder 机制在 HAL 中的应用

Binder 是 Android 进程间通信的基石。HAL Service 和客户端之间的每一次交互,本质上都是 Binder 调用。

3.4.1 Binder 的核心角色

角色 对应 HAL 中的实体 职责
Binder 驱动 /dev/binder 内核态的数据搬运工
Binder 实体 HAL Service 端 接收请求、处理逻辑
Binder 代理 客户端持有的引用 发起请求、接收回复
Service Manager 系统服务注册中心 服务名解析与路由

3.4.2 典型的 Binder 调用流程

我拿一个实际的 Camera HAL 调用举例:

// 客户端代码(App 进程)
ICameraService service = ICameraService.Stub.asInterface(
    ServiceManager.getService("camera"));
service.openCamera(cameraId, callback);

// 底层发生了什么?
// 1. 客户端调用 openCamera → 序列化参数到 Parcel
// 2. Binder 驱动拷贝数据到内核空间
// 3. 驱动找到目标 Service 的 Binder 节点
// 4. 唤醒 HAL Service 进程
// 5. Service 端反序列化 Parcel → 执行 openCamera
// 6. 结果原路返回

性能陷阱:Binder 调用是有代价的。一次跨进程调用大约耗时 50-200 微秒(取决于数据量)。我见过有人把 4K 视频帧通过 Binder 传——结果帧率直接掉到个位数。正确的做法是:控制数据量,大块数据走共享内存(ashmem),Binder 只传控制命令。

3.4.3 Binder 的线程模型

HAL Service 默认使用 Binder 线程池来处理请求。线程池大小通常为 16,但你可以调整:

// 在 HAL Service 初始化时设置
ProcessState::self()->setThreadPoolMaxThreadCount(8);
ProcessState::self()->startThreadPool();

嗯,这里有个经验:线程数不是越多越好。我曾经把一个音频 HAL 的线程池调到 32,结果锁竞争反而让性能下降了。对于大多数 HAL Service,8-16 个线程就够用了。

3.5 实战经验总结

我踩过的三个坑:

  • 服务名冲突:两个 HAL Service 用了相同的服务名,后注册的覆盖了前者。解决方案:统一命名规范,加前缀区分。
  • Binder 死锁:Service 端在处理请求时又去调用另一个 Service,而那个 Service 反过来调用了自己。解决方案:避免在 Binder 回调中发起新的 Binder 调用。
  • 服务重启丢失:HAL Service 崩溃后,SM 不会自动重新注册。解决方案:实现 watchdog 机制,检测到服务死亡后自动拉起。

说实话,HAL Service 和 Manager 这套设计,初看觉得繁琐,但用久了你会发现——它把分布式系统中的服务治理问题,用最轻量的方式解决了。你想想看,没有服务注册中心、没有服务发现、没有负载均衡,就靠一个 SM 加 Binder,跑通了整个 Android 硬件生态。这就是设计的魅力。