背景
hi,粉丝朋友们:
大家好!今天来一个经典hal的实现,首先说一下哈,经典hal在正常在Treble计划后就不再建议使用了,高版本一般都是hidl,aidl。目前新版本一般都是强制使用aidl,那为啥还需要讲解这个经典hal呢?
1、虽然采用了treble计划后,system/vendor隔离后,但是因为经典hal是个so,而且代码一般不需要强依赖so,具有一个非常好的移植性,导致虽然变成了google要求的hidl,其实内部依然实现是经典hal的so
2、经典hal so在trable计划以前已经经历了很多个版本,业务执行的稳定性很好,基本上厂商没啥动力推倒重来,基于新的hidl,aidl再写一遍hal业务
总结:treble计划虽然系统已经大部分都是hidl,aidl实现方式,但是这个只是一个对外接口层面的,内部实现的话其实完全可以继续调用以前写好的经典hal,这个方式保证了稳定性,修改量小
所以基于以上背景,掌握经典hal的相关知识完全是有必要的,不然就可能会导致你根本看不懂很多hal实现
HAL module的架构分析
注意这里只进行依赖libhardware库这种方式实现,不讲解legacy方式
HAL module架构主要分为以下3个结构体:
struct hw_module_t
struct hw_module_methods_t
struct hw_device_t
3个结构体是写这个标准方式hal的最关键部分,它们定义是在
hardware/libhardware/include/hardware/hardware.h
下面3个进行详细介绍其功能和作用
hw_module_t部分
相关的定义代码如下:
/**
* Every hardware module must have a data structure named HAL_MODULE_INFO_SYM
* and the fields of this data structure must begin with hw_module_t
* followed by module specific information.
*/
typedef struct hw_module_t {
/** tag must be initialized to HARDWARE_MODULE_TAG */
uint32_t tag;
/**
* The API version of the implemented module. The module owner is
* responsible for updating the version when a module interface has
* changed.
*
* The derived modules such as gralloc and audio own and manage this field.
* The module user must interpret the version field to decide whether or
* not to inter-operate with the supplied module implementation.
* For example, SurfaceFlinger is responsible for making sure that
* it knows how to manage different versions of the gralloc-module API,
* and AudioFlinger must know how to do the same for audio-module API.
*
* The module API version should include a major and a minor component.
* For example, version 1.0 could be represented as 0x0100. This format
* implies that versions 0x0100-0x01ff are all API-compatible.
*
* In the future, libhardware will expose a hw_get_module_version()
* (or equivalent) function that will take minimum/maximum supported
* versions as arguments and would be able to reject modules with
* versions outside of the supplied range.
*/
uint16_t module_api_version;
#define version_major module_api_version
/**
* version_major/version_minor defines are supplied here for temporary
* source code compatibility. They will be removed in the next version.
* ALL clients must convert to the new version format.
*/
/**
* The API version of the HAL module interface. This is meant to
* version the hw_module_t, hw_module_methods_t, and hw_device_t
* structures and definitions.
*
* The HAL interface owns this field. Module users/implementations
* must NOT rely on this value for version information.
*
* Presently, 0 is the only valid value.
*/
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;
#ifdef __LP64__
uint64_t reserved[32-7];
#else
/** padding to 128 bytes, reserved for future use */
uint32_t reserved[32-7];
#endif
} hw_module_t;
代码很少,注释很多,不过都是英文的,这里给大家总结一下,即常用的成员变量:
1、每一个hardware硬件抽象模块必须定义有一个模块结构体,名字是HAL_MODULE_INFO_SYM,即HMI,这个模块结构体第一个成员变量类型必须是hw_module_t
2、hw_module_t的tag的值必须为HARDWARE_MODULE_TAG,这个就是来标识是硬件抽象的结构体
3、id这个属性代表这个module的唯一性,即每个硬件抽象模块都会有自己的id
4、methods这个代表每个硬件抽象模块对应的方法结构体,类型hw_module_methods_t
常见定义和实现方式如下:
定义结构体:
#define INVCASE_HARDWARE_MODULE_ID "mytest"
struct mytest_module_t {
struct hw_module_t common;
};
实例化HAL_MODULE_INFO_SYM:
/* every module must have HAL_MODULE_INFO_SYM */
struct mytest_module_t HAL_MODULE_INFO_SYM = {
.common = {
.tag = HARDWARE_MODULE_TAG,
.version_major = 1,
.version_minor = 0,
.id = MYTEST_HARDWARE_MODULE_ID,
.name = MYTEST_HARDWARE_MODULE_ID,
.author = "test",
.methods = &mytest_module_methods,
},
};
hw_module_methods_t部分
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_module_methods_t 中只有一个成员,它是一个函数指针,名字是open,它主要作用就是用来打开硬件抽象层中给的硬件设备。因为一个硬件抽象模块module可以包含多个设备device,所以这里需要指定id。
moudlue:代表打开硬件设备设备所属模块
id:代表打开设备的id
device:这个双重指针,一般是输出内容到这个参数里面,即返回值,用来描述一个打开的硬件设备
hw_device_t部分
/**
* Every device data structure must begin with hw_device_t
* followed by module specific public methods and attributes.
*/
typedef struct hw_device_t {
/** tag must be initialized to HARDWARE_DEVICE_TAG */
uint32_t tag;
/**
* Version of the module-specific device API. This value is used by
* the derived-module user to manage different device implementations.
*
* The module user is responsible for checking the module_api_version
* and device version fields to ensure that the user is capable of
* communicating with the specific module implementation.
*
* One module can support multiple devices with different versions. This
* can be useful when a device interface changes in an incompatible way
* but it is still necessary to support older implementations at the same
* time. One such example is the Camera 2.0 API.
*
* This field is interpreted by the module user and is ignored by the
* HAL interface itself.
*/
uint32_t version;
/** reference to the module this device belongs to */
struct hw_module_t* module;
/** padding reserved for future use */
#ifdef __LP64__
uint64_t reserved[12];
#else
uint32_t reserved[12];
#endif
/** Close this device */
int (*close)(struct hw_device_t* device);
} hw_device_t;
其实这个和前面的hw_module_t高度相识,只不过一个代表整个模块一个代表模块的设备
同样总结一下hw_device_t:
1、每个硬件设备都必须自定义一个硬件设备的结构体,第一个成员变量为hw_device_t
2、tag必须为所在的硬件模块HARDWARE_DEVICE_TAG,代表他是个硬件设备结构体
3、会有一个close的函数指针,主要是关闭一个硬件设备
4、自定义设备结构体,除了第一个成员hw_device_t外,最重要就是会定义其他成员方法指针,这些方法就是用来对外提供操作硬件设备的
常见自定义的hw_device_t如下:
struct mytest_device_t {
struct hw_device_t common; //第一成员必须为hw_device_t
int(*addTest)(int a,int b);
};
构造结构过程:
// use calloc to initialize memory with 0
struct mytest_device_t * device = NULL;
device = ( struct mytest_device_t * )malloc(sizeof(*device));
memset(device,0,sizeof(*device));
device->common.tag = HARDWARE_DEVICE_TAG;
device->common.version = 0;
device->common.module = (hw_module_t*)module;
device->common.close = close_mytest;
device->addTest = addTest;
上面最重要就是把hw_device_t构造出,而且给成员变量test赋值了具体方法,后面获取了这个hw_device_t就可以调用真正的hal实现方法了
HAL module的使用步骤
上面只是一个个的定义好硬件抽象模块的步骤,接下来是要要怎么使用上面的hardware接口进行调用到相关的hal方法呢?
1、通过hw_get_module(char* id, struct hw_module_t ** module) 方法获取一个硬件抽象模块的指针
2、通过模块调用module->common.methods->open()方法获取一个硬件抽象即hw_device_t的指针
3、使用硬件抽象设备的hw_device_t指针调用具体的hal实现方法,比如上面 mytest_dev->addTest
相关调用hal的代码如下:
mytest_module_t* module;
if (hw_get_module(MYTEST_HARDWARE_MODULE_ID,(const hw_module_t**)&module) == 0) { //获取hw_module_t
//调用methods的open方法,目的获取hw_device_t的指针
if (((hw_module_t*)module)->methods->open((hw_module_t*)module,MYTEST_HARDWARE_MODULE_ID,(struct hw_device_t**)&mytest_device) == 0) { //获取hw_device_t对象指针
//利用获取的mytest_device,调用对于的方法
int result = mytest_device->addTest(1,2);
}
}
hardware.c源码分析
上面使用的hw_get_module这些方法到底在哪里实现的呢?
其实这些代码的实现都是在hardware.c文件中
下面从hw_get_module方法作为切入点全面展开分析一下相关的
hardware/libhardware/hardware.c
int hw_get_module(const char *id, const struct hw_module_t **module)
{
//调用到了hw_get_module_by_class,方法第二参数是NULL
return hw_get_module_by_class(id, NULL, module);
}
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);//把相关的id赋值给了name
//第一优先级遍历看看是否存在ro.hardware.mytest 的prop
snprintf(prop_name, sizeof(prop_name), "ro.hardware.%s", name);
if (property_get(prop_name, prop, NULL) > 0) { //如果找到了这个ro.hardware.mytest 的prop
if (hw_module_exists(path, sizeof(path), name, prop) == 0) { //根据prop值调用hw_module_exists看看是否hw存在
goto found;
}
}
//直接module的prop没有找到,则开启第二优先级遍历,这个主要便利设备相关属性,比如 "ro.hardware",
/* Loop through the configuration variants looking for a module */
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) {//然后去看看是否哪个prop的值有对应的so的
goto found;
}
}
//上面都没有找到,只能使用一个默认的hal so进行
/* Nothing found, try the default */
if (hw_module_exists(path, sizeof(path), name, "default") == 0) {
goto found;
}
return -ENOENT;
//开始处理找到so相关的业务
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);//传递找到的so路径然后进行so的加载
}
hw_module_exists会根据传递进来的name,subname,然后去拼出一个so的文件名字,看看这个so文件是否存在,存在这返回找到
/*
* 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)
{
//HAL_LIBRARY_PATH3 "/odm/lib64/hw" 最先看看odm是否有这个so
snprintf(path, path_len, "%s/%s.%s.so",
HAL_LIBRARY_PATH3, name, subname);
if (path_in_path(path, HAL_LIBRARY_PATH3) && access(path, R_OK) == 0)
return 0;
//HAL_LIBRARY_PATH2 "/vendor/lib64/hw“接下来是vendor下面是否有
snprintf(path, path_len, "%s/%s.%s.so",
HAL_LIBRARY_PATH2, name, subname);
if (path_in_path(path, HAL_LIBRARY_PATH2) && access(path, R_OK) == 0)
return 0;
#ifndef __ANDROID_VNDK__
snprintf(path, path_len, "%s/%s.%s.so",
HAL_LIBRARY_PATH1, name, subname);
if (path_in_path(path, HAL_LIBRARY_PATH1) && access(path, R_OK) == 0)
return 0;
#endif
return -ENOENT;
}
上面可以看出hw_module_exists会去几个路径找,优先级是oem > vendor> system
如果prop指定的都没有找到最后只能用最后的default看看是否存在,如果这个default so都没有,那么就会报错了,即无法获取到对于的硬件抽象模块,说明硬件抽象这部分功能就无法使用了。
一般情况下都最少可以找到一个default的so,那么找到后会执行上面的load方法,把这个so进行相关load
这个load方法是最关键的,来看看系统到底是怎么实现的:
static int load(const char *id,
const char *path,
const struct hw_module_t **pHmi)
{
//省略部分
/* Get the address of the struct hal_module_info. */
const char *sym = HAL_MODULE_INFO_SYM_AS_STR;//就是靠这个固定HMI字符去so中找符号
hmi = (struct hw_module_t *)dlsym(handle, sym);//这里是关键传递返回HMI的地址,也就相当于找到了这个结构体指针hmi
*pHmi = hmi;//把找到
return status;
}
补充介绍一下两个函数:
dlopen()是一个强大的库函数。该函数将打开一个新库,并把它装入内存。该函数主要用来加载库中的符号,这些符号在编译的时候是不知道的。
dlsym是一个计算机函数,功能是根据动态链接库操作句柄与符号,返回符号对应的地址,不但可以获取函数地址,也可以获取变量地址。
上面的load方法就是我们说的hardware动态性的关键,它是在运行时候才真正依赖具体硬件抽象so,编译期间只需要依赖公共的hardware相关公共类既可以,不需要so,所以这里就给硬件抽象提供商带来很大灵活性,哪怕硬件厂商不提供也一般会有一个default的so保证不会产生什么严重的崩溃和强依赖问题,大大减低了aosp的代码对于硬件抽象的各个厂商的耦合性。
这里看看手机上的相关hal so:
这里在看看哪个prop是ms8998
本文章对应视频手把手教你学framework:
https://mp.weixin.qq.com/s/LbVLnu1udqExHVKxd74ILg
私聊作者+v(androidframework007)
点击这里 https://mp.weixin.qq.com/s/Qv8zjgQ0CkalKmvi8tMGaw
视频:https://www.bilibili.com/video/BV1wc41117L4/