1.前言
随着Android系统学习的深入,最近开始跟踪到hardware层的源码。开个分栏,记录下hardware层的学习。
2.正文
hardware层【HAL】位于Android系统框架的第二层。
HAL被称为Android的硬件接口。对于Android中很多子系统来说,HAL都是必不可少的组成部分–HAL是这些子系统与Linux kernel驱动之间通信的统一接口。
hardware层对应的源码位于 source code/hardware/
本文从 source code/hardware/libhardware/着手分析。
对于hal层so的加载,必须经过hardware.c中的代码实现。
(1)先看hardware/libhardware/include/hardware/hardware.h
在hardware.h中定义了三个重要的结构体
⭐ struct hw_module_t;
/**
每个硬件模块都必须具有名为HAL_MODULE_INFO_SYM的数据结构,并且此数据结构的字段必须以hw_module_t开头,后跟模块特定信息。
*/
typedef struct hw_module_t {
/** 必须将标记初始化为HARDWARE_MODULE_TAG */
uint32_t tag;
/**
已实现模块的API版本。 模块所有者负责在模块接口具有时更新版本改变。
gralloc和audio等派生模块拥有并管理此字段。模块用户必须解释版本字段以决定是否与提供的模块实现进行互操作。例如,SurfaceFlinger负责确保它知道如何 要管理不同版本的gralloc-module API,AudioFlinger必须知道如何为音频模块API执行相同操作。
模块API版本应包括主要和次要组件。例如,版本1.0可以表示为0x0100。 此格式表示版本0x0100-0x01ff都与API兼容。
将来,libhardware将公开一个hw_get_module_version()(或等效的)函数,该函数将最小/最大支持的版本作为参数,并且能够拒绝版本超出所提供范围的模块。
*/
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.
*/
/**
这里提供了version_major / version_minor定义,用于临时源代码兼容性。 它们将在下一版本中删除。 所有客户端必须转换为新版本格式。
HAL接口拥有此字段。 模块用户/实现
不得依赖此值来获取版本信息。
目前,0是唯一有效的值。
*/
uint16_t hal_api_version;
#define version_minor hal_api_version
/**模块的标识符*/
const char *id;
/** 该模块的名称*/
const char *name;
/**模块的作者/所有者/实现者*/
const char *author;
/**模块方法 */
struct hw_module_methods_t* methods;
/** 模块的dso */
void* dso;
#ifdef __LP64__
uint64_t reserved[32-7];
#else
/** 填充到128个字节,保留供将来使用 */
uint32_t reserved[32-7];
#endif
} hw_module_t;
⭐ 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_device_t;
/**
每个设备数据结构必须以hw_device_t开头,后跟模块特定的公共方法和属性。
*/
typedef struct hw_device_t {
/** 必须将标记初始化为HARDWARE_DEVICE_TAG */
uint32_t tag;
/**
特定于模块的设备API的版本。 派生模块用户使用此值来管理不同的设备实现。
模块用户负责检查module_api_version和设备版本字段,以确保用户能够与特定模块实现进行通信。
一个模块可以支持具有不同版本的多个设备。 当设备接口以不兼容的方式更改但仍然需要同时支持较旧的实现时,这可能很有用。 一个这样的例子是Camera 2.0 API。
该字段由模块用户解释,并由HAL接口本身忽略。
*/
uint32_t version;
/** 参考该设备所属的模块 */
struct hw_module_t* module;
/**填充保留供将来使用 */
#ifdef __LP64__
uint64_t reserved[12];
#else
uint32_t reserved[12];
#endif
/** 关闭此设备 */
int (*close)(struct hw_device_t* device);
} hw_device_t;
同时,还有2个重要的方法
/**
通过id获取与模块关联的模块信息。
@return: 0 == success, <0 == error and *module == NULL
*/
int hw_get_module(const char *id, const struct hw_module_t **module);
/**
通过类'class_id'和实例'inst'获取与模块实例关联的模块信息。
某些模块类型需要多个实例。 例如,audio支持多个并发接口,因此'audio'是模块类,'primary'或'a2dp'是模块接口。 这意味着提供这些模块的文件将命名为audio.primary。<variant> .so和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);
看完这些,总结下,hardware.h做了些什么?
1.定义了三个重要的结构体
hw_module_t:hardware 模块结构体,包含了模块相关信息,如模块标识符,名称等。
hw_module_methods_t:hardware 模块方法结构体。即该模块拥有的方法。
hw_device_t:hardware 特定设备结构体。可以看到该结构体中包含了hw_module_t,也就是说该结构体是一个设备类型,参考它属于什么模块。
2.定义两个方法
int hw_get_module(const char *id, const struct hw_module_t **module);
int hw_get_module_by_class(const char *class_id, const char *inst,
const struct hw_module_t **module);
研究过hardware相关的,可能属性的都是hw_get_module,这个简单易懂。因为它就调用了hw_get_module_by_class。
总之这两个方法的最终目的就是找到对应的硬件模块。粗俗点就是hw_get_module查找指定模块的hw_module_t结构体。
(2)接着看到hardware/libhardware/hardware.c
该文件具体源码,感兴趣的可以自行阅读,这里只针对上面提到的两个方法进行分析,当然,方法实现用到什么在具体分析。
hw_get_module
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
int hw_get_module_by_class(const char *class_id, const char *inst,
const struct hw_module_t **module) //通过hw_get_module 传递进来的id,inst为NULL,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) //inst 为NULL
snprintf(name, PATH_MAX, "%s.%s", class_id, inst);
else
strlcpy(name, class_id, PATH_MAX); //走这 one
/*
在这里,我们依赖于在同一个.so上多次调用dlopen的事实将简单地增加引用计数(并且不加载库的新副本)。我们还假设dlopen()是线程安全的。
*/
/* 首先尝试特定于类和可能的实例的属性 */
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;
}
}
/*没有找到,请尝试默认值*/
if (hw_module_exists(path, sizeof(path), name, "default") == 0) {
goto found;
}
return -ENOENT;
found:
/* 加载模块,如果失败,我们注定要失败,我们不应该尝试加载不同的变体。*/
return load(class_id, path, module);
}
该方法看起来实现的很简单。流程如下:
1.strlcpy(name, class_id, PATH_MAX);
2.考虑三种情况,最终在hw_module_exists成功后选择一种进入load。
hw_module_exists实现很简单,这里不贴代码了,功能就是判断当前这个module是否存在。
case 1: 通过property_get获取ro.hardware.%s相关进入load
case 2:通过property_get获取variant_keys相关,这里的variant_keys在C文件中有定义。定义为这四种
static const char *variant_keys[] = {
"ro.hardware", /* 这首先是它可以在模拟器上拾取不同的文件。 */
"ro.product.board",
"ro.board.platform",
"ro.arch"
};
case 3:使用默认的方式查找对应的module,即带"default"的module。
接下来看看load的实现
/**
* 加载变量定义的文件,如果成功返回dlopen句柄和hmi。
* @return 0 = success, !0 = failure.
*/
static int load(const char *id,
const char *path,
const struct hw_module_t **pHmi) //通过hw_get_module得到的so路径 进行加载
int status = -EINVAL;
void *handle = NULL;
struct hw_module_t *hmi = NULL;
/*
在dlopen返回之前加载解析未定义符号的符号。 由于RTLD_GLOBAL未与RTLD_NOW一起使用,因此外部符号将不是全局符号
*/
handle = dlopen(path, RTLD_NOW);
if (handle == NULL) {
char const *err_str = dlerror();
ALOGE("load: module=%s\n%s", path, err_str?err_str:"unknown");
status = -EINVAL;
goto done;
}
/* 获取struct hal_module_info的地址。 */
const char *sym = HAL_MODULE_INFO_SYM_AS_STR;
hmi = (struct hw_module_t *)dlsym(handle, sym);
if (hmi == NULL) {
ALOGE("load: couldn't find symbol %s", sym);
status = -EINVAL;
goto done;
}
/*检查id是否匹配 */
if (strcmp(id, hmi->id) != 0) {
ALOGE("load: id=%s != hmi->id=%s", id, hmi->id);
status = -EINVAL;
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);
}
*pHmi = hmi;
return status;
}
可以看到load函数就做了一件简单的事–加载so【动态链接库】,其流程:dlopen dlsym dlclose。
3.总结
这里还得提一点,按照hw_get_module的做法,hardware会在如下路径中查找ID值匹配的库:
/** Base path of the hal modules */
#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
这里区分了64位的库。
lib库名有如下几种形式:(可以参考源码)
module.[ro.hardware].so
module.[ro.product.board].so
module.[ro.board.platform].so
module.[ro.arch].so
module.default.so
这里仅仅分析了hardware层中最基层的一部分,就是HAL如何对module进行加载,当然,具体的设备module的加载还需要继承hw_module_t来进行相应的实现,这个后面在分析。