1. 程式人生 > >Android系統kernel到APP整個流程demo分析

Android系統kernel到APP整個流程demo分析

一直想深入Android底層開發,首先就從寫一個完整的HAL層開發demo開始吧,步驟確實有很多,對我們這種不熟悉c/c++開發的人來說,確實是很痛苦,我看這簡單的demo都要理解半天。下面我就一步步的來實現HAL層開發,附程式碼。

我這裡簡單的歸納了下,一共8大步驟

  1. linux驅動實現
  2. 驅動測試
  3. hal層實現
  4. aidl實現
  5. service java實現
  6. service jni 實現
  7. 註冊service和jni方法
  8. 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 n

Makefile:

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_H

test2.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

是不允許外部直接訪問的,所以需要重新給該驅動設定許可權。

最終重新啟動模擬器就可以達到目標的效果了。