Android板级支持与硬件相关子系统
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

2.4 Android的硬件抽象层

↘2.4.1 硬件抽象层的地位和功能

硬件抽象层是位于用户空间的Android系统和位于内核空间的Linux驱动程序中间的一个层次。Android中硬件抽象层的结构如图2-2所示。

图2-2 Android中硬件抽象层的结构

Android BSP典型的实现方式是构建硬件抽象层和驱动程序,硬件抽象层调用驱动程序。在这种方式中,Android系统的核心部分实际上关心的只是硬件抽象层,并不关心驱动程序。这样做的好处是将Android系统的部分功能和Linux中的驱动程序隔离,Android不依赖于Linux的驱动程序。对于同一种功能的实现,可能具有不同的驱动程序。在某些情况下,硬件抽象层是标准的,这样就只需要实现驱动程序即可。这种情况下的驱动程序,一般也是Linux中的标准的驱动程序。

↘2.4.2 硬件抽象层接口方式

1.hardware模块的方式

Android的libhardware库提供了一种不依赖编译时绑定的机制,它可以动态加载硬件抽象层,硬件模块方式的硬件抽象层架构如图2-3所示。

图2-3 硬件模块方式的硬件抽象层架构

在libhardware的接口中定义了通用的内容和各个硬件模块的id,每一个硬件模块需要被实现成所要求的接口形式,并生成单独的动态库文件(*.so)。

在使用硬件抽象层的过程中,Android系统的框架层将调用libhardware的接口,根据每一个模块的id,将在指定路径动态打开(dlopen)各个硬件模块,然后找到符号(dlsym),调用硬件模块中的各个接口。

接口的调用方式是一致的,只是在不同系统中所打开的硬件模块是不相同的。在Android系统中各种硬件模块在目标系统中路径为/system/lib/hw/。例如,gralloc.default.so表示默认的gralloc模块(用于显示),sensors.goldfish.so表示默认的传感器模块。

libhardware的接口在以下目录中定义:

    hardware/libhardware/include/hardware/hardware.h

struct hw_module_t结构体用于定义了硬件模块的格式,如下所示:

    typedef struct hw_module_t {
        uint32_t tag;                               // tag,需要被初始化为HARDWARE_MODULE_TAG
        uintl6_t version_major;                     // 主版本号
        uintl6_t version_minor;                     // 次版本号
        const char *id;                            // 模块标识
        const char *name;                          // 模块的名称
        const char *author;                        // 模块作者
        struct hw_module_methods_t* methods;       // 模块方法
        void* dso;                                 // 模块的dso
        uint32_t reserved[32-7];                    // 填充字节,为以后使用
    } hw_module_t;

struct hw_module_t结构体定义了一个硬件模块的信息。在各个具体硬件模块中,需要以这个结构体为第一个成员,即表示“继承”了这个结构体。

struct 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_module_methods_t结构体只包含了一个打开模块的函数指针,这个结构体也作为struct hw_module_t结构体的一个成员。

struct hw_device_t表示一个硬件设备,如下所示:

    typedef struct hw_device_t {
        uint32_t tag;                                  /// tag需要被初始化为HARDWARE_DEVICE_TAG
        uint32_t version;                              // hw_device_t的版本号
        struct hw_module_t* module;                   // 引用这个设备属于的硬件模块
        uint32_t reserved[l2];                         // 填充保留字节
        int (*close)(struct hw_device_t* device);    // 关闭设备
    } hw_device_t;

struct hw_device_t也是需要被具体实现的结构体包含使用的,一个硬件模块可以包含多个硬件设备。

硬件的具体调用流程如下所示。

(1)通过id得到硬件模块。

(2)从硬件模块中得到hw_module_methods_t,打开得到硬件设备hw_device_t。

(3)调用hw_device_t中的各个方法(不同模块所实现的)。

(4)调用完成,通过hw_device_t的close关闭设备。

模块打开到使用的流程如图2-4所示。

图2-4 硬件模块方式的调用方式

在以上的流程中,还需要libhareware提供一个得到模块的函数,这个函数就是hw_get_module(),如下所示:

    int hw_get_module(const char *id, const struct hw_module_t **module);

hw_get_module()函数的实现在hardware/libhardware/目录的hardware.c文件中,其内容如下所示:

    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];
        for (i=0 ; i<HAL_VARIANT_KEYS_COUNT+l ; 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_PATH, id, prop);
            } else {
              snprintf(path, sizeof(path), "%s/%s.default.so",    // 得到默认模块的名称
                        HAL_LIBRARY_PATH, id);
            }
            if (access(path, R_OK)) { continue;  }
            break;                                                // 找到模块,然后退出
        }
        status = -ENOENT;
        if (i < HAL_VARIANT_KEYS_COUNT+l) {
            status = load(id, path, module);                      // 循环打开模块的动态库
        }
        return status;
    }

由hw_get_module()函数的内容可见,执行的是一个动态查找的过程,找到硬件动态库(*.so)打开,当没有动态库的时候,将打开默认的库文件(*.default.so)。

在hw_get_module()函数中调用的load()函数,其主要内容如下所示:

    static int load(const char *id, const char *path, const struct hw_module_t **pHmi)
    {
        int status;
        void *handle;
        struct hw_module_t *hmi;
        handle = dlopen(path, RTLD_NOW);      // 进行动态库的打开
        const char *sym = HAL_MODULE_INFO_SYM_AS_STR;
        hmi = (struct hw_module_t *)dlsym(handle, sym);
        //  省略部分内容
    }

load()函数实际上执行了一个动态打开(dlopen)和动态取出符号(dlsym)的过程。这个过程解除了在编译阶段的Android本地框架对特有的硬件模块的依赖。

硬件模块的调用方式如下所示:

    xxx_module_t * gModule;
    xxx_device_t * gDevice;
    {
        xxx_module_t const* module;
        err = hw_get_module(XXXX_HARDWARE_MODULE_ID, (const hw_module_t **)&module);
        if (err == 0)
            gModule = (xxxx_module_t*)module;
        gModule->ModuleFunction();        // 调用模块的函数
        gDevice->DeviceFunction();        // 调用设备的函数
    }

通常情况下,硬件模块的调用者是Android中的本地框架层,它们调用接口定义的标准,而不是接口的实现。

libhardware的接口头文件中,除hardware.h之外,其他各个头文件是相互并列的,每一个文件表示了一种硬件抽象层。

使用hardware模块形式构建的动态库不需要被其他部分链接,而是在运行的过程中通过Linux系统常用的dlopen()和dlsym()函数动态地打开和取出符号来使用。

2.调用预定义接口的方式

调用预定义接口的方式是在头文件中定义硬件抽象层的接口,由实现者根据接口实现,这种方式实际上并没有完全将硬件抽象层和Android的本地框架分开,其好处是接口的定义和实现比较简单。

hardware_legacy库中提供了一些各自独立的接口,由用户实现后形成库,被直接链接到系统中。这是实现硬件抽象层最简单也是最直接的方式。hardware_legacy的头文件路径为:

·hardware/libhardware_legacy/include/hardware_legacy

Android中的蓝牙库bluedroid与之类似,也是采用同样的方式,其头文件的路径为:

·system/bluetooth/bluedroid/include/bluedroid/bluetooth.h

hardware_legacy库中包含了几个C接口的文件,如power、wifi、vibrator等,同时在hardware_legacy库中也包含了power、wifi、vibrator。在适配一个新的硬件系统时,可以根据需要去实现这几个库,也可以使用系统默认的实现方式。

OpenGL和电话部分也类似于直接调用接口的方式,但是它们使用的是dlopen动态打开的方式,并且可以选择使用不同的库。

3.C++的继承实现方式

使用C++类的继承方式实现硬件抽象层,也是Android中的一种方式。为了使用这种方式,Android平台定义了C++的接口。由具体的实现者继承实现这些接口,同时在Android系统中,通常也有通用的实现方式,可以作为一个简易的实现或者“桩(stub)”的作用。

使用C++类的继承方式的硬件抽象层结构如图2-5所示。

图2-5 使用C++类的继承方式的硬件抽象层结构

在这种实现方式中,具体的硬件抽象层通常要求被编译为指定名称的动态库,由本地框架库链接;通用的实现被编译成静态库(*.a),本地框架库链接这些静态库的时候,其实就是包含了它们在其中的。在某个系统中,究竟是使用特定硬件抽象层还是通用的硬件抽象层,通常需要根据编译宏来指定。

Android 2.x及之前版本的照相机和音频系统使用的是C++类的继承方式。

4.直接调用驱动

在Android中有一些比较简单的子系统,并没有在独立存在的硬件抽象层,也就是说,实现其硬件抽象功能的部分不在单独的代码中。一种常见的情况是由JNI部分代码直接调用驱动程序的设备节点或者使用sys文件系统。这种直接调用驱动的方式一般适用于比较简单的子系统。

警报器子系统在JNI代码中直接调用字符设备节点。电池信息子系统在JNI中直接访问sys文件系统。检测耳机是否插入的功能实际上也通过在JNI中访问sys文件系统完成。