對於Android系統移植,主要是資訊中framework的移植,而且都會涉及到硬體。關於硬體相關,資料目前不算小,最先比較詳細介紹的是Jollen,其他資料也大部分基於他的分析而寫出了一些自己的理解,他的部落格地址是http://www.jollen.org/blog/2009/。
以下是自己的學習筆記及理解,以為備忘。
本文的主要內容如下:
寫在前面:關於分層
一、 Stub編寫
二、 JNI編寫
三、 framework的java編寫
四、 應用程式編寫
五、 linux硬體驅動編寫
盡量把每一部分內家寫得短些,太長看了會比較累。
------------------------------------------------------------------------------
寫在前面:關於分層
一個系統的分層,可以有比較清晰的層次感,基本上我們的世界都是在一個分層的理念中,生物圈,管理、行政體系、人類需求……都可以看到分層的影子,還有一些邏輯上的概念,如tcp/ip分層。
有些時候,我們稱之為等級。
分層不能太多層,否則鞭長莫及。也不能不太小,否則層面太寬,失去分層之要義。所謂適當的、有效、健壯的有分層,就是我們所常說的“中庸”,當然此中庸,非彼中庸了。
anroid系統的分層,有一張圖很清楚地顯示了,網上比較多這張圖,此處略去。apps->framework(apps service, manager)->jni->stub->linux kernel/driver。
對於整個系統來說,這個分層的確有點太多層了,不花點時間,會顧此失彼,而且涉及到的知識面比較廣。
對於效率來說,分層太多,效率肯定會打折扣,何況還應用程式還是用java寫的,java比較進階的語言了。目前來看,android系統對硬體要求也比較高了,似乎這些折扣,可以通過硬體的方式來補償了。硬體,硬體,發展也是會有瓶頸的吧??
一、Stub編寫
1、Stub的編寫
Stub是與linux驅動打交道,也就是直接調用了open,close,ioctl,read,write等函數。實際上等價於我們直接在linux上寫一些應用程式時所調用硬體驅動功能。只是在android裡叫做stub而已。
首先熟悉一下,兩個結構體:hw_module_t 和hw_device_t。
此兩結構體定義在 hardware/libhardware/include/hardware/hardware.h
typedef struct hw_module_t {<br /> /** tag must be initialized to HARDWARE_MODULE_TAG */<br /> uint32_t tag;<br /> /** major version number for the module */<br /> uint16_t version_major;<br /> /** minor version number of the module */<br /> uint16_t version_minor;<br /> /** Identifier of module */<br /> const char *id;<br /> /** Name of this module */<br /> const char *name;<br /> /** Author/owner/implementor of the module */<br /> const char *author;<br /> /** Modules methods */<br /> struct hw_module_methods_t* methods;<br /> /** module's dso */<br /> void* dso;<br /> /** padding to 128 bytes, reserved for future use */<br /> uint32_t reserved[32-7];<br />} hw_module_t;<br />typedef struct hw_module_methods_t {<br /> /** Open a specific device */<br /> int (*open)(const struct hw_module_t* module, const char* id,<br /> struct hw_device_t** device);<br />} hw_module_methods_t;<br />/**<br /> * Every device data structure must begin with hw_device_t<br /> * followed by module specific public methods and attributes.<br /> */<br />typedef struct hw_device_t {<br /> /** tag must be initialized to HARDWARE_DEVICE_TAG */<br /> uint32_t tag;<br /> /** version number for hw_device_t */<br /> uint32_t version;<br /> /** reference to the module this device belongs to */<br /> struct hw_module_t* module;<br /> /** padding reserved for future use */<br /> uint32_t reserved[12];<br /> /** Close this device */<br /> int (*close)(struct hw_device_t* device);<br />} hw_device_t;
寫Stub代碼,就是填充這兩個結體體。
在訪問硬體裝置時,是通過檔案標識來操作,因此實際上在hw_device_t還應該添一個檔案標識變fd,有兩種方式可以實現:1)把hw_device_t中reseverd變數來放檔案標識,這是被系統保留,應該說不太安全。2)自訂一個結構體,把hw_device_t 當作此結構體的成員變數。如下操作:
struct gpio_device_t {
struct hw_device_t hwdev;
/* attributes */
int fd; //the file description of the device in /dev/xxx
}
這樣在open裝置的時候,就可以把返回的檔案標識值,傳給fd了。
下面就是簡單寫Stub的流程:
1、 在vendor目錄下建一個hal目錄,再分別建立framework和modules目錄。對應於framework代碼和c/c++模組代碼,在moueles建立gpio目錄。gpio比較通用,led也是屬於gpio範疇。
2、建立gpio.h 如下
#include <hardware/hardware.h><br />#include <fcntl.h><br />#include <errno.h><br />#include <cutils/log.h><br />#include <cutils/atomic.h><br />struct gpio_module_t {<br /> struct hw_module_t hwmod;<br />};<br />struct gpio_device_t {<br /> struct hw_device_t hwdev;<br /> /* attributes */<br /> int fd; //the file description of the device in /dev/xxx<br /> /* supporting control APIs go here */<br /> int (*SetGPIO)(struct gpio_device_t *dev, int32_t gpio);<br /> int (*ClrGPIO)(struct gpio_device_t *dev, int32_t gpio);<br />};<br />#define GPIO_HARDWARE_MODULE_ID "gpio"
標頭檔:
#include <hardware/hardware.h> //上兩個基本結構體定義處
#include <fcntl.h>
#include <errno.h>
#include <cutils/log.h>
#include <cutils/atomic.h>
自己定義的兩個結構體
struct gpio_module_t {
struct hw_module_t hwmod;
};
struct gpio_device_t {
struct hw_device_t hwdev;
int fd; //the file description of the device in /dev/xxx
int (*SetGPIO)(struct gpio_device_t *dev, int32_t gpio);
int (*ClrGPIO)(struct gpio_device_t *dev, int32_t gpio);
};
定義一個模組id,#define GPIO_HARDWARE_MODULE_ID "gpio"
儲存到gpio目錄下。
3 編寫gpio.c
#include "gpio.h"<br />#define LOG_TAG "GPIO"<br />int gpio_device_close(struct hw_device_t* device)<br />{<br />struct gpio_device_t* gdev = (struct gpio_device_t*)device;<br />if (gdev)<br />{<br />free(gdev);<br />}<br />return 0;<br />}<br />int gpio_set(struct gpio_device_t *dev, int32_t gpio)<br />{<br />LOGI("gpio : %d set .", gpio);<br />if (dev->fd >= 0)<br />{</p><p>}<br />return 0;<br />}<br />int gpio_clr(struct gpio_device_t *dev, int32_t gpio)<br />{<br />LOGI("gpio : %d clr.", gpio);<br />if (dev->fd >= 0)<br />{</p><p>}<br />return 0;<br />}<br />static int gpio_device_open(const struct hw_module_t* module, const char* name,<br /> struct hw_device_t** device)<br />{<br />struct gpio_device_t *dev;<br />dev = (struct gpio_device_t *)malloc(sizeof(struct gpio_device_t));<br />memset(dev, 0, sizeof(struct gpio_device_t));<br />dev->hwdev.tag = HARDWARE_DEVICE_TAG;<br />dev->hwdev.version = 0;<br />dev->hwdev.module = module;<br />dev->hwdev.close = gpio_device_close;<br />dev->SetGPIO = gpio_set;<br />dev->ClrGPIO = gpio_clr;<br />dev->fd = open("/dev/gpio0", O_RDWR);<br />*device = &dev->hwdev;<br />if (dev->fd < 0)<br />{<br />free(dev);<br />dev = NULL;<br />return -1;<br />}<br />return 0;<br />}<br />static struct hw_module_methods_t gpio_module_methods =<br />{<br /> open: gpio_device_open<br />};<br />const struct gpio_module_t HAL_MODULE_INFO_SYM =<br />{<br /> hwmod:<br /> {<br /> tag: HARDWARE_MODULE_TAG,<br /> version_major: 1,<br /> version_minor: 0,<br /> id: GPIO_HARDWARE_MODULE_ID,<br /> name: "gpio Stub",<br /> author: "guim",<br /> methods: &gpio_module_methods,<br /> }</p><p>};
int gpio_device_close(struct hw_device_t* device) //關閉gpio裝置
int gpio_set(struct gpio_device_t *dev, int32_t gpio)//gpio置1
int gpio_clr(struct gpio_device_t *dev, int32_t gpio)//gpio置0
static int gpio_device_open(const struct hw_module_t* module, const char* name,
struct hw_device_t** device) //開啟gpio裝置,這個函數是一定要實現,因為之後要傳給hw_module_methods_t中的open函數指標:
static struct hw_module_methods_t gpio_module_methods =
{
open: gpio_device_open
};
在此函數中,要填充gpio_device_t一些介面,供上層調用:
dev->hwdev.tag = HARDWARE_DEVICE_TAG; //預設都為此值
dev->hwdev.version = 0; //應該可以隨便寫,根據自己定義
dev->hwdev.module = module;
dev->hwdev.close = gpio_device_close;
dev->SetGPIO = gpio_set;
dev->ClrGPIO = gpio_clr;
dev->fd = open("/dev/gpio0", O_RDWR); //開啟gpio裝置
//註冊介面,HAL_MODULE_INFO_SYM
const struct gpio_module_t HAL_MODULE_INFO_SYM = //固定為HAL_MODULE_INFO_SYM,不可修改,不知為什麼,照做。
{
hwmod:
{
tag: HARDWARE_MODULE_TAG,
version_major: 1,
version_minor: 0,
id: GPIO_HARDWARE_MODULE_ID,
name: "gpio Stub",
author: "guim",
methods: &gpio_module_methods,
}
};
4 編寫Android.mk檔案
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_PRELINK_MODULE := false
LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw
LOCAL_SHARED_LIBRARIES := liblog
LOCAL_SRC_FILES := gpio.c
#模組名為gpio.default
LOCAL_MODULE := gpio.default
include $(BUILD_SHARED_LIBRARY)
其中LOCAL_PRELINK_MODULE要置為false,否則編譯時間會出錯。
5 關於模組名的命名
在jni調用stub方法時,是用到hw_get_module函數來尋找這個庫。hw_get_module函數實現如下(位置hardware/libhardware/Hardware.c ):
在尋找模組的時候,預設是尋找system/lib/hwh目錄下的,也就是上面編譯好的gpio.default.so是放在此目錄下
#define HAL_LIBRARY_PATH "/system/lib/hw"
//模組的索引值,在尋找庫的時候,要根據模組的id,即GPIO_HARDWARE_MODULE_ID來尋找模組檔案
static const char *variant_keys[] = {
"ro.hardware", /* This goes first so that it can pick up a different file on the emulator. */
"ro.product.board",
"ro.board.platform",
"ro.arch"
};
static const int HAL_VARIANT_KEYS_COUNT =
(sizeof(variant_keys)/sizeof(variant_keys[0]));
int hw_get_module(const char *id, const struct hw_module_t **module)
{
int status;
int i;
const struct hw_module_t *hmi = NULL;
char prop[PATH_MAX];
char path[PATH_MAX];
/*
* Here we rely on the fact that calling dlopen multiple times on
* the same .so will simply increment a refcount (and not load
* a new copy of the library).
* We also assume that dlopen() is thread-safe.
*/
/* Loop through the configuration variants looking for a module */
for (i=0 ; i<HAL_VARIANT_KEYS_COUNT+1 ; i++) {
if (i < HAL_VARIANT_KEYS_COUNT) {
if (property_get(variant_keys, prop, NULL) == 0) {
continue;
}
snprintf(path, sizeof(path), "%s/%s.%s.so",
HAL_LIBRARY_PATH, id, prop);
} else {
snprintf(path, sizeof(path), "%s/%s.default.so",
HAL_LIBRARY_PATH, id);
}
if (access(path, R_OK)) {
continue;
}
/* we found a library matching this id/variant */
break;
}
status = -ENOENT;
if (i < HAL_VARIANT_KEYS_COUNT+1) {
/* load the module, if this fails, we're doomed, and we should not try
* to load a different variant. */
status = load(id, path, module);
}
return status;
}
property_get(variant_keys, prop, NULL) 會從variant_keys數組中去擷取相應變數所對應的值,然後返回給 prop :
數組中的變數對應的值,如下:
"ro.product.board=$TARGET_BOOTLOADER_BOARD_NAME"
"ro.board.platform=$TARGET_BOARD_PLATFORM"
TARGET_BOOTLOADER_BOARD_NAME會根據具體的設定而確定,當然要還要看在編譯android系統的時所選的target了,此處預設選擇genic,TARGET_BOOTLOADER_BOARD_NAME和TARGET_BOARD_PLATFORM都為空白,因此,此處就找不到對應的模組檔案,如果找到會傳給prop值,然後根據snprintf(path, sizeof(path), "%s/%s.%s.so", HAL_LIBRARY_PATH, id, prop);得到模組的絕對檔案名稱。否則則用snprintf(path,
sizeof(path), "%s/%s.default.so", HAL_LIBRARY_PATH, id);設定模組的全路徑名,即/system/lib/hw/xxx.default.so。因此,上面產生模組名是定義為gpio.default.so, 此處xxx即為gpio。如果都沒有找到模組檔案,那麼上層調用時就會提示出錯,可以通過LOG資訊來跟蹤。
此部分詳情,將由JNI的編寫具體討論。