在Android 8.0开始,Android引入了Treble的机制,为了方便Android系统的快速移植、升级,提升系统稳定性,Binder驱动设备被拓展成了"/dev/binder", "/dev/hwbinder","/dev/vndbinder"。
参考:Android10.0 Binder通信原理之Binder HwBinder VndBinder概要
一、Treble机制
Android系统在市场的占有率如上图(2018/10/26统计数据),从上图可以看出Android 系统在市场上使用的碎片化。虽然最新版早早的出现,但是使用率却还没有到50%。为什么Android 系统版本更新后,低版本的设备还占用大量的、不一样的比例?
- 难度:核心framework层和HAL层是紧密耦合的。
- 成本:当然,工程和测试的成本很高,不仅在SOC供应商层面,而且在OEM和运营商层面。
- 漫长:有时时间长短取决于定制的程度,这个过程非常冗长,可能需要数月。
Treble机制的诞生就是为了帮助解决厂商在原有的基础上能够快速方便的升级系统,因此通过treble化机制将google原生的OS代码与厂商定制化的代码分开。
Treble化之前的Android(Android 8.0之前)结构:对于Android O之前的系统升级都必须要将framework和HAL一起编译后生成systemi.img,进行统一升级。可以看出framework与HAL之间的耦合性很高。对于device makers(或者设备厂商)来说,得到新的Android 版本都需要进行HAL配置、编译、升级等,需要花费大量的精力。
Treble化之后的Android(Android 8.0之后)结构:在Android O中使用的Treble的架构,只需要升级framework,而无需再次将HAL部分加入编译、升级,去除了HAL与framework之间的耦合性。这样的设备厂商只需要维护framework与HAL之间的接口vendor interface(HIDL存在的意义)即可。HAL和 framework不需要一起编译,system和vendor是分开的两个partition。例如,Android 发布新的版本,芯片厂商在提供BSP 的时候,只需要知道Android 的新特性进行修改,对于HAL 相关的改动会很少(例如预留HAL 的新版本号),OEMs 在HAL 有更新的时候(性能优化或架构调整)也只需要更新vendor 即可。
1、Treble化的Vendor分区
Android系统的目录分区中有一个很重要的System分区,Android相关很多重要东西都放在里面,如下图中除了Linux kernel之外所有东西都放在System分区里面。
很多设备厂商(例如华为/小米/三星等)为了提升自身竞争力,往往将定制化上面的内容,例如小米手机坑爹的神隐模式,华为的EMUI等等。在Treble化之前,各大设备厂商只有定制System系统,即更改原有OS代码;在Treble化之后,规定各大厂商定制的服务进程必须放在新生的Vendor分区中,即对原有的System进行了解耦,分离成现如今的System和Vendor两大分区。
很多设备厂商可能使用不同的硬件芯片(例如wifi芯片,蓝牙模组),或者用于工业设备的其他器件,往往都需要不同的驱动程序支持,针对驱动程序不开源的准则,为Android新增了HAL层,向下封装Linux kernel驱动层,向上为framework的native提供硬件接口。在Treble化之前,使用了传统的HAL和Binder机制;在Treble之后HIDL接口。
2、Treble化的Binder机制
在Treble化之前就只有一个System分区,两个Java世界的进程可以使用AIDL机制进行通信,其中android.os.Binder(包括Stub)对应于C++世界的BBinder,android.os.BinderProxy(包括Stub.Proxy)对应于C++世界的BpBinder,它们通过binder驱动设备节点/dev/binder来进行消息的传递,除此之外还有一个管理进程service manager。
在Treble化之后除了有System分区还有Vendor分区,这时候就出现了两个分区之间所在的进程通信问题,那么就有如下几种情况:
- 两个Java世界的进程如果同是System分区,那么还是使用AIDL机制进行通信,在C++世界的BBinder和BpBinder通过驱动设备节点/dev/binder来进行消息传递,管理进程(守护进程)还是servicemanager。
- 两个Java世界的进程如果同是Vendor分区,那么还是使用AIDL机制进行通信,在C++世界的BBinder和BpBinder通过驱动设备节点/dev/vndbinder来进行消息传递,守护进程却是vndservicemanager。
- FW与HAL之间的通信,那么使用了新的HIDL机制进行通信,在C++世界使用了libhwbinder.so库,且通过驱动设备节点/dev/hwbinder来进行消息传递,守护进程却是hwservicemanager。
2.1 Binder
在Android 8.0之前,这是我们最熟悉也一直使用的Binder。Java层继承Binder,Native C/C++层继承BBinder/BpBinder,然后通过servicemanager进程注册实名Binder,然后通过已经创建好的Binder接口传递匿名Binder对象,拿到BinderProxy或者BpBinder以后,就可以Binder通信了。在Android 8.0后,这个Binder机制继续保留,/dev/binder设备节点成为框架(System分区)进程的专有节点,这意味着供应商(Vendor分区)进程无法再访问此节点。其中守护进程servicemanager的代码如下:
2.2 VndBinder
Android8.0 支持供供应商服务使用的新Binder域,访问此域需要使用 /dev/vndbinder(而非 /dev/binder)。vndbinder和binder共用libbinder.so这个系统库,只是在守护进程(vndservicemanager)和内核空间(/dev/vndbinder)有一些区别,整体代码流程和使用方式基本一致。
通常,供应商进程不直接打开Binder驱动程序,而是链接到打开Binder驱动程序的libbinder.so进程间通信库。供应商进程应该在调用 ProcessState和IPCThreadState 或发出任何普通 Binder 调用之前调用此方法。要使用该方法,请在供应商进程(客户端和服务器)的 main() 后放置以下调用:ProcessState::initWithDriver("/dev/vndbinder")。如下两个供应商进程例子:
注意dev/binder和dev/vndbinder无法在一个进程中同时使用,binder和vndbiner 的机制共用一套libbinder,因此两者使用时,每次只能指定一个设备节点,不能同时使用。
2.3 HwBinder
hwbinder是一套全新的流程,用于System分区和Vendor分区之间通信,有单独的驱动设备"/dev/hwbinder",独立的守护进程"hwservicemanager",独立的库"libhwbinder.so"。
其中守护进程hwservicemanager相关路径如下:
其中代替binder进程间通信库的hwbinder路径如下:
3、Binder、VndBinder、HwBinder异同
如上图,前几小节介绍了Treble化之后的三种方式,Binder和VndBinder共用一套代码,共用一套流程,唯一的区别是他们的守护进程不同,分别是是servicemanager和vndservicemanager,它们都是由init进程通过init.rc里面配置的服务启动。
3.1 守护进程
值得注意的是servicemanager目录下的Android.bp生成了两个可执行bin文件,即servicemanager和vndservicemanager(它们执行的代码一模一样),配置文件init.rc中启动了两个进程分别执行这两个可执行bin文件。如下:
#frameworks/native/cmds/servicemanager/Android.bp
cc_binary {
name: "servicemanager",
defaults: ["servicemanager_flags"],
srcs: [
"service_manager.c",
"binder.c",
],
shared_libs: ["libcutils", "libselinux"],
init_rc: ["servicemanager.rc"],
}
cc_binary {
name: "vndservicemanager",
defaults: ["servicemanager_flags"],
vendor: true,
srcs: [
"service_manager.c",
"binder.c",
],
cflags: [
"-DVENDORSERVICEMANAGER=1",
],
shared_libs: ["libcutils", "libselinux"],
init_rc: ["vndservicemanager.rc"],
}
#frameworks/native/cmds/servicemanager/servicemanager.rc
service servicemanager /system/bin/servicemanager
class core animation
user system
group system readproc
critical
onrestart restart healthd
onrestart restart zygote
onrestart restart audioserver
onrestart restart media
onrestart restart surfaceflinger
onrestart restart inputflinger
onrestart restart drm
onrestart restart cameraserver
onrestart restart keystore
onrestart restart gatekeeperd
writepid /dev/cpuset/system-background/tasks
shutdown critical
#frameworks/native/cmds/servicemanager/vndservicemanager.rc
service vndservicemanager /vendor/bin/vndservicemanager /dev/vndbinder
class core
user system
group system readproc
writepid /dev/cpuset/system-background/tasks
shutdown critical
#system/hwservicemanager/hwservicemanager.rc
service hwservicemanager /system/bin/hwservicemanager
user system
disabled
group system readproc
critical
onrestart setprop hwservicemanager.ready false
onrestart class_restart hal
onrestart class_restart early_hal
writepid /dev/cpuset/system-background/tasks
class animation
shutdown critical
3.2 进程间通信库
进程间通信库(即BBinder和BpBinder),也分别用了libbinder.so和libhwbinder.so。其中Binder和VndBinder共用进程间通信库libbinder.so;只有HwBinder作为新的一套机制使用了重新设计的libhwbinder.so。它们的区别如下:
即两个vendor分区里面的进程间通信,也需要使用libbinder.so,使用的也是里面的BBinder和BpBinder。如下示例gnss的配置,Android.bp中使用的libbinder.so库,main文件中使用了该库的头文件ProcessState.h,并调用了initWithDriver函数重置驱动设备节点为/dev/vndbinder,就这样配置,在第16行进行就是从/dev/vndbinder设备节点中轮询消息,而服务注册和查询均是通过该库向进程vndservicemanager进行消息交互:
3.3 如何分离Binder与VndBinder?
我们发现Binder与VndBinder都是公用的同一个库libbinder.so,该库也没有什么标识判断,那么是如何区分的呢?根据上小节的gnss的示例发现,在使用libbinder.so库,其中一个很重要的东西ProcessState,它代表了当前进程实例,无论是客户端进程还是服务端进程,要想使用binder通信,那么就必须实例化该对象。在使用VndBinder通信的时候,通过initWithDriver来实例化该对象:
//frameworks/native/libs/binder/ProcessState.cpp
sp<ProcessState> ProcessState::self() {
Mutex::Autolock _l(gProcessMutex);
if (gProcess != NULL) return gProcess;
gProcess = new ProcessState("/dev/binder");
return gProcess;
}
sp<ProcessState> ProcessState::initWithDriver(const char* driver) {
Mutex::Autolock _l(gProcessMutex);
if (gProcess != NULL) {
if (!strcmp(gProcess->getDriverName().c_str(), driver)) return gProcess;
}
if (access(driver, R_OK) == -1) {
driver = "/dev/binder";
}
gProcess = new ProcessState(driver);
return gProcess;
}
//构造ProcessState对象,VndBinder传递的参数是"/dev/vndbinder",否则默认"/dev/binder"
ProcessState::ProcessState(const char *driver)
: mDriverName(String8(driver))
, mDriverFD(open_driver(driver)) //打开驱动设备节点
, mVMStart(MAP_FAILED)
, mThreadCountLock(PTHREAD_MUTEX_INITIALIZER)
, mThreadCountDecrement(PTHREAD_COND_INITIALIZER)
, mExecutingThreadsCount(0)
, mMaxThreads(DEFAULT_MAX_BINDER_THREADS)
, mStarvationStartTimeMs(0)
, mManagesContexts(false)
, mBinderContextCheckFunc(NULL)
, mBinderContextUserData(NULL)
, mThreadPoolStarted(false)
, mThreadPoolSeq(1) {
if (mDriverFD >= 0) {
mVMStart = mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);
if (mVMStart == MAP_FAILED) {
close(mDriverFD);
mDriverFD = -1;
mDriverName.clear();
}
}
}
ProcessState::~ProcessState() {
if (mDriverFD >= 0) {
if (mVMStart != MAP_FAILED) munmap(mVMStart, BINDER_VM_SIZE);
close(mDriverFD);
}
mDriverFD = -1;
}
//VndBinder打开的设备节点"/dev/vndbinder"
//Binder打开的设备节点"/dev/binder"
static int open_driver(const char *driver) {
int fd = open(driver, O_RDWR | O_CLOEXEC);
if (fd >= 0) {
int vers = 0;
status_t result = ioctl(fd, BINDER_VERSION, &vers);
if (result == -1) {
close(fd);
fd = -1;
}
if (result != 0 || vers != BINDER_CURRENT_PROTOCOL_VERSION) {
close(fd);
fd = -1;
}
size_t maxThreads = DEFAULT_MAX_BINDER_THREADS;
result = ioctl(fd, BINDER_SET_MAX_THREADS, &maxThreads);
}
return fd;
}
根据上面代码可以得出结论:两个System进程之间进行通信,使用了native世界的libbinder.so库,该库默认打开了驱动设备文件"/dev/binder";两个Vendor进程之间进行通信,也使用了native世界的libbinder.so库,开发者往往在main函数第一行主动调用函数ProcessState::initWithDriver("/dev/vndbinder"),这样打开的驱动设备文件就是"/dev/vndbinder"。
那么这样又能怎么样呢?我们在看看守护进程servicemanager和vndservicemanager。目前大家都知道这两个进程执行的代码也是一模一样的,那么它们真的就完全一样吗?
#进程servicemanager
service servicemanager /system/bin/servicemanager
class core animation
user system
group system readproc
critical
onrestart restart healthd
onrestart restart zygote
onrestart restart audioserver
onrestart restart media
onrestart restart surfaceflinger
onrestart restart inputflinger
onrestart restart drm
onrestart restart cameraserver
onrestart restart keystore
onrestart restart gatekeeperd
writepid /dev/cpuset/system-background/tasks
shutdown critical
#进程vndservicemanager
#init在启动该进程的时候传递了参数"/dev/vndbinder"
service vndservicemanager /vendor/bin/vndservicemanager /dev/vndbinder
class core
user system
group system readproc
writepid /dev/cpuset/system-background/tasks
shutdown critical
//frameworks/native/cmds/servicemanager/service_manager.c
int main(int argc, char** argv) {
struct binder_state *bs;
union selinux_callback cb;
char *driver;
if (argc > 1) {
//vndservicemanager进程传递了参数"/dev/vndbinder"
driver = argv[1];
} else {
//servicemanager进程没有传递任何参数,默认打开节点"/dev/binder"
driver = "/dev/binder";
}
//Binder打开的驱动设备节点"/dev/binder"
//VndBinder打开的驱动设备节点"/dev/vndbinder"
bs = binder_open(driver, 128*1024);
if (!bs) {
#ifdef VENDORSERVICEMANAGER
while (true) sleep(UINT_MAX);
#else
ALOGE("failed to open binder driver %s\n", driver);
#endif
return -1;
}
if (binder_become_context_manager(bs)) {
ALOGE("cannot become context manager (%s)\n", strerror(errno));
return -1;
}
cb.func_audit = audit_callback;
selinux_set_callback(SELINUX_CB_AUDIT, cb);
cb.func_log = selinux_log_callback;
selinux_set_callback(SELINUX_CB_LOG, cb);
#ifdef VENDORSERVICEMANAGER
sehandle = selinux_android_vendor_service_context_handle();
#else
sehandle = selinux_android_service_context_handle();
#endif
selinux_status_open(true);
if (sehandle == NULL) abort();
if (getcon(&service_manager_context) != 0) abort();
binder_loop(bs, svcmgr_handler);
return 0;
}
根据上面代码可以得出结论:进程servicemanager打开的驱动设备节点是"/dev/binder";进程vndservicemanager打开的驱动设备节点是"/dev/vndbinder"。这样是不是跟上面的libbinder.so对应了起来。即:
- 两个System进程通信,他们通过libbinder.so库打开的是设备节点"/dev/binder",其中服务端进程通过该库向这个设备节点注册服务(最终向servicemanager进程维护的列表中添加该服务及相关信息),客户端进程通过该库向这个设备节点获取服务(是从servicemanager维护的进程表中获取)。
- 如果两个Vendor进程通信,他们通过libbinder.so库打开的是设备节点"/dev/vndbinder",其中服务端进程通过该库向这个设备节点注册服务(最终向vndservicemanager进程维护的列表中添加该服务及相关信息),客户端进程通过该库向这个设备节点获取服务(其实是从vndservicemanager维护的进程表中获取)。
3.4 为什么引入HwBinder?
Android 8.0中引入了Treble机制,Treble项目通过将底层供应商实现从Android内核框架中剥离出来,使Android更新变得更简单。这种模块化的设计允许分别独立更新平台和供应商提供的组件。让更新变得更轻松、更快速已经很棒,然而Treble加强模块化设计还有一个目的:提高安全性。
在Android 8.0之前,HAL是一个个的.so库,通过dlopen来进行打开,库和framework位于同一个进程, 在此之前的Android 系统架构当中,Android Framework 与Android HAL是打包成一个system.img的,而且Framework 与HAL之间是紧耦合的,通过链接的方式使用相应的硬件相关so库。所以每次Android framework的升级需要对应的Android HAL升级,这需要供应商花费很多的人力去升级相应的 Vendor HAL Implemetation,这也就是导致Android版本升级很缓慢,很多用户几年之后,Android都不能得到及时更新。如下图为Treble化之前的通信机制:
在Android8.0之后,新增了一个vendor.img, 即原先的system分区,被拆分为了system分区和vendor分区,soc及供应商的功能实现都需要放到vendor分区,这样将system和vendor相关的镜像分开,便于能方便地更新和升级system,并且不依赖vendor等底层。在Treble化之后,Android设计了一套新的机制来隔离HAL层,引入了一个HIDL的语言来定义Framework和HAL之间的接口,Android Framework会在system分区当中,而VendorHAL Implemetation会在一个新定义的分区(Vendor.img)当中,这样刷新的system.img 才不会影响到Vendor HAL Implemetation。因此HAL库和framework不在同一个进程,他们之间使用hwbinder进行进程间通信。如下图为Treble化之后的通信机制:
二、HAL的前世今生
根据第一章的介绍,我们了解到Treble化之后的变化主要体现在HAL层及其接口上。那么HAL层到底是什么呢?HAL又叫做硬件抽象层,由于部分硬件厂商不想把自己的核心代码公开,如果把代码放在内核空间里就需要遵循GUN License,会损害厂家的利益。所以,Google为了响应厂家在Android的架构里提出HAL的概念,把对硬件的支持分为用户空间和内核空间,而HAL层就属于这里面的用户空间,该部分代码遵循Apache License,所以厂家可以把核心的代码实现在HAL层,无需对外开放源代码。
这样说可能有些抽象,个人理解:厂商并不想开源驱动代码,那么驱动程序就完成最基本的操作read/write/ioctl,把具体业务逻辑的实现提取出来放在HAL层,即HAL层其实是对驱动程序做了业务逻辑的封装。例如读写硬件寄存器的通道,至于从硬件中读到了什么值或者写了什么值到硬件中的逻辑,都放在硬件抽象层中去了,这样就可以把商业秘密隐藏起来了。
参考:Android架构分析之使用自定义硬件抽象层(HAL)模块
1、HAL实现机制
HAL是为了保护一些硬件提供商的知识产权而提出的,是为了避免Linux的GPL束缚,把控制硬件的动作都放到了HAL中。在最新HAL架构每一个硬件模块称为一个stub(代理人)并以so的形式编译,所有的代理都要通过libhardware.so才能找到每一个代理(hardware.c提供的函数hw_get_module实现),才能回调每一个代理中硬件抽象接口,当然代理在编写时需要按照一个很重要的符号表HAL_MODULE_INFO_SYM的格式来写,通过libhardware.so找到stub时,就会将该stub加载到内存,返回该stub的模块指针。libhardware.so库的编译配置如下:
#hardware/libhardware/Android.bp
cc_library_shared {
name: "libhardware",
srcs: ["hardware.c"],
shared_libs: [
"libcutils",
"liblog",
"libdl",
"libvndksupport",
],
cflags: [
"-DQEMU_HARDWARE",
"-Wall",
"-Werror",
],
header_libs: ["libhardware_headers"],
export_header_lib_headers: ["libhardware_headers"],
vendor_available: true,
vndk: {
enabled: true,
support_system_process: true,
},
}
1.1 HAL通用结构
传统的HAL层是Android中专门用来对接用户空间和内核空间的一套框架,它被定义在hardware.h文件中,主要有三个结构体和一个函数。如下代码:
//hardware/libhardware/include/hardware/hardware.h
//结构体hw_module_t表示一个HAL层模块
typedef struct hw_module_t {
//标签
uint32_t tag; //必须被初始化HARDWARE_MODULE_TAG,表示该结构体是一个hw_module_t类型,即表示一个HAL层模块
//版本号
uint16_t module_api_version; //HAL层为用户进程提供的API版本号
#define version_major module_api_version
uint16_t hal_api_version; //HAL层提供的接口版本号
#define version_minor hal_api_version
//module标识符
const char *id; //标识HAL模块的唯一ID
//module名称
const char *name; //HAL模块的名字
//module作者
const char *author;
//module的入口方法
struct hw_module_methods_t* methods; //HAL模块的入口函数
//module's dso
void* dso;
} hw_module_t;
//结构体hw_device_t表示一个驱动层的设备节点
typedef struct hw_device_t {
//标签
uint32_t tag; //必须被初始化HARDWARE_DEVICE_TAG ,表示该结构体是一个hw_device_t类型,即对于驱动层的一个ko模块
//版本号
uint32_t version;
//所属的HAL层模块
struct hw_module_t* module;
//关闭该设备节点将调用的函数
int (*close)(struct hw_device_t* device);
} hw_device_t;
//HAL层模块入口方法,通常用来打开某个设备节点
typedef struct hw_module_methods_t {
//这里只定义了open打开设备hw_device_t的方法
int (*open)(const struct hw_module_t* module,
const char* id,
struct hw_device_t** device);
} hw_module_methods_t;
//通过HAL层模块的唯一标识,来获取关联的模块
int hw_get_module(const char *id, const struct hw_module_t **module);
- 模块结构体hw_module_t
结构体hw_module_t代表一个HAL层模块(硬件抽象层模块),因为这不是C++没有类的概念,因此需要在结构体中的成员变量tag赋值为HARDWARE_MODULE_TAG,这样后续使用中才能判断它是否是一个hw_module_t类型。除此之外,它还不能像C++那样进行类的继承,因此具体的硬件抽象层模块必须使用结构体的方式来实现继承,该方式在后文中将详细介绍。
结构体hw_module_t成员变量id用来作为HAL层模块的唯一标识(类似身份证号码),因此用户进程可以通过它来得到指定的硬件抽象层HAL模块。通常是使用的函数hw_get_module,该函数将在后文中详细介绍。
结构体hw_module_t成员变量methods是一个结构体指针,指向的结构体中存储了HAL层模块所有提供的函数,上面只定义了open函数用来打开一个hw_device_t,至于其他接口函数需要自己在后面定义。
- 设备结构体hw_device_t
结构体hw_device_t代表一个驱动模块(对应一个设备节点),同样不是C++没有类的概念,需要结构体中的成员变量tag赋值为HARDWARE_DEVICE_TAG,这样在后续使用中才能判断它是否是一个hw_device_t类型。除此之外,它还不能像C++那样进行类的继承,因此必须使用结构体的方式来实现继承。
结构体hw_device_t成员变量module表示它所在的HAL层模块。其实一个HAL层模块,它可能有一个或多个设备节点,例如网络模块,它除了封装对net的支持之外还需要封装wifi的操作。即一个hw_module_t结构体代表的HAL层模块,它包含一个或者多个hw_device_t对应的设备节点。
- 模块方法结构体hw_module_methods_t
结构体hw_module_methods_t代表一个HAL层模块的入口方法,它内部函数指针成员open,HAL架构库libhardware.so对外提供的入口函数中将调用open指向的函数进行初始化打开一个驱动设备节点,并返回hw_device_t引用。
1.2 HAL基本原理
上一小节介绍了libhardware.so的几个很重要的结构体,现在我们来看看它的具体实现,根据该库的Android.bp配置,可以发现主要逻辑实现都放在了hardware.c文件中。
//hardware/libhardware/hardware.c
#include <hardware/hardware.h>
#include <cutils/properties.h>
#if defined(__LP64__)
#define HAL_LIBRARY_PATH1 "/system/lib64/hw"
#define HAL_LIBRARY_PATH2 "/vendor/lib64/hw"
#define HAL_LIBRARY_PATH3 "/odm/lib64/hw"
#else
#define HAL_LIBRARY_PATH1 "/system/lib/hw"
#define HAL_LIBRARY_PATH2 "/vendor/lib/hw"
#define HAL_LIBRARY_PATH3 "/odm/lib/hw"
#endif
static const char *variant_keys[] = {
"ro.hardware",
"ro.product.board",
"ro.board.platform",
"ro.arch"
};
//用户进程在使用HAL层接口之前需要通过该函数得到一个hw_module_t
//参数id表示获取哪一个HAL层模块,(HAL层不同的模块都对应有唯一标识的ID)
//参数module是一个二级指针,如果查询到指定id的HAL层模块,就将其保存在module
//返回值为0表示查询到指定ID的HAL层模块,否则查询失败
int hw_get_module(const char *id, const struct hw_module_t **module) {
return hw_get_module_by_class(id, NULL, module);
}
//上面函数通过hw_get_module_by_class来查找一个HAL层模块hw_module_t
int hw_get_module_by_class(const char *class_id, const char *inst, const struct hw_module_t **module) {
int i = 0;
char prop[PATH_MAX] = {0};
char path[PATH_MAX] = {0};
char name[PATH_MAX] = {0};
char prop_name[PATH_MAX] = {0};
if (inst)
snprintf(name, PATH_MAX, "%s.%s", class_id, inst);
else
strlcpy(name, class_id, PATH_MAX);
snprintf(prop_name, sizeof(prop_name), "ro.hardware.%s", name);
if (property_get(prop_name, prop, NULL) > 0) {
if (hw_module_exists(path, sizeof(path), name, prop) == 0) goto found;
}
//遍历上面几个属性
for (i=0 ; i<HAL_VARIANT_KEYS_COUNT; i++) {
if (property_get(variant_keys[i], prop, NULL) == 0) continue;
//属性值存在,判断对应动态库文件是否存在
if (hw_module_exists(path, sizeof(path), name, prop) == 0) goto found;
}
//上面四个属性不存在,就是要default,判断动态库文件是否存在
if (hw_module_exists(path, sizeof(path), name, "default") == 0) goto found;
return -ENOENT;
found:
return load(class_id, path, module); //根据path加载动态库
}
//检测动态库文件是否存在
/* 动态库文件路径拼接规则:
* HAL_LIBRARY_PATH3/模块ID.属性值
* /system/lib64/hw/light.default.so
*/
static int hw_module_exists(char *path, size_t path_len, const char *name, const char *subname) {
snprintf(path, path_len, "%s/%s.%s.so", HAL_LIBRARY_PATH3, name, subname);
if (access(path, R_OK) == 0) return 0;
snprintf(path, path_len, "%s/%s.%s.so", HAL_LIBRARY_PATH2, name, subname);
if (access(path, R_OK) == 0) return 0;
#ifndef __ANDROID_VNDK__
snprintf(path, path_len, "%s/%s.%s.so", HAL_LIBRARY_PATH1, name, subname);
if (access(path, R_OK) == 0) return 0;
#endif
return -ENOENT;
}
如上代码,硬件抽象库libhardware.so对native世界提供了一个很重要的函数hw_get_module,用来加载一个硬件抽象层模块。系统在加载HAL层模块时,依次按照ro.hardware、ro.product.board、ro.board.platform、ro.arch顺序来获取他们的属性值,如果其中一个系统属性存在,那么就按照HAL_LIBRARY_PATH?/模块ID.属性值.so的规则来查找动态库文件是否存在,如果那四个属性都不存在,就按照HAL_LIBRARY_PATH?/模块ID.default.so来查找。最终得到文件路径path,通过函数load来加载运行动态库,如下代码:
//hardware/libhardware/hardware.c
#include <dlfcn.h>
static int load(const char *id, const char *path, const struct hw_module_t **pHmi) {
int status = -EINVAL;
void *handle = NULL;
struct hw_module_t *hmi = NULL;
//打开动态库文件,得到句柄handle,后续可通过handle来得到动态库中指定符号代表的函数或变量
if (try_system && strncmp(path, HAL_LIBRARY_PATH1, strlen(HAL_LIBRARY_PATH1)) == 0) {
handle = dlopen(path, RTLD_NOW);
} else {
handle = android_load_sphal_library(path, RTLD_NOW);
}
if (handle == NULL) {
//...错误操作...
goto done;
}
//通过句柄handle得到动态库里面的符号表HAL_MODULE_INFO_SYM_AS_STR代表的某个结构体
const char *sym = HAL_MODULE_INFO_SYM_AS_STR;
hmi = (struct hw_module_t *)dlsym(handle, sym);
if (hmi == NULL) {
//...错误操作...
goto done;
}
//其实上面得到的是一个被HAL_MODULE_INFO_SYM_AS_STR符号表修饰的hw_module_t结构体"子类"
//校验得到的HAL层模块hw_module_t的ID
if (strcmp(id, hmi->id) != 0) {
//...错误操作...
goto done;
}
//保存句柄 设置状态成功
hmi->dso = handle;
status = 0;
done:
if (status != 0) {
hmi = NULL;
if (handle != NULL) {
dlclose(handle);
handle = NULL;
}
} else {
ALOGV("loaded HAL id=%s path=%s hmi=%p handle=%p", id, path, *pHmi, handle);
}
//将得到的hw_module_t保存在指针pHmi中,说白了,用户进程通过hw_get_module函数找到对应的HAL层模块,并把HAL_MODULE_INFO_SYM返回,用户进程通过它就可以调用HAL层模块的open函数,最终进行初始化
*pHmi = hmi;
return status;
}
如上代码,每一个HAL层模块最终都被编译成了一个动态库文件,并可通过那几个属性设置它的路径,最终找到该动态库文件,在load函数中使用了linux中的dlopen、dlsym及dlclose来加载调用关闭动态链接,不清楚的可以点击我。从这个流程中我们可以明白几个道理:HAL层模块对驱动程序进行封装,它将本应该在驱动程序中实现的逻辑放在了HAL层实现,并以动态库的方式存在,这样也就绕过了Linux开源机制;而用户进程要想调用驱动模块,只有通过libhardware.so提供的这套机制来加载HAL层模块(也是一些so文件),最终实现了对驱动层的调用。
1.3 HAL的入口HAL_MODULE_INFO_SYM?
上小节大致介绍了哈加载HAL模块的基本流程,即可通过libhardware.so库中的函数hw_get_module来加载链接一个硬件抽象层模块动态库。在load函数中先dlopen动态库文件,紧接着使用dlsym查找了符号表为"HMI"的地址。
#define HAL_MODULE_INFO_SYM_AS_STR "HMI"
const char *sym = HAL_MODULE_INFO_SYM_AS_STR;
struct hw_module_t *hmi = (struct hw_module_t *)dlsym(handle, sym);
在实际使用中,通常需要我们先定义"继承于"结构体hw_module_t的硬件抽象层模块,注意这里的继承指结构体的继承。规定使用符号表HAL_MODULE_INFO_SYM来表示我们即将定义的HAL模块。
//结构体的"继承"
//定义了子类结构体led_module_t,它的第一个成员是父类结构体hw_module_t类型
//这样就可以通过C语言里面的强制转换对led_module_t相互转换
struct led_module_t{
struct hw_module_t common;
};
//定义子类结构体led_module_t类型的变量,变量名一定为HAL_MODULE_INFO_SYM符号表
const struct led_module_t HAL_MODULE_INFO_SYM = {
common: {
tag: HARDWARE_MODULE_TAG,
version_major: 1,
version_minor: 0,
id: LED_HARDWARE_MODULE_ID,
name: "led HAL module",
author: "farsight",
methods: &led_module_methods,
},
};
如上定义一个led硬件抽象层模块,用结构体led_module_t代表他,且该结构体的第一个成员common必须是hw_module_t类型;同时用HAL_MODULE_INFO_SYM修饰。那么符号表HAL_MODULE_INFO_SYM到底干了什么呢?
还记得系统在加载HAL动态库文件的时候使用dlsym来查找了符号表为"HMI",其实HAL_MODULE_INFO_SYM对应的符合表就是"HMI",也就是说load函数中先通过dlopen打开了动态库文件,紧接着就通过dlsym得到了一个HAL层模块结构体变量,这个变量就是HAL_MODULE_INFO_SYM,最终把它的句柄返回给用户进程。在介绍几个HAL示例,你就会惊奇的发现用户进程拿到了HAL_MODULE_INFO_SYM的句柄后,根据common的信息初始化打开对应的驱动程序。
2、HAL模块的编写
我们已经知道了HAL模块的实现机制,即HAL模块以动态库的方式存在,native世界的用户进程可以通过libhardware.so来加载HAL模块。那么如何编写生成一个HAL模块的动态库呢?基本步骤如下:
- 定义HAL模块ID和通用结构体:需要为HAL模块定义唯一标识ID,除此之外还需要定义HAL模块结构体(第一个成员类型必须是hw_module_t类型)和设备结构体(第一个成员类型必须是hw_device_t)。
- 实现HAL模块的open函数:需要为HAL模块实现一个函数用来打开初始化驱动设备和设置HAL模块为用户进程提供的接口。
- 关联hw_module_methods_t:上面编写的函数还不是HAL模块的入口函数,只有将其赋值到该结构体的成员open之后,两者才进行绑定,用户进程调用hw_get_module的时候最终才会调用上面的函数。
- 定义HAL_MODULE_INFO_SYM:前面已经说过了HAL_MODULE_INFO_SYM才是HAL模块的真正入口。所有的HAL模块都必须有一个
HAL_MODULE_INFO_SYM
变量,一般为hw_module_t的或者其子结构体,会初始化此结构体,其中id和methods最重要,id表示HAL模块在Android系统中的标识。 - 实现HAL模块的close函数:HAL模块被卸载后会调用close函数。通常在hw_device_t中被指定。
- 实现HAL模块对用户进程提供的接口。
注意:硬件抽象层中的硬件设备是由所在的HAL模块(hw_modlue_t的成员变量hw_module_methods_t的函数指针成员变量open)提供接口来打开,而关闭则是由硬件设备(hw_device_t的函数指针成员变量close)自身提供接口来完成的。
我们了解了硬件抽象层的基本数据结构和模块编写规则,现在我们就来看怎样编写一个自定义的硬件抽象层模块并加入到Android系统中,同时我们还要介绍应用程序怎样使用我们自定义的硬件抽象层模块。
2.1 创建自定义硬件抽象层模块头文件
Android原生HAL层模块在hardware/libhardware/include/hardware/目录下都有对应的一个头文件,该头文件中定义了它们的唯一标识和几个重要结构体,如下图:
现在我们仿照上面截图,为自定义硬件抽象模块取个名字为led,在该目录下也创建一个led.h文件,其中硬件抽象层模块和设备的唯一标识都为"led",它们的结构体分别“继承于”hw_module_t和hw_device_t。即重新定义结构体,它的第一个成员必须是它要继承的结构体类型,如下代码,重新定义led_device_t,它的第一个成员common是hw_device_t类型,后面定义了驱动设备的两个函数指针set_led_state和get_led_state分别用来调用驱动程序读写硬件寄存器:
//hardware/libhardware/include/hardware/led.h
#ifndef ANDROID_LED_INTERFACE_H
#define ANDROID_LED_INTERFACE_H
#include <hardware/hardware.h>
__BEGIN_DECLS
//自定义硬件抽象层模块的唯一标识ID
#define LED_HARDWARE_MODULE_ID "led"
//自定义硬件抽象层设备的唯一标识ID
#define LED_HARDWARE_DEVICE_ID "led"
//自定义硬件抽象层模块的结构体,"继承于"hw_module_t
struct led_module_t {
struct hw_module_t common;
};
//自定义硬件抽象层设备的结构体,"继承于"hw_device_t
struct led_device_t {
struct hw_device_t common;
int fd; //驱动程序设备节点文件描述符
int (*set_led_state)(struct led_device_t* dev, int val); //设置LED状态
int (*get_led_state)(struct led_device_t* dev, int* val);//获取LED状态
};
__END_DECLS
#endif
2.2 创建自定义硬件抽象层模块编译脚本
接下来就需要实现硬件抽象层具体业务逻辑代码了,同样根据Android原生hardware/libhardware/modules/目录下所有的硬件抽象层模块代码,如下图:
同样我们仿照上面截图,在hardware/libhardware/modules/目录下创建一个led文件夹,在该文件夹中创建led.cpp和编译脚本Android.mk,如下代码:
#hardware/libhardware/modules/led/Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_PRELINK_MODULE := false
LOCAL_CFLAGS += -Wno-unused-parameter
LOCAL_CFLAGS += -Wunsed-function
LOCAL_CFLAGS += -Wall
LOCAL_CFLAGS += -Werror
#生成的动态链接库文件存放在$(TARGET_OUT_SHARED_LIBRARIES)/hw目录下
LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw
LOCAL_SHARED_LIBRARIES := liblog
#链接同目录下文件led.cpp
LOCAL_SRC_FILES := led.cpp
#动态链接库名为libled.default.so
LOCAL_MODULE := led.default
#该硬件抽象层模块编译为动态链接库文件
include $(BUILD_SHARED_LIBRARY)
2.3 创建自定义硬件抽象层模块业务代码
一切都准备好了,现在就差最后HAL层模块的具体代码实现,现在我们根据前面梳理的步骤和对libhardware.so中三个结构体的理解来依次完善上面创建的led.cpp。
//#hardware/libhardware/modules/led/led.cpp
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
//引入头文件hardware.h 因为我们将要使用到hw_module_t和hw_device_t等重要符号
#include <hardware/hardware.h>
//引入头文件led.h 因为我们定义了led专有的led_module_t和led_device_t且“继承”hardware.h中定义的
#include <hardware/led.h>
//值得注意的是,我们为什么要把led.h与hardware.h放在同一目录呢,mk文件中并没有链接libhardware.so,因为我们的模块就放在libhardware目录下面
#define DEVICE_NAME "/dev/led"
#define MODULE_NAME "led"
#define MODULE_AUTHOR "shen"
//声明驱动设备打开函数
//第一个参数必须是hw_module_t,第二个参数是模块ID,因为只有通过硬件抽象层模块和唯一标识才能找到对应的设备,并将其保存在hw_device_t中
static int led_device_open(const struct hw_module_t* module, const char* id, struct hw_device_t** device);
//声明驱动设备关闭函数
static int led_device_close(struct hw_device_t* device);
//声明对驱动设备其他操作的调用:内部实现了对驱动寄存器具体的业务逻辑封装,即硬件厂商可以在这些函数中实现一些不想开源的复杂业务逻辑,因为这里是Android独有的HAL层,没有在Linux中也不用遵循Linux的开源策略
//这里简单定义了设置led灯的状态和获取led灯的接口,这两个接口相当于对驱动程序进行了代理,用户进程就可以通过这两个接口来达到控制硬件的目的
static int led_state_set(struct led_device_t* dev, int val);
static int led_state_get(struct led_device_t* dev, int* val);
如上代码,头文件包含了hardware.h和我们前面定义的led.h,需要注意的是这两个头文件在编译libhardware.so库的时候自动被链接进去,因为他们都在同一个项目下。
static struct hw_module_methods_t led_module_methods = {
.open = led_device_open,
};
struct led_module_t HAL_MODULE_INFO_SYM = {
.common = {
.tag = HARDWARE_MODULE_TAG,
.version_major = 1,
.version_minor = 0,
.id = LED_HARDWARE_MODULE_ID,
.name = MODULE_NAME,
.author = MODULE_AUTHOR,
.methods = &led_module_methods,
},
};
如上代码,还记得符号HAL_MODULE_INFO_SYM吗,它是HAL层模块的入口,系统在加载硬件抽象层模块的时候找到对应的so文件之后,使用dlopen打开它紧接着使用dlsym来查找签名为"HMI"的符号,这里其实拿到的就是上面定义的这个变量。上面代码中HAL_MODULE_INFO_SYM初始化了common并对其赋值,这段代码可以解读为:用户进程调用hw_get_module来加载指定的硬件抽象层模块,得到的就是结构体变量HAL_MODULE_INFO_SYM句柄,其中定义了该HAL层模块版本号和名称以及作者,更重要的是指定了获取驱动设备的入口函数,即用户进程在得到led_module_t之后可以通过它在得到led_device_t。
上面的代码定义了硬件抽象层模块led的模块结构体,指定了唯一标识ID和作者,还指定了模块入口函数led_device_open,该函数如下代码:
//HAL层模块入口函数,用来得到一个hw_device_t设备结构体
static int led_device_open(const struct hw_module_t* module, const char* id, struct hw_device_t** device) {
if(!strcmp(id, LED_HARDWARE_DEVICE_ID)) {
//创建设备结构体并为其分配空间
struct led_device_t* dev;
dev = (struct led_device_t*)malloc(sizeof(struct led_device_t));
if(!dev) return -EFAULT;
//初始化设备结构体led_device_t
memset(dev, 0, sizeof(struct led_device_t));
dev->common.tag = HARDWARE_DEVICE_TAG;
dev->common.version = 0;
dev->common.module = (hw_module_t*)module;
dev->common.close = led_device_close; //设置设备关闭函数
dev->set_led_state = led_state_set; //设置对用户进程开放的接口
dev->get_led_state = led_state_get; //设置对用户进程开放的接口
//打开硬件设备驱动节点
if((dev->fd = open(DEVICE_NAME, O_RDWR)) == -1) {
LOGE("Failed to open device file /dev/led -- %s.", strerror(errno));
free(dev);
return -EFAULT;
}
//返回给用户进程
*device = &(dev->common);
LOGI("Open device file /dev/led successfully.");
return 0;
}
return -EFAULT;
}
到此为止,HAL层模块的入口流程基本完成,用户进程通过hw_get_module得到硬件抽象层模块hw_module_t句柄,然后通过它成员变量methods指定的入口函数来打开设备驱动节点,并返回hw_device_t句柄,用户进程拿到hw_device_t句柄之后就可以为所欲为任意调用HAL层提供的硬件接口了。
下面我们来完成hw_device_t向用户进程提供的硬件接口,用户进程除了使用dev->common.close函数指针关闭设备驱动之外,还可以使用dev->set_led_state函数指针来设置led灯的状态,和dev->get_led_state函数指针获取led灯的状态。这三个函数指针已经被赋值为具体函数,对应函数代码如下:
//关闭HAL层模块设备
static int led_device_close(struct hw_device_t* device) {
//因为led_device_t的第一个成员common就是hw_device_t类型,这是结构体独有的继承方式,因此他们可以对其强制转换成led_device_t
struct led_device_t* led_device = (struct led_device_t*)device;
if(led_device) {
close(led_device->fd); //关闭驱动设备节点文件描述符
free(led_device); //释放内存
}
return 0;
}
//HAL层模块对用户进程提供的接口,用户进程通过它设置led开关状态
//对驱动程序做了业务逻辑复杂的封装,驱动程序只需要实现最基本的寄存器写操作就OK了
static int led_state_set(struct led_device_t* dev, int val) {
//复杂的一系列逻辑....
write(dev->fd, &val, sizeof(val)); //向驱动程序写入值
//复杂的一系列逻辑...
return 0;
}
//HAL层模块对用户进程提供的接口,用户进程通过它获取led开关状态
//对驱动程序做了业务逻辑复杂的封装,驱动程序只需要实现最基本的寄存器读操作就OK了
static int led_state_get(struct led_device_t* dev, int* val) {
//复杂的一系列逻辑...
read(dev->fd, val, sizeof(*val));
//复杂的一系列逻辑...
return 0;
}
2.4 生成自定义硬件抽象层模块动态库文件
执行如下命令编译led HAL模块,跳转到led目录下面直接mm就可以编译生成一个HAL动态库了。
3、HAL动态库的调用
通过上节,我们自定义硬件抽象层模块,它对下内核空间封装了驱动程序,向上用户空间提供了一些操作硬件(调用驱动设备节点)的接口。在本节我们将介绍用户进程的几种调用方式。
3.1 基于JNI的调用
这里介绍一种最简单的HAL实现方式,即Java应用程序通过JNI的方式,通过libhardware.so库直接调用上节自定义的硬件抽象层led模块。这里总共有三层调用:Java应用程序-->JNI库-->HAL层模块,下面我们依次来完成他们的编写。
- Java应用层实现:直接在Activity中定义了三个native本地方法,initLed(初始化LED)、isLedOn(判断LED是否亮)和setLedOn(设置LED亮灭状态)
public class LedCtrActivity extends Activity {
static { //JNI方式 加载JNI的动态库
System.load("/system/lib/libmokoid_runtime.so");
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//JNI方式初始化LED灯
initLed();
//JNI方式获取LED灯状态
boolean isOn = isLedOn();
TextView tv = new TextView(this);
if(isOn)
tv.setText("LED last state is ON");
else
tv.setText("LED last state is OFF");
setContentView(tv);
//JNI方式设置LED灯状态(翻转LED灯)
setLedOn(~isOn);
}
private static native boolean initLed();
private static native boolean isLedOn();
private static native boolean setLedOn(boolean isOn);
}
- JNI层实现:这里使用动态注册JNI,如下代码
//虚拟机加载
extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env = NULL;
jint result = -1;
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
LOGE("GetEnv failed!");
return result;
}
LOG_ASSERT(env, "Could not retrieve the env!");
register_mokoid_server_LedService(env);
return JNI_VERSION_1_4;
}
//注册Java中的JNI
int register_mokoid_server_LedService(JNIEnv* env) {
static const char* const kClassName = "com/mokoid/LedClient/LedClient";
jclass clazz;
/* look up the class */
clazz = env->FindClass(kClassName);
if (clazz == NULL) {
LOGE("Can't find class %s\n", kClassName);
return -1;
}
/* register all the methods */
//JNI方法映射
if (env->RegisterNatives(clazz, gMethods, sizeof(gMethods) / sizeof(gMethods[0])) != JNI_OK) {
LOGE("Failed registering methods for %s\n", kClassName);
return -1;
}
/* fill out the rest of the ID cache */
return 0;
}
//JNI方法映射
static const JNINativeMethod gMethods[] = {
{ "initLed", "()Z", (void *)mokoid_initLed },
{ "isLedOn", "()Z", (void *)mokoid_getLedState },
{ "setLedOn", "(Z)Z", (void *)mokoid_setLedState },
};
JNI的方法映射已经建立了,目前initLed对应的JNI函数是mkoid_init,该函数做了初始化操作,例如对硬件抽象层模块的支持,在该函数中通过hw_get_module函数得到了led模块的led_module_t结构体变量,最后通过它的methods成员的open函数来得到了设备结构体led_device_t,该结构体持有了设备驱动文件节点fd,还有持有两个函数指针,这两个函数指针指向的函数能够实现控制led硬件亮灭状态和获取led灯的亮灭状态。
//自定义硬件抽象层设备结构体:通过它能够操作硬件
struct led_control_device_t *sLedDevice = NULL;
//初始化LED:实际上通过libhardware.so的hw_get_module函数查询对应的hw_module_t和hw_device_t
static jboolean mokoid_initLed(JNIEnv *env, jclass clazz) {
//自定义硬件抽象层模块结构体led_module
led_module_t* module;
//通过hw_get_module查询指定唯一标识为led的硬件抽象层模块,并存储在led_module
//hw_get_module返回的其实是HAL层模块中的HAL_MODULE_INFO_SYM
if (hw_get_module(LED_HARDWARE_MODULE_ID, (const hw_module_t**)&module) == 0) {
//通过hw_get_module来得到HAL层设备结构体hw_device_t,赋值给全局变量sLedDevice
if (led_control_open(&module->common, &sLedDevice) == 0) {
LOGI("LedService JNI: Got Stub operations.");
return 0;
}
} else {
// hw_get_module的返回值不为0表示没有找到对应的HAL层模块
}
return -1;
}
static inline int led_control_open(const struct hw_module_t* module, struct led_control_device_t** device) {
//实际上是通过硬件抽象层hw_module_t的methods中的open函数打开设备节点并返回句柄方便其他接口调用
return module->methods->open(module, LED_HARDWARE_MODULE_ID, (struct hw_device_t**)device);
}
//设置LED灯状态:hw_device_t提供的面向用户进程的接口
static jboolean mokoid_setLedState(JNIEnv* env, jobject thiz, jint led) {
if (sLedDevice == NULL) {
LOGI("LedService JNI: sLedDevice was not fetched correctly.");
return -1;
} else {
//在自定义HAL层模块的时候,已经实现了该函数用来设置硬件led灯的状态
return sLedDevice->led_state_set(sLedDevice, led);
}
}
//获取LED灯状态:hw_device_t提供的面向用户进程的接口
static jboolean mokoid_getLedState (JNIEnv* env, jobject thiz, jint led ) {
if (sLedDevice == NULL) {
LOGI("LedService JNI: sLedDevice was not fetched correctly.");
return -1;
} else {
//在自定义HAL层模块的时候,已经实现了该函数用来设置硬件led灯的状态
return sLedDevice->led_state_get(sLedDevice, &led);
}
}
- 总结:该方式简单直接粗暴,Java层直接通过JNI的方式来到native世界,在native世界直接调用libhardware.so库里面的函数hw_get_moudle函数得到HAL层模块中定义的led_moudle_t,然后根据led_moudle_t->common->methods->open函数得到HAL层模块中定义的led_device_t,它内部持有驱动设备文件描述符,并向用户进程提供了一系列操作硬件设备的接口。
3.2 基于Service的调用
第一小节这种HAL的实现方式比较简单,但是也存在一个很大的问题,就是JNI库只能提供给某一个特定的Java使用,如何克服这个问题?我们可以在APP和Jni之间加一层Java service,该Jni提供给Java service使用,而所有的APP利用该service来使用Jni提供的接口。这样的话,在应用程序层,就不需要关心JNI是如何实现的了。
这里介绍一种比上小节稍微复杂点的HAL实现方式,即Java应用程序访问远程Service,这个Service通过JNI的方式,通过libhardware.so库直接调用上节自定义的硬件抽象层led模块。这里总共有三层调用:Java应用程序-->Java 本地Service-->JNI库-->HAL层模块,下面我们依次来完成他们的编写。
- Java应用应用程序:还是上面的那个Activity,但是这里不再通过Activity去调用JNI了,我们把led的功能进行封装,并寄宿在使用Service里面,当然这里介绍的Service是本地。
public class LedCtrActivity extends Activity {
private LedService service;
private TextView tv;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = new Intent();
intent.setClass(this, LedService.class);
bindService(intent, new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
service = iBinder.getService();
//调用LedService提供的接口:获取LED灯的状态
boolean isOn = service.getLedState()
if(isOn)
tv.setText("LED last state is ON");
else
tv.setText("LED last state is OFF");
//调用LedService提供的接口:设置LED灯的状态
service.setLedState(~isOn);
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
}, Context.BIND_AUTO_CREATE);
setContentView(R.layout.activity_main);
tv = findViewById(R.id.tv);
}
}
- Java 本地Service:在LedService中,我们也是使用了JNI的方式调用HAL层模块。代码如下:
public final class LedService {
static { //JNI方式 加载JNI的动态库
System.load("/system/lib/libmokoid_runtime.so");
}
private IBinder iBinder = new Binder() {
MyService getService(){
return MyService.this;
}
};
public void onStart(Intent intent, int startId) {
super.onStart(intent, startId);
initLed(); //JNI方式初始化LED灯
}
public IBinder onBind(Intent intent) {
return iBinder;
}
//对Activity提供的接口实际上调用了本地JNI方法
public boolean getLedState() {
return isLedOn();
}
//对Activity提供的接口实际上调用了本地JNI方法
public boolean setLedState(boolean state) {
return setLedOn(state);
}
private static native boolean initLed();
private static native boolean isLedOn();
private static native boolean setLedOn(boolean isOn);
}
- JNI层实现:这里使用动态注册JNI,同上小节类似,只不过这里JNI动态方法注册不在是注册Activity的方法了,而是注册Service的方法,其他都一样。动态注册Service本地方法代码如下:
//虚拟机加载
extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved) {
//...同上
}
//注册Java中的JNI
int register_mokoid_server_LedService(JNIEnv* env) {
//注册LedService
static const char* const kClassName = "com/mokoid/server/LedService";
jclass clazz;
clazz = env->FindClass(kClassName);
if (clazz == NULL) return -1;
//JNI方法注册
if (env->RegisterNatives(clazz, gMethods, sizeof(gMethods) / sizeof(gMethods[0])) != JNI_OK) return -1;
return 0;
}
//JNI方法映射
static const JNINativeMethod gMethods[] = {
{ "initLed", "()Z", (void *)mokoid_initLed },
{ "isLedOn", "()Z", (void *)mokoid_getLedState },
{ "setLedOn", "(Z)Z", (void *)mokoid_setLedState },
};
3.3 基于Manager的调用
上小节将HAL层的JNI接口封装在了本地Service,通常HAL层的功能不是为了一个应用程序提供的,而是为整个系统提供,所以大多数情况下不可避免的是需要跨进程通信,即需要使用AIDL。同样这里总共三层:Java应用程序<--AIDL-->Java 远程服务进程-->JNI库-->HAL层模块,下面我们依次来完成他们的编写。
- AIDL接口:因为这里是跨进程通信, Java应用程序与Java远程Service需要使用AIDL接口来进行通信,定义ILedService.aidl文件如下:
package mokoid.hardware;
interface ILedService {
boolean setLedState(boolean state);
boolean getLedState();
}
- AIDL的Stub/Proxy实现:因为这里要使用AIDL的框架,因此需要依次实现他的存根和代理类,此外还实现了一个系统级服务LedSystemService并注册到servicemanager进程中。后续如果有应用进程想要使用这个系统级服务,完全可以使用它的代理LedManager完成接口调用。如下:
//定义AIDL接口服务端组件 Stub
public final class LedService extends ILedService.Stub {
static { //JNI方式 加载JNI的动态库
System.load("/system/lib/libmokoid_runtime.so");
}
public LedService() {
initLed(); //JNI方式初始化LED灯
}
//对外提供的接口实际上调用了本地JNI方法
public boolean getLedState() {
return isLedOn();
}
//对外提供的接口实际上调用了本地JNI方法
public boolean setLedState(boolean state) {
return setLedOn(state);
}
private static native boolean initLed();
private static native boolean isLedOn();
private static native boolean setLedOn(boolean isOn);
}
//定义AIDL接口客户端组件 Proxy
public class LedManager
{
private ILedService mLedService;
public LedManager() {
//从servicemanager进程中查询led服务
mLedService = ILedService.Stub.asInterface(ServiceManager.getService("led"));
}
//对系统提供接口:实际上是对服务的代理
public boolean setLedState(boolean state) {
boolean result = false;
try {
result = mLedService.setLedOn(state);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in LedManager.LedOn:", e);
}
return result;
}
//应用层可以通过LedManager来访问系统服务led
public boolean getLedState() {
boolean result = false;
try {
result = mLedService.isLedOn();
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in LedManager.LedOff:", e);
}
return result;
}
}
//实现系统级服务LedSystemService,其实是ILedService.Stub的业务子类
public class LedSystemServer extends Service {
@Override
public IBinder onBind(Intent intent) {
return null;
}
public void onStart(Intent intent, int startId) {
LedService ls = new LedService();
try {
//系统服务注册
ServiceManager.addService("led", ls);
} catch (RuntimeException e) {
Log.e("LedSystemServer", "Start LedService failed.");
}
}
}
- JNI层实现:上面实现的系统级服务LedSystemService内部功能其实也是LedService完成的,因为类名没有变,因此JNI的代码同上小节。
- Java应用程序:Java应用程序就不在需要什么JNI了,使用了AIDL的代理类LedManager向系统级服务发送消息实现数据通信,最终系统级服务LedSystemService持有了AIDL的本地类LedService,它使用JNI的方式来到nativie世界调用了HAL层模块提供的接口。
public class LedCtrActivity extends Activity {
private LedManager mLedManager = null;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//实例化AIDL的代理类
if (mLedManager == null) mLedManager = new LedManager();
//通过代理类LedManager来完成对系统级服务的接口调用,最终调用了HAL层模块接口
boolean isOn = mLedManager.isLedOn();
TextView tv = new TextView(this);
if(isOn)
tv.setText("LED last state is ON");
else
tv.setText("LED last state is OFF");
setContentView(tv);
//HAL层模块最终调用了驱动程序,实现对硬件的控制
mLedManager.setLedOn(~isOn);
}
}
- 总结:该方式是不是不在那么简单,因为结合了AIDL,符合了系统级的设计方式,虽然复杂,但是流程清晰,没错Android原生的HAL层模块,例如camera/audio/lights等都是使用这种方式实现的。
三、Android U HAL案例
参考:Android P HIDL服务绑定模式与直通模式的分析 (原创)
对HAL的使用,从Android低版本到现在的Android U其实经历可一些列的变化和更新,经历了直通式和服务绑定式。前面第二章讲解的hal的调用,其实在U的源码里面可能并不存在了,这里结合U的代码和几个案例来剖析一下HAL与HIDL的使用。
1、NFC HAL之Native层的调用
这里选择MTK的NFC HIDL 1.0为例,因为比较简单,代码量也比较小,其源码路径:vendor/hardware/interfaces/nfc/1.0/
1.1 NFC HAL的定义
1.1.1 基本数据类型types.hal
通常定义在types.hal里面,其语法和java/c/c++可能不一致,详细参考https://source.android.com/docs/core/architecture/hidl/types?hl=zh-cn
1.1.2 回调接口IXXXCallnback.hal
HAL的回调接口,即通常被定义为IXXXCallback
INfcClientCallback从命名可以知道给客户端的回调接口,即给客户端进程或者framework层提供的回调接口,即hal可以通过该接口向对方回调数据
1.1.3 模块接口IXXX.hal
HAL的正式接口,同前面的回调接口刚好相反,即
HAL接口:客户端/Framework -------> HAL进程(HAL进程是被调用者)
CALL接口:HAL进程 ------>客户端/Framework (HAL进程主动发起)
1.2 NFC HAL逻辑实现
NFC HAL 1.0的版本是一个典型的直通式,其源码就nfc.cpp,逻辑相对比较简单
其中defaultPassthroughServiceImplementation表示此服务为直通式HAL服务。
//vendor/hardware/interfaces/nfc/1.0/default/Nfc.h
#ifndef ANDROID_HARDWARE_NFC_V1_0_NFC_H
#define ANDROID_HARDWARE_NFC_V1_0_NFC_H
#include <android/hardware/nfc/1.0/INfc.h>
#include <hidl/Status.h>
#include <hardware/hardware.h>
#include <hardware/nfc.h>
namespace android {
namespace hardware {
namespace nfc {
namespace V1_0 {
namespace implementation {
using ::android::hardware::nfc::V1_0::INfc;
using ::android::hardware::nfc::V1_0::INfcClientCallback;
using ::android::hardware::Return;
using ::android::hardware::Void;
using ::android::hardware::hidl_vec;
using ::android::hardware::hidl_string;
using ::android::sp;
struct Nfc : public INfc, public hidl_death_recipient {
Nfc(nfc_nci_device_t* device);
::android::hardware::Return<NfcStatus> open(
const sp<INfcClientCallback>& clientCallback) override;
::android::hardware::Return<uint32_t> write(const hidl_vec<uint8_t>& data) override;
::android::hardware::Return<NfcStatus> coreInitialized(const hidl_vec<uint8_t>& data) override;
::android::hardware::Return<NfcStatus> prediscover() override;
::android::hardware::Return<NfcStatus> close() override;
::android::hardware::Return<NfcStatus> controlGranted() override;
::android::hardware::Return<NfcStatus> powerCycle() override;
static void eventCallback(uint8_t event, uint8_t status) {
if (mCallback != nullptr) {
auto ret = mCallback->sendEvent((::android::hardware::nfc::V1_0::NfcEvent)event,
(::android::hardware::nfc::V1_0::NfcStatus)status);
if (!ret.isOk()) {
ALOGW("Failed to call back into NFC process.");
}
}
}
static void dataCallback(uint16_t data_len, uint8_t* p_data) {
hidl_vec<uint8_t> data;
data.setToExternal(p_data, data_len);
if (mCallback != nullptr) {
auto ret = mCallback->sendData(data);
if (!ret.isOk()) {
ALOGW("Failed to call back into NFC process.");
}
}
}
virtual void serviceDied(uint64_t /*cookie*/,
const wp<::android::hidl::base::V1_0::IBase>& /*who*/) {
close();
}
private:
static sp<INfcClientCallback> mCallback;
const nfc_nci_device_t* mDevice;
};
extern "C" INfc* HIDL_FETCH_INfc(const char* name);
} // namespace implementation
} // namespace V1_0
} // namespace nfc
} // namespace hardware
} // namespace android
#endif // ANDROID_HARDWARE_NFC_V1_0_NFC_H
//vendor/hardware/interfaces/nfc/1.0/default/Nfc.cpp
#define LOG_TAG "android.hardware.nfc@1.0-impl"
#include <log/log.h>
#include <hardware/hardware.h>
#include <hardware/nfc.h>
#include "Nfc.h"
namespace android {
namespace hardware {
namespace nfc {
namespace V1_0 {
namespace implementation {
sp<INfcClientCallback> Nfc::mCallback = nullptr;
Nfc::Nfc(nfc_nci_device_t* device) : mDevice(device) {}
// Methods from ::android::hardware::nfc::V1_0::INfc follow.
::android::hardware::Return<NfcStatus> Nfc::open(const sp<INfcClientCallback>& clientCallback) {
mCallback = clientCallback;
if (mDevice == nullptr || mCallback == nullptr) {
return NfcStatus::FAILED;
}
mCallback->linkToDeath(this, 0 /*cookie*/);
int ret = mDevice->open(mDevice, eventCallback, dataCallback);
return ret == 0 ? NfcStatus::OK : NfcStatus::FAILED;
}
::android::hardware::Return<uint32_t> Nfc::write(const hidl_vec<uint8_t>& data) {
if (mDevice == nullptr) {
return -1;
}
return mDevice->write(mDevice, data.size(), &data[0]);
}
::android::hardware::Return<NfcStatus> Nfc::coreInitialized(const hidl_vec<uint8_t>& data) {
hidl_vec<uint8_t> copy = data;
if (mDevice == nullptr || copy.size() == 0) {
return NfcStatus::FAILED;
}
int ret = mDevice->core_initialized(mDevice, ©[0]);
return ret == 0 ? NfcStatus::OK : NfcStatus::FAILED;
}
::android::hardware::Return<NfcStatus> Nfc::prediscover() {
if (mDevice == nullptr) {
return NfcStatus::FAILED;
}
return mDevice->pre_discover(mDevice) ? NfcStatus::FAILED : NfcStatus::OK;
}
::android::hardware::Return<NfcStatus> Nfc::close() {
if (mDevice == nullptr || mCallback == nullptr) {
return NfcStatus::FAILED;
}
mCallback->unlinkToDeath(this);
return mDevice->close(mDevice) ? NfcStatus::FAILED : NfcStatus::OK;
}
::android::hardware::Return<NfcStatus> Nfc::controlGranted() {
if (mDevice == nullptr) {
return NfcStatus::FAILED;
}
return mDevice->control_granted(mDevice) ? NfcStatus::FAILED : NfcStatus::OK;
}
::android::hardware::Return<NfcStatus> Nfc::powerCycle() {
if (mDevice == nullptr) {
return NfcStatus::FAILED;
}
return mDevice->power_cycle(mDevice) ? NfcStatus::FAILED : NfcStatus::OK;
}
INfc* HIDL_FETCH_INfc(const char * /*name*/) {
nfc_nci_device_t* nfc_device;
int ret = 0;
const hw_module_t* hw_module = nullptr;
ret = hw_get_module (NFC_NCI_HARDWARE_MODULE_ID, &hw_module);
if (ret == 0) {
ret = nfc_nci_open (hw_module, &nfc_device);
if (ret != 0) {
ALOGE ("nfc_nci_open failed: %d", ret);
}
}
else
ALOGE ("hw_get_module %s failed: %d", NFC_NCI_HARDWARE_MODULE_ID, ret);
if (ret == 0) {
return new Nfc(nfc_device);
} else {
ALOGE("Passthrough failed to load legacy HAL.");
return nullptr;
}
}
} // namespace implementation
} // namespace V1_0
} // namespace nfc
} // namespace hardware
} // namespace android
1.2.1 如何集成了驱动?
首先在nfc.h定义了很关键的成员变量mDevice,熟悉C/C++代码的从命名来看应该是一个驱动关联的句柄:
const nfc_nci_device_t* mDevice;
在nfc.cpp代码中可以很明显的看到通过linux和hal的机制去打开nfc驱动设备节点:
因此有理由相信这里的mDevice其实就是nfc驱动设备节点的一个句柄,所以解析来的代码逻辑其实就是对nfc驱动设备节点的文件操作了。
1.2.2 客户端如何通过hal调用驱动?
对驱动设备节点的第一个操作就是open,在open之后我们就可以对设备节点进行write或者其他操作,如下几个函数,都是NFC HAL接口的定义,因此HAL进程这里都是作为被动调用的一方,最后通过mDevice->XXX的方式调用驱动代码,驱动代码实现具体功能。
1.2.3 驱动阶段如何主动返回数据?
那么如果驱动程序想主动返回数据给到客户端,或者给到系统framework层,那么如何操作呢?
这时需要在看看open函数:
我们来看看函数指针eventCallback和dataCallback如何实现?
1.3 Native实现客户端调用
native层的代码都是C/C++,因此native层的代码调用需要用C/C++实现,特别是针对回调接口的调用实现起来看起稍微有些复杂。这里还是按照NFC HAL 1.0 VtsHalNfcV1_0TargetTest.cpp为例进行说明。
1.3.1 客户端主动调用hal层服务
这里主要展示native客户端进程 主动调用hal的接口,并通过hal去调用了驱动代码
//vendor/hardware/interfaces/nfc/1.0/vts/functional/VtsHalNfcV1_0TargetTest.cpp
#define LOG_TAG "nfc_hidl_hal_test"
#include <android-base/logging.h>
#include <android/hardware/nfc/1.0/INfc.h>
//引用NFC HAL回调接口头文件
#include <android/hardware/nfc/1.0/INfcClientCallback.h>
#include <android/hardware/nfc/1.0/types.h>
using ::android::hardware::nfc::V1_0::INfc;
using ::android::hardware::nfc::V1_0::INfcClientCallback;
using ::android::hardware::nfc::V1_0::NfcEvent;
using ::android::hardware::nfc::V1_0::NfcStatus;
using ::android::hardware::nfc::V1_0::NfcData;
// The main test class for NFC HIDL HAL.
class NfcHidlTest : public ::testing::TestWithParam<std::string> {
public:
virtual void SetUp() override {
//获取服务,赋值给nfc_
nfc_ = INfc::getService(GetParam());
ASSERT_NE(nfc_, nullptr);
//实例化回调接口,赋值nfc_cb_
nfc_cb_ = new NfcClientCallback();
ASSERT_NE(nfc_cb_, nullptr);
//调用服务的open函数,并传递回调接口对象nfc_cb_
EXPECT_EQ(NfcStatus::OK, nfc_->open(nfc_cb_));
//阻塞调用,等待hal层那边回调,即程序会卡在这里,客户端被调用后被唤醒往下执行
//驱动程序->HAL进程的eventCallback函数(mCallback->sendEvent)-> 客户端被回调
auto res = nfc_cb_->WaitForCallback(kCallbackNameSendEvent); // Wait for OPEN_CPLT event
EXPECT_TRUE(res.no_timeout);
//获取hal进程返回的结果,详情参考后面
EXPECT_EQ(NfcEvent::OPEN_CPLT, res.args->last_event_);
EXPECT_EQ(NfcStatus::OK, res.args->last_status_);
/* Get the NCI version that the device supports */
std::vector<uint8_t> cmd = CORE_RESET_CMD;
NfcData data = cmd;
//调用服务的write函数
//客户端进程->hal层write函数->bsp执行
EXPECT_EQ(data.size(), nfc_->write(data));
//阻塞调用,等待hal层那边回调,即程序会卡在这里,客户端被调用后被唤醒往下执行
//驱动程序->HAL进程的dataCallback函数(mCallback->sendData)-> 客户端被回调
res = nfc_cb_->WaitForCallback(kCallbackNameSendData);// Wait for CORE_RESET_RSP
EXPECT_TRUE(res.no_timeout);
EXPECT_GE(6ul, res.args->last_data_.size());
EXPECT_EQ((int)NfcStatus::OK, res.args->last_data_[3]);
if (res.args->last_data_.size() == 6) {
nci_version = res.args->last_data_[4];
} else {
EXPECT_EQ(4ul, res.args->last_data_.size());
nci_version = NCI_VERSION_2;
res = nfc_cb_->WaitForCallback(kCallbackNameSendData);
EXPECT_TRUE(res.no_timeout);
}
//.......
}
1.3.2 阻塞等待hal层回调函数调用
这里主要展示驱动代码回调给hal服务函数,native层客户端进程通过特殊方法去获取hal回调函数执行的结果
//vendor/hardware/interfaces/nfc/1.0/vts/functional/VtsHalNfcV1_0TargetTest.cpp
//定义回调函数名称
constexpr char kCallbackNameSendEvent[] = "sendEvent";
constexpr char kCallbackNameSendData[] = "sendData";
//定义回调函数返回的一些参数
class NfcClientCallbackArgs {
public:
NfcEvent last_event_;
NfcStatus last_status_;
NfcData last_data_;
};
//定义回调接口,继承INfcClientCallback.hal
class NfcClientCallback /* Callback class for data & Event. */
: public ::testing::VtsHalHidlTargetCallbackBase<NfcClientCallbackArgs>,
public INfcClientCallback {
public:
virtual ~NfcClientCallback() = default;
//由hal进程主动调用,由客户端实现该接口,客户端实现的时候可以接收参数,这个参数就说hal进程那边想传递过来的值
// sendEvent callback function - Records the Event & Status and notifies the TEST
Return<void> sendEvent(NfcEvent event, NfcStatus event_status) override {
//保存回调接口传递过来的值,并封装为NfcClientCallbackArgs
NfcClientCallbackArgs args;
args.last_event_ = event;
args.last_status_ = event_status;
//nfc_cb_->WaitForCallback(kCallbackNameSendEvent) 会导致程序阻塞
//NotifyFromCallback(kCallbackNameSendEvent, args) 会通知唤醒程序执行
NotifyFromCallback(kCallbackNameSendEvent, args);
return Void();
};
/* sendData callback function. Records the data and notifies the TEST*/
Return<void> sendData(const NfcData& data) override {
NfcClientCallbackArgs args;
args.last_data_ = data;
NotifyFromCallback(kCallbackNameSendData, args);
return Void();
};
};
2、Thermal HAL之FW层的调用
因为MTK的定制太多,所以这里选择高通的IThermal为例,因为其实现了1.0和2.0,并且分别给我演示了直通式到绑定式的过渡。源码路径:xref/vendor/hardware/interfaces/thermal/
- 1.0使用了直通式的方式实现
- 1.1新增了Callback接口,但并美誉具体的default实现
- 2.0使用了绑定式方式实现
2.1 IThermal的接口实现
2.1.1 IThermal的继承
IXXX.hal可以进行继承,这里2.0 和1.1的IThermal.hal都继承于1.0的IThermal.hal
因为1.1的IThermal.hal没有被实现,所以这里贴一下2.0的IThermal.hal
//vendor/hardware/interfaces/thermal/2.0/IThermalChangedCallback.hal
package android.hardware.thermal@2.0;
import android.hardware.thermal@2.0::Temperature;
// IThermalChangedCallback send throttling notification to clients.
interface IThermalChangedCallback {
// Send a thermal throttling event to all ThermalHAL thermal event listeners.
// @param temperature The temperature associated with the throttling event.
oneway notifyThrottling (Temperature temperature);
};
//vendor/hardware/interfaces/thermal/2.0/IThermal.hal
package android.hardware.thermal@2.0;
import android.hardware.thermal@1.0::IThermal;
import android.hardware.thermal@1.0::ThermalStatus;
import IThermalChangedCallback;
interface IThermal extends @1.0::IThermal { //2.0的IThermal继承于1.0的IThermal
// Retrieves temperatures in Celsius.
getCurrentTemperatures(bool filterType, TemperatureType type) generates (ThermalStatus status, vec<Temperature> temperatures);
// Retrieves static temperature thresholds in Celsius.
getTemperatureThresholds(bool filterType, TemperatureType type) generates (ThermalStatus status, vec<TemperatureThreshold> temperatureThresholds);
// Retrieves the cooling devices information.
getCurrentCoolingDevices(bool filterType, CoolingType type) generates (ThermalStatus status, vec<CoolingDevice> devices);
/* Register an IThermalChangedCallback, used by the Thermal HAL
* to receive thermal events when thermal mitigation status changed.
* Multiple registrations with different IThermalChangedCallback must be allowed.
* Multiple registrations with same IThermalChangedCallback is not allowed, client
* should unregister the given IThermalChangedCallback first.
* @param callback the IThermalChangedCallback to use for receiving thermal events (nullptr callback will lead to failure with status code FAILURE).
* @param filterType if filter for given sensor type.
* @param type the type to be filtered.
* @return status Status of the operation. If status code is FAILURE, the status.debugMessage must be populated with a human-readable error message.
*/
registerThermalChangedCallback(IThermalChangedCallback callback, bool filterType, TemperatureType type) generates (ThermalStatus status);
/* Unregister an IThermalChangedCallback, used by the Thermal HAL
* to receive thermal events when thermal mitigation status changed.
* @param callback the IThermalChangedCallback used for receiving thermal events (nullptr callback will lead to failure with status code FAILURE).
* @return status Status of the operation. If status code is FAILURE, the status.debugMessage must be populated with a human-readable error message.
*/
unregisterThermalChangedCallback(IThermalChangedCallback callback) generates (ThermalStatus status);
};
2.1.2 IThermal的回调接口注册
如上比较重要的registerThermalChangedCallback方法。参数是一个IThermalChangedCallback callback回调接口。客户端可以通过此方法/函数注册回调函数,Thermal.cpp中通常会使用一个集合来存储所有客户端注册的回调接口,然后再需要的时候调用遍历集合进行函数回调。有点类似观察者的设计模式。
2.2 直通式/绑定式逻辑实现
2.2.1 IThermal直通式实现
Thermal 1.0为直通式实现,主要通过defaultPassthroughServiceImplementation方法注册直通式 HIDL 服务。如下代码:
//vendor/hardware/interfaces/thermal/1.0/default/service.cpp
#define LOG_TAG "android.hardware.thermal@1.0-service"
#include <android/hardware/thermal/1.0/IThermal.h>
#include <hidl/LegacySupport.h>
using android::hardware::thermal::V1_0::IThermal;
using android::hardware::defaultPassthroughServiceImplementation;
int main() {
return defaultPassthroughServiceImplementation<IThermal>();
}
该方法用于将传统 HAL 实现(以动态库 .so
形式存在)封装为 HIDL 接口服务,并注册到 Android 的 ServiceManager。其底层原理为:
- 根据
IThermal
接口名(如android.hardware.thermal@1.0::IThermal
)定位对应的 HAL 实现动态库(如android.hardware.thermal@1.0-impl.so
) - 通过
hw_get_module
或HIDL_FETCH_IThermal
函数加载动态库并获取 HAL 实现句柄 - 调用动态库中的
HIDL_FETCH_IThermal
函数,将传统 HAL 的hw_module_t
结构体转换为IThermal
HIDL 接口对象 - 若未显式定义
HIDL_FETCH_*
函数,框架会自动通过hw_device_t
的open()
方法创建实例 - 将生成的
IThermal
接口实例注册到HwServiceManager
(通过registerAsService("default")
默认服务名) - 注册完成后,Framework 层(如
ThermalManagerService
)可通过IThermal::getService()
获取该接口实例
2.2.2 IThermal绑定式实现
Thermal 2.0为绑定式实现,该方式创建的服务进程与客户端调用进程处在不同的进程中,如下代码开启binder线程池,通过binder来实现进程间通信。如下代码:
//vendor/hardware/interfaces/thermal/2.0/default/service.cpp
#define LOG_TAG "android.hardware.thermal@2.0-service-mock"
#include <android-base/logging.h>
#include <hidl/HidlTransportSupport.h>
#include "Thermal.h"
using ::android::OK;
using ::android::status_t;
// libhwbinder:
using ::android::hardware::configureRpcThreadpool;
using ::android::hardware::joinRpcThreadpool;
// Generated HIDL files:
using ::android::hardware::thermal::V2_0::IThermal;
using ::android::hardware::thermal::V2_0::implementation::Thermal;
static int shutdown() {
LOG(ERROR) << "Thermal Service is shutting down.";
return 1;
}
int main(int /* argc */, char** /* argv */) {
status_t status;
android::sp<IThermal> service = nullptr;
LOG(INFO) << "Thermal HAL Service Mock 2.0 starting...";
service = new Thermal();
if (service == nullptr) {
LOG(ERROR) << "Error creating an instance of ThermalHAL. Exiting...";
return shutdown();
}
configureRpcThreadpool(1, true /* callerWillJoin */);
status = service->registerAsService();
if (status != OK) {
LOG(ERROR) << "Could not register service for ThermalHAL (" << status << ")";
return shutdown();
}
LOG(INFO) << "Thermal Service started successfully.";
joinRpcThreadpool();
// We should not get past the joinRpcThreadpool().
return shutdown();
}
该方式为目前最新的使用方式,主要还是四部曲:
- 创建服务实例化对象,通过android::sp<IThermal> service=new Thermal(),这里的IXXX为HAL文件编译生成,封装管理HAL的逻辑
- 配置binder线程池,通过configureRpcThreadpool,第一个参数表示线程池数量,第二个参数
callerWillJoin
标记主线程将加入线程池等待,防止进程提前退出 - 注册到 ServiceManager,通过
registerAsService()
将IThermal
接口实例注册为默认服务(服务名为"default"
) - 阻塞主线程
,通过joinRpcThreadpool()
使主线程进入等待状态,持续处理来自 Framework 或其他进程的 HIDL 请求
2.2.3 直通式 VS 绑定式
直通式和绑定式区别如下
2.3 Framework实现客户端调用
IThermal hal的客户端可以framework的ThermalManagerService.java,这里以TMS作为案例来介绍java层如何直接调用hal。
2.3.1 HAL静态库的引用
如上图TMS直接import android.hardwar.thermal包名,那么tms是如何能够找到这些包的呢?
那么这些hardware静态库是怎么来的呢?其实AOSP源码中根本搜索不到xxx.hardware.xxx-vx.x-java,其实这些静态库是hal框架自动生成,前提是hardware模块需要配置gen_java: true。如下代码:
2.3.2 HAL服务代理对象
因为IThermal HAL存在1.0/1.1/2.0版本的实现和aidl替换hidl的实现,因此这里TMS对其进行封装。这里依次介绍一下1.0/1.1/2.0的服务代理对象实例:
- IThermal 1.0的封装
//frameworks/base/services/core/java/com/android/server/power/ThermalManagerService.java
static class ThermalHal10Wrapper extends ThermalHalWrapper {
/** Proxy object for the Thermal HAL 1.0 service. */
@GuardedBy("mHalLock") //HAL 1.0的代理实例对象
private android.hardware.thermal.V1_0.IThermal mThermalHal10 = null;
ThermalHal10Wrapper(TemperatureChangedCallback callback) {
mCallback = callback;
}
@Override
protected boolean connectToHal() {
synchronized (mHalLock) {
try { //获取HAL 1.O的代理对象实例,即IXXX.getService()
mThermalHal10 = android.hardware.thermal.V1_0.IThermal.getService(true);
mThermalHal10.linkToDeath(new DeathRecipient(), THERMAL_HAL_DEATH_COOKIE);
Slog.i(TAG, "Thermal HAL 1.0 service connected, no thermal call back will be called due to legacy API.");
} catch (NoSuchElementException | RemoteException e) {
Slog.e(TAG, "Thermal HAL 1.0 service not connected.");
mThermalHal10 = null;
}
return (mThermalHal10 != null);
}
}
@Override
protected List<Temperature> getCurrentTemperatures(boolean shouldFilter, int type) {
//...省略...通过HAL 1.0的代理实例对象调用到硬件抽象层
mThermalHal10.getTemperatures((ThermalStatus status, ArrayList<android.hardware.thermal.V1_0.Temperature> temperatures) ->
//...省略...
}
@Override
protected List<CoolingDevice> getCurrentCoolingDevices(boolean shouldFilter, int type) {
//...省略...通过HAL 1.0的代理实例对象调用到硬件抽象层
mThermalHal10.getCoolingDevices((status, coolingDevices) ->
//...省略...
}
}
- IThermal 1.1的封装
static class ThermalHal11Wrapper extends ThermalHalWrapper {
/** Proxy object for the Thermal HAL 1.1 service. */
@GuardedBy("mHalLock") //HAL1.1的代理对象实例
private android.hardware.thermal.V1_1.IThermal mThermalHal11 = null;
/** HWbinder callback for Thermal HAL 1.1. */
private final IThermalCallback.Stub mThermalCallback11 =
new IThermalCallback.Stub() { // HAL1.1 回调接口对象实例,注意stub意味着这里进行了跨进程转换
@Override //实现回调接口的抽象方法,即hal层回调该对象此方法,那么fw层这里就会被回调,这是java机制
public void notifyThrottling(boolean isThrottling, android.hardware.thermal.V1_0.Temperature temperature) {
Temperature thermalSvcTemp = new Temperature( temperature.currentValue, temperature.type, temperature.name, isThrottling ? Temperature.THROTTLING_SEVERE : Temperature.THROTTLING_NONE);
final long token = Binder.clearCallingIdentity();
try {
mCallback.onValues(thermalSvcTemp);
} finally {
Binder.restoreCallingIdentity(token);
}
}
};
// fw层通过此方法注册回调接口
ThermalHal11Wrapper(TemperatureChangedCallback callback) {
mCallback = callback;
}
@Override
protected boolean connectToHal() {
synchronized (mHalLock) {
try { // HAL 1.1代理对象实例
mThermalHal11 = android.hardware.thermal.V1_1.IThermal.getService(true);
mThermalHal11.linkToDeath(new DeathRecipient(), THERMAL_HAL_DEATH_COOKIE);
mThermalHal11.registerThermalCallback(mThermalCallback11);
Slog.i(TAG, "Thermal HAL 1.1 service connected, limited thermal functions due to legacy API.");
} catch (NoSuchElementException | RemoteException e) {
Slog.e(TAG, "Thermal HAL 1.1 service not connected.");
mThermalHal11 = null;
}
return (mThermalHal11 != null);
}
}
}
- IThermal 2.0的封装
static class ThermalHal20Wrapper extends ThermalHalWrapper {
/** Proxy object for the Thermal HAL 2.0 service. */
@GuardedBy("mHalLock")
private android.hardware.thermal.V2_0.IThermal mThermalHal20 = null;
/** HWbinder callback for Thermal HAL 2.0. */
private final android.hardware.thermal.V2_0.IThermalChangedCallback.Stub
mThermalCallback20 =
new android.hardware.thermal.V2_0.IThermalChangedCallback.Stub() {
@Override
public void notifyThrottling(
android.hardware.thermal.V2_0.Temperature temperature) {
Temperature thermalSvcTemp = new Temperature( temperature.value, temperature.type, temperature.name, temperature.throttlingStatus);
final long token = Binder.clearCallingIdentity();
try {
mCallback.onValues(thermalSvcTemp);
} finally {
Binder.restoreCallingIdentity(token);
}
}
};
ThermalHal20Wrapper(TemperatureChangedCallback callback) {
mCallback = callback;
}
@Override
protected boolean connectToHal() {
synchronized (mHalLock) {
try {
mThermalHal20 = android.hardware.thermal.V2_0.IThermal.getService(true);
mThermalHal20.linkToDeath(new DeathRecipient(), THERMAL_HAL_DEATH_COOKIE);
mThermalHal20.registerThermalChangedCallback(mThermalCallback20, false, 0 /* not used */);
Slog.i(TAG, "Thermal HAL 2.0 service connected.");
} catch (NoSuchElementException | RemoteException e) {
Slog.e(TAG, "Thermal HAL 2.0 service not connected.");
mThermalHal20 = null;
}
return (mThermalHal20 != null);
}
}
}
通过上面的连环代码,这里提取如下几点需要关注:
- HAL层代理对象实例化:hardware.IXXX halService = IXXX.getService()
- HAL层通用接口调用:直接通过halService代理对象进行方法调用
- HAL层回调接口对象实例化:hardware.IXXXCallback halCall = new IXXXCallback.Stub(){}
2.3.3 HAL回调接口注册与回调
接着上文,hal回调接口IXXXCallback被实例化之后,紧接着需要注册,然后实现回调接口抽象方法。回到TMS这个案例,这里其实做了一层封装,写的稍微有些复杂,这里大概截取一些重要的代码片段。
- 回调接口实例化
- 连接HAL 2成功的时候注册回调接口
- HAL模块有需要调用IXXXCallback接口,fw的这个回调实例对象对应方法就会被回调。
四、HAL关联selinux权限解读
关于HAL进程的启动,HAL接口对象的注册,以及HAL进程如何与HAL接口对象进行绑定。围绕这些问题我们如何来配置selinux权限?
我先总结一下大体思路如下:
- hal服务进程的启动:针对hal服务进程,作为主体的selinux权限。hal服务端进程被init启动的过程。
- hal服务进程与hal对象进行绑定:针对hal对象,作为客体的selinux权限。hal服务端进程对hal对象进行服务注册。
- hal服务进程与客户进程关联:hal服务端进程与hal客户端进程进行相互调用,关于binder通信的权限配置。
同样,这里以Android U IThermal为例来进行说明。
1、Selinux之Thermal HAL服务进程启动
如上代码init.rc在启动vendor.tinno.hardware.demo进程的时候,在开启selinux的设备运行时,发现init启动该进程失败,kernel日志如下:
<14>[ 10.728014] .(5)[1:init]init 24: Parsing file /vendor/etc/init/vendor.tinno.hardware.demo@1.0-service.rc...
<11>[ 17.617416] .(0)[1:init]init 34: [17448][0]Could not start service 'vendor.tinno.hardware.demo' as part of class 'hal':
File /vendor/bin/hw/vendor.tinno.hardware.demo@1.0-service(labeled "u:object_r:vendor_file:s0")
has incorrect label or no domain transition from u:r:init:s0 to another SELinux domain defined.
Have you configured your service correctly?
https://source.android.com/security/selinux/device-policy#label_new_services_and_address_denials.
Note: this error shows up even in permissive mode in order to make auditing denials possible.
# init.rc中配置一个native进程,并指定了对应的bin文件路径/vendor/bin/hw/vendor.tinno.hardware.demo@1.0-service
# init进程在启动该natvie服务的时候,发现vendor/bin/hw/vendor.tinno.hardware.demo@1.0-service.bin文件上下文不匹配,因此启动失败
# 针对此问题google定义进程域和相关的上下文,并提供了一个示例
接下来我们以thermal native服务进程为例来介绍:
1.1 Thermal HAL进程rc配置解读
init.rc里面的配置进程可执行文件bin文件的路径,那么在init fork子进程的时候,是不收影响的,但是启动子线程之后,子线程执行的代码段和数据段都继承了init父进程的,因此这个时候通过使用exec来指定可执行bin文件的代码段和数据段。因此在这个过程中可以拆解为:
- 主体为子进程vendor.thermal-hal-x-0-x
- 客体为可执行bin文件,路径为/vendor/bin/hw/android.hardware.thermal@xxx
- interface声明支持Thermal HAL 的 HIDL 1.0或者2.0的版本接口
1.2 客体hal可执行文件配置file_contexts
回到rc里面的配置,native hal进程执行exec来执行自己的代码,那么就需要对bin文件的读写权限,bin文件本质也是一个文件,因此针对文件的上下文配置还是在file_contexts:
如上将可执行bin文件的上下文定义为了hal_thermal_default_exec。
1.3 主体hal服务进程hal_xxx_default.te
每个进程在selinux中基本上都对应一个te文件,我们可以在这个te文件中配置selinux相关策略,例如system_server进程的权限就需要在system_server.te里面进行配置。
hal服务进程的通常命名为hal_xxx_default.te
hal thermal进程的权限文件为hal_thermal_default.te
1.4 主体hal服务进程域转换init_daemon_domain
我们回到hal_thermal_default.te的权限配置,看看有些什么东西:
//AOSP/system/sepolicy/vendor/hal_thermal_default.te
#声明上下文hal_thermal_default,继承domain,表示是一个进程,这里其实对natvie hal进程的上下文声明
type hal_thermal_default, domain;
#后文解析
hal_server_domain(hal_thermal_default, hal_thermal)
#声明上下文hal_thermal_default_exec,继承vendor_file_type,表示一个客体资源,是vendor可执行文件的属性
type hal_thermal_default_exec, exec_type, vendor_file_type, vendor_file_type, file_type;
#init进程域转换,即init创建子线程,子线程运行的时候上下文还是init进程的域,通过此宏将上下文改成了hal_thermal_default
init_daemon_domain(hal_thermal_default)
重点总结参考如上注释,重点如下:
- 声明hal_thermal_default的type(type就是安全上下文),让其继承domain属性(属性attribute表示这个type的特性或者类型或者功能)。如何理解type和attribute请参考selinux
- 声明hal_thermal_default_exec的type,让其继承exec_type和file_type等属性,表示这个文件是一个在vendor分区的可执行文件
- init_daemon_domain:类型转换,通常init进程通过rc启动新进程,都需要通过该宏进行域的转换。理解起来很抽象,如下是我个人的理解(即init创建子线程,子线程运行的时候上下文还是init进程的域,通过此宏将上下文改成了hal_thermal_default)。
通过如上配置,我们的rc配置的native hal进程就可以被fork出来了。即前文init报错问题能够解决。
但是到这一步只是能够让我们的native hal进程能够运行起来,接下来会介绍该进程起来之后,注册hal接口实例的时候失败。
2、 Selinux之Thermal HAL实例对象注册
如上代码当IXXX.hal接口对应的对象实例进行服务注册的时候,在开启selinux的设备运行时会暴露如下AVC日志:
04-28 05:49:08.249 13892 13892 E vendor.tinno.hardware.demo@1.0-service: ================SHEN:main
04-28 05:49:08.251 13892 13892 E vendor.tinno.hardware.demo@1.0-service: ================SHEN:Registering
04-28 05:49:08.253 454 454 E SELinux : avc: denied { find } for interface=vendor.tinno.hardware.demo::IDemo sid=u:r:hal_demo_default:s0 pid=13892 scontext=u:r:hal_demo_default:s0 tcontext=u:object_r:default_android_hwservice:s0 tclass=hwservice_manager permissive=0
04-28 05:49:08.254 13892 13892 E HidlServiceManagement: Service vendor.tinno.hardware.demo@1.0::IDemo/default must be in VINTF manifest in order to register/get.
04-28 05:49:08.254 13892 13892 E vendor.tinno.hardware.demo@1.0-service: ==============SHEN:Could not register
#主体进程:pid=12278,即当前hal service 进程
#主体上下文:scontext=u:r:hal_demo_default:s0
#客体上下文:tcontext=u:object_r:default_android_hwservice:s0
#客体类别:tclass=hwservice_manager
#白话文翻译:hal服务进程在注册服务的时候,出现客体资源service_manager关联的资源的时候没有find权限
#注意:tclass=hwservice_manager表示这里的客体资源非文件,而是service_manager维护的系统服务注册表,形成一个抽象的客体资源
这块逻辑没有细看,但是可以想象的到,和之前写的上层服注册一致, 在service_manager进行add的时候出现find权限丢失。
最后根据此avc通过工具生成te语句,在hal_demo_default.te中添加如下,编译出现neverallow报错:
allow hal_demo_default default_android_hwservice:hwservice_manager find;
总结:阻止所有域(*
通配符)向Service Manager注册或操作使用,要求开发者必须为服务定义专属类型(如system_app_service
),而非复用默认的通用类型。
这里我们结合源码来解读一下IThermal 2.0服务的权限是如何配置。
2.1 IThermal接口实例对象的命名
在此回到这条avc报错日志,其中 interface=vendor.tinno.hardware.demo::IDemo非常关键,没错这里interface其实就是对象名称,对应sp<IDemo> service = new Demo。这个对于后面客体上下文指定非常重要,这里更加形象的说明,IDemo service实例对象是一个抽象的客体资源。
2.2 hwservice_contexts指定服务实例客体上下文
在aosp源码中system/sepolicy/private/hwservice_contexts 对所有的hal层服务和fw层服务进行了上下文的定义,也是这个地方进行了与前文Thermal.hal root package一致
- 客体名称:IXXX,他的本质是一个XXX.HAL转换过来的实例对象,命名为service = new Thermal(),因此他是一个客体资源,本质还是一个死的东西。
- 客体上下文:u:object_r:hal_thermal_hwservice:s0,客体上下文,详情后面介绍。
这里的服务名和服务对应的上下文,其实是一个客体资源。如何理解他是一个客体资源呢?这里可以把他想象成她是一个XXX服务的实例对象,因此它绝对不可能是主体即我们的native service进程。2.0/default/service.cpp对应的进程将此服务实例对象传递到servicemanager中调用add将其注册到系统级服务里面,因此这里的实例对象其实就是一个比较抽象的客体资源。
2.3 hwservice.te关联服务实例客体上下文属性?
前文绑定了客体的上下文:u:object_r:hal_thermal_hwservice:s0。那么hal_thermal_hwservice是在哪里定义的呢?答案就在system/sepolicy/public/hwservice.te:
此文件每一行的都以type开头,其语法规则如下:
type hal_thermal_hwservice, hwservice_manager_type, protected_hwservice;
# 其语法为:type <新类型名>, <属性1>, <属性2>, ..., <属性N>;
# 其中type字段:是SELinux策略关键字,用于声明一个新的客体类型(Object Type)
# 其中<新类型名>:就是声明的新的客体类型的名称,上面就是声明了新的客体上下文,名字叫做hal_thermal_hwservice
# 其中属性1-N:用来给新声明的客体上下文赋予对应的规则和权限,可以简单的理解为继承,属性1-N他们是同级的,并没有什么关联,都被定义在attributes文件中
# 其中hwservice_manager_type属性的权限说明:服务需注册到servicemanager其他进程需find权限才能发现该服务
# 其中protected_hwservice属性的权限说明:敏感操作隔离(如温度阈值设置、传感器数据读取),看起来和硬件相关的
如上语法解析,声明了一个新的客体上下文,叫做hal_thermal_hwservice,他同时继承了hwservice_manager_type/protected_hwservice具有的权限,其中我们这里最重要的是hwservice_manager_type所有具有的权限。即客体上下文被指定为hal_thermal_hwservice之后的所有客体资源,都具有hwservice_manager_type带来的能力,能够被add到servicemanager维护的系统服务列表里面。
2.4 attributes的定义
上一节讲解的type属性的定义,都在attributes文件里面,这里我们暂不深究其语法。
原生aosp路径为:system/sepolicy/public/attributes
2.5 HAL服务进程配置访问策略
这里我们回到avc权限被拒绝的地方,
# selinux拒绝add的AVC日志:
04-28 06:43:43.127 454 454 E SELinux : avc: denied { find } for interface=vendor.tinno.hardware.demo::IDemo sid=u:r:hal_demo_default:s0 pid=15177 scontext=u:r:hal_demo_default:s0 tcontext=u:object_r:default_android_hwservice:s0 tclass=hwservice_manager permissive=0
#触发的neverallow的selinux规则语句:
allow hal_demo_default default_android_hwservice:hwservice_manager find;
#允许hal服务进程对客体上下文default_android_hwservice的服务对象实例,拥有service_manager场景下的添加操作。default_android_hwservice是一个通用的服务客体资源。
#改进之后的selinux规则语句:
#============= hal_demo_default ==============
allow hal_demo_default hal_demo_hwservice:hwservice_manager find
#允许hal_demo_default 进程对客体上下文hal_demo_hwservice这个服务对象实例,拥有service_manager场景下的添加操作,即能够将vendor.tinno.hardware.demo::IDemo实例添加到service_manager维护的列表中。
如上演示的是新增自定义服务的配置方式。回到IThermal这个案例,主体进程对应的hal_thermal.te文件
由此可以看出来主要靠add_hwservice(hal_thermal_server, hal_thermal_hwservice)解决如上服务注册失败的selinux权限问题,至于allow hal_thermal_client hal_thermal_hwservice:hwservice_manager find的目的是让客户端进程能够通过service_manager去查询此服务对象实例,所以也还是一个客体资源,且在api 28之下的代码才有,意味着U不需要进行这样的配置?
3、Selinux之HAL服务进程和HAL接口之间的关联
其实上面才是正确的标准写法,这里涉及到hal进程与hal接口之间的绑定和转换,带着这个问题在本节,我们来研究一下HAL服务进程hal_demo_default.te和hal_demo.te之间的关系
四、总结与后续
本篇先引入了treble化机制,将android的跨进程通信分成三个块,将servicemanager等都分成了三部分,因此引入了hidl和aidl,已经hal里面的直通式服务和绑定式服务。
同时本篇重点结合U上面的几个hal服务作为案例,梳理了natvie层如何和hal进行跨进程通信,framework层如何和hal进行跨进程通信,同事还结合案例引入selinux权限的基本配置规则。
但是在实际新增一个hal服务的时候,遇到的问题还不仅仅是这些,后续将专门写一篇新增hal服务的笔记,包括遇到的编译问题,selinux问题都是如何解决。
同时hidl在Android U/V开始被启用,google并提供了aidl的替代方案,所以hidl仅仅只跨度了几个aosp版本,然而理解这些更加的有助于我们理解aidl。