1. Android系统架构概览:从Linux内核到应用层,HAL的定位与作用
做Android底层开发这么多年,我经常被问到同一个问题:"HAL到底是个什么东西?" 很多应用层开发者觉得它很神秘,驱动工程师又觉得它多此一举。其实说白了,HAL就是一座桥——一座连接Android框架和硬件驱动的桥。
今天这一章,我们先不急着写代码。先把整个Android系统的骨架搭起来,搞清楚每一层在干什么,HAL为什么非有不可。我个人习惯,做任何底层开发之前,先把架构图画在脑子里,否则后面调试起来会非常痛苦。
1.1 Android系统的分层结构
Android系统从上到下,大致可以分为五层。我画了一张图,你先感受一下这个层次关系:
这张图我建议你保存下来。每次遇到问题,先定位问题出在哪一层,再动手去查。这个习惯帮我省了无数时间。
1.2 每一层都在干什么?
从上往下,我们快速过一遍:
- 应用层:你手机上那些图标。电话、微信、相机。它们通过Android SDK调用系统服务。
- 应用框架层:Google帮你封装好的Java API。比如你想打开相机,调的是
Camera.open(),而不是直接操作硬件寄存器。 - 系统运行库层:C/C++写的核心库。WebView、OpenGL、MediaCodec都在这一层。ART虚拟机也在这里。
- HAL层:我们这30章的主角。它定义了一套标准接口,让上层不用关心底层用的是哪家的芯片。
- Linux内核层:真正的硬件驱动在这里。GPIO、I2C、SPI、中断处理,都是内核的事。
关键点:从应用层到HAL,都是用户空间。只有Linux内核跑在内核空间。这个区分很重要——用户空间崩溃了不会死机,内核空间崩了直接panic。
1.3 HAL的定位——为什么非要有它?
你可能会想:"为什么应用层不直接调内核驱动?绕这么大一圈干嘛?"
嗯,这个问题我当年也问过我的mentor。他给我讲了一个真实的故事:
早期Android版本(2.x时代),厂商要适配一个新硬件,得改框架层的Java代码。改完以后,Google一升级系统版本,厂商的修改全废了,又得重来。这就是所谓的"碎片化噩梦"。
HAL的出现,就是为了解决这个问题。它的定位非常清晰:
| 角色 | 职责 | 不做什么 |
|---|---|---|
| 接口标准化 | 定义固定的API,上层只管调用 | 不关心具体硬件实现 |
| 解耦 | 厂商实现HAL,Google升级框架 | 不强制厂商开源驱动 |
| 兼容性 | 同一套HAL接口适配不同芯片 | 不处理硬件差异 |
说白了,HAL就是一份契约。Google说:"你给我实现这几个函数,我就能用你的硬件。" 厂商说:"好,我按你的规矩来,你别管我里面怎么写的。"
我的经验:我在项目中遇到过一家芯片厂商,他们的HAL实现里直接操作了/dev/mem。这种做法虽然能跑,但Google CTS(兼容性测试)绝对过不了。HAL的正确做法是调用内核驱动的标准接口,而不是绕过它。
1.4 HAL的两种形态
Android HAL发展到现在,主要有两种形态。我列个表对比一下:
| 特性 | 传统HAL (Legacy HAL) | HIDL HAL (Treble架构) |
|---|---|---|
| 引入版本 | Android 1.0 ~ 7.x | Android 8.0+ |
| 通信方式 | 直接函数调用(dlopen + dlsym) | Binder化IPC调用 |
| 接口定义 | 头文件(.h) | .hal文件(自动生成代码) |
| 升级影响 | 升级framework必须重新编译HAL | framework和HAL可独立升级 |
| 典型模块 | 老版本Camera、Audio | 新版本Camera、GPS、Sensors |
Treble架构是Android 8.0的一次大手术。Google把HAL和framework彻底拆开,中间用HIDL(HAL Interface Definition Language)定义了一套稳定的IPC接口。这样一来,厂商的HAL实现可以独立于系统升级——Google发新版本,厂商不用重新写驱动。
注意:虽然Treble架构已经推广了好几年,但很多老设备或者IoT设备仍然在用传统HAL。作为工程师,两种形态你都得会。我们这门课会覆盖传统HAL和HIDL HAL两种实现方式。
1.5 一个典型的HAL调用链路
光说理论太枯燥。我们看一个实际例子:App打开相机预览,数据是怎么从摄像头传感器一路传到屏幕上的?
- App调用:
Camera.open()→ 进入Java Framework - Framework:通过JNI调用Native层的CameraService
- CameraService:调用HAL接口
camera_device_t->ops->open() - HAL实现:厂商的HAL库打开内核驱动的设备节点(如
/dev/video0) - 内核驱动:通过I2C配置摄像头传感器,启动MIPI CSI数据流
- 数据回传:内核驱动→HAL→CameraService→Framework→App→SurfaceFlinger→屏幕
你看,从第1步到第3步,都是标准化的接口。从第4步开始,才是厂商的"黑盒"。HAL就是第3步和第4步之间的那个分界线。
// 传统HAL中,Camera模块的典型接口定义
// hardware/libhardware/include/hardware/camera.h
typedef struct camera_device {
hw_device_t common;
int (*set_preview_window)(struct camera_device *,
struct preview_stream_ops *);
void (*start_preview)(struct camera_device *);
void (*stop_preview)(struct camera_device *);
// ... 其他操作
} camera_device_t;
这段代码看起来简单,但背后涉及的东西很多。你想想看,start_preview这个函数指针背后,可能是几百行I2C配置代码,可能是复杂的ISP pipeline初始化。但上层完全不需要知道这些——这就是HAL的价值。
1.6 我的一点体会
做了这么多年底层开发,我越来越觉得HAL的设计哲学值得学习。它不追求"最优雅",而是追求"最实用"。Google知道厂商不可能把驱动代码全部开源,所以HAL给了厂商一个"黑盒"的合法位置。只要接口对,里面你随便折腾。
但同时,HAL也是一把双刃剑。如果接口设计得不好,或者厂商实现得敷衍,整个系统的稳定性和性能都会受影响。我在项目中就遇到过因为HAL实现里忘记释放锁,导致相机打开后卡死的情况。查了两天才定位到问题——嗯,这种坑后面我们会专门讲怎么避免。
本章小结:
- Android系统分五层:应用层、框架层、运行库层、HAL层、内核层
- HAL是用户空间和内核空间的分界线
- HAL的核心作用是解耦和标准化
- Treble架构让HAL和framework可以独立升级
- HAL接口是函数指针或HIDL定义,具体实现由厂商完成