关于Android的HAL的一些理解
之前一直在學習基于Linux內核的一些字符型驅動的編程,對Linux內核驅動也算有了一些基本的了解吧,后來也做過一些基于Linux內核的驅動開發,像基于Android的CC1101高頻模塊的驅動開發,以及基于V4L2的USB攝像頭開發。但是還是一直都沒有用到過Android的HAL模塊,現在整理一下。?
說到HAL,我想目前市面上關于這方面的書應該也有不少,或者隨便到網上一搜,都是一大把。但是作為一個只了解了一點Linux驅動方面的知識,懂一點初級的C語言,要完全了解Android的HAL還是有一定困難的,下面我也就將我在這一塊理解的一些心得同大家分享。?
HAL(Hardware Abstraction Layer),中文是硬件抽象層,也就是說是對硬件的一種抽象。在我們之前所開發的Linux驅動程序當中,在編寫好驅動程序之后會在/dev的目錄下生成相應的設備文件,然后如果該驅動程序是應用在Android系統里面的話,可能還要編寫相應的NDK(jni)部分,使之生成動態鏈接庫(.so文件),以方便上層的java程序調用。整個模型如下圖所示。?
?
這個模型圖的結構比較簡單,也很清晰。我想大家也很清楚能夠看懂。既然這樣,為什么還要加入HAL呢,個人認為主要有以下幾點原因:
協議方面的原因,因為我們知道所有的Linux程序都必須要遵循GPL協議,也就是全部開源協議。而對于有些企業和個人其并不想完全將自己的勞動成果或者是知識產權吧完全公開,所以就在Linux驅動程序的基礎之上,在弄了一個HAL層。由于對于一個驅動程序來說,其主要包括兩個部分,訪問寄存器的代碼和業務邏輯代碼。而對于訪問硬件寄存器的代碼,并沒有什么秘密可言,無非就是一些Linux內核向寄存器發號施令的標準函數(如ioread32、iowrite32等),所以一個驅動的核心部分應該是在業務邏輯代碼上,而開發者將這些業務邏輯的代碼放在HAL層,由于HAL層屬于用戶空間部分,所以并不要遵循Linux的GPL協議,因而HAL便得以廣泛應用。
統一調用接口,我們知道Linux驅動程序的調用接口復雜,不統一,而HAL提供了標準的調用接口,這樣很方便
針對一些特殊要求,例如有些硬件,可能需要訪問用戶空間的資源,而由于Linux驅動程序是放在內核空間的,所以加入存放用戶空間的HAL有利于對用戶空間資源的訪問。
通過上面的一些分析,我想應該也大概對HAL有一些了解了,現在我給出整個HAL模型圖。?
?
看到這個圖,大家一定會想,怎么又多了一個Service程序庫過來呀,確實在HAL模型剛剛出來的那會兒,的確沒有這么一個Service程序庫部分。而沒有Service程序的HAL構架雖然已經將訪問寄存器的代碼和業務邏輯代碼區分開了,但是其仍然有很多的問題,就是其還是一個孤立與Android系統之外的一個部分,沒有與Android系統本身融為一體,根本就沒有發揮出HAL的強大優勢。所以用于調用HAL程序庫的Service層便出現了。?
下面我就以一個非常常見的led燈的例子,來講解HAL框架。?
第一部分?
Linux內核層(Linux內核驅動程序),相對于沒有HAL框架的Linux內核驅動程序,有HAL框架的Linux驅動程序就顯得結構比較簡單了,無非就是對寄存器的一些簡單操作。
?
上面驅動程序的兩個頭文件是:?
s3c6410_leds_hal.h
?
leds_hal_define.h
#define S3C6410_LEDS_HAI_WRITE_GPMPUD 1 #define S3C6410_LEDS_HAI_WRITE_GPMCON 2 #define S3C6410_LEDS_HAI_WRITE_GPMDAT 3 #define S3C6410_LEDS_HAI_READ_GPMPUD 4 #define S3C6410_LEDS_HAI_READ_GPMCON 5 #define S3C6410_LEDS_HAI_READ_GPMDAT 6?
由于這里的Linux驅動程序沒有采用platform設備驅動模型,所以驅動程序都需要自己編寫腳本文件,使之加載進Linux內核。
第二部分?
HAL層,這一層是處于用戶空間,這也是為什么,在上面的Linux驅動程序的頭文件中s3c6410_leds_hal.h和leds_hal_define.h并沒有寫在一個文件里面,因為在用戶空間中有些Linux內核的頭文件,在用戶空間是使用不了的。而我們的HAL層則需要leds_hal_define.h這個頭文件。?
在給出HAL層的代碼之前,我想先簡單向大家分析一下HAL層的整個代碼結構。而HAL層的結構其實就是由三個關鍵點結構體貫穿,因此要理解HAL層的代碼,只需要了解三個關鍵的結構體就行,這三個結構體分別是:
?
而這三個結構體的關系如下圖所示:?
?
下面分別介紹一下這三個結構體?
首先是hw_module_t,該結構體用于描述HAL模塊,可以說是HAL層的入口,因為想要知道HAL的所有信息,都要通過hw_module_t結構體,上層的Service在調用該HAL模塊時,也是要首先找到該模塊的module_ID,hw_module_t定義在/hardware/libhardware/include/hardware/hardware.h文件中,其定義如下
?
由于HAL規定不能直接使用這個結構體,因此我們在實際編寫代碼過程中,對要對這個結構體進行封裝,或者說是做一個結構體的繼承吧。?
接下來是hw_device_t,這個結構體便是描述HAL設備的,HAL不是叫硬件抽象層嘛,該結構體便是對這一稱謂的集中展示。讓我們來看看hw_device_t長什么樣,
?
最后一個便是hw_module_method_t,這個函數呢,是屬于前面兩個結構體之間的一個橋梁,也相當于HAL設備的一個入口,因為其里面open成員變量函數,通過該函數就可以做一些打開設備文件啦、初始化hw_device_t等工作啦。
typedef struct hw_module_methods_t { //該結構體唯一的一個成員,一個open函數指針,注意其里面的參數,最后一個參數用了指針的指針,作用很大 //后面會講到 int(* open )(const struct hw_module_t *module, const char *id, struct hw_device_t **device) }?
接下來就附上完整的HAL層的源代碼啦
#include "leds_hal.h" #include "../leds_hal_define.h"int dev_file = 0;// on_off: 1表示開,0表示關 int led_on_off(struct led_control_device_t *dev, int32_t led, int32_t on_off) { if (led >= 0 && led <= 3) { if (on_off == 1) LOGI("LED Stub:set %d on", led); else LOGI("LED Stub:set %d off", led); unsigned char buf[5]; buf[0] = S3C6410_LEDS_HAI_READ_GPMDAT; write(dev_file, buf, 5); read(dev_file, buf, 5); buf[0] = S3C6410_LEDS_HAI_WRITE_GPMDAT; // 修改GPMDAT寄存器的值 switch (led) { case 0: if (on_off == 1) // 打開 buf[4] &= 0xFE; // 11111110 else if (on_off == 0) // 關閉 buf[4] |= 0x1; // 00000001 break; case 1: if (on_off == 1) // 打開 { buf[4] &= 0xFD; // 11111101 } else if (on_off == 0) // 關閉 { buf[4] |= 0x2; // 00000010 } break; case 2: if (on_off == 1) // 打開 buf[4] &= 0xFB; // 11111011 else if (on_off == 0) // 關閉 buf[4] |= 0x4; // 00000100 break; case 3: if (on_off == 1) // 打開 buf[4] &= 0xF7; // 11110111 else if (on_off == 0) // 關閉 buf[4] |= 0x8; // 00001000 break; } //為什么這里只給buf[4]賦值,是由于在Linux驅動程序中的那個bytes_to_int函數導致的。 write(dev_file, buf, 5); } else { LOGI("LED Stub: set led %d on error,no this led", led); } return 0; } int led_on(struct led_control_device_t *dev, int32_t led) { return led_on_off(dev, led, 1); } int led_off(struct led_control_device_t *dev, int32_t led) { return led_on_off(dev, led, 0); } int led_device_close(struct hw_device_t* device) { struct led_control_device_t* ctx = (struct led_control_device_t*) device; if (ctx) { free(ctx); } close(dev_file); return 0; } static void leds_init_gpm() { int tmp = 0; // 初始化端口配置寄存器 unsigned char buf[5]; buf[0] = S3C6410_LEDS_HAI_READ_GPMCON; write(dev_file, buf, 5); read(dev_file, buf, 5); buf[3] |= 0x11; buf[4] |= 0x11; buf[0] = S3C6410_LEDS_HAI_WRITE_GPMCON; write(dev_file, buf, 5); // 初始化端口上拉電路寄存器 buf[0] = S3C6410_LEDS_HAI_READ_GPMPUD; write(dev_file, buf, 5); read(dev_file, buf, 5); buf[4] |= 0xAA; buf[0] = S3C6410_LEDS_HAI_WRITE_GPMPUD; write(dev_file, buf, 5); } static int led_device_open(const struct hw_module_t* module, const char* name, struct hw_device_t** device) { struct led_control_device_t *dev; dev = (struct led_control_device_t *) malloc(sizeof(*dev)); memset(dev, 0, sizeof(*dev)); dev->hw_device.tag = HARDWARE_DEVICE_TAG; dev->hw_device.version = 0; dev->hw_device.module = (struct hw_module_t*) module; dev->hw_device.close = led_device_close; dev->set_on = led_on; dev->set_off = led_off; //*device = &dev->hw_device; //*dev強制類型轉換,即這里向父結構體hw_device_t轉換,關于這一點,我會專門寫一篇文章來講解 *device = (hw_device_t*)dev; //打開設備文件 dev_file = open("/dev/s3c6410_leds_hal", O_RDWR); if (dev_file < 0) { LOGI("LED Stub: open /dev/s3c6410_leds_hal fail."); } else { LOGI("LED Stub: open /dev/s3c6410_leds_hal success ."); } leds_init_gpm(); return 0; } //將open函數指針指向自定義的led_device_open static struct hw_module_methods_t led_module_methods = { open: led_device_open }; /** 這里給led_module_t結構體變量命名只能是HAL_MODULE_INFO_SYM,這是HAL規定的。 */ struct led_module_t HAL_MODULE_INFO_SYM = { hw_module: { tag: HARDWARE_MODULE_TAG, version_major: 1, version_minor: 0, id : LED_HARDWARE_MODULE_ID, name: "Sample LED HAL Stub", author: "Lining", //初始化led_module_method methods: &led_module_methods, } };?
上面的HAL程序用到的頭文件源碼?
led_hal.h文件
?
這里還要提一下HAL層代碼的編譯問題,該層代碼最終要編譯成led_hal.default.so文件,并且要使用adb工具將該.so文件上傳至開發板的/system/lib/hw目錄下,HAL模塊的.so文件一般都是放在這個目錄下,后面會解釋為什么這么放置,同時也會解釋為什么該.so文件后面會有一個default的后綴
第三部分,Service層?
終于可以講到Service Library層啦,Service Library層應該是上層的應用程序訪問HAL層的一個橋梁,盡管在以前的舊HAL框架上,并沒有這一部分,但新的HAL框架都需要我們加入Servicer Library。剛剛我們說了Service Library是上層Android應用程序和下層HAL層的一個連接的橋梁,那么它是如何發揮橋梁作用的呢?因為要完成對HAL的訪問,我們不能像之前上層應用程序訪問設備文件一樣,通過open函數打開設備文件,然后返回該文件的句柄,最后再通過該句柄完成其它函數的操作。而HAL層是通過hw_module_t這樣一個結構體對外提供接口,因此在Service Library我們要使用一個非常重要的函數hw_get_module函數,通過該函數就可以在上面的led_hal.h文件中所定義的LED_HARDWARE_MODULE_ID來查找相應的LED HAL模塊。我們知道剛剛上面的HAL層,我們都是采用C語言編寫的,而上層應用程序有是用JAVA編寫,因此這里我們應該需要有一個JNI Library(也就是NDK程序),來完成C語言和JAVA之間的對接。?
?
下面給出Service Library層的源代碼
?
下面附上Android.mk程序
# Android.mk LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE_TAGS := eng //定義該共享庫的名稱,因而后面就會生成.so文件 LOCAL_MODULE:= led_hal_jni #指定編譯完成后,其.so文件存放的路徑,如果不指定,就會編譯進默認目錄 LOCAL_MODULE_PATH := /root/drivers/s3c6410_leds_hal/leds_hal_jni #指定源文件 LOCAL_SRC_FILES:= LedHalService.cpp #指定共享庫的位置 LOCAL_SHARED_LIBRARIES := \ libandroid_runtime \ libcutils \ libhardware \ libhardware_legacy \ libnativehelper \ libsystem_server \ libutils \ libui \ libsurfaceflinger_client #指定頭文件位置(或者叫頭文件的搜索路徑)這里有兩個路徑,JNI_H_INCLUDE和hardware/leds_hal LOCAL_C_INCLUDES += \ $(JNI_H_INCLUDE) \ hardware/leds_hal #指定預鏈接模式 LOCAL_PRELINK_MODULE := false #生成共享庫(.so文件) include $(BUILD_SHARED_LIBRARY)?
用于編譯的build.sh腳本
source ~/drivers/common.sh cd $OK6410_ANDROID_SRC_PATH source ./build/envsetup.sh cd $OK6410_ANDROID_SRC_PATH/frameworks/base/services/leds_hal_jni mm cd /root/drivers/s3c6410_leds_hal/leds_hal_jni # cp $OK6410_ANDROID_SRC_PATH/out/target/product/generic/obj/lib/led_hal_jni.so . find_devices if [ "$selected_device" == "" ]; then exit else #將生成的led_hal_jni.so文件上傳至/system/lib目錄下 adb -s $selected_device push ./led_hal_jni.so /system/lib | echo "已成功上傳到$selected_device" fi?
到現在為止基于HAL的LED驅動就差不多編譯完成了,其實到現在就可以在你的Android應用程序當中通過NDK調用上一步的Service Library,從而調用后面的HAL和驅動程序。當然我們在這一步也可以采用更加靈活,或者說是更易于應用程序使用的方式,即將調用Service程序庫的java類封裝在jar文件中,這樣做,任何Android應用程序只要引用了這個jar文件就可以向調用普通Java類一樣訪問LED驅動了。當然這里還有另外一種方式就是采用ServiceManager的方式,此種方式更加符合目前主流Android編程的規范,但是相對前者,編寫起來稍微復雜一些。后面我也會專門寫一篇文章來講解該方式。
轉載于:https://www.cnblogs.com/jiangzhaowei/p/8431122.html
總結
以上是生活随笔為你收集整理的关于Android的HAL的一些理解的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 使用PXE+NFS EFI引导安装RHE
- 下一篇: Angular——单页面与路由的使用
