一般情况下,APP不直接访问硬件,而是由SystemServer通过HAL和JNI来访问硬件,实质即java访问C,建议把对硬件的操作写在一个HAL文件里面,对上一章写的代码com_android_server_LedService.cpp这个JNI文件做进一步的修改,除了向上注册本地函数之外,还要去加载HAL文件,然后调用HAL里面的函数。
使用HAL有两个好处:
1. 容易修改,只需直接修改HAL文件然后编译成so文件放入系统即可
2. 保密作用,linux基于GPL协议,一旦使用linux内核就必须公开linux内核的源代码,如果我们在linux内核里面实现这些硬件操作的话就需要把这些操作全部公开出去,很多人厂家不愿这么做,所以他们只在内核写一个最简单的驱动程序来操作寄存器,而硬件内部的复杂操作放在HAL文件里面,厂家只提供HAL文件的so文件给客户,这样就避开了GPL协议,android基于Apache协议,你可以去修改它的代码,而不需要去公开你的修改。
对于HAL层,摘取老罗博客中的一段来进行简述:
Android的硬件抽象层,简单来说,就是对Linux内核驱动程序的封装,向上提供接口,屏蔽低层的实现细节。也就是说,把对硬件的支持分成了两层,一层放在用户空间(User Space),一层放在内核空间(Kernel Space),其中,硬件抽象层运行在用户空间,而linux内核驱动程序运行在内核空间。为什么要这样安排呢?把硬件抽象层和内核驱动整合在一起放在内核空间不可行吗?从技术实现的角度来看,是可以的,然而从商业的角度来看,把对硬件的支持逻辑都放在内核空间,可能会损害厂家的利益。我们知道,Linux内核源代码版权遵循GNU License,而android源代码版权遵循Apache License,前者在发布产品时,必须公布源代码,而后者无须发布源代码。如果把对硬件支持的所有代码都放在Linux驱动层,那就意味着发布时要公开驱动程序的源代码,而公开源代码就意味着把硬件的相关参数和实现都公开了,在手机市场竞争激烈的今天,这对厂家来说,损害是非常大的。因此,Android才会想到把对硬件的支持分成硬件抽象层和内核驱动层,内核驱动层只提供简单的访问硬件逻辑,例如读写硬件寄存器的通道,至于从硬件中读到了什么值或者写了什么值到硬件中的逻辑,都放在硬件抽象层中去了,这样就可以把商业秘密隐藏起来了。也正是由于这个分层的原因,Android被踢出了Linux内核主线代码树中。大家想想,Android放在内核空间的驱动程序对硬件的支持是不完整的,把Linux内核移植到别的机器上去时,由于缺乏硬件抽象层的支持,硬件就完全不能用了,这也是为什么说Android是开放系统而不是开源系统的原因。
博客原文:http://blog.csdn.net/luoshengyang/article/details/6567257
所以android里面对硬件的操作我们一般分为两个文件,一个是JNI文件(向上提供(注册)本地函数,向下加载HAL文件并调用HAL文件的函数),一个是HAL文件(负责访问驱动程序执行具体的硬件操作),这两个文件都是C/C++语言写的,所以JNI来加载HAL的实质就是怎么使用dl_open来加载动态库,android里面对dlopen又做了一层封装,我们使用的是“hw_get_module”这个函数(位于Hardware.c),函数实现过程如下
hw_get_module(“led”);//假设模块名为led,最终由这个模块名转为文件名
代码路径:external\chromium_org\third_party\hwcplus\src\Hardware.c
调用过程:
- 模块名(“led”)===>文件名(filename)
hw_get_module
----hw_get_module_by_class(“led”, NULL)
------name = “led”
---------property_get(根据名字获得某个属性值)
---------hw_module_exists(去目录下判断某个模块是否存在) - 加载
dlopen(filename)------>(android源目录使用man dlopen查看使用方法)
上面最后两个函数的具体解析如下:
hw_module_exists(char *path, size_t path_len, const char *name, const char *subname)
它是用来判断”name”.”subname”.so文件是否存在,总共在三个目录下查找a. HAL_LIBRARY_PATH
b. /vendor/lib/hw
c. /system/lib/hw
对应源码及注解如下:
getenv(“HAL_LIBRARY_PATH”); //获得环境变量,在这个目录(“HAL_LIBRARY_PATH”)所指示的那个目录下查到是否有那个so文件
snprintf(path, path_len, “%s/%s.%s.so”,HAL_LIBRARY_PATH2, name, subname);//在目录(HAL_LIBRARY_PATH2)下查找是否有那个文件存在
snprintf(path,path_len,"%s/%s.%s.so", HAL_LIBRARY_PATH1, name, subname); //在目录(HAL_LIBRARY_PATH1)下查找是否有那个文件存在
上面的宏对应如下:
#ifndef HAL_LIBRARY_PATH1
#define HAL_LIBRARY_PATH1 “/system/lib” _64 “/hw”
#endif
#ifndef HAL_LIBRARY_PATH2
#define HAL_LIBRARY_PATH2 “/vendor/lib” _64 “/hw”
#endif直接到android下进行搜索,我们只能搜索到存在一个 system/lib/hw 这个文件,所以最终会去这个文件里面搜索so文件
另一个函数的解析如下:
property_get
这个函数涉及到android的一个重要系统:属性系统(可以从name里面获得一个value),对应代码如下:
属性< 键,值 >< name, value > 可以从名字获得一个值
/* First try a property specific to the class and possibly instance */
snprintf(prop_name, sizeof(prop_name), "ro.hardware.%s", name);
if (property_get(prop_name, prop, NULL) > 0) {
if (hw_module_exists(path, sizeof(path), name, prop) == 0) {
goto found;
}
}
传入 prop_name 获得某个 prop(这个属性就是它的值), 这个prop会传入hw_module_exists里面的subname作为参数
比如传入的prop_name是led,则他会去查找以下文件:
Led.XXX.so(XXX来源于prop这个属性,查找"ro.hardware.led"属性是否存在),查找结果如下:
没有找到
接着去数组中定义的属性中查找
static const char variant_keys[] = {
“ro.hardware”, / This goes first so that it can pick up a different
file on the emulator. */
“ro.product.board”,
“ro.board.platform”,
“ro.arch”
};
依次查找到属性值的结果如下:
找到这个属性则会去判断文件是否存在, 在这三个目录(HAL_LIBRARY_PATH、/vendor/lib/hw、/system/lib/hw)下面依次查找这两个led文件
led.nanopi3.so
led.slsiap.so
如果这三个目录都不存在这两个so文件,依据源码
hw_module_exists(path, sizeof(path), name, “default”)
会去搜索
led.defualt.so
如果找到了某个so文件,则会把这个文件的绝对路径就会保存在path数组里面
整个过程可以总结如下:
总结:
hw_module_exists
property_get
这两个函数用来判断是否存在led.xxx.so是否存在(xxx是属性),一旦查找到就用dlopen(filename)加载进来
load(class_id, path, module)
-----dlopen(filename)—>加so文件
-----dlsym(“HMI”)—>从so文件中获得名为HMI的hw_module_t结构体
-----strcmp(id, hmi->id)—>判断这个hw_module_t结构体跟我们找的文件的名字是否一样,如比较(“led”, hmi->id)是否一致
下面是HAL代码实现:
JNI 怎么使用 HAL
参考com_android_lights_LightsService.cpp
- hw_get_module -> get_device获得一个hw_module_t结构体
- 调用 module->methods->open(module, device_name, &device)
根据device_name的名字获得一个hw_device_t(设备)结构体,并且把hw_device_t结构体转换为设备自定义的结构体
(light_device_t*)device;
为什么能直接这样转换的原因是light_device_t这个结构体第一个成员就是hw_device_t类型的参数。
HAL 怎么写
- 实现一个名为HMI的hw_module_t结构体
- 实现一个open函数, 它会根据name返回一个设备自定义的结构体
这个设备自定义的结构体的第1个成员是 hw_device_t结构体
还可以定义设备相关的成员
编写代码实现过程:
- 首先是JNI文件的实现:
#define LOG_TAG "LedService"
#include "jni.h"
#include "JNIHelp.h"
#include "android_runtime/AndroidRuntime.h"
#include <utils/misc.h>
#include <utils/Log.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <hardware/led_hal.h>
namespace android
{
static led_device_t* led_device;
/* 获取模块并打开设备 */
jint ledOpen(JNIEnv *env, jobject cls)
{
jint err;
hw_module_t* module;
hw_device_t* device;
ALOGI("native ledOpen ...");
/* 1. hw_get_module */
err = hw_get_module("led", (hw_module_t const**)&module);//name跟android.mk一致
if (err == 0) {
/* 2. get device : module->methods->open */
err = module->methods->open(module, NULL, &device);
if (err == 0) {
/* 3. call led_open */
led_device = (led_device_t *)device;
return led_device->led_open(led_device);
} else {
return -1;
}
}
return -1;
}
void ledClose(JNIEnv *env, jobject cls)
{
//ALOGI("native ledClose ...");
//close(fd);
}
/* 根据从模块中获得的设备调用它的相关控制函数 */
jint ledCtrl(JNIEnv *env, jobject cls, jint which, jint status)
{
ALOGI("native ledCtrl %d, %d", which, status);
return led_device->led_ctrl(led_device, which, status);
}
/* 定义JNI字段描述符,实现映射数组的内容 */
static const JNINativeMethod methods[] = {
{"native_ledOpen", "()I", (void *)ledOpen},
{"native_ledClose", "()V", (void *)ledClose},
{"native_ledCtrl", "(II)I", (void *)ledCtrl},
};
/* 注册Service */
int register_android_server_LedService(JNIEnv *env)
{
return jniRegisterNativeMethods(env, "com/android/server/LedService",
methods, NELEM(methods));
}
}
2.编写HAL文件,需要实现 led_hal.c 和 led_hal.h 两个文件
led_hal.c代码如下:
#define LOG_TAG "LedHal"
/* 参考: \hardware\libhardware\modules\vibrator\Vibrator.c */
#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 <math.h>
#include <hardware/led_hal.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <utils/Log.h>
static int fd;
int led_close(struct hw_device_t* device)
{
close(fd);
return 0;
}
static int led_open(struct led_device_t* dev)
{
fd = open("/dev/leds_nano", O_RDWR);//可读可写
ALOGI("ledOpen : %d", fd);
if(fd >= 0)
return 0;
else
return -1;
}
static int led_ctrl(struct led_device_t* dev, int which, int status)
{
int ret = ioctl(fd, status, which);
ALOGI("ledCtrl %d, %d, %d", which, status, ret);
return ret;
}
/* 3.实现设备自定义(led_device_t)的结构体 */
static struct led_device_t led_dev = {
.common = {
.tag = HARDWARE_DEVICE_TAG,//表明是设备相关
.close = led_close,
},
.led_open = led_open,
.led_ctrl = led_ctrl,
};
static int led_device_open(const hw_module_t* module, const char* id __unused,
hw_device_t** device __unused)
{
*device = &led_dev;
return 0;
}
/* 2.实现一个open函数, 它会根据name返回一个设备自定义(led_device_t)的结构体*/
static struct hw_module_methods_t led_module_methods = {
.open = led_device_open,
};
/* 1.实现一个名为HMI的hw_module_t结构体 */
struct hw_module_t HAL_MODULE_INFO_SYM = {
.tag = HARDWARE_MODULE_TAG,
.id = "LED",
.methods = &led_module_methods,
};
led_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 led_device_t {
struct hw_device_t common;
int (*led_open)(struct led_device_t* dev);
int (*led_ctrl)(struct led_device_t* dev, int which, int status);
};
__END_DECLS
#endif // ANDROID_LIGHTS_INTERFACE_H
重新上传以上文件进行编译即可:
JNI: frameworks/base/services/core/jni/com_android_server_LedService.cpp
hal.c:hardware/libhardware/modules/led/led_hal.c
hal.h:hardware/libhardware/include/hardware/led_hal.h修改 hardware/libhardware/modules/led/Android.mk
Android.mk内容如下:`
# Copyright (C) 2012 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := led.default
# HAL module implementation stored in
# hw/<VIBRATOR_HARDWARE_MODULE_ID>.default.so
LOCAL_MODULE_RELATIVE_PATH := hw
LOCAL_C_INCLUDES := hardware/libhardware
LOCAL_SRC_FILES := led_hal.c
LOCAL_SHARED_LIBRARIES := liblog
LOCAL_MODULE_TAGS := optional
include $(BUILD_SHARED_LIBRARY)
编译:
mmm frameworks/base/services
mmm hardware/libhardware/modules/led
make snod
验证(在android系统命令行中输入搜索):
Logcat | grep “LedHal”Android打印信息简介:
1.有三类打印信息: app, system, radio
程序里使用 ALOGx, SLOGx, RLOGx来打印
>
2. x表示6种打印级别,有:
V Verbose
D Debug
I Info
W Warn
E Error
F Fatal
比如:
#define LOG_TAG "LedHal"
ALOGI("led_open : %d", fd);
>
3. 打印出来的格式为:
I/LedHal ( 1987): led_open : 65
(级别) LOG_TAG 进程号 打印信息
>
4. 使用 logcat 命令查看
logcat LedHal:I *:S