Android HAL开发

参考老罗的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



你可能感兴趣的:(Android系统,Android,C,C++)