ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
学习Android硬件抽象层模块的加载过程有助于理解它的编写规范以及实现原理。Android系统中的硬件抽象层模块是由系统统一加载的,当调用者需要加载这些模块时,只要指定它们的ID值就可以了。 在Android硬件抽象层中,负责加载硬件抽象层模块的函数是hw_get_module,它的原型如下: **hardware/libhardware/include/hardware/hardware.h** ~~~ /** * Get the module info associated with a module by id. * @return: 0 == success, <0 == error and *pHmi == NULL */ int hw_get_module(const char *id, const struct hw_module_t **module); ~~~ 它有id和module两个参数。其中,id是输入参数,表示要加载的硬件抽象层模块ID;module是输出参数,如果加载成功,那么它指向一个自定义的硬件抽象层模块结构体。函数的返回值是一个整数,如果等于0,则表示加载成功;如果小于0,则表示加载失败。 下面我们就开始分析hw_get_module函数的实现。 **hardware/libhardware/hardware.c** ~~~ /** Base path of the hal modules */ #define HAL_LIBRARY_PATH1 "/system/lib/hw" #define HAL_LIBRARY_PATH2 "/vendor/lib/hw" 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])); 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]; /* * 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. */ /* Loop through the configuration variants looking for a module */ for (i=0 ; i<HAL_VARIANT_KEYS_COUNT+1 ; 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_PATH1, id, prop); if (access(path, R_OK) == 0) break; snprintf(path, sizeof(path), "%s/%s.%s.so", HAL_LIBRARY_PATH2, id, prop); if (access(path, R_OK) == 0) break; } else { snprintf(path, sizeof(path), "%s/%s.default.so", HAL_LIBRARY_PATH1, id); if (access(path, R_OK) == 0) break; } } status = -ENOENT; if (i < HAL_VARIANT_KEYS_COUNT+1) { /* load the module, if this fails, we're doomed, and we should not try * to load a different variant. */ status = load(id, path, module); } return status; } ~~~ 在前面的2.3.1.1小节中,我们已经见过数组variant_keys的定义了,它用来组装要加载的硬件抽象层模块的文件名称。常量HAL_VARIANT_KEYS_COUNT表示数组variant_keys的大小。 宏HAL_LIBRARY_PATH1和HAL_LIBRARY_PATH2用来定义要加载的硬件抽象层模块文件所在的目录。在前面的2.3.2小节中提到,编译好的模块文件位于out/target/product/generic/system/lib/hw目录中,而这个目录经过打包后,就对应于设备上的/system/lib/hw目录。宏HAL_LIBRARY_PATH2所定义的目录为/vendor/lib/hw,用来保存设备厂商所提供的硬件抽象层模块接口文件。 函数第32行到第50行的for循环根据数组variant_keys在HAL_LIBRARY_PATH1和HAL_LIBRARY_PATH2目录中检查对应的硬件抽象层模块文件是否存在,如果存在,则结束for循环;第56行调用load函数来执行加载硬件抽象层模块的操作。 我们以在Android模块器中加载硬件抽象层模块freg为例,来分析硬件抽象层模块的加载过程。在调用hw_get_module函数加载硬件抽象层模块freg时,传入的参数id的值为FREG_HARDWARE_MODULE_ID,即“freg”。在上面的for循环中,首先找到通过property_get函数获得的系统属性“ro.hardware”的值。在Android模拟器中,这个属性的值定义为“goldfish”,于是通过第38和39两行的snprintf函数,就得到变量path的值为“/system/lib/hw/freg.goldfish.so”。第40行调用access函数判断文件/system/lib/hw/freg.goldfish.so是否存在,如果存在,就跳出循环;否则,再通过第42行到第44行的代码来判断文件/vendor/lib/hw/freg.goldfish.so是否存在,如果存在,那么也会跳出循环,因为要加载的硬件抽象层模块文件已经找到了。如果这两个文件都不存在,那么按照相同的方法来依次查找数组variant_keys中其他元素所对应的硬件抽象层模块文件是否存在。如果数组variant_keys中的所有元素对应的硬件抽象层模块文件都不存在,那么第46行到第48行的代码就会在/system/lib/hw目录中检查是否存在一个freg.default.so文件。如果也不存在,那么硬件抽象层模块freg的加载就失败了。 找到了硬件抽象层模块文件之后,第56行就调用load函数来执行硬件抽象层模块的加载操作,它的实现如下所示。 **hardware/libhardware/hardware.c** ~~~ static int load(const char *id, const char *path, const struct hw_module_t **pHmi) { int status; void *handle; struct hw_module_t *hmi; /* * 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 */ handle = dlopen(path, RTLD_NOW); if (handle == NULL) { char const *err_str = dlerror(); LOGE("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 = (struct hw_module_t *)dlsym(handle, sym); if (hmi == NULL) { LOGE("load: couldn't find symbol %s", sym); status = -EINVAL; goto done; } /* Check that the id matches */ if (strcmp(id, hmi->id) != 0) { LOGE("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 { LOGV("loaded HAL id=%s path=%s hmi=%p handle=%p", id, path, *pHmi, handle); } *pHmi = hmi; return status; } ~~~ 前面提到,硬件抽象层模块文件实际上是一个动态链接库文件,即so文件。因此,第14行调用dlopen函数将它加载到内存中。加载完成这个动态链接库文件之后,第24行就调用dlsym函数来获得里面名称为HAL_MODULE_INFO_SYM_AS_STR的符号。这个HAL_MODULE_INFO_SYM_AS_STR符号指向的是一个自定义的硬件抽象层模块结构体,它包含了对应的硬件抽象层模块的所有信息。HAL_MODULE_INFO_SYM_AS_STR是一个宏,它的值定义为“HMI”。根据硬件抽象层模块的编写规范,每一个硬件抽象层模块都必须包含一个名称为“HMI”的符号,而且这个符号的第一个成员变量的类型必须定义为hw_module_t,因此,第24行可以安全地将模块中的HMI符号转换为一个hw_module_t结构体指针。 获得了这个hw_module_t结构体指针之后,第32行调用strcmp函数来验证加载得到的硬件抽象层模块ID是否与所要求加载的硬件抽象层模块ID一致。如果不一致,就说明出错了,函数返回一个错误值-EINVAL。最后,第38行将成功加载后得到的模块句柄值handle保存在hw_module_t结构体指针hmi的成员变量dso中,然后将它返回给调用者。 至此,硬件抽象层模块的加载过程就介绍完了。接下来,我们分析在硬件抽象层模块的加载过程中,经常会碰到的一个权限问题,即无法调用open函数打开对应的设备文件。