Android系统kernel到APP整个流程demo分析
2018-03-21 15:10
148 查看
注意:本文转载大侠文章,详情请点阅: https://www.jianshu.com/p/fa36e5faea67
一直想深入Android底层开发,首先就从写一个完整的HAL层开发demo开始吧,步骤确实有很多,对我们这种不熟悉c/c++开发的人来说,确实是很痛苦,我看这简单的demo都要理解半天。下面我就一步步的来实现HAL层开发,附代码。
我这里简单的归纳了下,一共8大步骤
linux驱动实现
驱动测试
hal层实现
aidl实现
service java实现
service jni 实现
注册service和jni方法
android app调用测试
下面我一步步实现。
然后编写对应的Kconfig/Makefile文件。这2个文件比较简单。
Kconfig:
当然仅仅这样还不够,因为你并没有让内核编译关联到我们新创建的test2驱动。
在kernel/goldfish/drivers/Makefile中添加
4FC0D48C-4556-475A-BF46-4D11F38EFB7C.png在kernel/goldfish/arch/arm/Kconfig中添加
154A0CCE-5B28-4F5F-AE05-61FB90370921.png然后在kernel/goldfish下执行命令
3.png然后找到对应的驱动,按键按
4.png最后执行,make.
5.png成功生成zImage文件
emulator -kernel kernel/goldfish/arch/arm/boot/zImage
然后执行adb shell进入android系统控制台
执行 cat /proc/devices
6.png可以看到test2驱动已经注册了。
但是,我们还希望能在/dev下有test2驱动,那么还需要在test_init最后面再添加2行代码。
emulator -kernel kernel/goldfish/arch/arm/boot/zImage 重新启动模拟器
就会发现,/dev下出现了test2驱动。
7.png
Android.mk
屏幕快照 2017-03-27 14.57.54.png编译成功后,如图,会安装至
然后使用
然后控制台输入
效果就是把驱动里面保存的char读出来,然后+1再写入,如果驱动没有错误的话,则如下运行
屏幕快照 2017-03-27 15.04.49.png
屏幕快照 2017-03-27 15.56.48.png这样就代表HAL层已经编译成功。
然后在
Test2Service需要在
mmm编译
然后使用make snod,生成system.img,最后启动模拟器。
最终终于把整个流程走完了,看下运行效果。
屏幕快照 2017-03-27 19.13.04.png最终的效果应该是setVal 一个4 getVal也应该是一个4,但是当前不对,这是因为
屏幕快照 2017-03-27 19.15.27.png是不允许外部直接访问的,所以需要重新给该驱动设置权限。
由于篇幅太大了,我给个链接参考下
http://www.cnblogs.com/LoongEmbedded/p/5298388.html
最终重新启动模拟器就可以达到目标的效果了。
作者:javalong
链接:https://www.jianshu.com/p/fa36e5faea67
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
一直想深入Android底层开发,首先就从写一个完整的HAL层开发demo开始吧,步骤确实有很多,对我们这种不熟悉c/c++开发的人来说,确实是很痛苦,我看这简单的demo都要理解半天。下面我就一步步的来实现HAL层开发,附代码。
我这里简单的归纳了下,一共8大步骤
linux驱动实现
驱动测试
hal层实现
aidl实现
service java实现
service jni 实现
注册service和jni方法
android app调用测试
下面我一步步实现。
开发环境:Ubuntu 14.04 Android源码 2.3.1 内核 2.6.9
linux驱动实现
下面我就简单实现一个字符驱动// // Created by javalong on 17/3/24. // #include <linux/module.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/kernel.h> #include <linux/slab.h> #include <asm/uaccess.h> #include <linux/proc_fs.h> #include <linux/device.h> struct test2_cdev{ char val; struct cdev cv; }; dev_t devNo2; struct test2_cdev *test2_c; int test2_open (struct inode *node, struct file *filp){ //获取到当前自定义结构体变量,保存至file中,以便其他函数方便访问 struct test2_cdev *n_cdev = container_of(node->i_cdev,struct test2_cdev,cv); filp->private_data = n_cdev; return 0; } int test2_release (struct inode *node, struct file *filp){ filp->private_data = NULL; return 0; } ssize_t test2_read (struct file *filp, char __user *buf, size_t len, loff_t *pos){ struct test2_cdev *n_cdev = (struct test2_cdev *)filp->private_data; int err; printk("test2 read %c count : %d",n_cdev->val, sizeof(n_cdev->val)); if(copy_to_user(buf,&n_cdev->val, sizeof(n_cdev->val))){ err = -EFAULT; return err; } return sizeof(n_cdev->val); } ssize_t test2_write (struct file *filp, const char __user *buf, size_t len, loff_t *pos){ struct test2_cdev *n_cdev = (struct test2_cdev *)filp->private_data; int err; printk("test2 write %s count : %d",(*buf),len); if(copy_from_user(&n_cdev->val,buf,len)){ err = -EFAULT; return err; } return sizeof(n_cdev->val); } struct file_operations test2_op = { .owner = THIS_MODULE, .open = test2_open, .release = test2_release, .write = test2_write, .read = test2_read }; static int test2_init(void){ //申请设备号,自动分配 fs.h int err = alloc_chrdev_region(&devNo2,0,1,"test2"); if(err){ printk("获取设备号失败~ %d",err); return err; } //申请自定义结构体内存 slab.h test2_c = kmalloc(sizeof(struct test2_cdev),GFP_KERNEL); //初始化 字符设备 cdev_init(&test2_c->cv,&test2_op); //添加字符设备 err = cdev_add(&test2_c->cv,devNo2,1); if(err){ printk("添加设备失败~ %d",err); return err; } test2_c->val = '8'; //在/dev中创建节点 struct class * cls = class_create(THIS_MODULE,"test2"); device_create(cls,NULL,devNo2,NULL,"test2"); return 0; } static void test2_exit(void){ //释放设备号 unregister_chrdev_region(devNo2,1); //释放自定义结构体的内存 kfree(test2_c); //移除字符设备 cdev_del(&test2_c->cv); } module_init(test2_init); module_exit(test2_exit);
代码编译
在kernel/goldfish/drivers/下创建驱动文件夹,这里我创建test2,
然后编写对应的Kconfig/Makefile文件。这2个文件比较简单。
Kconfig:
config TEST2 tristate 'TEST2 Driver' default nMakefile:
obj-$(CONFIG_TEST2) += test2.o把这2个文件都放在test2目录下。
当然仅仅这样还不够,因为你并没有让内核编译关联到我们新创建的test2驱动。
在kernel/goldfish/drivers/Makefile中添加
4FC0D48C-4556-475A-BF46-4D11F38EFB7C.png在kernel/goldfish/arch/arm/Kconfig中添加
154A0CCE-5B28-4F5F-AE05-61FB90370921.png然后在kernel/goldfish下执行命令
make menuconfig
3.png然后找到对应的驱动,按键按
Y,启动该驱动,然后
exit退出保存。
4.png最后执行,make.
5.png成功生成zImage文件
查看驱动
启动android虚拟机,并指定内核为当前生成的内核。emulator -kernel kernel/goldfish/arch/arm/boot/zImage
然后执行adb shell进入android系统控制台
执行 cat /proc/devices
6.png可以看到test2驱动已经注册了。
但是,我们还希望能在/dev下有test2驱动,那么还需要在test_init最后面再添加2行代码。
struct class *cls = class_create(THIS_MODULE,"test2"); device_create(cls,NULL,devNo2,NULL,"test2");然后替换掉原来的test2.c文件,重新make生成zImage文件,然后再调用
emulator -kernel kernel/goldfish/arch/arm/boot/zImage 重新启动模拟器
就会发现,/dev下出现了test2驱动。
7.png
注意:如果调用emulator启动android模拟器的时候发现一直黑屏,无法启动,就说明可能是你的驱动编写出错了,导致android系统无法正常启动,你就需要认真检查下代码了。
驱动测试
既然驱动已经编写完毕,那么我们需要编写一个可执行文件,去访问一下驱动是否能正常运行,因为在我们后面的代码中,其实我们是直接在app中通过Service去访问Hal层,然后Hal层再调用底层驱动,所以我们必须保证驱动是正常的,否者到了整个过程都完成后,发现没有正常运行,会很难调试的。#include <fcntl.h> #include <stdio.h> int main(void){ char val = '1'; int fd = open("/dev/test2",O_RDWR); read(fd,&val,1); printf("test04 read %c\n",val); val = val+1; write(fd,&val,1); printf("test04 write %c\n",val); return 0; }
代码编译
需要编译成可执行文件,然后编译成system.img,然后启动虚拟机。Android.mk
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE_TAGS := optional LOCAL_SRC_FILES := test_test2.c LOCAL_MODULE := test_test2 include $(BUILD_EXECUTABLE)然后在external文件夹下新建test2,把Android.mk和test_test2.c这2个文件放入,然后使用
mmm编译成可执行模块。
屏幕快照 2017-03-27 14.57.54.png编译成功后,如图,会安装至
out/target/product/generic/system/bin
然后使用
make snod生成新的system.img,然后再启动android模拟器。
然后控制台输入
adb shell进入Android控制台。然后执行我们刚才生成的test_test2可执行文件。
效果就是把驱动里面保存的char读出来,然后+1再写入,如果驱动没有错误的话,则如下运行
屏幕快照 2017-03-27 15.04.49.png
HAL层实现
test2.h// // Created by javalong on 17/3/25. // #ifndef ANDROIDDRIVER_TEST2_H #define ANDROIDDRIVER_TEST2_H #include <hardware/hardware.h> struct test2_module_t { struct hw_module_t common; }; struct test2_device_t { struct hw_device_t common; int fd; int (*get_val)(struct test2_device_t *dev,int *val); int (*set_val)(struct test2_device_t *dev,int val); }; int test2_dri_open(const struct hw_module_t* module, const char* id, struct hw_device_t** device); #endif //ANDROIDDRIVER_TEST2_Htest2.c
// // Created by javalong on 17/3/25. // #include <hardware/hardware.h> #include <stdlib.h> #include <fcntl.h> #include <hardware/test2.h> #include <cutils/log.h> struct hw_module_methods_t test2_method = { open : test2_dri_open }; struct test2_module_t HAL_MODULE_INFO_SYM = { common : { tag : HARDWARE_MODULE_TAG, version_major : 1, version_minor : 0, id : "test2", name : "test2", author : "javalong", methods : &test2_method } }; int test2_get_val(struct test2_device_t *dev,int *val){ read(dev->fd,val, sizeof(val)); LOGI("test2_get_val %d",val); return val; } int test2_set_val(struct test2_device_t *dev,int val){ write(dev->fd,&val, sizeof(val)); LOGI("test2_set_val %d",val); return 0; } int test2_close(struct hw_device_t* device){ //关闭文件,释放内存 struct test2_device_t *dev = (struct test2_device_t *)device; close(dev->fd); free(dev); return 0; } int test2_dri_open(const struct hw_module_t* module, const char* id, struct hw_device_t** device){ struct test2_device_t *dev = malloc(sizeof(struct test2_device_t)); memset(dev,0, sizeof(struct test2_device_t)); dev->common.tag = HARDWARE_DEVICE_TAG; dev->common.version = 0; dev->common.module = module; dev->common.close = test2_close; dev->get_val = test2_get_val; dev->set_val = test2_set_val; *device = dev; if((dev->fd = open("/dev/test2",O_RDWR))== -1){ LOGI("fail open /dev/test2"); } return 0; }将test2.h 放入hardware/libhardware/inlcude下,然后再在hardware/libhardware/modules创建test2. 创建Android.mk
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE_TAGS := optional LOCAL_SRC_FILES := test2.c LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw LOCAL_MODULE := test.default LOCAL_PRELINK_MODULE := false LOCAL_SHARED_LIBRARIES := liblog include $(BUILD_SHARED_LIBRARY)然后将Android.mk和test2.c放入刚才新建的test2文件夹下。然后执行
mmm ./hardware/libhardware/modules/test2/
屏幕快照 2017-03-27 15.56.48.png这样就代表HAL层已经编译成功。
aidl实现
我们最终其实要实现的效果是实现一个SystemService,然后可以在自己的app中调用这个SystemService,而这个SystemService是跟我们的app处于不同的进程的,所以就必须要使用AIDL.package android.os; interface ITest2Service{ void setVal(int val); int getVal(); }这一步同样需要编译,把ITest2Service.aidl放入
framework/base/core/java/android/os下
然后在
framework/base下的Android.mk添加一行,让编译的时候把这个aidl也一起编译进去。
LOCAL_SRC_FILES += \ ... ... core/java/android/os/ITest2Service.aidl也是使用 mmm,我就不过多介绍了。
mmm ./frameworks/base/
service java实现
package com.android.server; import android.os.ITest2Service; /** * Created by javalong on 17/3/25. */ public class Test2Service extends ITest2Service.Stub { private int ptr = 0; Test2Service(){ ptr = init_test(); } public int getVal(){ return getVal_native(ptr); } public void setVal(int val){ setVal_native(ptr,val); } public native int init_test(); public native int getVal_native(int ptr); public native void setVal_native(int ptr,int val); }
service jni 实现
// // Created by javalong on 17/3/27. // #include <jni.h> #include "JNIHelp.h" #include <hardware/hardware.h> #include <android_runtime/AndroidRuntime.h> #include <hardware/test2.h> #include <utils/Log.h> namespace android{ static jint getVal(JNIEnv *env,jobject obj,jint ptr){ struct test2_device_t *dev = (struct test2_device_t *)ptr; int val = 0; dev->get_val(dev,&val); return val; } static void setVal(JNIEnv *env,jobject obj,jint ptr,jint val){ struct test2_device_t *dev = (struct test2_device_t *)ptr; dev->set_val(dev,val); } static jint test_init(JNIEnv *env,jobject obj){ struct test2_module_t *module; struct test2_device_t *device; hw_get_module("test2",(const struct hw_module_t **)&module); module->common.methods->open(&(module->common),"test2",(struct hw_device_t **)&device); return (jint)device; } static const JNINativeMethod method_table[] = { {"init_test","()I",(void *)test_init}, {"getVal_native","(I)I",(void *)getVal}, {"setVal_native","(II)V",(void *)setVal} }; int register_Test2_Service(JNIEnv *env){ jniRegisterNativeMethods(env,"com/android/server/Test2Service",method_table,NELEM(method_table)); return 0; } }
注册service和jni方法
上面的service和jni代码写好后,需要在对应的文件中注册。Test2Service需要在
framework/base/services/java/com/android/server/SystemServer.java注册
@Override public void run() { ... Slog.i(TAG, "Telephony Registry"); ServiceManager.addService("telephony.registry", new TelephonyRegistry(context)); ServiceManager.addService("test2", new Test2Service()); ...Test2Service.cpp 需要注册在
framework/base/services/jni/onload.cpp
extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved) { ... ... register_Test2_Service(env); }还需要在当前目录的Android.mk添加Test2Service.cpp
LOCAL_SRC_FILES:= \ ... ... Test2Service.cpp \ ...编译,生成新的system.img
android app调用测试
最后一步,写一个简单的app,获取到Test2Service,然后调用其方法。package com.example.javalong.myapplication; package com.example.javalong.myapplication; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.EditText; import android.widget.TextView; import android.os.ITest2Service; import android.widget.Toast; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); final ITest2Service testService = ITest2Service.Stub.asInterface(android.os.ServiceManager.getService("test2")); final EditText et_set = (EditText) findViewById(R.id.et_set); final TextView tv_get = (TextView) findViewById(R.id.tv_test); findViewById(R.id.bt_getval) .setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { try { Toast.makeText(MainActivity.this, "c:"+testService.getVal(), Toast.LENGTH_SHORT).show(); tv_get.setText(testService.getVal()+""); } catch (Throwable e) { Toast.makeText(MainActivity.this, "b:"+e, Toast.LENGTH_SHORT).show(); e.printStackTrace(); } } }); findViewById(R.id.bt_setval).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { try { testService.setVal(Integer.parseInt(et_set.getText().toString())); Toast.makeText(MainActivity.this, "d:"+Integer.parseInt(et_set.getText().toString()), Toast.LENGTH_SHORT).show(); } catch (Throwable e) { Toast.makeText(MainActivity.this, "e:"+e, Toast.LENGTH_SHORT).show(); e.printStackTrace(); } } }); } }这个app放在
packages/experimental/下,然后也是使用
mmm编译
mmm ./packages/experimental/app/
然后使用make snod,生成system.img,最后启动模拟器。
最终终于把整个流程走完了,看下运行效果。
屏幕快照 2017-03-27 19.13.04.png最终的效果应该是setVal 一个4 getVal也应该是一个4,但是当前不对,这是因为
/dev/test2驱动
屏幕快照 2017-03-27 19.15.27.png是不允许外部直接访问的,所以需要重新给该驱动设置权限。
由于篇幅太大了,我给个链接参考下
http://www.cnblogs.com/LoongEmbedded/p/5298388.html
最终重新启动模拟器就可以达到目标的效果了。
作者:javalong
链接:https://www.jianshu.com/p/fa36e5faea67
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
相关文章推荐
- OSChina App代码分析之摇一摇Demo
- 从注册流程 分析如何安全退出多个Activity 多种方式(附DEMO)
- AppWidget启动流程部分 Launcher分析
- flume监控spoolDir日志到HDFS整个流程小Demo
- android 应用监听输入法按键事件【比如搜索和回车键等】的整个流程分析
- Android Launcher分析和修改9——Launcher启动APP流程
- ios app上架整个流程
- 从注冊流程 分析怎样安全退出多个Activity 多种方式(附DEMO)
- 【IAP支付之一】In-App Purchase Walk Through 整个支付流程
- 从注册流程 分析如何安全退出多个Activity 多种方式(附DEMO)
- 微信app支付java后台流程、原理分析及nei网穿透 转载自(http://www.cnblogs.com/MrRightZhao/p/7930916.html)
- 【IAP支付之一】In-App Purchase Walk Through 整个支付流程
- 从注册流程 分析如何安全退出多个Activity 多种方式(附DEMO)
- servlet代码分析-整个执行流程
- Android Launcher分析和修改9——Launcher启动APP流程
- 从注册流程 分析如何安全退出多个Activity 多种方式(附DEMO)
- SealTalk App 流程分析(1)
- 从注册流程 分析如何安全退出多个Activity 多种方式(附DEMO)
- RIL层代码分析--拨号整个流程
- Android 当App内存不足或在后台运行时回收部分activity的流程分析。