Android硬件抽象層(HAL)
Android的硬件抽象層,簡單來說,就是對Linux內核驅動程序的封裝,向上提供接口,屏蔽低層的實現細節。也就是說,把對硬件的支持分成了兩層,一層放在用戶空間(User Space),一層放在內核空間(Kernel Space),其中,硬件抽象層運行在用戶空間,而Linux內核驅動程序運行在內核空間。爲什麼要這樣安排呢?把硬件抽象層和內核驅動整合在一起放在內核空間不可行嗎?從技術實現的角度來看,是可以的,然而從商業的角度來看,把對硬件的支持邏輯都放在內核空間,可能會損害廠家的利益。我們知道,Linux內核源代碼版權遵循GNU License,而Android源代碼版權遵循Apache License,前者在發佈產品時,必須公佈源代碼,而後者無須發佈源代碼。如果把對硬件支持的所有代碼都放在Linux驅動層,那就意味着發佈時要公開驅動程序的源代碼,而公開源代碼就意味着把硬件的相關參數和實現都公開了,在手機市場競爭激烈的今天,這對廠家來說,損害是非常大的。因此,Android纔會想到把對硬件的支持分成硬件抽象層和內核驅動層,內核驅動層只提供簡單的訪問硬件邏輯,例如讀寫硬件寄存器的通道,至於從硬件中讀到了什麼值或者寫了什麼值到硬件中的邏輯,都放在硬件抽象層中去了,這樣就可以把商業祕密隱藏起來了。也正是由於這個分層的原因,Android被踢出了Linux內核主線代碼樹中。大家想想,Android放在內核空間的驅動程序對硬件的支持是不完整的,把Linux內核移植到別的機器上去時,由於缺乏硬件抽象層的支持,硬件就完全不能用了,這也是爲什麼說Android是開放系統而不是開源系統的原因。
下面這個圖闡述了硬件抽象層在Android系統中的位置,以及它和其它層的關係:
重要的三個結構體:
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;
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;
}
ALOGI("ibo JNI_OnLoad ...");
cls = (*env)->FindClass(env, "com/ibo/jnidemo/TestJni");
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");
}
}
}
完成了!