參考老羅的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裝置檔案:
[email protected]:/ # cd dev
[email protected]:/dev # ls
進入到proc目錄,可以看到hello檔案:
[email protected]:/ # cd proc
[email protected]:/proc # ls
通路hello檔案的值:
[email protected]:/proc # cat hello
[email protected]:/proc # echo '55' > hello
[email protected]:/proc # cat hello
55
進入到sys/class目錄,可以看到hello目錄:
[email protected]:/ # cd sys/class
[email protected]:/sys/class # ls
進入到hello目錄,可以看到hello目錄:
[email protected]:/sys/class # cd hello
[email protected]:/sys/class/hello # ls
進入到下一層hello目錄,可以看到val檔案:
[email protected]:/sys/class/hello # cd hello
[email protected]:/sys/class/hello/hello # ls
通路屬性檔案val的值:
[email protected]:/sys/class/hello/hello # cat val
55
[email protected]:/sys/class/hello/hello # echo '101' > val
[email protected]:/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是在<hardware/hello.h>中定義的。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的類名上添加注釋:
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<android/log.h>
(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