参考老罗的Android之旅
Android硬件抽象层(HAL)概要介绍和学习计划
基于android5.1.1系统源码,清华镜像站https://mirrors.tuna.tsinghua.edu.cn/help/AOSP/下载系统源码和kernel3.4源码
1.在Android内核源代码中,编写Linux驱动程序(driver)
(1).进入到kernel/goldfish/drivers目录,新建hello目录;
(2). 在hello目录中增加hello.h文件;定义了一个字符设备结构体hello_android_dev;
(3).在hello目录中增加hello.c文件,这是驱动程序的实现部分;定义三种访问设备寄存器的方法;
定义传统的设备文件访问方法
定义通过devfs文件系统访问方法
定义通过proc文件系统访问方法
定义模块加载和卸载方法
(4)..在hello目录中新增Kconfig和Makefile两个文件;
Kconfig文件的内容
config HELLO
tristate "First Android Driver"
default n
help
This is the first android driver.
Makefile文件的内容
obj-$(CONFIG_HELLO) += hello.o
(5).修改arch/arm/Kconfig和drivers/kconfig两个文件;
在menu "Device Drivers"和endmenu之间添加一行:
source "drivers/hello/Kconfig"
在arch/arm/Kconfig中没有找到enu "Device Drivers"则添加
menu "Device Drivers"
source "drivers/hello/Kconfig"
endmenu
(6).修改drivers/Makefile文件,添加一行: obj-$(CONFIG_HELLO) += hello/;
(7).配置编译选项:make menuconfig;
找到"Device Drivers" => "First Android Drivers"选项,设置为y。
(8).编译:make;
编译成功后,就可以在hello目录下看到hello.o文件了,这时候编译出来的zImage已经包含了hello驱动。
编译遇到的问题:
a.在make menuconfig之前,没有设置环境变量
export ARCH=arm
export SUBARCH=arm
export CROSS_COMPILE=arm-eabi-
b.在make后报错
error: 'struct proc_dir_entry' has no member named 'owner'
即proc_dir_entry结构体没有成员变量owner,proc_dir_entry定义在include/linux/proc_fs.h文件中,在此文件中添加成员变量owner,
struct module *owner;
重新编译即可。
c.在make后报错
error: implicit declaration of function 'init_MUTEX' [-Werror=implicit-function-declaration] init_MUTEX(&(dev->sem));
在新版本的Linux内核中,init_mutex已经被废除了,新版本使用sema_init函数。
将
init_MUTEX(&(dev->sem));
改为
sema_init(&(dev->sem),1);
重新编译即可。
哈哈哈!!!编译成功!!!
(9).验证,启动模拟器
emulator -kernel arch/arm/boot/zImage &
adb shell
进入到dev目录,可以看到hello设备文件:
root@android:/ # cd dev
root@android:/dev # ls
进入到proc目录,可以看到hello文件:
root@android:/ # cd proc
root@android:/proc # ls
访问hello文件的值:
root@android:/proc # cat hello
0
root@android:/proc # echo '55' > hello
root@android:/proc # cat hello
55
进入到sys/class目录,可以看到hello目录:
root@android:/ # cd sys/class
root@android:/sys/class # ls
进入到hello目录,可以看到hello目录:
root@android:/sys/class # cd hello
root@android:/sys/class/hello # ls
进入到下一层hello目录,可以看到val文件:
root@android:/sys/class/hello # cd hello
root@android:/sys/class/hello/hello # ls
访问属性文件val的值:
root@android:/sys/class/hello/hello # cat val
55
root@android:/sys/class/hello/hello # echo '101' > val
root@android:/sys/class/hello/hello # cat val
101
ok,验证通过!!!
2.在Android系统中增加C可执行程序来访问硬件驱动程序(kernel ------> driver)
(1).验证Linux内核驱动程序按1.(9)进行;
(2).进入到Android源代码工程的external目录,创建hello目录,在hello目录中新建hello.c文件;
(3). 在hello目录中新建Android.mk文件;
内容为
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE := hello
LOCAL_SRC_FILES := $(call all-subdir-c-files)
include $(BUILD_EXECUTABLE)
注意,BUILD_EXECUTABLE表示我们要编译的是可执行程序。
(4).使用mmm命令进行hello模块编译;
mmm /external/hello
(5).重新打包Android系统文件system.img;
make snod
(6).运行模拟器验证,使用/system/bin/hello可执行程序来访问Linux内核驱动程序;
emulator -kernel ./kernel/common/arch/arm/boot/zImage &
adb shell
./system/bin/hello
ok!这一步比较简单。
3.在Android硬件抽象层增加接口模块来访问硬件驱动程序(HAL ------> kernel)
(1).进入到hardware/libhardware/include/hardware目录,新建hello.h文件;
(2).进入到hardware/libhardware/modules目录,新建hello目录,并添加hello.c文件;
(3).在hello目录下新建Android.mk文件;
内容为
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_PRELINK_MODULE := false
LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw
LOCAL_SHARED_LIBRARIES := liblog
LOCAL_SRC_FILES := hello.c
LOCAL_MODULE := hello.default
include $(BUILD_SHARED_LIBRARY)
注意,LOCAL_MODULE的定义规则,hello后面跟有default,hello.default能够保证我们的模块总能被硬象抽象层加载到。
(4). 给hello添加root权限,类似于Linux的udev规则,打开Android源代码工程目录下,进入到system/core/rootdir目录,
里面有一个名为ueventd.rc文件,往里面添加一行:
/dev/hello 0666 root root;
(5). 编译;
mmm hardware/libhardware/modules/hello
###编译的时候遇到的问题LOGI或LOGE没有有定义的问题,即需要包好头文件。
编译成功后,就可以在out/target/product/generic/system/lib/hw目录下看到hello.default.so文件了。
(6). 重新打包Android系统镜像system.img;
make snod
重新打包后,system.img就包含我们定义的硬件抽象层模块hello.default了。
这一步也比较简单。
4.在Android系统中编写JNI方法在应用程序框架层提供Java接口访问硬件(frameworks(JNI接口) ------> HAL)
(1).进入到frameworks/base/services/core/jni目录,新建com_android_server_HelloService.cpp文件;
在com_android_server_HelloService.cpp文件中,实现JNI方法。注意文件的命令方法,com_android_server前缀表示的是包名,
表示硬件服务HelloService是放在frameworks/base/services/core/java目录下的com/android/server目录的
注意,在hello_init函数中,通过Android硬件抽象层提供的hw_get_module方法来加载模块ID为HELLO_HARDWARE_MODULE_ID的硬件抽象层模块,其中,HELLO_HARDWARE_MODULE_ID是在
中定义的。Android硬件抽象层会根据HELLO_HARDWARE_MODULE_ID的值在Android系统的/system/lib/hw目录中找到相应的模块,然后加载起来,并且返回hw_module_t接口给调用者使用。在jniRegisterNativeMethods函数中,第二个参数的值必须对应HelloService所在的包的路径,即com.android.server.HelloService。
(2). 修改同目录下的onload.cpp文件。
a.首先在namespace android增加register_android_server_HelloService函数声明:
namespace android {
...
int register_android_server_HelloService(JNIEnv *env);
};
b.在JNI_onLoad增加register_android_server_HelloService函数调用:
extern "C" jint JNI_onLoad(JavaVM* vm, void* reserved)
{
...
register_android_server_HelloService(env);
...
}
(3).修改同目录下的Android.mk文件,在LOCAL_SRC_FILES变量中增加一行:
LOCAL_SRC_FILES:= \
com_android_server_AlarmManagerService.cpp \
com_android_server_BatteryService.cpp \
com_android_server_InputManager.cpp \
com_android_server_LightsService.cpp \
com_android_server_PowerManagerService.cpp \
com_android_server_SystemServer.cpp \
com_android_server_UsbService.cpp \
com_android_server_VibratorService.cpp \
com_android_server_location_GpsLocationProvider.cpp \
#添加hello模块的javaj接口
com_android_server_HelloService.cpp \
onload.cpp
注意要在onload.cpp之前添加。
(4).编译和重新打包system.img;
mmm frameworks/base/services/core/jni
make snod
这样,重新打包的system.img镜像文件就包含我们刚才编写的JNI方法了,也就是我们可以通过Android系统的Application Frameworks层
提供的硬件服务HelloService来调用这些JNI方法,进而调用低层的硬件抽象层接口去访问硬件了。
这一步也很简单。
5.在Android系统的应用程序框架层增加硬件服务接口(frameworks(aidl的Service) ------> frameworks(JNI接口))
(1).进入到frameworks/base/core/java/android/os目录,新增IHelloService.aidl接口定义文件;
(2).返回到frameworks/base目录,打开Android.mk文件,修改LOCAL_SRC_FILES变量的值,增加IHelloService.aidl源文件:;
(3).编译IHelloService.aidl接口;
mmm frameworks/base
这样,就会根据IHelloService.aidl生成相应的IHelloService.Stub接口。
(4).进入到frameworks/base/services/core/java/com/android/server目录,新增HelloService.java文件;
(5). 修改同目录的SystemServer.java文件;
在ServerThread::run函数中调用了startOtherServices函数,startOtherServices函数中增加加载HelloService的代码:
try {
Slog.i(TAG, "Hello Service");
ServiceManager.addService("hello", new HelloService());
} catch (Throwable e) {
Slog.e(TAG, "Failure starting Hello Service", e);
}
(6).修改external/sepolicy/service_contexts文件,添加一句:
hello u:object_r:system_server_service:s0
hello作为系统服务,这样ServiceManager.addService才有效。
(7).编译HelloService和重新打包system.img;
mmm frameworks/base/services
make snod
!!!编译程中,出现com_android_server_HelloService.cpp文件中LOGI没有定义问题,先注释掉。
这样,重新打包后的system.img系统镜像文件就在Application Frameworks层中包含了我们自定义的硬件服务HelloService了,并且会在系统启动的时候,
自动加载HelloService。这时,应用程序就可以通过Java接口来访问Hello硬件服务了。
6.在Android系统中编写APP通过应用程序框架层访问硬件服务(APP -----> frameworks(aidl的Service))
这就简单多了,JNI编程嘛,so easy!
(1).现在eclipse或AS中建好Hello项目,然后复制到package/apps/Hello目录下;
(2).新建Android.mk文件;
内容为
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_PACKAGE_NAME := Hello
include $(BUILD_PACKAGE)
(3).添加Hello项目的编译,进入device/htc/flounder目录,修改device.mk文件;
在最后添加一句
PRODUCT_PACKAGES += HALHello
(4).添加Hello项目到手机system/app中,进入到/build/target/product目录中,修改core.mk文件,把自已的项目加入编译行列中;
PRODUCT_PACKAGES := \
framework-res \
Hello \
(5).编译和重新打包;
make -j8
make snod
(6).运行模拟器验证
emulator -kernel arch/arm/boot/zImage
编译过程中可能报错:
1.注: 某些输入文件使用或覆盖了已过时的 API。 注: 有关详细信息, 请使用 -Xlint:deprecation 重新编译。 24 个警告;
解决:进入需要编译的目录,修改Android.mk文件:
a.将 android.policy_phone中的_phone删掉
b.注释掉LOCAL_UNINSTALLABLE_MODULE := true
修改后的文件内容如下:
LOCAL_MODULE := android.policy
#LOCAL_UNINSTALLABLE_MODULE := true
使用make android.policy 命令编译。
2.修改了framework,android对API的修改有一定的规则,要么隐藏,要么更新API:
******************************
You have tried to change the API from what has been previously approved.
To make these errors go away, you have two choices:
1) You can add "@hide" javadoc comments to the methods, etc. listed in the
errors above.
2) You can update current.txt by executing the following command:
make update-api
To submit the revised current.txt to the main Android repository,
you will need approval.
******************************
解决:说得很清楚,两个选择。
a.在方法上添加@hide,隐藏API
b.更新API。
a.在方法名和aidl的类名上添加注释: /** {@hide} */
b.更新API:make update-api
系统就会自动的把我们新增的API 写入 frameworks/base/api/current.xml 文件中。
3.编译程中,可能会出现com_android_server_HelloService.cpp或其他本项目cpp文件中LOGI没有定义问题,
是因为没有导包的原因,先注释掉,解决:
(1).修改Android.mk文件配置,添加如下语句
LOCAL_LDLIBS += -L$(SYSROOT)/usr/lib -llog
(2).在.c文件中修改为如下语句
#include
(3).使用方法
#define LOG_TAG "HelloService"
#define LOGI(fmt, args...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, fmt, ##args)
#define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, fmt, ##args)
#define LOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, fmt, ##args)
(4).打印语句
LOGI("test log!!!!");
LOGI("the string is: %s \n",buff);
4.系统启动报错:/system_process E/HelloService: Hello JNI: failed to get hello stub module.这是自己打印的,
` 即加载hello module 失败,原因是找不到hello.default.so库文件。
解决:
重新编译模块 hardware下的hello模块
mmm hardware/libhardware/modules/hello
out/target/product/generic/system/lib/hw目录下,就能看到生成的hello.default.so库文件了。
5.系统启动报错:
/system_process E/HelloService: Hello JNI: failed to open hello device.
/system_process E/HelloStub: Hello Stub: failed to open /dev/hello -- Permission denied.
这是自己打印的,即打开hello module失败,原因是没有root权限。
解决:
(1)、添加文件权限
进入到Android源代码工程目录下,修改system/core/rootdir目录下的ueventd.rc文件,添加一行:
/dev/hello 0666 root root
将修改后的ueventd.rc文件拷贝到out/target/product/generic/root目录下,并且最终打包在ramdisk.img镜像文件中。
但是现在还不能完全解决这个问题,因为Android L以后引入了SELinux防火墙机制,每个进程对文件的访问是有规定的,所以不可忽视的一条log信息:
12-20 15:13:26.500 311-311/? W/system_server: type=1400 audit(0.0:4): avc: denied { read write } for name="hello" dev="tmpfs" ino=1583 scontext=u:r:system_server:s0 tcontext=u:object_r:device:s0 tclass=chr_file permissive=0
12-20 15:13:26.530 311-311/? W/system_server: type=1400 audit(0.0:5): avc: denied { read write } for name="hello" dev="tmpfs" ino=1583 scontext=u:r:system_server:s0 tcontext=u:object_r:device:s0 tclass=chr_file permissive=0
这句话的意思是system_server 进程想要访问device:chr_file 缺少read和write的权限!这就需要在修改SELinux的策略,赋予system_server 这个权限。
(2)、修改SELinux策略
第一步:找到需要访问该内核节点的进程(process),笔者自己这个节点由system_server进程来访问
第二步:打开文件AndroidL/android/external/sepolicy/file_contexts.be
仿照这个文件里的写法,为这个定义一个你想要的名字:
# We add here
/dev/hello u:object_r:hello_device:s0
wf_bt_device是自定义,其他左右两边的内容都和上面的范例一致。
第三步:打开文件AndroidL/android/external/sepolicy/device.te
仿照这个文件里的写法,将刚刚第二步写的wf_bt_device声明为dev_type:
# We add here
type hello_device, dev_type;
第四步:AndroidL/android/external/sepolicy/目录下很多.te文件都是以进程名来结尾的,比如有针对surfaceflinger进程的surfaceflinger,
有针对vold进程的vold.te,
刚刚从第一步得到,这个节点是由system_server进程来访问,所以,我们找到system_server.te打开,加入允许这个进程对/dev/wf_bt的读写权限,
# Read/Write to /dev/hello
allow system_server hello_device:chr_file rw_file_perms;
# chr_file表示字符设备文件,如果是普通文件用file,目录请用dir
# rw_file_perms代表读写权限
allow system_server hello_device:chr_file rw_file_perms;
这句话的意思是:允许system_server进程拥有对hello_device的这个字符设备的读写权限。
改了这些之后,你就可以make clean;make -j8编译在make snod重新打包来验证权限是否获取成功。
6.open成功,但却无法读写,或读写无效open返回0
解决:
将
if(mDevice->fd = open(DEVICE_NAME, O_RDWR) == -1)
分开写成
mDevice->fd = open(DEVICE_NAME, O_RDWR);
if(mDevice->fd == -1)
或者加括号
if((mDevice->fd = open(DEVICE_NAME, O_RDWR)) == -1)
==判定条件符,几乎是级别最低的,至于,为什么这样,我也无法理解,可能是编译器的问题。
查看Kernel打印的log。
a. emulator -kernel arch/arm/boot/zImage -show-kernel &
b. adb shell
cat /proc/kmsg