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 0;
}

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");
        }
    }
}

完成了!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章