Android源码分析二 硬件抽象层(HAL)

时间:2022-08-30 19:39:48

一 什么是HAL

  HAL 可定义一个标准接口以供硬件供应商实现,这可让 Android 忽略较低级别的驱动程序实现。借助 HAL,您可以顺利实现相关功能,而不会影响或更改更高级别的系统。HAL 实现会被封装成模块,并由 Android 系统适时地加载。

  硬件抽象层是介于android内核kernel和上层之间的抽象出来的一层结构。他是对linux驱动的一个封装,对上层提供统一接口,上层应用不必知道下层硬件具体怎么实现工作的,它屏蔽了底层的实现细节。

      Android源码分析二 硬件抽象层(HAL)

  您必须为您的产品所提供的特定硬件实现相应的 HAL(和驱动程序)。HAL 实现通常会内置在共享库模块(.so 文件)中,但 Android 并不要求 HAL 实现与设备驱动程序之间进行标准交互,因此您可以视情况采取适当的做法。不过,要使 Android 系统能够与您的硬件正确互动,您必须遵守各个特定于硬件的 HAL 接口中定义的合同。

  为了保证 HAL 具有可预测的结构,每个特定于硬件的 HAL 接口都要具有 hardware/libhardware/include/hardware/hardware.h 中定义的属性。这类接口可让 Android 系统以一致的方式加载 HAL 模块的正确版本。HAL 接口包含两个组件:模块和设备。

1.1 HAL 模块

  模块表示被封装且存储为共享库 (.so file) 的 HAL 实现。hardware/libhardware/include/hardware/hardware.h标头文件会定义一个表示模块的结构体 (hw_module_t),其中包含模块的版本、名称和作者等元数据。Android 会根据这些元数据来找到并正确加载 HAL 模块。

/**
* 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; //tag,根据引文注释可以看到必须被初始化为HARDWARE_MODULE_TAG /** major version number for the module */
uint16_t version_major;//主版本号 /** minor version number of the module */
uint16_t version_minor;//次版本号 /** Identifier of module */
const char *id;//模块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[-]; } hw_module_t;

  另外,hw_module_t 结构体还包含指向另一个结构体 hw_module_methods_t 的指针,后面这个结构体会包含一个指向相应模块的 open 函数的指针。此 open 函数用于与相关硬件(此 HAL 是其抽象形式)建立通信。

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,这个结构体主要是用来描述模块中硬件设备的属性信息什么的。一个硬件模块可能有多个硬件设备。

  比如说,传感器模块,sensor_module,是一个硬件模块,但是手机中的传感器就对应的有好多种,比如加速度acc_sensor,磁传感器M_sensor等,那么他们都属于sensor_module,但是他们有都有自己的hw_device_t结构体来描述。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; //设备tag /** version number for hw_device_t */
uint32_t version;//版本 /** reference to the module this device belongs to */
struct hw_module_t* module;//本设备归属的硬件模块 /** padding reserved for future use */
uint32_t reserved[];//保留 /** Close this device */
int (*close)(struct hw_device_t* device);//关闭设备的函数指针 } hw_device_t;

  第三个成员module指向的是这个设备归属的硬件模块结构体。

  每个特定于硬件的 HAL 通常都会使用附加信息为该特定硬件扩展通用的 hw_module_t 结构体。例如,在相机 HAL 中,camera_module_t 结构体会包含一个 hw_module_t 结构体以及其他特定于相机的函数指针:

typedef struct camera_module {
hw_module_t common;
int (*get_number_of_cameras)(void);
int (*get_camera_info)(int camera_id, struct camera_info *info);
} camera_module_t;

  实现 HAL 并创建模块结构体时,您必须将其命名为 HAL_MODULE_INFO_SYM。以下是 Nexus 9 音频 HAL 的示例:

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 = "NVIDIA Tegra Audio HAL",
.author = "The Android Open Source Project",
.methods = &hal_module_methods,
},
};

1.2 HAL 设备

  设备是产品硬件的抽象表示。例如,一个音频模块可能包含主音频设备、USB 音频设备或蓝牙 A2DP 音频设备。

  设备由 hw_device_t 结构体表示。与模块类似,每类设备都定义了一个通用 hw_device_t 的详细版本,其中包含指向硬件特定功能的函数指针。例如,audio_hw_device_t 结构体类型会包含指向音频设备操作的函数指针:

struct audio_hw_device {
struct hw_device_t common; /**
* used by audio flinger to enumerate what devices are supported by
* each audio_hw_device implementation.
*
* Return value is a bitmask of 1 or more values of audio_devices_t
*/
uint32_t (*get_supported_devices)(const struct audio_hw_device *dev);
...
};
typedef struct audio_hw_device audio_hw_device_t;

  除了这些标准属性之外,每个特定于硬件的 HAL 接口都可以定义更多的自有功能和要求。有关详情,请参阅 HAL 参考文档以及各 HAL 的单独说明。

1.3 编译 HAL 模块

  HAL 实现会内置在模块 (.so) 文件中,并由 Android 适时地动态链接。您可以为每个 HAL 实现创建 Android.mk 文件并指向源文件,从而编译模块。一般来说,您的共享库必须以特定格式命名,以方便找到并正确加载。各模块的命名方案略有不同,但它们都遵循以下通用模式:<module_type>.<device_name>

1.4 HAL 类型

  为了更好地实现模块化,Android 8.0 对 Android 操作系统底层进行了重新架构。作为此变化的一部分,运行 Android 8.0 的设备必须支持绑定式或直通式 HAL:

  • 绑定式 HAL。以 HAL 接口定义语言 (HIDL) 表示的 HAL。这些 HAL 取代了早期 Android 版本中使用的传统 HAL 和旧版 HAL。在绑定式 HAL 中,Android 框架和 HAL 之间通过 Binder 进程间通信 (IPC) 调用进行通信。所有在推出时即搭载了 Android 8.0 或后续版本的设备都必须只支持绑定式 HAL。
  • 直通式 HAL。以 HIDL 封装的传统 HAL 或旧版 HAL。这些 HAL 封装了现有的 HAL,可在绑定模式和 Same-Process(直通)模式下使用。升级到 Android 8.0 的设备可以使用直通式 HAL。

1.4.1  HAL 模式要求

设备 直通式 绑定式
搭载 Android 8.0 的设备 直通式 HAL 中列出的 HAL 必须为直通式。 所有其他 HAL 均为绑定式(包括作为供应商扩展程序的 HAL)。
升级到 Android 8.0 的设备 直通式 HAL 中列出的 HAL 必须为直通式。 绑定式 HAL 中列出的 HAL 必须为绑定式。
供应商映像提供的所有其他 HAL 既可以在直通模式下使用,也可以在绑定模式下使用。

1.4.2 绑定式 HAL

Android 要求所有 Android 设备(无论是搭载 Android O 的设备还是升级到 Android O 的设备)上的下列 HAL 均为绑定式:

  • android.hardware.biometrics.fingerprint@2.1。取代 Android 8.0 中已不存在的 fingerprintd
  • android.hardware.configstore@1.0。Android 8.0 中的新 HAL。
  • android.hardware.dumpstate@1.0。此 HAL 提供的原始接口可能无法继续使用,并且已更改。因此,dumpstate_board 必须在指定的设备上重新实现(这是一个可选的 HAL)。
  • android.hardware.graphics.allocator@2.0。在 Android 8.0 中,此 HAL 必须为绑定式,因此无需在可信进程和不可信进程之间分享文件描述符。
  • android.hardware.radio@1.0。取代由存活于自身进程中的 rild 提供的接口。
  • android.hardware.usb@1.0。Android 8.0 中的新 HAL。
  • android.hardware.wifi@1.0。Android 8.0 中的新 HAL,可取代此前加载到 system_server 的旧版 WLAN HAL 库。
  • android.hardware.wifi.supplicant@1.0。在现有 wpa_supplicant 进程之上的 HIDL 接口

注意:Android 提供的以下 HIDL 接口将一律在绑定模式下使用:android.frameworks.*android.system.* 和 android.hidl.*(不包括下文所述的 android.hidl.memory@1.0)。

1.4.3 直通式 HAL

Android 要求所有 Android 设备(无论是搭载 Android O 的设备还是升级到 Android O 的设备)上的下列 HAL 均在直通模式下使用:

  • android.hardware.graphics.mapper@1.0。将内存映射到其所属的进程中。
  • android.hardware.renderscript@1.0。在同一进程中传递项(等同于 openGL)。

上方未列出的所有 HAL 在搭载 Android O 的设备上都必须为绑定式。

Same-Process HAL

Same-Process HAL (SP-HAL) 一律在使用它们的进程中打开,其中包括未以 HIDL 表示的所有 HAL,以及那些非绑定式的 HAL。SP-HAL 集的成员只能由 Google 控制,这一点没有例外。

SP-HAL 包括以下 HAL:

  • openGL
  • Vulkan
  • android.hidl.memory@1.0(由 Android 系统提供,一律为直通式)
  • android.hardware.graphics.mapper@1.0
  • android.hardware.renderscript@1.0

1.5 传统 HAL 和旧版 HAL

  传统 HAL(在 Android 8.0 中已弃用)是指与具有特定名称及版本号的应用二进制接口 (ABI) 标准相符的接口。大部分 Android 系统接口(相机音频传感器等)都采用传统 HAL 形式(已在 hardware/libhardware/include/hardware 下进行定义)。

  旧版 HAL(也已在 Android 8.0 中弃用)是指早于传统 HAL 的接口。一些重要的子系统(WLAN、无线接口层和蓝牙)采用的就是旧版 HAL。虽然没有统一或标准化的方式来指明是否为旧版 HAL,但如果 HAL 早于 Android 8.0 而出现,那么这种 HAL 如果不是传统 HAL,就是旧版 HAL。有些旧版 HAL 的一部分包含在 libhardware_legacy 中,而其他部分则分散在整个代码库中。

二、操作硬件

  HAL层的主要的两个结构体hw_module_t(硬件模块)和hw_device_t(硬件设备)的成员,下面我们来具体看看上层app到底是怎么实现操作硬件的?

  一些硬件厂商不愿意将自己的一些核心代码开放出去,所以将这些代码放到HAL层,但是怎么保证它不开放呢?HAL层代码不是也让大家知道下载吗?其实硬件厂商的HAL核心代码是以共享库的形式出现的,每次在需要的时候,hal会自动加载调用相关共享库。那么是怎么加载找到某一硬件设备对应的共享库的呢?这也是我们这篇都要说的。

  上层app通过jni调用hal层的hw_get_module函数获取硬件模块,这个函数是上层与hal打交道的入口。所以如果我们以程序调用执行的流程去看源码的话,这个函数就是hal层第一个被调用的函数,下面我们就从这个函数开始,沿着程序执行的流程走下去。

  hw_get_module函数定义在/hardware/libhardware/hardware.c中,打开这个文件可以看到定义如下:

/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/ #include <hardware/hardware.h> #include <cutils/properties.h> #include <dlfcn.h>
#include <string.h>
#include <pthread.h>
#include <errno.h>
#include <limits.h> #define LOG_TAG "HAL"
#include <utils/Log.h> /** 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 /**
* 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[])); /**
* 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
*/
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;
} /* 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) {
ALOGE("load: couldn't find symbol %s", sym);
status = -EINVAL;
goto done;
} /* Check that the id matches */
if (strcmp(id, hmi->id) != ) {
ALOGE("load: id=%s != hmi->id=%s", id, hmi->id);
status = -EINVAL;
goto done;
} hmi->dso = handle; /* success */
status = ; done:
if (status != ) {
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;
} /*
* 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)
{
snprintf(path, path_len, "%s/%s.%s.so",
HAL_LIBRARY_PATH2, name, subname);
if (access(path, R_OK) == )
return ; snprintf(path, path_len, "%s/%s.%s.so",
HAL_LIBRARY_PATH1, name, subname);
if (access(path, R_OK) == )
return ; return -ENOENT;
} int hw_get_module_by_class(const char *class_id, const char *inst,
const struct hw_module_t **module)
{
int i = ;
char prop[PATH_MAX] = {};
char path[PATH_MAX] = {};
char name[PATH_MAX] = {};
char prop_name[PATH_MAX] = {}; 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 */
snprintf(prop_name, sizeof(prop_name), "ro.hardware.%s", name);
if (property_get(prop_name, prop, NULL) > ) {
if (hw_module_exists(path, sizeof(path), name, prop) == ) {
goto found;
}
} /* Loop through the configuration variants looking for a module */
for (i= ; i<HAL_VARIANT_KEYS_COUNT; i++) {
if (property_get(variant_keys[i], prop, NULL) == ) {
continue;
}
if (hw_module_exists(path, sizeof(path), name, prop) == ) {
goto found;
}
} /* Nothing found, try the default */
if (hw_module_exists(path, sizeof(path), name, "default") == ) {
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);
} int hw_get_module(const char *id, const struct hw_module_t **module)
{
return hw_get_module_by_class(id, NULL, module);
}

  在dlopen的()函数以指定模式打开指定的动态连接库文件,并返回一个句柄给调用进程。使用dlclose()来卸载打开的库。

hw_get_module

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= ; i<HAL_VARIANT_KEYS_COUNT+ ; i++) {
if (i < HAL_VARIANT_KEYS_COUNT) {
if (property_get(variant_keys[i], prop, NULL) == ) {//获取属性
continue;
}
snprintf(path, sizeof(path), "%s/%s.%s.so",
HAL_LIBRARY_PATH1, id, prop);
if (access(path, R_OK) == ) break;//检查system路径是否有库文件 snprintf(path, sizeof(path), "%s/%s.%s.so",
HAL_LIBRARY_PATH2, id, prop);
if (access(path, R_OK) == ) break;//检查vender路径是否有库文件
} else {
snprintf(path, sizeof(path), "%s/%s.default.so",//如果都没有,则使用缺省的
HAL_LIBRARY_PATH1, id);
if (access(path, R_OK) == ) break;
}
} status = -ENOENT;
if (i < HAL_VARIANT_KEYS_COUNT+) {
/* load the module, if this fails, we're doomed, and we should not try
* to load a different variant. */
status = load(id, path, module);//装载库,得到module
} return status;
}

  snprintf(),函数原型为int snprintf(char *str, size_t size, const char *format, ...)。将可变参数 “…” 按照format的格式格式化为字符串,然后再将其拷贝至str中。

access(filename, 0) 表示判断文件是否存在 access 返回值是0的时候,表示存在,而返回-1的时候,表示失败。

  property_set/property_get位于libcutils.so库。任何进程若要调用这两个函数,需要链接libcutils.so(https://blog.csdn.net/decisiveness/article/details/49852295)。

  看第一行我们知道有两个参数,第一参数id就是要获取的硬件模块的id,第二个参数module就是我们想得到的硬件模块结构体的指针。所以可以看出,上层首先给hal需要获取的硬件模块的id,hw_get_module函数根据这个id去查找匹配和这个id对应的硬件模块结构体的。

  下面看看怎么找的:

  17行有个for循环,上限是HAL_VARIANT_KEYS_COUNT+1,那么这个HAL_VARIANT_KEYS_COUNT是什么呢?查看同文件下找到有:

static const int HAL_VARIANT_KEYS_COUNT = (sizeof(variant_keys)/sizeof(variant_keys[0]));

  原来它是ariant_keys这个数组的元素个数。那么这个数组又是什么呢?在本文件找,有:

/**
* 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"
};

  可以看到它其实是个字符串数组。暂且不知道干什么的。继续看hw_get_module函数,进入for循环里面,看22行,其实它是将HAL_LIBRARY_PATH1, id, prop这三个串拼凑一个路径出来,

HAL_LIBRARY_PATH1定义如下:

/** Base path of the hal modules */
#define HAL_LIBRARY_PATH1 "/system/lib/hw"
#define HAL_LIBRARY_PATH2 "/vendor/lib/hw"

  id是上层提供的,prop这个变量的值是前面19行property_get(variant_keys[i], prop, NULL)函数获取到的,其实这个函数是通过ariant_keys数组的的属性查找到系统中对应的变种名称。不同的平台获取到prop值是不一样的。

  假如在获取到的prop值是tout,需要获取的硬件模块的id是leds,那么最后path组成的串是/system/lib/hw/leds.tout.so。后面24行access是检查这个路径下是否存在,如果有就break,跳出循环。如果没有,继续走下面,

  可以看到下面几行和刚才形式差不多,

snprintf(path, sizeof(path), "%s/%s.%s.so",  HAL_LIBRARY_PATH2, id, prop);

if (access(path, R_OK) == 0) break;//检查vender路径是否有库文件

  结合 HAL_LIBRARY_PATH2 为"/vendor/lib/hw",假设同样获取到的prop值是tout,需要获取的硬件模块的id是leds,这种情况下path拼出来的值是/vender/lib/hw/leds.tout.so,然后在判断文件是否存在。如果存在跳出循环。

  从以上分析,其实这就是hal层搜索动态共享库的方式,从中我们可以得到两点:

  • 1.动态共享库一般放在 "/system/lib/hw"和"/vendor/lib/hw"这两个路径下。
  • 2.动态库的名称是以"id.variant.so"的形式命名的,其中id为上层提供,中间variant为变种名称,是随系统平台变化的。

接着,从29到32行我们可以看到,当所有变种名称形式的包都不存在时,就以"id.default.so"形式包名查找是否存在。

37行, if (i < HAL_VARIANT_KEYS_COUNT+1),如果i小于变种名称数组的话,表示找到了对应的库,那么38行load(id, path, module);//装载库,得到module。

三 如何实现加载共享库

  以下为load函数定义,同样在/hardware/libhardware/hardware.c中实现的。

/**
* 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)
{//传入硬件模块id和库所在路径,获取到硬件模块结构体
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) != ) {//匹配解析出硬件模块的id和传入我们实际想要得到的模块id是否一致
LOGE("load: id=%s != hmi->id=%s", id, hmi->id);
status = -EINVAL;
goto done;
} hmi->dso = handle; //将打开库得到句柄传给硬件模块的dso /* success */
status = ; done:
if (status != ) {
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;//将得到的module的结果通过第三个参数传给hw_module_t return status;
}

Linux提供了一套API来动态装载库。下面列出了这些API:

- dlopen,打开一个库,并为使用该库做些准备。
- dlsym,在打开的库中查找符号的值。
- dlclose,关闭库。
- dlerror,返回一个描述最后一次调用dlopen、dlsym,或dlclose的错误信息的字符串。

C语言用户需要包含头文件dlfcn.h才能使用上述API。glibc还增加了两个POSIX标准中没有的API:
- dladdr,从函数指针解析符号名称和所在的文件。
- dlvsym,与dlsym类似,只是多了一个版本字符串参数。

  可以看到load函数传入的几个参数:

  第一个参数就是需要加载的硬件模块对应动态库的硬件模块的id,

  第二个参数就是动态库存放的路径,就是在hw_get_module函数前部分搜索库得到的path,

  第三个参数就是我们需要得到的硬件模块结构体,通过它传给hw_get_module,hw_get_module函数在通过参数传给jni。

  第19行,首先调用dlopen打开共享库,该函数通过传入的库的路径找到库,并且打开它,传回一个操作句柄handle,然后再调用dlsym函数解析这个打开的库,下面第29行,得到库中包含的硬件模块结构体,并将它返回回来。所以硬件厂商或者硬件移植者都必须根据hal的这个架构去实现填充这个和自己硬件相关的硬件模块结构体hw_module_t,供使用。

  通过dlsym解析之后就得到了hw_module_t,随后第37行,将从库中解析得到的结构体中的id和传入的id做比较,看是否一致。如果一致则证明就是得到正确的硬件模块了。最后第60行,将hw_module_t结构体指针传给第三个参数,传给hw_get_module函数。

  到此,hw_get_module函数就得到了硬件模块结构体hw_module_t.有了hw_module_t,那么通过其内部的method open就能打开硬件模块对应的设备了,通过结构体中的一些方法就能操作硬件设备了。

四 实践

见番外篇

参考:

https://www.cnblogs.com/y041039/archive/2013/05/22/3092774.html

https://www.cnblogs.com/y041039/archive/2013/05/22/3092868.html

https://www.cnblogs.com/y041039/archive/2013/05/23/3094579.html

https://www.cnblogs.com/y041039/archive/2013/05/23/3094928.html

https://www.cnblogs.com/y041039/archive/2013/05/23/3095621.html

https://www.linuxidc.com/Linux/2011-07/38983.htm

进一步了解  Linux内核 -> HAL -> JNI -> framework -> app:

别人的实例

Android硬件抽象层(HAL)概要介绍和学习计划

Android之 看“马达”如何贯通Android系统 (从硬件设计 --> 驱动 --> HAL --> JNI --> Framework --> Application)

Android应用层到Framework到HAL再到驱动层的整个流程分析

Android源码分析二 硬件抽象层(HAL)的更多相关文章

  1. Android源码分析(十二)-----Android源码中如何自定义TextView实现滚动效果

    一:如何自定义TextView实现滚动效果 继承TextView基类 重写构造方法 修改isFocused()方法,获取焦点. /* * Copyright (C) 2015 The Android ...

  2. Android源码分析(二)-----如何编译修改后的framework资源文件

    一 : 编译framework资源文件 如果修改android framework资源文件,需要先编译资源文件,然后再编译framework才可以正常引用, 进入项目目录 cd work/source ...

  3. Fresco 源码分析&lpar;二&rpar; Fresco客户端与服务端交互&lpar;1&rpar; 解决遗留的Q1问题

    4.2 Fresco客户端与服务端的交互(一) 解决Q1问题 从这篇博客开始,我们开始讨论客户端与服务端是如何交互的,这个交互的入口,我们从Q1问题入手(博客按照这样的问题入手,是因为当时我也是从这里 ...

  4. Android源码分析(六)-----蓝牙Bluetooth源码目录分析

    一 :Bluetooth 的设置应用 packages\apps\Settings\src\com\android\settings\bluetooth* 蓝牙设置应用及设置参数,蓝牙状态,蓝牙设备等 ...

  5. Android源码分析(十七)----init&period;rc文件添加脚本代码

    一:init.rc文件修改 开机后运行一次: chmod 777 /system/bin/bt_config.sh service bt_config /system/bin/bt_config.sh ...

  6. Android源码分析(十六)----adb shell 命令进行OTA升级

    一: 进入shell命令界面 adb shell 二:创建目录/cache/recovery mkdir /cache/recovery 如果系统中已有此目录,则会提示已存在. 三: 修改文件夹权限 ...

  7. Android源码分析(十五)----GPS冷启动实现原理分析

    一:原理分析 主要sendExtraCommand方法中传递两个参数, 根据如下源码可以知道第一个参数传递delete_aiding_data,第二个参数传递null即可. @Override pub ...

  8. Android源码分析(十四)----如何使用SharedPreferencce保存数据

    一:SharedPreference如何使用 此文章只是提供一种数据保存的方式, 具体使用场景请根据需求情况自行调整. EditText添加saveData点击事件, 保存数据. diff --git ...

  9. Android源码分析(十三)----SystemUI下拉状态栏如何添加快捷开关

    一:如何添加快捷开关 源码路径:frameworks/base/packages/SystemUI/res/values/config.xml 添加headset快捷开关,参考如下修改. Index: ...

随机推荐

  1. CSS3与页面布局学习笔记(三)——BFC、定位、浮动、7种垂直居中方法

    一.BFC与IFC 1.1.BFC与IFC概要 BFC(Block Formatting Context)即“块级格式化上下文”, IFC(Inline Formatting Context)即行内格 ...

  2. git 使用笔记&lpar;一&rpar;

    1. 环境介绍 windows10 2.使用 2.1 安装git for windows 2.2 创建一个文件夹, 开始git管理 2.3 查看该目录,包括隐藏文件 2.4 把testgit.txt添 ...

  3. BZOJ 4390 Max Flow

    同运输计划. #include<iostream> #include<cstdio> #include<cstring> #include<algorithm ...

  4. &lbrack;置顶&rsqb; 【SQL】查询重复人名的次数并列出

    select count(姓名) as 重复次数,姓名from 某表 group by 姓名order by 重复次数 asc 首先,group by 姓名,可以将所有相同姓名的项集合在一起.然后,c ...

  5. &lbrack;转&rsqb;ActiveMQ 即时通讯服务 浅析

    一. 概述与介绍 ActiveMQ 是Apache出品,最流行的.功能强大的即时通讯和集成模式的开源服务器.ActiveMQ 是一个完全支持JMS1.1和J2EE 1.4规范的 JMS Provide ...

  6. Canny 边缘检测及相关应用

    该内容为 <学习图像局部特征检测和描述-基于OpenCV源码分析的算法与实现> <OpenCV 3 计算机视觉-Python语言实现> OpenCV官方网站的 https:// ...

  7. 终极C语言指针

    // ex1.cpp : Defines the entry point for the console application. // #include "stdafx.h" # ...

  8. unigui web app之title buttons

    unigui web app之title buttons title buttons:窗体标题按钮 通过 窗体标题按钮,可以方便地导航,比如返回主页. 处理窗体标题按钮的点击事件: procedure ...

  9. Tensorflow图像操作

    图像操作 图像基本概念 在图像数字化表示当中,分为黑白和彩色两种.在数字化表示图片的时候,有三个因素.分别是图片的长.图片的宽.图片的颜色通道数.那么黑白图片的颜色通道数为1,它只需要一个数字就可以表 ...

  10. rac数据库单连接报错ora-12537解决办法

    1.现象如下: C:\Users\Administrator.DBA-PC>sqlplus sys/oracle@192.168.100.33:1521/orcl as sys dba SQL* ...