最近接手了一个android系统车载电子项目,我是零android基础,零Java基础的,因此开始慢慢的探索之路。
我的目标首先是打通一条从apk到linux kernel的通路,了解清楚android驱动层次、如何工作、如何编写之后再去扩展相关知识,没想到一头扎进去就是一个多星期。
一个传统型的(旧架构)android驱动应该包含这几块:kernel驱动、HAL驱动、JNI层、service层,再往上就是apk了,程序运行时候的调用关系是:apk-->service--->JNI-->HAL-->kernel驱动。
一、编写kernel驱动
一个典型的例子是helloworld,编写kernel驱动主要完成两部分的工作:
1、配置编译环境-------主要有三个工作:
a. 设置linux的系统配置菜单,打开文件 android/kernel/drivers/char/Kconfig,添加下面的内容,
config HELLO
tristate"HELLO driver support"
defaulty
help
hellodriver test
b.把hello.c添加进makefile中,打开文件android/kernel/drivers/char/Makefile,添加下面的内容
obj-$(CONFIG_HELLO) += hello.o
c.修改default_config文件,打开文件 android/kernel/arch/arm/configs/tcc893x_nav360_defconfig,添加下面内容:
CONFIG_HELLO= y // 配置成y表示把hello这个驱动编译进内核,当然可以把hello以模块的方式编译,这时就要设置成m
2、编写代码
在目录/nav360/android/kernel/drivers/char下创建两个文件,分别是hello.c, hello.h,两个文件的内容如下:
//hello.h
#ifndef _HELLO_ANDROID_H_
#define_HELLO_ANDROID_H_
#include<linux/init.h>
#include<linux/module.h>
#include<linux/types.h>
#include<linux/fs.h>
#include<linux/proc_fs.h>
#include<linux/device.h>
#include <asm/uaccess.h>
#include<linux/miscdevice.h>
#include<linux/cdev.h>
#include<linux/semaphore.h>
#define DEBUG "<0>"
#endif
//hello.c
#include"hello.h"
static inthello_open(struct inode* inode, struct file* filp) {
printk(DEBUG"hello_open...\n");
return 0;
}
static inthello_release(struct inode* inode, struct file* filp) {
printk(DEBUG"hello_release...\n");
return 0;
}
static ssize_thello_read(struct file* filp, char __user *buf, size_t count, loff_t* f_pos){
printk(DEBUG"hello_read...\n");
return 0;
}
static ssize_thello_write(struct file* filp, const char __user *buf, size_t count, loff_t*f_pos) {
printk(DEBUG"hello_write...\n");
return 0;
}
static longhello_ioctl(struct file *filp, unsigned int cmd, unsigned int arg){
printk(DEBUG"hello_ioctl...\n");
switch(cmd) {
case 0:
break;
case 1:
break;
default:
break;
}
}
static structfile_operations DEV_FOPS = {
.owner = THIS_MODULE,
.open = hello_open,
.release = hello_release,
.read =hello_read,
.write =hello_write,
.unlocked_ioctl = hello_ioctl,
};
static structmiscdevice misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "HELLO",
.fops = &DEV_FOPS,
};
static int __init hello_init(void){
printk(DEBUG"hello_init...\n");
misc_register(&misc);
return 0;
}
static void __exithello_exit(void){
printk(DEBUG"hello_exit...\n");
misc_deregister(&misc);
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("FirstAndroid Driver");
至此,kernel驱动就编写完毕,编译kernel之后这个驱动就在内核中了。把系统下载到目标机之后,在/dev目录下会看到一个设备文件HELLO。注意,这个设备文件名是我们在代码中指定的(上面有一行代码是.name = "HELLO",这个HELLO就是设备名字),上一层就是根据这个设备名字来打开驱动的。还需要注意的是, 设备文件HELLO需要root权限才能执行,因此在目标机上更改一下这个文件的执行权限:chmod 777 /dev/HELLO,这种方法比较麻烦。当然也可以有一劳永逸的方法,那就更改init.rc文件,让系统一上电就去自动更改权限,暂时不作详细描述。
二、编写HAL层驱动
编写HAL层驱动只要按照固定的格式套进去,然后再注意一些细节的地方就OK了。首先关注到android自带的三个结构体,分别是hw_module_t,hw_module_methods_t,hw_device_t,这三个结构体的功能有点绕,通过代码来讲解一下。
1、进入 到在 hardware/libhardware/include/hardware 目录,添加hello.h文件,添加如下内容
//hello.h
#ifndef ANDROID_HELLO_INTERFACE_H
#define ANDROID_HELLO_INTERFACE_H
#include <hardware/hardware.h>
#define HELLO_HARDWARE_MODULE_ID "hello"
struct hello_module_t
{
struct hw_module_t common;
};
struct hello_device_t
{
struct hw_device_t common;
int fd;
int (*set_val)(struct hello_device_t* dev,int val );
int (*get_val)(struct hello_device_t* dev ,int*val);
};
#endif
按照硬件抽象层HAL的规范要求,定义模块ID 即HELLO_HARDWARE_MODULE_ID、模块结构体以及硬件接口结构体。其中fd表示文件描述符,set_val、get_val是要向上一层暴露的接口,如果还有别的接口,可以再后面接续添加。需要注意的是:结构体hello_module_t的第一个成员必须是hw_module_t 变量、结构体hello_device_t的第一成员必须是 hw_device_t变量,为什么?这里其实是用C语言实现JAVA里边的多态。想想看,hw_device_t是android标准的,而hello_device_t是用户的,用户的东西如何跟标准的东西融合在一起?换个角度来说,我们可以hw_device_t看成是父类,而hello_device_t是子类,子类里边有父类的特征(struct hw_device_t common)。子类的拓展功能(set_val、get_val等)如何在标准规范中实现?且看,当上一次函数要求返回hw_device_t(父类)的指针,此时我们把hello_device_t(子类)的指针作为返回值,同样不会出错,因为子类的第一个成员是父类的变量。但是通过这种方式我成功地把其余拓展的接口也给到了上一层。
2、进入到 hardware/libhardware/modules 目录,新建 hello 目录,并添加hello.c 文件,分段讲解一下
#define LOG_TAG "HelloStub"
#include <hardware/hardware.h>
#include <hardware/hello.h>
#include <fcntl.h>
#include <errno.h>
#include <cutils/log.h>
#include <cutils/atomic.h>
#define DEVICE_NAME "/dev/HELLO" //在上一章中,我们指定了设备名称,并且事先知道设备文件会放到/dev目录下
#define MODULE_NAME "Hello"
#define MODULE_AUTHOR "qiufengsyb"
static int hello_device_open(const struct hw_module_t* module, const
char* name, struct hw_device_t** device);
static int hello_device_close(struct hw_device_t* device);
static int hello_set_val(struct hello_device_t* dev, int val);
static int hello_get_val(struct hello_device_t* dev, int* val);
static struct hw_module_methods_t hello_module_methods =
{
open: hello_device_open
};
// hello_module_t的变量名必须为HAL_MODULE_INFO_SYM,因为其实HAL_MODULE_INFO_SYM是一个宏,在hardware.h中有定义为HMI
//系统在库文件中查找该模块的时候是按照HMI这关键字来查找的
struct hello_module_t HAL_MODULE_INFO_SYM= {
common:
{
tag: HARDWARE_MODULE_TAG,
version_major: 1,
version_minor: 0,
id: HELLO_HARDWARE_MODULE_ID,
name: MODULE_NAME,
author: MODULE_AUTHOR,
methods: &hello_module_methods,
}
};
//下面这个函数会在JNI层被调用,也就上一层。且看其传进来的是标准的参数hw_device_t** device,但是返回的时候
//其实用的是用户定义结构体的hello_device_t的强制转换,这样,set_val, get_val这两个用户接口就以标准的方式向上一层暴露了
static int hello_device_open(const struct hw_module_t* module, const
char* name, struct hw_device_t** device)
{
struct hello_device_t* dev;
ALOGI("Hello HAL: read to open device.");
dev = (struct hello_device_t*)malloc(sizeof(struct hello_device_t));
if(!dev)
{
ALOGE("Hello Stub: failed to alloc space");
return -EFAULT;
}
memset(dev, 0, sizeof(struct hello_device_t));
dev->common.tag = HARDWARE_DEVICE_TAG;
dev->common.version = 0;
dev->common.module = (hw_module_t*)module;
dev->common.close = hello_device_close;
dev->set_val = hello_set_val;
dev->get_val = hello_get_val;
if((dev->fd = open(DEVICE_NAME, O_RDWR)) == -1)
{
ALOGE("Hello HAL: failed to open /dev/hello --%s.", strerror(errno));free(dev);
return -EFAULT;
}
*device = &(dev->common);
ALOGI("Hello HAL: open /dev/hello successfully.");
return 0;
}
static int hello_device_close(struct hw_device_t* device)
{
struct hello_device_t* hello_device = (struct hello_device_t*)device;
ALOGI("Hello HAL: read to close device.");
if(hello_device)
{
close(hello_device->fd);
free(hello_device);
}
return 0;
}
static int hello_set_val(struct hello_device_t* dev, int val)
{
ALOGI("Hello HAL: set value %d to device.", val);
write(dev->fd, &val, sizeof(val));
return 0;
}
static int hello_get_val(struct hello_device_t* dev, int* val)
{
ALOGI("Hello HAL: ready to get value.");
if(!val)
{
ALOGE("Hello HAL: error val pointer");
return -EFAULT;
}
read(dev->fd, val, sizeof(*val));
ALOGI("Hello Stub: get value %d from device", *val);
return 0;
}
3、在 hello 目录下新建 Android.mk 文件,Android.mk相当于linux 下的makefile文件,内容如下:
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)
关于Android.mk文件的意思不在这里详细解说,主要关注3个地方,LOCAL_SRC_FILES表示生成目标所依赖的文件,这里是hello.c;LOCAL_MODULE表示生成的目标名;
最后一行include $(BUILD_SHARED_LIBRARY)表示要生成什么样的目标,这里是BUILD_SHARED_LIBRARY,表示生成一个动态库。最后我们的目标文件就是hello.default.so文件,也就通常说的so文件。
4、编译
在android目录下执行下面命令:mmm hardware/libhardware/modules/hello, 执行mmm命令之前需要android目录下执行两个命令: . build/envsetup.sh以及 lunch,
5、重新打包 Android 系统镜像 system.img:
make snod
重新打包后,system.img 就包含我们定义的硬件抽象层模块 hello.default 了
三、编写JNI层驱动
进入到 frameworks/base/services/jni 目录,新建com_android_server_HelloService.cpp 文件:
在 com_android_server_HelloService.cpp 文件中,实现 JNI 方法。注意文件的命令方 法,com_android_server 前缀表示的是包名,表示硬件服务 HelloService 是放在
frameworks/base /services/java 目录下的 com/android/server 目录的,即存在一个命令为com.android.server.HelloService 的类。这里,我们暂时略去 HelloService 类的描述,在下一篇文章中,我们将回 到 HelloService 类来。简单地说,HelloService 是一个提供 Java 接口的硬件访问服务类。
//com_android_server_HelloService.cpp
#define LOG_TAG "HelloService"
#include "jni.h"
#include "JNIHelp.h"
#include "android_runtime/AndroidRuntime.h"
#include <utils/misc.h>
#include <utils/Log.h>
#include <hardware/hardware.h>
#include <hardware/hello.h>
#include <stdio.h>
namespace android
{
/*在硬件抽象层中定义的硬件访问结构体,参考<hardware/hello.h>*/
struct hello_device_t* hello_device = NULL;
/*通过硬件抽象层定义的硬件访问接口设置硬件寄存器 val 的值*/
static void hello_setVal(JNIEnv* env, jobject clazz, jint value)
{
int val = value;
ALOGI("Hello JNI: set value %d to device.", val);
if(!hello_device)
{
ALOGI("Hello JNI: device is not open.");
return;
}
hello_device->set_val(hello_device, val);
}
/*通过硬件抽象层定义的硬件访问接口读取硬件寄存器 val 的值*/
static jint hello_getVal(JNIEnv* env, jobject clazz)
{
int val = 0;
ALOGI("Hello JNI: get value......");
if(!hello_device)
{
ALOGI("Hello JNI: device is not open.");
return val;
}
hello_device->get_val(hello_device, &val);
ALOGI("Hello JNI: get value %d from device.", val);
return val;
}
/*通过硬件抽象层定义的硬件模块打开接口打开硬件设备*/
static inline int hello_device_open(const hw_module_t* module,
struct hello_device_t** device)
{
return module->methods->open(module, HELLO_HARDWARE_MODULE_ID, (struct hw_device_t**)device);
}
static jboolean hello_init(JNIEnv* env, jclass clazz)
{
hello_module_t* module;
ALOGI("Hello JNI: initializing......");
ALOGI("HELLO_HARDWARE_MODULE_ID = %s",HELLO_HARDWARE_MODULE_ID);
if(hw_get_module(HELLO_HARDWARE_MODULE_ID, (const struct hw_module_t**)&module) == 0)
{
ALOGI("Hello JNI: hello Stub found.");
if(hello_device_open(&(module->common), &hello_device) == 0)
{
ALOGI("Hello JNI: hello device is open.");
return 0;
}
ALOGE("Hello JNI: failed to open hello device.");
return -1;
}
ALOGE("Hello JNI: failed to get hello stub module.");
return -1;
}
static void hello_close(JNIEnv* env, jobject clazz)
{
ALOGI("Hello JNI: close module......");
if(!hello_device)
{
ALOGI("Hello JNI: device is not open.");
return;
}
ALOGI("Hello JNI: close module");
hello_device->common.close((struct hw_device_t*)hello_device);
hello_device = NULL;
}
/*JNI 方法表*/
//通过这个注册表,可以实现C/C++与JAVA的相互调用,其功能是把右边一列的C/C++接口映射成左边一列的JAVA接口,中间一列描述了这个接口
//的参数以及返回值,比如第一行的"()z",“()”表示参数为空,"z"表示返回值为布尔型。换句话说,JAVA层调用init_native(),其本质是调用hello_init()
static const JNINativeMethod method_table[] =
{
{"init_native", "()Z", (void*)hello_init},
{"setVal_native", "(I)V", (void*)hello_setVal},
{"getVal_native", "()I", (void*)hello_getVal},
{"close_native", "()V",(void*)hello_close},
};
/*注册 JNI 方法*/
int register_android_server_HelloService(JNIEnv *env)
{
return jniRegisterNativeMethods(env, "com/android/server/HelloService", method_table, NELEM(method_table));
}
};
//end of file
注册JNI方法的函数要被调用才能生效, 修改同目录下的 onload.cpp 文件,首先在 namespace android 增加register_android_server_HelloService 函数声明,然后在函数extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)中调用register_android_server_HelloService(env);
至此,JNI层编写结束,编译:~/Android$ mmm frameworks/base/services/jni
重新打包镜像文件:~/Android$ make snod
四、编写Service
在 Android 系统中,硬件服务一般是运行在一个独立的进程中为各种应用程序
提供服务。因此,调用这些硬件服务的应用程序与这些硬件服务之间的通信需要 通过代理来
进行。为此,我们要先定义好通信接口。
1、进入到 frameworks/base/core/java/android/os 目录,新增 IHelloService.aidl 接口定义文件:
//IHelloService.aidl
interface IHelloService
{
void setVal_test(int val);
int getVal_test();
void close();
}
2、返回到 frameworks/base 目录,打开 Android.mk 文件,修改 LOCAL_SRC_FILES 变量的值:
LOCAL_SRC_FILES += /
..........................
core/java/android/os/IHelloService.aidl \
.......................
3、 编译 IHelloService.aidl 接口:
~/Android$ mmm frameworks/base
如果编译不过需要重新全编译一下,因为编译aidl的过程中依赖一些中间文件,这些中间文件在之前的编译中被删除了,所以需要重新编译一下来得到这些中间文件。以上纯属猜测。
4、进入到 frameworks/base/services/java/com/android/server 目录,新增HelloService.java 文件:
//HelloService.java
package com.android.server;
import android.content.Context;
import android.os.IHelloService;
import android.util.Slog;
public class HelloService extends IHelloService.Stub
{
private static final String TAG = "HelloService";
static
{
System.load("/system/lib/libandroid_servers.so");//在上一节编写的JNI接口,最后编译出来会变成一个动态库,名字为libandroid_servers.so
//具体名字需要到路径android\frameworks\base\services\jni下查看Android.mk文件中的LOCAL_MODULE
//注意:执行make snod镜像文件打包后,该库就会被打包到目标机的/system/lib目录
}
public HelloService()
{
init_native();
}
public void setVal_test(int val)
{
setVal_native(val);
}
public int getVal_test()
{
return getVal_native();
}
public void close()
{
close_native();
}
private static native boolean init_native();
private static native void setVal_native(int val);
private static native int getVal_native();
private static native void close_native();
};
//end of file
5、编译:~/Android$ mmm frameworks/base/services/java
打包镜像文件::~/Android$ make snod
至此,service层就编译好了,下一步就是要编写apk测试程序。问题来了,上面所述的kernel驱动、hal驱动、jni驱动以及service都是在android源码环境下完成的,而按照一般的习惯,apk测试程序一般都是在集成开发环境下完成的,比如大家用得很火的eclipse,那么直接在eclipse里边new一个HelloService,能识别吗?不能。有两种方式可以解决问题,第一种是把当前的android源码生成一个SDK,然后把这个SDK加载进eclipse里边就行了。第二种是,直接找到service对应的jar包,然后拷贝到eclipse的android工程目录下的lib目录,在通过右键--->Properties--->Java Bulid Path---->Libraries---->Add External JARs把jar包加到工程来就可以了。其中,service对应的jar包在android\out\target\common\obj\JAVA_LIBRARIES\services_intermediates\classes.jar
五、编写apk程序
这里就不对android工程的布局等进行描述,编写apk的前提是把上一节生成service对应jar包加载进来。代码如下:
package com.hello_driver_test;
import com.android.server.HelloService;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
public class HelloDriverActivity extends Activity implements OnClickListener {
int i = 0 ;
private final static String LOG_TAG = "syb.Hello";
private HelloService helloService = null;
private Button bt_open,bt_close,bt_ioctr,bt_read,bt_write;
public native void Open();
public native void Close();
public native void IoCtr();
public native void Read();
public native void Write();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
bt_open = (Button) this.findViewById(R.id.button_open);
bt_close = (Button) this.findViewById(R.id.button_close);
bt_ioctr = (Button) this.findViewById(R.id.button_ioctr);
bt_read = (Button) this.findViewById(R.id.button_read);
bt_write = (Button) this.findViewById(R.id.button_write);
bt_open.setOnClickListener(this);
bt_close.setOnClickListener(this);
bt_ioctr.setOnClickListener(this);
bt_read.setOnClickListener(this);
bt_write.setOnClickListener(this);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.hello_driver, menu);
return true;
}
public void onClick(View v) {
switch (v.getId()) {
case R.id.button_open:
helloService = new HelloService();
break;
case R.id.button_close:
helloService.close();
break;
case R.id.button_ioctr:
break;
case R.id.button_read:
helloService.getVal_test();
break;
case R.id.button_write:
helloService.setVal_test(1);
break;
}
}
}
到此,从apk到service再到JNI再到HAL,最后到kernel驱动的通路就打通了。这个实验的完成参考了《老罗的Android之旅》,感谢老罗的无私共享。