Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android開發實例 >> [Android的系統移植與平台開發]HAL Stub框架分析

[Android的系統移植與平台開發]HAL Stub框架分析

編輯:Android開發實例

1.      HAL Stub框架分析

HAL stub的框架比較簡單,三個結構體、兩個常量、一個函數,簡稱321架構,它的定義在:

@hardware/libhardware/include/hardware/hardware.h

@hardware/libhardware/hardware.c

  1. /*   
  2. 每一個硬件都通過hw_module_t來描述,我們稱之為一個硬件對象。你可以去“繼承”這個hw_module_t,然後擴展自己的屬性,硬件對象必須定義為一個固定的名字:HMI,即:Hardware Module Information的簡寫,每一個硬件對象裡都封裝了一個函數指針open用於打開該硬件,我們理解為硬件對象的open方法,open調用後返回這個硬件對應的Operation interface。  
  3. */ 
  4. struct hw_module_t{  
  5.     uint32_t tag;           // 該值必須聲明為HARDWARE_MODULE_TAG  
  6.     uint16_t version_major; // 主版本號  
  7.     uint16_t version_minor;     // 次版本號  
  8.     const char *id;         //硬件id名,唯一標識module  
  9.     const char *name;       // 硬件module名字  
  10.     const char * author;        // 作者  
  11.     struct hw_module_methods_t* methods;    //指向封裝有open函數指針的結構體  
  12.     void* dso;              // module’s dso  
  13.     uint32_t reserved[32-7];    // 128字節補齊  
  14. };  
  15.  
  16. /*   
  17. 硬件對象的open方法描述結構體,它裡面只有一個元素:open函數指針  
  18. */ 
  19. struct hw_module_methods_t{  
  20.     // 只封裝了open函數指針  
  21.     int (*open)(const struct hw_module_t* module, const char * id,  
  22.         struct hw_device_t** device);  
  23. };  
  24.  
  25. /*  
  26. 硬件對象hw_module_t的open方法返回該硬件的Operation interface,它由hw_device_t結構體來描述,我們稱之為:該硬件的操作接口  
  27. */ 
  28. struct hw_device_t{  
  29.     uint32_t tag;               // 必須賦值為HARDWARE_DEVICE_TAG  
  30.     uint32_t version;               // 版本號  
  31.     struct hw_module_t* module; // 該設備操作屬於哪個硬件對象,可以看成硬件操作接口與硬件對象的聯系  
  32.     uint32_t reserved[12];          // 字節補齊  
  33.     int (*close)(struct hw_device_t* device);   // 該設備的關閉函數指針,可以看做硬件的close方法  
  34. };  
  35.  

上述三個結構之間關系緊密,每個硬件對象由一個hw_module_t來描述,只要我們拿到了這個硬件對象,就可以調用它的open方法,返回這個硬件對象的硬件操作接口,然後就可以通過這些硬件操作接口來間接操作硬件了。只不過,open方法被struct hw_module_methods_t結構封裝了一次,硬件操作接口被hw_device_t封裝了一次而已。

那用戶程序如何才能拿到硬件對象呢?

答案是通過硬件id名來拿。

我們來看下321架構裡的:兩個符號常量和一個函數:
 

 

 
  1. // 這個就是HAL Stub對象固定的名字  
  2. #define HAL_MODULE_INFO_SYM             HMI  
  3. // 這是字符串形式的名字  
  4. #define HAL_MODULE_INFO_SYM_AS_STR      "HMI"  
  5. //這個函數是通過硬件名來獲得硬件HAL Stub對象  
  6. int hw_get_module(const char *id, const struct hw_module_t **module);  
  7.  

 

當用戶調用hw_get_module函數時,第一個參數傳硬件id名,那麼這個函數會從當前系統注冊的硬件對象裡查找傳遞過來的id名對應的硬件對象,然後返回之。

從調用者的角度,我們基本上沒有什麼障礙了,那如何注冊一個硬件對象呢?

很簡單,只需要聲明一個結構體即可,看下面這個Led Stub注冊的例子:

  1. const struct led_module_t HAL_MODULE_INFO_SYM = {  
  2.     common: {   // 初始化父結構hw_module_t成員  
  3.         tag: HARDWARE_MODULE_TAG,  
  4.         version_major: 1,  
  5.         version_minor: 0,  
  6.         id: LED_HARDWARE_MODULE_ID,  
  7.         name: "led HAL Stub",  
  8.         author: "farsight",  
  9.         methods: &led_module_methods,  
  10.     },   
  11.     // 擴展屬性放在這兒  
  12. };  
  13.  

 

對,就這麼簡單,我們只需要聲明一個結構體led_moduel_t,起名叫HAL_MODULE_INFO_SYM,也就是固定的名字:HMI,然後將這個結構體填充好就行了。led_module_t又是什麼結構體類型啊?前面分析hw_modult_t類型時說過,我們可以“繼承”hw_module_t類型,創建自己的硬件對象,然後自己再擴展特有屬性,這裡的led_module_t就是“繼承”的hw_module_t類型。注意,繼承加上了雙引號,因為在C語言裡沒有繼承這個概念:

  1. struct led_module_t {  
  2.     struct hw_module_t common;  
  3. };  
  4.  

結構體led_module_t封裝了hw_module_t結構體,也就是說led_module_t這個新(子)結構體包含了舊(父)結構體,在新結構體裡可以再擴展一些新的成員。結構體本身就具有封裝特性,這不就是面向對象的封裝和繼承嗎!為了顯得專業點,我們用UML描述一下:

 

 

 

在上面的類圖裡,把hw_module_methods_t裡封裝的open函數指針指針寫成open方法。

該open方法既:methods,自然也被子結構體給“繼承”下來,我們將它初始化為led_module_methods的地址,該結構是hw_module_methods_t類型的,其聲明代碼如下:

  1. static struct hw_module_methods_t led_module_methods = {  
  2.     open: led_device_open    
  3. };  
  4.  

簡潔,我喜歡!!,它裡面僅有的open成員是個函數指針,它被指向led_device_open函數:

  1. static int led_device_open(const struct hw_module_t* module, const char* name,  
  2.     struct hw_device_t** device)  
  3. {  
  4.     struct led_device_t *led_device;  
  5.     LOGI("%s E ", __func__);  
  6.     led_device = (struct led_device_t *)malloc(sizeof(*led_device));  
  7.     memset(led_device, 0, sizeof(*led_device));   
  8.  
  9.     // init hw_module_t  
  10.     led_device->common.tag= HARDWARE_DEVICE_TAG;  
  11.     led_device->common.version = 0;  
  12.     led_device->common.module= module;  
  13.     led_device->common.close = led_device_close;   
  14.  
  15.     // init operation interface  
  16.     led_device->set_on= led_set_on;  
  17.     led_device->set_off= led_set_off;  
  18.     led_device->get_led_count = led_getcount;  
  19.     *device= (struct hw_device_t *)led_device;  
  20.  
  21.     if((fd=open("/dev/leds",O_RDWR))==-1)  
  22.     {  
  23.         LOGI("open error");  
  24.         return -1;  
  25.     }else 
  26.     LOGI("open ok\n");  
  27.  
  28.     return 0;  
  29. }  
  30.  

 

 

 

 

 

led_device_open函數的功能:

Ø  分配硬件設備操作結構體led_device_t,該結構體描述硬件操作行為

Ø  初始化led_device_t的父結構體hw_device_t成員

Ø  初始化led_device_t中擴展的操作接口

Ø  打開設備,將led_device_t結構體以父結構體類型返回(面向對象裡的多態)

hw_module_t與hw_module_methods_t及硬件open函數的關系如下:


我們來看下led_device_t和其父結構體hw_device_t的關系:
 

 
  1. struct led_device_t {  
  2.     struct hw_device_t common;   // led_devict_t的父結構,它裡面只封裝了close方法  
  3.     // 下面三個函數指針是子結構led_device_t對父結構hw_device_t的擴展,可以理解為子類擴展了父類增加了三個方法  
  4.     int (*getcount_led)(struct led_device_t *dev);  
  5.     int (*set_on)(struct led_device_t *dev);  
  6.     int (*set_off)(struct led_device_t *dev);  
  7. };  
  8.  

用UML類圖來表示:

 

 

由類圖可知,led_device_t擴展了三個接口:seton(), setoff(),get_led_count()。

那麼剩下的工作就是實現子結構中新擴展的三個接口了:

 

 

 

  1. static int led_getcount(struct led_control_device_t*dev)  
  2. {  
  3.          LOGI("led_getcount");  
  4.          return 4;  
  5. }  
  6.    
  7. static int led_set_on(struct led_control_device_t *dev)  
  8. {     
  9.          LOGI("led_set_on");  
  10.          ioctl(fd,GPG3DAT2_ON,NULL);  
  11.          return 0;  
  12. }  
  13.    
  14. static int led_set_off(struct led_control_device_t*dev)  
  15. {  
  16.          LOGI("led_set_off");  
  17.          ioctl(fd,GPG3DAT2_OFF,NULL);  
  18.          return 0;  
  19. }  

這三個接口函數直接和底層驅動打交道去控制硬件,具體驅動部分我們不去講,那是另外一個體系了。

總結一下:

我們有一個硬件id名,通過這個id調用hw_get_module(char*id, struct hw_module_t **module),這個函數查找注冊在當前系統中與id對應的硬件對象並返回之,硬件對象裡有個通過hw_module_methods_t結構封裝的open函數指針,回調這個open函數,它返回封裝有硬件操作接口的led_device_t結構體,這樣我們可以通過這個硬件接口去間接的訪問硬件了。

在這個過程中hw_get_module返回的是子結構體類型led_module_t,雖然函數的第二個參數類型為hw_module_t的父類型,這裡用到了面向對象裡的多態的概念。

下面還有一個問題我們沒有解決,為什麼我們聲明了一個名字為HMI結構體後,它就注冊到了系統裡?hw_get_module函數怎麼找到並返回led_module_t描述的硬件對象的?

 

殺雞取卵找HAL Stub

如果要知道為什麼通過聲明結構體就將HALStub注冊到系統中,最好的方法是先知道怎麼樣通過hw_get_module_t來找到注冊的硬件對象。

我們分析下hw_get_module函數的實現:

 

  1. static const char *variant_keys[] = {  
  2.     “ro.hardware”,  
  3.     “ro.product.board”,  
  4.     “ro.board.platform”,  
  5.     “ro.arch”  
  6. };  
  7. // 由上面定義的字符串數組可知,HAL_VARIANT_KEYS_COUNT的值為4  
  8. struct constint HAL_VARIANT_KEYS_COUNT = (sizeof(variant_keys)/sizeof(variant_keys[0]));  
  9.  
  10. int hw_get_module(const char *id, const struct hw_module_t **module){  
  11.     // 調用3個參數的hw_get_module_by_class函數  
  12. return hw_get_module_by_class(id, NULL, module);  
  13. }  
  14.  
  15. int hw_get_module_by_class(const char *class_id, const char *inst,   
  16. const struct hw_module_t **module){  
  17.     int status;  
  18.     int i;  
  19.     // 聲明一個hw_module_t指針變量hmi  
  20.     const struct hw_module_t *hmi = NULL;  
  21.     char prop[PATH_MAX};  
  22.     char path[PATH_MAX];  
  23.     char name[PATH_MAX];  
  24.     // 由前面調用函數可知,inst = NULL,執行else部分,將硬件id名拷貝到name數組裡  
  25.     if(inst)  
  26.         snprintf(name, PATH_MAX, “%s.%s”, class_id, inst);  
  27.     else 
  28.         strlcpy(name, class_id, PATH_MAX);  
  29.     // i 循環5次  
  30.     for(i=0; i<HAL_VARIANT_KEYS_COUNT+1; i++){  
  31.         if(i<HAL_VARIANT_KEYS_COUNT){  
  32.             // 從系統屬性裡依次查找前面定義的4個屬性的值,找其中一個後,執行後面代碼,找不到,進入else部分執行  
  33.             if(property_get(variant_keys[i], prop, NULL) == 0){  
  34.                 continue;  
  35.             }  
  36.             // 找到一個屬性值prop後,拼寫path的值為:/vendor/lib/hw/硬件id名.prop.so  
  37.             snprintf(path, sizeof(path), “%s/%s.%s.so”,  
  38.                 HAL_LIBRARY_PATH2, name, prop);  
  39.             if(access(path, R_OK) ==0) break;   // 如果path指向有效的庫文件,退出for循環  
  40.             // 如果vendor/lib/hw目錄下沒有庫文件,查找/system/lib/hw目錄下有沒有:硬件id名.prop.so的庫文件  
  41.             snprintf(path, sizeof(path), “%s/%s.%s.so”,  
  42.                 HAL_LIBRARY_PATH1, name, prop);  
  43.             If(access(path, R_OK) == 0) break;  
  44.         } else {  
  45.             // 如果4個系統屬性都沒有定義,則使用默認的庫名:/system/lib/hw/硬件id名.default.so  
  46.             snprintf(path, sizeof(path), “%s/%s.default.so”,  
  47.                 HAL_LIBRARY_PATH1, name);  
  48.             If(access(path, R_OK) == 0) break;  
  49.         }  
  50.     }  
  51.     status = -ENOENT;  
  52.     if(i<HAL_VARIANT_KEYS_COUNT+1){  
  53.         status = load(class_id, path, module);  // 難道是要加載前面查找到的so庫??  
  54.     }  
  55.     return status;  
  56. }  
  57.  
  58. static int load(const char *id, counst char *path, const struct hw_module_t **pHmi){  
  59.     void *handle;  
  60.     struct hw_module_t * hmi;  
  61.     // 通過dlopen打開so庫  
  62.     handle = dlopen(path, RTLD_NOW);  
  63.     // sym的值為”HMI”,這個名字還有印象嗎?  
  64.     const char * sym = HAL_MODULE_INFO_SYM_AS_STR;  
  65.     // 通過dlsym從打開的庫裡查找”HMI”這個符號,如果在so代碼裡有定義的函數名或變量名為HMI,dlsym返回其地址hmi,將該地址轉化成hw_module_t類型,即,硬件對象,這招夠狠,“殺雞取卵”  
  66.     hmi = (struct hw_module_t *)dlsym(handle, sym);   
  67.     // 判斷找到的硬件對象的id是否和要查找的id名一致,不一致出錯退出  
  68. // 取了卵還要驗證下是不是自己要的“卵”  
  69.     if(strcmp(id, hmi->) != 0){  
  70.         // 出錯退出處理  
  71.     }  
  72.     // 將庫的句柄保存到hmi硬件對象的dso成員裡  
  73.     hmi->dso = handle;  
  74.     // 將硬件對象地址送給load函數者,最終將硬件對象返回到了hw_get_module的調用者  
  75.     *pHmi = hmi;  
  76.     // 成功返回  
  77. }  
  78.  

 

通過上面代碼的注釋分析可知,硬件對象聲明的結構體代碼被編譯成了so庫,由於該結構體聲明為const類型,被so庫包含在其靜態代碼段裡,要找到硬件對象,首先要找到其對應的so庫,再通過dlopen,dlsym這種“殺雞取卵”的方式找到硬件對象,當然這兒的:“雞”是指:so庫,“卵”既:硬件對象led_module_t結構。

在聲明結構體led_module_t時,其名字統一定義為了HMI,而這麼做的目的就是為了通過dlsym來查找led HAL Stub源碼生成的so庫裡的”HMI”符號。現在很明顯了,我們寫的HAL Stub代碼最終要編譯so庫文件,並且庫文件名為:led.default.so(當然可以設置四個系統屬性之一來指定名字為:led.屬性值.so),並且庫的所在目錄為:/system/lib/hw/。

現在底層的實現部分基本上吃透了,現在我們把目光放到調用者上,根據本章開頭介紹可知,上層調用本地代碼要使用JNI技術,我們先來惡補下JNI的知識吧。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved