2.4 Android的硬件抽象层
↘2.4.1 硬件抽象层的地位和功能
硬件抽象层是位于用户空间的Android系统和位于内核空间的Linux驱动程序中间的一个层次。Android中硬件抽象层的结构如图2-2所示。
图2-2 Android中硬件抽象层的结构
Android BSP典型的实现方式是构建硬件抽象层和驱动程序,硬件抽象层调用驱动程序。在这种方式中,Android系统的核心部分实际上关心的只是硬件抽象层,并不关心驱动程序。这样做的好处是将Android系统的部分功能和Linux中的驱动程序隔离,Android不依赖于Linux的驱动程序。对于同一种功能的实现,可能具有不同的驱动程序。在某些情况下,硬件抽象层是标准的,这样就只需要实现驱动程序即可。这种情况下的驱动程序,一般也是Linux中的标准的驱动程序。
↘2.4.2 硬件抽象层接口方式
1.hardware模块的方式
Android的libhardware库提供了一种不依赖编译时绑定的机制,它可以动态加载硬件抽象层,硬件模块方式的硬件抽象层架构如图2-3所示。
图2-3 硬件模块方式的硬件抽象层架构
在libhardware的接口中定义了通用的内容和各个硬件模块的id,每一个硬件模块需要被实现成所要求的接口形式,并生成单独的动态库文件(*.so)。
在使用硬件抽象层的过程中,Android系统的框架层将调用libhardware的接口,根据每一个模块的id,将在指定路径动态打开(dlopen)各个硬件模块,然后找到符号(dlsym),调用硬件模块中的各个接口。
接口的调用方式是一致的,只是在不同系统中所打开的硬件模块是不相同的。在Android系统中各种硬件模块在目标系统中路径为/system/lib/hw/。例如,gralloc.default.so表示默认的gralloc模块(用于显示),sensors.goldfish.so表示默认的传感器模块。
libhardware的接口在以下目录中定义:
hardware/libhardware/include/hardware/hardware.h
struct hw_module_t结构体用于定义了硬件模块的格式,如下所示:
typedef struct hw_module_t { uint32_t tag; // tag,需要被初始化为HARDWARE_MODULE_TAG uintl6_t version_major; // 主版本号 uintl6_t version_minor; // 次版本号 const char *id; // 模块标识 const char *name; // 模块的名称 const char *author; // 模块作者 struct hw_module_methods_t* methods; // 模块方法 void* dso; // 模块的dso uint32_t reserved[32-7]; // 填充字节,为以后使用 } hw_module_t;
struct hw_module_t结构体定义了一个硬件模块的信息。在各个具体硬件模块中,需要以这个结构体为第一个成员,即表示“继承”了这个结构体。
struct hw_module_methods_t是一个表示模块方法的结构体,如下所示:
typedef struct hw_module_methods_t { int (*open)(const struct hw_module_t* module, const char* id, struct hw_device_t** device); // 打开设备的方法 } hw_module_methods_t;
struct hw_module_methods_t结构体只包含了一个打开模块的函数指针,这个结构体也作为struct hw_module_t结构体的一个成员。
struct hw_device_t表示一个硬件设备,如下所示:
typedef struct hw_device_t { uint32_t tag; /// tag需要被初始化为HARDWARE_DEVICE_TAG uint32_t version; // hw_device_t的版本号 struct hw_module_t* module; // 引用这个设备属于的硬件模块 uint32_t reserved[l2]; // 填充保留字节 int (*close)(struct hw_device_t* device); // 关闭设备 } hw_device_t;
struct hw_device_t也是需要被具体实现的结构体包含使用的,一个硬件模块可以包含多个硬件设备。
硬件的具体调用流程如下所示。
(1)通过id得到硬件模块。
(2)从硬件模块中得到hw_module_methods_t,打开得到硬件设备hw_device_t。
(3)调用hw_device_t中的各个方法(不同模块所实现的)。
(4)调用完成,通过hw_device_t的close关闭设备。
模块打开到使用的流程如图2-4所示。
图2-4 硬件模块方式的调用方式
在以上的流程中,还需要libhareware提供一个得到模块的函数,这个函数就是hw_get_module(),如下所示:
int hw_get_module(const char *id, const struct hw_module_t **module);
hw_get_module()函数的实现在hardware/libhardware/目录的hardware.c文件中,其内容如下所示:
int hw_get_module(const char *id, const struct hw_module_t **module){ int status; int i; const struct hw_module_t *hmi = NULL; char prop[PATH_MAX]; char path[PATH_MAX]; for (i=0 ; i<HAL_VARIANT_KEYS_COUNT+l ; i++) { if (i < HAL_VARIANT_KEYS_COUNT) { if (property_get(variant_keys[i], prop, NULL) == 0) { continue; } snprintf(path, sizeof(path), "%s/%s.%s.so", // 得到模块的名称 HAL_LIBRARY_PATH, id, prop); } else { snprintf(path, sizeof(path), "%s/%s.default.so", // 得到默认模块的名称 HAL_LIBRARY_PATH, id); } if (access(path, R_OK)) { continue; } break; // 找到模块,然后退出 } status = -ENOENT; if (i < HAL_VARIANT_KEYS_COUNT+l) { status = load(id, path, module); // 循环打开模块的动态库 } return status; }
由hw_get_module()函数的内容可见,执行的是一个动态查找的过程,找到硬件动态库(*.so)打开,当没有动态库的时候,将打开默认的库文件(*.default.so)。
在hw_get_module()函数中调用的load()函数,其主要内容如下所示:
static int load(const char *id, const char *path, const struct hw_module_t **pHmi) { int status; void *handle; struct hw_module_t *hmi; handle = dlopen(path, RTLD_NOW); // 进行动态库的打开 const char *sym = HAL_MODULE_INFO_SYM_AS_STR; hmi = (struct hw_module_t *)dlsym(handle, sym); // 省略部分内容 }
load()函数实际上执行了一个动态打开(dlopen)和动态取出符号(dlsym)的过程。这个过程解除了在编译阶段的Android本地框架对特有的硬件模块的依赖。
硬件模块的调用方式如下所示:
xxx_module_t * gModule; xxx_device_t * gDevice; { xxx_module_t const* module; err = hw_get_module(XXXX_HARDWARE_MODULE_ID, (const hw_module_t **)&module); if (err == 0) gModule = (xxxx_module_t*)module; gModule->ModuleFunction(); // 调用模块的函数 gDevice->DeviceFunction(); // 调用设备的函数 }
通常情况下,硬件模块的调用者是Android中的本地框架层,它们调用接口定义的标准,而不是接口的实现。
libhardware的接口头文件中,除hardware.h之外,其他各个头文件是相互并列的,每一个文件表示了一种硬件抽象层。
使用hardware模块形式构建的动态库不需要被其他部分链接,而是在运行的过程中通过Linux系统常用的dlopen()和dlsym()函数动态地打开和取出符号来使用。
2.调用预定义接口的方式
调用预定义接口的方式是在头文件中定义硬件抽象层的接口,由实现者根据接口实现,这种方式实际上并没有完全将硬件抽象层和Android的本地框架分开,其好处是接口的定义和实现比较简单。
hardware_legacy库中提供了一些各自独立的接口,由用户实现后形成库,被直接链接到系统中。这是实现硬件抽象层最简单也是最直接的方式。hardware_legacy的头文件路径为:
·hardware/libhardware_legacy/include/hardware_legacy
Android中的蓝牙库bluedroid与之类似,也是采用同样的方式,其头文件的路径为:
·system/bluetooth/bluedroid/include/bluedroid/bluetooth.h
hardware_legacy库中包含了几个C接口的文件,如power、wifi、vibrator等,同时在hardware_legacy库中也包含了power、wifi、vibrator。在适配一个新的硬件系统时,可以根据需要去实现这几个库,也可以使用系统默认的实现方式。
OpenGL和电话部分也类似于直接调用接口的方式,但是它们使用的是dlopen动态打开的方式,并且可以选择使用不同的库。
3.C++的继承实现方式
使用C++类的继承方式实现硬件抽象层,也是Android中的一种方式。为了使用这种方式,Android平台定义了C++的接口。由具体的实现者继承实现这些接口,同时在Android系统中,通常也有通用的实现方式,可以作为一个简易的实现或者“桩(stub)”的作用。
使用C++类的继承方式的硬件抽象层结构如图2-5所示。
图2-5 使用C++类的继承方式的硬件抽象层结构
在这种实现方式中,具体的硬件抽象层通常要求被编译为指定名称的动态库,由本地框架库链接;通用的实现被编译成静态库(*.a),本地框架库链接这些静态库的时候,其实就是包含了它们在其中的。在某个系统中,究竟是使用特定硬件抽象层还是通用的硬件抽象层,通常需要根据编译宏来指定。
Android 2.x及之前版本的照相机和音频系统使用的是C++类的继承方式。
4.直接调用驱动
在Android中有一些比较简单的子系统,并没有在独立存在的硬件抽象层,也就是说,实现其硬件抽象功能的部分不在单独的代码中。一种常见的情况是由JNI部分代码直接调用驱动程序的设备节点或者使用sys文件系统。这种直接调用驱动的方式一般适用于比较简单的子系统。
警报器子系统在JNI代码中直接调用字符设备节点。电池信息子系统在JNI中直接访问sys文件系统。检测耳机是否插入的功能实际上也通过在JNI中访问sys文件系统完成。