今天给大家讲一下android框架中的硬件抽象层HAL(hardware abstract layer),硬件抽象层在软件与硬件之间起到了桥梁作用,作为一个framework工程师是必须掌握的,如果你是一个应用软件工程师或者framework工程师,向驱动工程师转型,hal层也是很好的入门。并且个人认为,掌握hal层相关原理能够大大提高你整个底层到上层垂直开发能力,下面开始讲解。 一、定义及作用 HAL全称hardware abstract layer,即硬件抽象层,它是对底层硬件驱动进行了一层封装,向framework层提供调用驱动的通用接口,厂家只要按照HAL规范,实现相应接口,并且以共享库的形式存放在特定目录下,那么我上层只要加载这个共享库并找到相应的模块对应的设备的指针,一旦拿到真个设备的指针就可以操作底层硬件。 二、背景 那么为什么google会增加这一层(HAL)呢,我们知道手机行业竞争是很激烈的,特别是手机厂商为了追求手机的个性化,而往往这些个性化的东西类似控制算法或者图像算法往往是跟底层硬件打交道的,如果放在内核空间的Linux层,那么这些算法就要遵循GUN Lisence协议进行开源,而如果将这些算法放到android用户空间,而android代码是遵循Apache Lisence协议可以公开也可以不公开代码,如果一旦代码公开,这些厂商势必损失利益,因此可以说,google为了维护各个厂家的利益,设计了这么一个硬件抽象层。也不难理解android源码是开放而不是开源的。 三、HAL调用流程 几个重要的结构体: hw_module_t 这个结构体是通用模块结构体,是具体模块的一个基类,如果你要实现音频模块就需要继承这个通用模块 typedef struct hw_module_t { /** tag must be initialized to HARDWARE_MODULE_TAG */ uint32_t tag; uint16_t module_api_version; #define version_major module_api_version uint16_t hal_api_version; #define version_minor hal_api_version /** Identifier of module */ const char *id; /** Name of this module */ const char *name; /** Author/owner/implementor of the module */ const char *author; /** Modules methods */ struct hw_module_methods_t* methods; /** module's dso */ void* dso; /** padding to 128 bytes, reserved for future use */ uint32_t reserved[32-7]; } hw_module_t;hw_module_methods_t 这个结构体是定义一个打开具体设备的函数open typedef struct hw_module_methods_t { /** Open a specific device */ int (*open)(const struct hw_module_t* module, const char* id, struct hw_device_t** device); } hw_module_methods_t;hw_device_t 这个结构体是通用设备结构体,上层找到对应模块之后需要定位具体设备,一个模块可以有多个设备(根据device id来区别),上面的open函数就是去初始化这个设备的各个参数的 typedef struct hw_device_t { /** tag must be initialized to HARDWARE_DEVICE_TAG */ uint32_t tag; uint32_t version; /** reference to the module this device belongs to */ struct hw_module_t* module; /** padding reserved for future use */ uint32_t reserved[12]; /** Close this device */ int (*close)(struct hw_device_t* device); } hw_device_t;HAL_MODULE_INFO_SYM是定义具体模块的结构体变量,结构体内部是一个hw_module_t结构体,这种包含关系可以理解为,具体模块继承通用模块hw_module_t结构体 /** * Name of the hal_module_info */ #define HAL_MODULE_INFO_SYM HMI下面是audio模块的一个定义,内部hw_module_t定义了一个common变量 struct audio_module HAL_MODULE_INFO_SYM = { .common = { .tag = HARDWARE_MODULE_TAG, .module_api_version = AUDIO_MODULE_API_VERSION_0_1, .hal_api_version = HARDWARE_HAL_API_VERSION, .id = AUDIO_HARDWARE_MODULE_ID, .name = "Default audio HW HAL", .author = "The Android Open Source Project", .methods = &hal_module_methods, }, };下面这个函数供上层获取模块用的函数,需要传入module id和指向hw_module_t的指针的指针 int hw_get_module(const char *id, const struct hw_module_t **module);这个函数才是真正的通过module id去获取具体的module,上面那个hw_get_module函数也是调用的这个函数,之后都是通过hardware\libhardware\hardware.c文件中的load方法去打开(dlopen, dlsym)共享库,通过module id找到对应的模块,最后返回的就是上面的HMI值也就是HAL_MODULE_INFO_SYM /** * Get the module info associated with a module instance by class 'class_id' * and instance 'inst'. * * Some modules types necessitate multiple instances. For example audio supports * multiple concurrent interfaces and thus 'audio' is the module class * and 'primary' or 'a2dp' are module interfaces. This implies that the files * providing these modules would be named audio.primary.<variant>.so and * audio.a2dp.<variant>.so * * @return: 0 == success, <0 == error and *module == NULL */ int hw_get_module_by_class(const char *class_id, const char *inst, const struct hw_module_t **module);具体模块也就是load的加载过程如下: 我们知道每个硬件抽象模块都对应一个动态链接库,这个是厂商提供的,存放在默认的路径下;HAL在需要的时候会去匹配和加载动态链接库。那么HAL是如何找到某个硬件模块对应的正确的共享库呢? 首先,每个模块对应的动态链接库的名字是遵循HAL的命名规范的。举例说明,以GPS模块为例,典型的共享库名字如下: gps.mt6753.so ro.hardware ro.product.board ro.board.platform ro.arch 硬件抽象模块的动态链接库文件名命名规范定义在:\\\: /** * There are a set of variant filename for modules. The form of the filename * is "<MODULE_ID>.variant.so" so for the led module the Dream variants * of base "ro.product.board", "ro.board.platform" and "ro.arch" would be: * * led.trout.so * led.msm7k.so * led.ARMV6.so * led.default.so */ static const char *variant_keys[] = { "ro.hardware", /* This goes first so that it can pick up a different file on the emulator. */ "ro.product.board", "ro.board.platform", "ro.arch" }; //后面会用到 static const int HAL_VARIANT_KEYS_COUNT = (sizeof(variant_keys)/sizeof(variant_keys[0]));HAL会按照variant_keys[]定义的属性名称的顺序逐一来读取属性值,若值存在,则作为variant的值加载对应的动态链接库。如果没有读取到任何属性值,则使用<MODULE_ID>.default.so 作为默认的动态链接库文件名来加载硬件模块。 有了模块的文件名字规范,那么共享库的存放路径也是有规范的。HAL规定了2个硬件模块动态共享库的存放路径 /** Base path of the hal modules */ #if defined(__LP64__) #define HAL_LIBRARY_PATH1 "/system/lib64/hw" #define HAL_LIBRARY_PATH2 "/vendor/lib64/hw" #else #define HAL_LIBRARY_PATH1 "/system/lib/hw" #define HAL_LIBRARY_PATH2 "/vendor/lib/hw" #endif也就是说硬件模块的共享库必须放在/system/lib/hw 或者 /vendor/lib/hw 这2个路径下的其中一个。HAL在加载所需的共享库的时候,会先检查HAL_LIBRARY_PATH2路径下面是否存在目标库;如果没有,继续检查HAL_LIBRARY_PATH1路径下面是否存在。具体实现在函数hw_module_exists /* * Check if a HAL with given name and subname exists, if so return 0, otherwise * otherwise return negative. On success path will contain the path to the HAL. */ static int hw_module_exists(char *path, size_t path_len, const char *name, const char *subname) { //检查/vendor/lib/hw路径下是否存在目标模块 snprintf(path, path_len, "%s/%s.%s.so", HAL_LIBRARY_PATH2, name, subname); if (access(path, R_OK) == 0) return 0; //检查/system/lib/hw路径下是否存在目标模块 snprintf(path, path_len, "%s/%s.%s.so", HAL_LIBRARY_PATH1, name, subname); if (access(path, R_OK) == 0) return 0; return -ENOENT; }name:其实对应上面提到的MODULE_ID subname: 对应从上面提到的属性值variant 现在我们知道了HAL是如何命名和存放模块共享库的,以及HAL基于这种机制来检查目标模块库是否存在的方法。下面来看上传framework打开和加载模块共享库的具体实现过程。 上传framework和应用打开HAL库的入口函数为hw_get_module,定义如下////: /** * Get the module info associated with a module by id. * * @return: 0 == success, <0 == error and *module == NULL */ int hw_get_module(const char *id, const struct hw_module_t **module);传入目标模块的唯一id,得到表示该模块的hw_module_t结构体指针 具体实现在文件//,下面我们具体来分析。 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实际上调用了hw_get_module_by_class来执行实际的工作。 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}; //根据id生成module name,这里inst为NULL if (inst) snprintf(name, PATH_MAX, "%s.%s", class_id, inst); else strlcpy(name, class_id, PATH_MAX); /* * Here we rely on the fact that calling dlopen multiple times on * the same .so will simply increment a refcount (and not load * a new copy of the library). * We also assume that dlopen() is thread-safe. */ /* First try a property specific to the class and possibly instance */ //首先查询特定的属性名称来获取variant值 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; //存在,找到了 } } /* Loop through the configuration variants looking for a module */ //逐一查询variant_keys数组定义的属性名称 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; } } //没有找到,尝试默认variant名称为default的共享库 /* Nothing found, try the default */ if (hw_module_exists(path, sizeof(path), name, "default") == 0) { goto found; } return -ENOENT; found: /* load the module, if this fails, we're doomed, and we should not try * to load a different variant. */ return load(class_id, path, module); //执行加载和解析共享库的工作 }首先根据class_id生成module name,这里就是hw_get_module函数传进来的id; 根据属性名称“ro.hardware.<id>”获取属性值,如果存在,则作为variant值调用前面提到的hw_module_exits检查目标是否存在。如果存在,执行load。 如果不存在,则遍历variant_keys数组中定义的属性名称来获取属性值,得到目标模块库名字,检查其是否存在; 如果根据属性值都没有找到模块共享库,则尝试检查default的库是否存在;如果仍然不存在,返回错误。 如果上述任何一次尝试找到了目标共享库,path就是目标共享库的文件路径,调用load执行真正的加载库的工作。 下面来看load函数: /** * Load the file defined by the variant and if successful * return the dlopen handle and the hmi. * @return 0 = success, !0 = failure. */ 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; /* * load the symbols resolving undefined symbols before * dlopen returns. Since RTLD_GLOBAL is not or'd in with * RTLD_NOW the external symbols will not be global */ //使用dlopen打开path定义的目标共享库,得到库文件的句柄handle handle = dlopen(path, RTLD_NOW); if (handle == NULL) { //出错,通过dlerror获取错误信息 char const *err_str = dlerror(); ALOGE("load: module=%s\n%s", path, err_str?err_str:"unknown"); status = -EINVAL; goto done; } /* Get the address of the struct hal_module_info. */ const char *sym = HAL_MODULE_INFO_SYM_AS_STR; //"HMI" //使用dlsym找到符号为“HMI”的地址,这里应该是hw_module_t结构体的地址;并且赋给hmi hmi = (struct hw_module_t *)dlsym(handle, sym); if (hmi == NULL) { ALOGE("load: couldn't find symbol %s", sym); status = -EINVAL; goto done; } /* Check that the id matches */ //检查模块id是否匹配 if (strcmp(id, hmi->id) != 0) { ALOGE("load: id=%s != hmi->id=%s", id, hmi->id); status = -EINVAL; goto done; } //保存共享库文件的句柄 hmi->dso = handle; /* success */ 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 = hmi; return status; }load函数的主要工作时通过dlopen来打开目标模块共享库,打开成功后,使用dlsym来得到符号名字为"HMI"的地址。这里的HMI应该是模块定义的hw_module_t结构体的名字,如此,就得到了模块对应的hw_module_t的指针。 至此,我么终于得到了表示硬件模块的hw_module_t的指针,有了这个指针,就可以对硬件模块进行操作了。HAL是如何查找和加载模块共享库的过程就分析完了,最终还是通过dlopen和dlsym拿到了模块的hw_module_t的指针,就可以为所欲为了。 四、GPS HAL加载过程 前面分析完了HAL的框架和机制,以GPS HAL的加载过程为例把上面的知识串起来。我们从framework层的hw_get_module函数作为入口点,初步拆解分析。 GPS_HARDWARE_MODULE_ID定义在hardware/libhardware/include/hardware/gps.h中: /** * The id of this module */ #define GPS_HARDWARE_MODULE_ID "gps"调用hw_get_module得到GPS模块的hw_module_t指针,保存在module变量中;我们来看下GPS模块的hw_module_t长得什么样。以hardware/qcom/gps/loc_api/libloc_api_50001/gps.c为例: struct hw_module_t HAL_MODULE_INFO_SYM = { .tag = HARDWARE_MODULE_TAG, .module_api_version = 1, .hal_api_version = 0, .id = GPS_HARDWARE_MODULE_ID, .name = "loc_api GPS Module", .author = "Qualcomm USA, Inc.", .methods = &gps_module_methods, //自定义的函数指针,这里既是获取hw_device_t的入口了 };接着调用GPS模块自定义的hw_module_t的methods中的open函数,获取hw_device_t指针。上面的代码中我们看到,GPS模块的hw_module_t的methods成员的值为gps_module_methods,其定义如下: static struct hw_module_methods_t gps_module_methods = { .open = open_gps };OK,我们来看open_gps函数做了什么: static int open_gps(const struct hw_module_t* module, char const* name, struct hw_device_t** device) { //为gps_device_t分配内存空间 struct gps_device_t *dev = (struct gps_device_t *) malloc(sizeof(struct gps_device_t)); if(dev == NULL) return -1; memset(dev, 0, sizeof(*dev)); //为gps_device_t的common成员变量赋值 dev->common.tag = HARDWARE_DEVICE_TAG; dev->common.version = 0; dev->common.module = (struct hw_module_t*)module; //通过下面的函数就能得到GPS模块所有interface dev->get_gps_interface = gps__get_gps_interface; //将gps_device_t指针强转为hw_device_t指针,赋给device *device = (struct hw_device_t*)dev; return 0; }我们看到open_gps创建了gps_device_t结构体,初始化完成后,将其转为hw_device_t。所以module->methods->open得到实际上是gps_device_t结构体指针。这里我们可以理解为gps_device_t是hw_device_t的子类,将子类对象转为父类对象返回,是很正常的使用方法。为什么可以这么理解,看一下gps_device_t长得什么样子就明白了。 啊哈,第一个成员是名为common的hw_device_t类型的变量;所以可以理解为gps_device_t继承了hw_device_t。 得到GPS的hw_device_t指针后将其强转回gps_devcie_t指针,然后调用GPS device定义的get_gps_interface接口,得到保存GPS 接口的GpsInterface结构体指针。 GpsInterface定义了操作GPS模块的基本的标准接口,得到了GpsInterface就可以通过这些接口操作GPS了,终于可以硬件打交道了。某一个具体的GPS模块会将GpsInterface中的接口初始化为其平台相关的具体实现。比如:hardware/qcom/gps/loc_api/libloc_api_50001/loc.cpp // Defines the GpsInterface in gps.h static const GpsInterface sLocEngInterface = { sizeof(GpsInterface), loc_init, loc_start, loc_stop, loc_cleanup, loc_inject_time, loc_inject_location, loc_delete_aiding_data, loc_set_position_mode, loc_get_extension };到这里,整个GPS HAL的加载过程就结束了,后面就可以通过GpsInterface操作GPS模块了。 本文分析了Android HAL定义背景以及机制流程,介绍了它的核心数据结构,分析了硬件模块的查询和加载过程;然后以GPS为例说明了如何通过HAL得到硬件的接口函数。 (责任编辑:) |