[置頂] 開發Android硬件訪問服務


開發Android硬件訪問服務

這篇文章主要研究兩方面的知識,一個是我們在使用已開發好的動態鏈接庫來訪問硬件設備的訪問權限問題,另一個就是為應用程序開發訪問硬件設備的硬件訪問服務,下面就進入正題。

Android硬件訪問權限處理:

在上面的“硬件抽象層”一篇中,我們知道硬件訪問服務會調用動態鏈接庫文件中的helloworld_device_open這個函數方法來打開欲訪問的硬件設備。可是,當我們執行這個方法之后,就會莫名的報出日志中的錯誤:failed to open /dev/helloworld ---permission denied,原因就是因為當前的用戶沒有訪問這個硬件設備的權限。無可置疑,解決這個問題的辦法就是改寫這個設備訪問的權限,使除了root之外的其他用戶也可以擁有打開這個設備的權限。

Android中提供了一種改變訪問設備文件權限的uevent機制,通過這個機制我們可以在系統啟動的時候來修改設備文件/dev/helloworld的訪問權限。這個訪問權限控制文件位於目錄:system/core/rootdir中,通過在文件ueventd.rc中添加一行內容,

內容為:/dev/helloworld  0666   root   root即可使所有用戶都擁有訪問這個設備文件的權限。當然,我們知道修改了源代碼文件,如果想使其生效,我們就得重新編譯修改的源代碼。但在這里面,我們通過另一種方式來達到不需重新編譯源代碼就可以使其修改生效的方法。原因就是在編譯源代碼的時候,系統會將文件system/core/rootdir/ueventd.rc拷貝到目錄out/target/product/generic/root中,這是一個根目錄,表示在系統啟動的時候,會把鏡像文件ramdisk.img中的ueventd.rc文件安裝在設備根目錄中,並調用init進行來解析和修改更新相應的設備文件的訪問權限,因此我們只需要修改ramdisk.img鏡像文件中的ueventd.rc文件即可。下面即為修改方法:

A、解壓ramdisk.img文件

鏡像文件ramdisk.img文件是一個gzip格式文件,我們可以使用命令gunzip來進行解壓,並保存在源代碼目錄Android中。

Android$ mv ./out/target/product/generic/ramdisk.img ./ramdisk.img.gz

Android$gunzip ./ramdisk.img.gz

B、還原ramdisk,img文件

解壓之后的ramdisk.img文件是一個cpio格式的歸檔文件,我們需要對其使用cpio命令來解除歸檔,並將解檔后的文件保存在目錄Android/ramdisk/目錄中。

Android$mkdir ramdisk

Android/ramdisk$cpio -i -F ../ramdisk.img

C、修改ueventd.rc文件

進入到Android/ramdisk/中,找到文件ueventd.rc文件,並在文件中添加一行內容:/dev/helloworld   0666    root   root來賦予所有用戶對這個設備的訪問操作權限。

D、重新打包ramdisk.rc文件

重新打包就是對AB的逆過程操作,操作如下:

Android/ramdisk$rm -f ../ramdisk.img

Android/ramdisk$find . | cpio -o -H newc > ../ramdisk.img.unzip

Android/ramdisk$cd ..

Android$gzip -c ./ramdisk.img.unzip > ./ramdisk.img.gz

Android$rm -f ./ramdisk.img.unzip

Android$rm -r ./ramdisk

Android$mv ./ramdisk.img.gz ./out/target/product/generic/ramdisk.img

經過上面的操作后,我們就成功得修改了鏡像文件中ueventd.rc配置文件,為所有用戶賦予了訪問設備/dev/helloworld文件的權限,那么下面我們就可以編寫對應的硬件訪問服務來調用open函數方法訪問設備了。

開發Android硬件訪問服務:

開發好硬件抽象層動態鏈接庫文件之后,我們需要在應用程序框架層中實現一個硬件訪問服務,為應用程序訪問設備提供服務接口。但需要注意的是框架層中的服務是由Java語言編寫的,而抽象層中動態鏈接庫文件是由C++語言編寫的,因此,我們需要解決跨語言的橋接需求。很慶幸,Android系統提供了Java本地接口(Java Native InterfaceJNI)機制來實現多語言之間的功能訪問橋接(在后面文章中會分析這個JNI的實現機制)。

解決了多語言之間的橋接之后,我們還面臨一個問題就是多進程間的通信了。原因是這樣的,在Android中硬件訪問服務一般是運行在系統進程system中的,而是用這些硬件訪問服務的應用程序是運行在其他的進行中的,因此,需要解決多進程之間的通信和資源共享需求了。又是一個值得慶幸的地方,聰明的人們也為Android多進程間通信提供了一種高效而易理解的機制,即為IBinder進程間通信機制(在后面的文章中分析它的機制,因為它很重要)。應用程序就是通過這個機制來訪問運行在系統進程system中的硬件訪問服務的。值得注意的是,IBinder進程通信機制要求服務的一方必須為使用方提供一個訪問接口來供調用方使用,這樣使用方就可以訪問這個硬件訪問服務了,那么首先就需要定義一個服務方接口了。

定義硬件訪問服務接口:

Android系統提供了一種用來解決跨進程訪問服務的描述性語言---Android接口描述語言(Android Interface Definition Language,AIDL)。用AIDL語言定義的服務接口的文件后綴為.aidl,在解析編譯的時候,系統會把這種類型的文件轉換為Java文件,供虛擬機編譯使用,那么下面就開始用AIDL 定義硬件訪問服務接口文件IHelloWorldService.aidl

Android中,一般會將AIDL定義的硬件訪問服務接口文件放入於目錄:framworks/base/core/java/android/os中,那么我們也將IHelloWorldService.aidl放在這里。下面為具體定義:

IHelloWorldService.aidl:

Package android.os;

Interface IHelloWorld {

Void setValue(int val);

Int getValue();

}

定義完接口后,由於這個接口是使用AIDL語言編寫,所以需要對其進行編譯,然后才能得到對應的Java文件。具體操作如下:

進入到框架根目錄:frameworks/base/中,打開里面的Android.mk編譯配置文件,修改里面的LOCAL_SRC_FILES變量的值。

LOCAL_SRC_FILES += \

....

Core/java/android/os/IHelloWorld.aidl

修改保存之后,我們就需要使用命令mmm來對這個硬件訪問服務接口定義文件進行編譯了。

Android$mmm ./frameworks/base/

編譯成功后,得到了一個framework.jar文件,下面對這個文件做簡單的介紹:

在這個jar文件中就包含有接口文件IHelloWorldService接口了,並且這個接口繼承自android.os.IInterface接口。在IHelloWorldService接口內部定義了一個IBinder本地對象類Stub,它同時也實現了接口IHelloWorldService,並繼承了android.os.Binder類。此外,在IHelloWorldService中還定義了一個Binder代理對象類Proxy,它也實現了IHelloWorldService接口文件。

正如前面說的,我們使用AIDL定義了一個服務訪問接口文件來實現進程間的通信,服務的一方稱之為server進程端,使用服務一方則為client進程端了。另外,在server進程中,每一個服務都對應着一個IBinder本地對象,並借助一個通信點(Stub)來等待使用服務方發送進程間通信的請求。而client端在訪問運行在sever中的服務時,首先要得到一個它的IBinder代理對象接口,然后通過這個代理對象接口來發送進程間通信的請求。下面即為具體實現(這個Java文件放在了frameworks/base/core/services/java/com/android/server中):

HelloWorldService.java:

package com.android.server;

import android.content.Context;

import android.os.IHelloworldService;

import android.util.Slog;

public class HelloworldService extends IHelloworldService.Stub {

private static final String TAG = "HelloworldService";

private int ptr = 0;

helloworldService() {

ptr = init_native();//調用JNI方法init_native打開設備,並得到對應設備句柄值

if (ptr == 0) {

Slog.e(TAG,"failed to init helloworld service.");

}

}

public void setVal(int val) {

if (ptr == 0) {

Slog.e(TAG,"helloworld service is not init.");

return;

}

setVal_native(ptr,val);//調用JNI方法setVal_native向硬件設備寫入值

}

public int getVal() {

if (ptr == 0 ) {

Slog.e(TAG,"helloworld service is not init.");

return 0;

}

return getVal_native(ptr);//調用JNI方法setVal_native從硬件設備值取值

}

//初始化JNI相關方法,並傳入值

private static native int init_native();

private static native void setVal_native(int ptr,int val);

private static native int getVal_native(int ptr);

}

編寫完硬件訪問服務之后,需要執行mmm命令來重新編譯系統的services模塊了。

Android$mmm ./frameworks/base/services/java/

編譯成功之后,得到了一個service.jar程序包,當然這個包就包好有我們編寫的HelloWorldService.java類了。下面繼續分析硬件訪問服務HelloWorldServiceJNI實現了。

HelloWorldServiceJNI實現:

Android中,一般把硬件訪問服務JNI方法放在目錄:frameworks/base/services/jni/中,那么我們也將HelloWorldServiceJNI實現文件放於這個目錄中,同時,文件的命名規范也遵循着Android的命名規范習慣。

Com_android_server_HelloWorldService.cpp

#define LOG_TAG "HelloWorldServiceJNI"

#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/helloworld.h>

#include <stdio.h>

namespace android

{

/*設置硬件設備的寄存器的值*/

static void helloworld_setVal(JNIEnv* env, jobject clazz, jint ptr, jint value) {

helloworld_device_t* device = (helloworld_device_t*)ptr;

if(!device) {

LOGE("device helloworld is not open.");

return;

}

int val = value;

LOGI("Set value %d to device helloworld.", val);

device->set_val(device, val);

}

/*讀取硬件設備寄存器的值*/

static jint helloworld_getVal(JNIEnv* env, jobject clazz, jint ptr) {

helloworld_device_t* device = (helloworld_device_t*)ptr;

if(!device) {

LOGE("device helloworld is not open.");

return 0;

}

int val = 0;

device->get_val(device, &val);

LOGI("Get value %d from device helloworld.", val);

return val;

}

/*打開虛擬硬件設備*/

static inline int helloworld_device_open(const hw_module_t* module, struct helloworld_device_t** device) {

return module->methods->open(module, helloworld_HARDWARE_device_ID, (struct hw_device_t**)device);

}

/*初始化虛擬硬件設備*/

static jint helloworld_init(JNIEnv* env, jclass clazz) {

helloworld_module_t* module;

helloworld_device_t* device;

LOGI("Initializing HAL stub helloworld......");

//加載導入硬件抽象層模塊helloworld

if(hw_get_module(helloworld_HARDWARE_MODULE_ID, (const struct hw_module_t**)&module) == 0) {

LOGI("device helloworld found.");

if(helloworld_device_open(&(module->common), &device) == 0) {

LOGI("device helloworld is open.");

return (jint)device;

}

LOGE("Failed to open device helloworld.");

return 0;

}

LOGE("Failed to get HAL stub helloworld.");

return 0;

}

/*Java本地接口方法列表*/

static const JNINativeMethod method_table[] = {

{"init_native", "()I", (void*)helloworld_init}, 

{"setVal_native", "(II)V", (void*)helloworld_setVal},

{"getVal_native", "(I)I", (void*)helloworld_getVal},

};

/*注冊Java本地接口方法*/

int register_android_server_helloworldService(JNIEnv *env) {

     return jniRegisterNativeMethods(env, "com/android/server/helloworldService", method_table, NELEM(method_table));

}

};

注意:在使用helloworld_setValhelloworld_getVal方法時,使用者需要且必須先使用JNI方法helloworld_init來打開硬件設備,這樣就可以獲得對應的helloworld_devict_t接口。

具體可以參看上面的注釋說明,當然,有些關於JNI的說明不是很多,這個沒關系。在后面的文章中,我會繼續分析JNI的實現。這里只是演示了實現的流程,把握Android系統的執行原理。

編寫完硬件訪問服務HelloWorldServiceJNI實現之后,需要修改jni目錄下的onload.cpp文件,因為這個文件的實現是在libandroid_servers模塊中的。而當系統加載這個模塊的時候,就會初始化執行onload.cpp文件中的JNI_Onload函數。這樣就可以將定義的init_nativesetVal_nativegetVal_native三個方法注冊到Java虛擬機中了。然后,我們切換到目錄:frameworks/base/services/jni/中,打開文件Android.mk,修改變量:

LOCAL_SRC_FILES += \

...

Onload.cpp

同樣修改保存之后,執行命令mmm進行編譯工作:

Android$mmm ./fremeworks/base/services/jni/

編譯后得到修改模塊的jar包,那么這個包中就含有我們實現的方法init_nativesetVal_nativegetVal_native了。

 

搞定了多語言橋接之后,等着我們的問題就是如何解決跨進程通行的機制了。原因在上面已經說明了,就是因為硬件訪問服務一般是運行在系統system進程中,而欲使用這個服務的應用程序進程是位於其他進程中,解決辦法已在上面列出。接下來就是如何啟動這個硬件訪問服務了。具體如下:

我們在“Android系統體系結構分析”中也已分析了,系統進程system是由進程Zygote(我們稱之為應用程序孵化器,這個會在以后的文章分析)來啟動的。而這個孵化器是在系統啟動的時候啟動的,因此,就會把硬件訪問服務運行在了系統進程system中,即實現開啟啟動了。

那么下面即為如何將硬件訪問服務運行在系統進程system中,首先進入到frameworks/base/services/java/com/android/server/中,打開修改里面的ServerThread進程類。具體如下:

Public void run() {

...

If (factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {

...

Try {

Slog.i(TAG,”Helloworld SERVICE start”);

ServiceManager.addService(“helloworld”,new HelloWorldService());

}catch(Throwable a) {

Slog.e(TAG,”failed to start helloworld service”,e);

}

...

...

}

上面有提到一個ServiceManager這個類,它的作用就是管理和協調Android系統中的IBinder進程間通信機制的很重要的角色,它負責管理系統中的服務對象。注冊到ServiceManager中的服務對象都有一個服務名字,這些服務名字就用來供應用程序使用來向ServiceManager申請對應的IBinder代理對象接口,以便可以訪問對應的硬件服務。而硬件訪問服務注冊完成之后,它的啟動就結束了。

最后,我們需要執行命令mmm 來編譯services模塊。

Android$mmm ./frameworks/base/services/java/

編譯后得到的service.java文件包就含有我們自己編寫的HelloWorldService訪問服務了,並且在系統啟動的時候,會將服務的執行放在系統進程system中。走到這里,我們的硬件訪問服務就完全的編寫完成了,我們可以使用make snod重新打包Android系統鏡像文件system.img了,使我們的硬件訪問服務運行在system中生效。

為了演示我們的整個流程思路,在下一篇會開發一個應用程序驗證使用硬件訪問服務訪問設備的正確性。

本人剛創建了一個QQ群,目的是共同研究學習Android,期待興趣相投的同學加入!!

群號:179914858


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com