一、Treble机制

1、Treble化的Vendor分区

2、Treble化的Binder机制

3、Binder、VndBinder、HwBinder异同

二、HAL的前世今生

1、HAL实现机制

2、HAL模块的编写

3、HAL动态库的调用

三、HIDL架构下的HAL


在Android 8.0 之前,Binder机制比较简单,只有一个驱动设备"/dev/binder",一个守护进程"/system/bin/servicemanager",一个binder库"/system/lib64/libbinder.so"。即前面几篇文章讲解的内容。

在Android 8.0开始,Android引入了Treble的机制,为了方便Android系统的快速移植、升级,提升系统稳定性,Binder驱动设备被拓展成了"/dev/binder", "/dev/hwbinder","/dev/vndbinder"。

参考:Android10.0 Binder通信原理之Binder HwBinder VndBinder概要

参考:Android Treble架构解析

一、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

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)、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,因此两者使用时,每次只能指定一个设备节点,不能同时使用。

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里面配置的服务启动。

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/servicemanagerclass core animationuser systemgroup system readproccriticalonrestart restart healthdonrestart restart zygoteonrestart restart audioserveronrestart restart mediaonrestart restart surfaceflingeronrestart restart inputflingeronrestart restart drmonrestart restart cameraserveronrestart restart keystoreonrestart restart gatekeeperdwritepid /dev/cpuset/system-background/tasksshutdown critical
#frameworks/native/cmds/servicemanager/vndservicemanager.rc
service vndservicemanager /vendor/bin/vndservicemanager /dev/vndbinderclass coreuser systemgroup system readprocwritepid /dev/cpuset/system-background/tasksshutdown critical
#system/hwservicemanager/hwservicemanager.rc
service hwservicemanager /system/bin/hwservicemanageruser systemdisabledgroup system readproccriticalonrestart setprop hwservicemanager.ready falseonrestart class_restart halonrestart class_restart early_halwritepid /dev/cpuset/system-background/tasksclass animationshutdown critical

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)、如何分离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/servicemanagerclass core animationuser systemgroup system readproccriticalonrestart restart healthdonrestart restart zygoteonrestart restart audioserveronrestart restart mediaonrestart restart surfaceflingeronrestart restart inputflingeronrestart restart drmonrestart restart cameraserveronrestart restart keystoreonrestart restart gatekeeperdwritepid /dev/cpuset/system-background/tasksshutdown critical
#进程vndservicemanager
#init在启动该进程的时候传递了参数"/dev/vndbinder"
service vndservicemanager /vendor/bin/vndservicemanager /dev/vndbinderclass coreuser systemgroup system readprocwritepid /dev/cpuset/system-background/tasksshutdown 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 VENDORSERVICEMANAGERwhile (true)  sleep(UINT_MAX);
#elseALOGE("failed to open binder driver %s\n", driver);
#endifreturn -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 VENDORSERVICEMANAGERsehandle = selinux_android_vendor_service_context_handle();
#elsesehandle = selinux_android_service_context_handle();
#endifselinux_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维护的进程表中获取)。

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)模块

参考: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)、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_versionuint16_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 dsovoid* 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引用

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);elsestrlcpy(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;
#endifreturn -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的IDif (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文件),最终实现了对驱动层的调用

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系统中,同时我们还要介绍应用程序怎样使用我们自定义的硬件抽象层模块。

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)、创建自定义硬件抽象层模块编译脚本

接下来就需要实现硬件抽象层具体业务逻辑代码了,同样根据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)

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_tmemset(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_tstruct 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;
}

4)、生成自定义硬件抽象层模块动态库文件

执行如下命令编译led HAL模块,跳转到led目录下面直接mm就可以编译生成一个HAL动态库了。

3、HAL动态库的调用

通过上节,我们自定义硬件抽象层模块,它对下内核空间封装了驱动程序,向上用户空间提供了一些操作硬件(调用驱动设备节点)的接口。在本节我们将介绍用户进程的几种调用方式。

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");elsetv.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_moduleled_module_t* module; //通过hw_get_module查询指定唯一标识为led的硬件抽象层模块,并存储在led_module//hw_get_module返回的其实是HAL层模块中的HAL_MODULE_INFO_SYMif (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,它内部持有驱动设备文件描述符,并向用户进程提供了一系列操作硬件设备的接口。

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() {@Overridepublic void onServiceConnected(ComponentName componentName, IBinder iBinder) {service = iBinder.getService();//调用LedService提供的接口:获取LED灯的状态boolean isOn = service.getLedState()if(isOn) tv.setText("LED last state is ON");elsetv.setText("LED last state is OFF"); //调用LedService提供的接口:设置LED灯的状态service.setLedState(~isOn);}@Overridepublic 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) { //注册LedServicestatic 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)、基于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来访问系统服务ledpublic 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");elsetv.setText("LED last state is OFF"); setContentView(tv); //HAL层模块最终调用了驱动程序,实现对硬件的控制mLedManager.setLedOn(~isOn);}
} 
  • 总结:该方式是不是不在那么简单,因为结合了AIDL,符合了系统级的设计方式,虽然复杂,但是流程清晰,没错Android原生的HAL层模块,例如camera/audio/lights等都是使用这种方式实现的。

三、HIDL架构下的HAL

参考:Android-HAL与HIDL分析使用总结

参考:Android P HIDL服务绑定模式与直通模式的分析 (原创)

Binder死磕到底(四):Treble化架构相关推荐

  1. Binder死磕到底

    Binder是Android系统提供的一种IPC( 进程间通信) 机制. 由于Android是基于Linux内核的, 因此除了Binder以外,还存在其他的IPC机制, 例如管道和socket等. B ...

  2. 对于创业者来说,如何规避版权风险——遇到“版权流氓”死磕到底

    遇到"版权流氓"死磕到底 小编的一个大学同学现在在一个新媒体公司工作.视觉中国这件事爆发后,小编的同学给小编聊了一下他们遇到的版权事件. 去年,他们收到了来自版权图库网站" ...

  3. 那些在一个公司死磕到底的人,最后都怎么样了?在一棵树上吊死真的好吗?

    推荐阅读:前阿里P7架构师,分享多年工作心得and面试经验,祝你圆大厂梦 为面阿里P8,我肝了一份651个技术分支的脑图,要么?(限时领) 最近在知乎上看到一个话题 那些在一个公司死磕了5-10年的人 ...

  4. 大厂都在拆中台了,为什么我们还死磕到底?

    " 中台的概念最早是由 2015 年阿里提出的"大中台,小前台"战略中衍生出来.作为解决企业多业务线发展到一定阶段后出现瓶颈的有效解决方案,一经提出各大互联网公司迅速跟进 ...

  5. 腾讯 2017 年投资项目榜单 TOP 10,与阿里死磕到底?

    点击上方"CSDN",选择"置顶公众号" 关键时刻,第一时间送达! [编者按]从社交.电商,再到娱乐.出行.文化.医疗.AR/VR.企业服务.线下零售等各个领域 ...

  6. tomcat中间件的默认端口号_死磕Tomcat系列(1)——整体架构

    点击上方"Java技术前线",选择"置顶或者星标" 与你一起成长 在许多的高端开发的岗位中都会或多或少有要求面试人员要研究过一些常用中间件源码.这是因为一切的秘 ...

  7. 【死磕DDD】领域驱动架构设计核心概念

    为什么领域驱动那么火? 它解决了架构师的一个通用问题:Do the RIGHT thing RIGHT! 领域驱动架构设计就是以客户和产品为导向,进行业务拆分的一套架构设计思路. 领域设计4层模型 它 ...

  8. UNIX再学习 -- 死磕内存管理

    malloc/free简化实现:malloc 和 sbrk 关系:虚拟内存机制. 一个内存管理 C 语言部分讲,UNIX部分讲,Linux部分还讲,死磕到底!! 一.mallc/free简化实现 上篇 ...

  9. mysql查询前段时间_没想到!我在简历上写了“精通MySQL”,阿里面试官跟我死磕后就给我发了高薪offer...

    事情是这样的 前段时间面试了阿里,大家也都清楚,如果你在简历上面写着你精通XX技术,那面试官就会跟你死磕到底. 我就是在自己的简历上写了精通MySQL,然后就开启了和阿里面试官的死磕之路,结果就是拿到 ...

最新文章

  1. 由动态规划计算编辑距离引发的思考
  2. CVPR2020:端到端学习三维点云的局部多视图描述符
  3. Linux加密和安全
  4. 什么是分布式任务调度
  5. 尝试使用jBPM Console NG(测试版)
  6. Win7旗舰版系统时间不准确怎么办
  7. Eclipse跌落神坛了。。
  8. 转载:小心别让圆角成了你列表的帧数杀手
  9. HashTable、ConcurrentHashMap、TreeMap、HashMap关于键值的区别
  10. C# 之 Win32 Api使用
  11. android netcfg 源码分析
  12. 工作展望简短_简短的工作计划
  13. 分布式深度强化学习的内功修炼之隐式分布
  14. 微博博主侮辱女性 街猫koryili
  15. 免费搜索引擎登陆入口
  16. Android Studio创建虚拟机选定指定位置
  17. 1024: 大小写转换(C语言)
  18. 活久见系列:微信推出收费服务了!
  19. 亚马逊测评项目有哪些风险
  20. 后台管理系统--首页及登录认证

热门文章

  1. showDoc的基本使用方法
  2. 北斗三号精密单点定位(PPP-B2b)
  3. 反射弧包括那些组成部分_反射弧的组成部分包括
  4. TortoiseGit使用详解(1)
  5. notepad++设置网络代理
  6. matlab设置固定的窗宽窗位,【经验谈】如何设定窗宽窗位,附正常人体组织CT值...
  7. 七、Kali Linux 2 渗透攻击
  8. 目前流行前端web几大UI框架排行榜
  9. 人工智能导论(第四版)王万良编著课后习题答案
  10. 基于神经网络的模式识别