4-安卓底层-HAL层-实现安卓控制LED

Android硬件抽象层(HAL)

Android的硬件抽象层,简单来说,就是对Linux内核驱动程序的封装,向上提供接口,屏蔽低层的实现细节。也就是说,把对硬件的支持分成了两层,一层放在用户空间(User Space),一层放在内核空间(Kernel Space),其中,硬件抽象层运行在用户空间,而Linux内核驱动程序运行在内核空间。为什么要这样安排呢?把硬件抽象层和内核驱动整合在一起放在内核空间不可行吗?从技术实现的角度来看,是可以的,然而从商业的角度来看,把对硬件的支持逻辑都放在内核空间,可能会损害厂家的利益。我们知道,Linux内核源代码版权遵循GNU License,而Android源代码版权遵循Apache License,前者在发布产品时,必须公布源代码,而后者无须发布源代码。如果把对硬件支持的所有代码都放在Linux驱动层,那就意味着发布时要公开驱动程序的源代码,而公开源代码就意味着把硬件的相关参数和实现都公开了,在手机市场竞争激烈的今天,这对厂家来说,损害是非常大的。因此,Android才会想到把对硬件的支持分成硬件抽象层和内核驱动层,内核驱动层只提供简单的访问硬件逻辑,例如读写硬件寄存器的通道,至于从硬件中读到了什么值或者写了什么值到硬件中的逻辑,都放在硬件抽象层中去了,这样就可以把商业秘密隐藏起来了。也正是由于这个分层的原因,Android被踢出了Linux内核主线代码树中。大家想想,Android放在内核空间的驱动程序对硬件的支持是不完整的,把Linux内核移植到别的机器上去时,由于缺乏硬件抽象层的支持,硬件就完全不能用了,这也是为什么说Android是开放系统而不是开源系统的原因。
下面这个图阐述了硬件抽象层在Android系统中的位置,以及它和其它层的关系:
0_1308977488PkP8.gif

重要的三个结构体:

hw_module_t
86 typedef struct hw_module_t {    //声明模块                                                                            
87     /** tag must be initialized to HARDWARE_MODULE_TAG */
88     uint32_t tag;

132     /** Identifier of module */
133     const char *id;
134 
135     /** Name of this module */
136     const char *name;
137                                                                                                             
138     /** Author/owner/implementor of the module */
139     const char *author;
140 
141     /** Modules methods */
142     struct hw_module_methods_t* methods;
143      uint16_t module_api_version
144     
145     void* dso;
146 
147 #ifdef __LP64__
148     uint64_t reserved[32-7];
149 #else
150     /** padding to 128 bytes, reserved for future use */
151     uint32_t reserved[32-7];
152 #endif
153 
154 } hw_module_t;
hw_module_methods_t
156 typedef struct hw_module_methods_t {
157     /** Open a specific device */
158     int (*open)(const struct hw_module_t* module, const char* id,
159             struct hw_device_t** device);
160 
161 } hw_module_methods_t;
hw_device_t
typedef struct hw_device_t {
168     /** tag must be initialized to HARDWARE_DEVICE_TAG */
169     uint32_t tag;
                                                                                                    
189     /** reference to the module this device belongs to */
190     struct hw_module_t* module;
            
187     uint32_t version;
188 
189     /** reference to the module this device belongs to */
190     struct hw_module_t* module;
191 
192     /** padding reserved for future use */
193 #ifdef __LP64__
194     uint64_t reserved[12];
195 #else
196     uint32_t reserved[12];
197 #endif
198 
199     /** Close this device */
200     int (*close)(struct hw_device_t* device);
201 
202 } hw_device_t;

重要的两个函数:

/**
 * Get the module info associated with a module by id.
 *
 * @return: 0 == success, <0 == error and *module == NULL
 */
int hw_get_module(const char *id, const struct hw_module_t **module)


/**
 * Get the module info associated with a module instance by class 'class_id'
 * and instance 'inst'.
 *
 * Some modules types necessitate multiple instances. For example audio supports
 * multiple concurrent interfaces and thus 'audio' is the module class
 * and 'primary' or 'a2dp' are module interfaces. This implies that the files
 * providing these modules would be named audio.primary.<variant>.so and
 * 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);

dlopen 函数 :
  • 包含头文件:#include <dlfcn.h>
  • 函数定义: void * dlopen( const char * pathname, int mode);
  • 函数描述:
    • mode是打开方式,其值有多个,不同操作系统上实现的功能有所不同,在linux下,按功能可分为三类:
      1、解析方式
      RTLD_LAZY:在dlopen返回前,对于动态库中的未定义的符号不执行解析(只对函数引用有效,对于变量引用总是立即解析)。
      RTLD_NOW: 需要在dlopen返回前,解析出所有未定义符号,如果解析不出来,在dlopen会返回NULL,错误为:: undefined symbol: xxxx.......
      2、作用范围,可与解析方式通过“|”组合使用。
      RTLD_GLOBAL:动态库中定义的符号可被其后打开的其它库解析。
      RTLD_LOCAL: 与RTLD_GLOBAL作用相反,动态库中定义的符号不能被其后打开的其它库重定位。如果没有指明是RTLD_GLOBAL还是RTLD_LOCAL,则缺省为RTLD_LOCAL。
      3、作用方式
      RTLD_NODELETE: 在dlclose()期间不卸载库,并且在以后使用dlopen()重新加载库时不初始化库中的静态变量。这个flag不是POSIX-2001标准。
      RTLD_NOLOAD: 不加载库。可用于测试库是否已加载(dlopen()返回NULL说明未加载,否则说明已加载),也可用于改变已加载库的flag,如:先前加载库的flag为RTLD_LOCAL,用dlopen(RTLD_NOLOAD|RTLD_GLOBAL)后flag将变成RTLD_GLOBAL。这个flag不是POSIX-2001标准。
      RTLD_DEEPBIND:在搜索全局符号前先搜索库内的符号,避免同名符号的冲突。这个flag不是POSIX-2001标准。
      返回值:
      打开错误返回NULL
      成功,返回库引用
      编译时候要加入 -ldl (指定dl库)
      例如
      gcc test.c -o test -ldl

实现安卓控制LED

1. 在 hardware/libhardware/include/hardware 下新建ibo_hal.h文件
ibo_hal.h
#ifndef ANDROID_LED_INTERFACE_H
#define ANDROID_LED_INTERFACE_H

#include <stdint.h>
#include <sys/cdefs.h>
#include <sys/types.h>

#include <hardware/hardware.h>

__BEGIN_DECLS

struct iboled_device_t {
    struct hw_device_t common;
    int (*ibo_open)(struct iboled_device_t* dev);
    int (*ibo_ctrl)(struct iboled_device_t* dev, int cmd);
};

__END_DECLS

#endif 
2. 在 hardware/libhardware/modules目录下 新建 iboled 文件夹
3. 在 iboled 文件夹下新建 ibo_hal.c 和 Android.mk 两个文件
ibo_hal.c
#define LOG_TAG "IboHal"

#include <hardware/vibrator.h>
#include <hardware/hardware.h>
#include <cutils/log.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <utils/Log.h>

#include <hardware/ibo_hal.h>

#define LED_ON _IO('k',1)
#define LED_OFF _IO('k',0)
static int fd;

/** Close this device */
static int ibo_close(struct hw_device_t* device)
{
    close(fd);
    ALOGI("ibo_close: %d",fd);
    return 0;
}

static int ibo_open(struct iboled_device_t* dev)
{
    fd = open("/dev/led", O_RDWR);
    ALOGI("ibo_open : %d", fd);
        return fd ;
}

static int ibo_ctrl(struct iboled_device_t* dev, int cmd)
{
    if(cmd == 0)
        ioctl(fd,LED_OFF);
    else
        ioctl(fd,LED_ON);

    ALOGI("ibo_ctrl: %d",cmd);
    return 0;
}

static struct iboled_device_t ibo_dev = {
    .common = {
        .close = ibo_close,
    },
    .ibo_open  = ibo_open,
    .ibo_ctrl  = ibo_ctrl,
};

static int iboled_device_open(const struct hw_module_t* module, const char* id,
        struct hw_device_t** device)
{
    *device =(hw_device_t*) &ibo_dev;
    return 0;
}


static struct hw_module_methods_t led_module_methods = {
    .open = iboled_device_open,
};

struct hw_module_t HAL_MODULE_INFO_SYM = {
    .id = "iboled",
    .methods = &led_module_methods,
};
Android.mk
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := iboled.default

LOCAL_MODULE_RELATIVE_PATH := hw
LOCAL_C_INCLUDES := hardware/libhardware
LOCAL_SRC_FILES := ibo_hal.c
LOCAL_SHARED_LIBRARIES := liblog
LOCAL_MODULE_TAGS := eng
LOCAL_MODULE_PATH:= $(LOCAL_PATH)
include $(BUILD_SHARED_LIBRARY)
4.在 iboled 文件夹下输入 mm 命令 , hw 文件夹下生成 iboled.default.so 文件
5. 把 iboled.default.so 文件拷贝到 fastboot 根目录
6.输入adb push iboled.default.so /system/lib/hw命令把文件传到平板的 /system/lib/hw 文件夹下,hal层的代码完成了, 现在去写jni 。
7.在 androidL 目录下新建 testhal 文件夹
8.testhal 文件夹下新建Android.mk文件
Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE:= libiboled
LOCAL_SRC_FILES:= src/iboled.c
LOCAL_MODULE_TAGS:= eng
LOCAL_SHARED_LIBRARIES:= liblog libhardware 
LOCAL_MODULE_PATH:= $(LOCAL_PATH)/lib
LOCAL_C_INCLUDES:= $(LOCAL_PATH)/inc
include $(BUILD_SHARED_LIBRARY)
9.在testhal 文件夹下新建src文件夹 , src 文件夹下新建 iboled.c文件。
iboled.c
#include <jni.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <android/log.h>
#include <hardware/hardware.h>
#include <utils/Log.h>
#include <hardware/ibo_hal.h>

struct iboled_device_t *iboled_device;

jint Jopen(JNIEnv *env,jobject obj){
    jint err;
    hw_module_t* module;
    hw_device_t* device;

    ALOGI("ibo ledOpen ...");

    err = hw_get_module("iboled", (hw_module_t const**)&module);
    if (err == 0) {
        err = module->methods->open(module, NULL, &device);
        if (err == 0) {
            iboled_device = (struct led_device_t *)device;
            return iboled_device->ibo_open(iboled_device);
        }
    }
    return -1;  
}

void Jclose(JNIEnv *env,jobject obj){
    ALOGI("ibo Jclose ...");
    iboled_device -> common.close((hw_device_t *)iboled_device);
    return ;
}

void  Jioctl(JNIEnv *env,jobject obj,jint cmd){
    ALOGI("ibo Jioctl ...");
    iboled_device -> ibo_ctrl(iboled_device,cmd);
    return ;
}

JNINativeMethod method[] = {
    {(char *)"ibo_open",(char *)"()I",(void *)Jopen},
    {(char *)"ibo_close",(char *)"()V",(void *)Jclose},
    {(char *)"ibo_ioctl",(char *)"(I)V",(void *)Jioctl},
};

JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *jvm, void *reserved)
{
    JNIEnv *env;
    jclass cls;

    if ((*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_2)) {
        return JNI_ERR; /* JNI version not supported */
    }
    ALOGI("ibo JNI_OnLoad ...");
    cls = (*env)->FindClass(env, "com/ibo/jnidemo/TestJni"); //注意这里的包名换成自己app的包名。
    if (cls == NULL) {
        return JNI_ERR;
    }

    (*env) -> RegisterNatives(env,cls,method,sizeof(method)/sizeof(JNINativeMethod));
    return JNI_VERSION_1_2;
}
10.在testhal 文件夹下输入 mm 命令,testhal/lib 目录下生成了 libiboled.so 文件。
11.输入adb push libiboled.so /system/lib/命令把文件传到平板的 /system/lib/ 目录下 。
12.打开eclipse新建android项目,新建一个类.
TestJni.java
class TestJni{
    static{
        System.loadLibrary("iboled");
    }

    public native int ibo_open();
    public native void ibo_close();
    public native void ibo_ioctl(int cmd);
}
13.MainActivity :
MainActivity.java
public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private Button btn;
    boolean isLight = false;
    private TestJni ibo;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ibo = new TestJni();
        int open = ibo.ibo_open();
        if (open<0){
            System.out.println("####  open error ! "+open);
            return;
        }

        btn = (Button) findViewById(R.id.btn);
        btn.setOnClickListener(this);
    }

    @Override
    protected void onDestroy() {
        ibo.ibo_ioctl(0);
        ibo.ibo_close();
        super.onDestroy();
    }

    @Override
    public void onClick(View v) {
        if (isLight){
            isLight=false;
            ibo.ibo_ioctl(1);
            btn.setText("on");
        }else{
            isLight=true;
            ibo.ibo_ioctl(0);
            btn.setText("off");
        }
    }
}

完成了!

推荐阅读更多精彩内容