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;
/** 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");
}
}
}
完成了!