1. 程式人生 > >Android系統移植與平臺開發(八)- HAL Stub框架分析

Android系統移植與平臺開發(八)- HAL Stub框架分析


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

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

@hardware/libhardware/hardware.c

/* 
每一個硬體都通過hw_module_t來描述,我們稱之為一個硬體物件。你可以去“繼承”這個hw_module_t,然後擴充套件自己的屬性,硬體物件必須定義為一個固定的名字:HMI,即:Hardware Module Information的簡寫,每一個硬體物件裡都封裝了一個函式指標open用於開啟該硬體,我們理解為硬體物件的open方法,open呼叫後返回這個硬體對應的Operation interface。
*/
struct hw_module_t{
	uint32_t tag;			// 該值必須宣告為HARDWARE_MODULE_TAG
	uint16_t version_major;	// 主版本號
	uint16_t version_minor; 	// 次版本號
	const char *id;			//硬體id名,唯一標識module
	const char *name;		// 硬體module名字
	const char * author;		// 作者
	struct hw_module_methods_t* methods;	//指向封裝有open函式指標的結構體
	void* dso;				// module’s dso
	uint32_t reserved[32-7];	// 128位元組補齊
};

/* 
硬體物件的open方法描述結構體,它裡面只有一個元素:open函式指標
*/
struct hw_module_methods_t{
	// 只封裝了open函式指標
	int (*open)(const struct hw_module_t* module, const char * id,
		struct hw_device_t** device);
};

/*
硬體物件hw_module_t的open方法返回該硬體的Operation interface,它由hw_device_t結構體來描述,我們稱之為:該硬體的操作介面
*/
struct hw_device_t{
	uint32_t tag;				// 必須賦值為HARDWARE_DEVICE_TAG
	uint32_t version;				// 版本號
	struct hw_module_t* module;	// 該裝置操作屬於哪個硬體物件,可以看成硬體操作介面與硬體物件的聯絡
	uint32_t reserved[12];			// 位元組補齊
	int (*close)(struct hw_device_t* device);	// 該裝置的關閉函式指標,可以看做硬體的close方法
};

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

那使用者程式如何才能拿到硬體物件呢?

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

我們來看下321架構裡的:兩個符號常量和一個函式:
// 這個就是HAL Stub物件固定的名字
#define HAL_MODULE_INFO_SYM         	HMI
// 這是字串形式的名字
#define HAL_MODULE_INFO_SYM_AS_STR  	"HMI"
//這個函式是通過硬體名來獲得硬體HAL Stub物件
int hw_get_module(const char *id, const struct hw_module_t **module);

當用戶呼叫hw_get_module函式時,第一個引數傳硬體id名,那麼這個函式會從當前系統註冊的硬體物件裡查詢傳遞過來的id名對應的硬體物件,然後返回之。

從呼叫者的角度,我們基本上沒有什麼障礙了,那如何註冊一個硬體物件呢?

很簡單,只需要宣告一個結構體即可,看下面這個Led Stub註冊的例子:
const struct led_module_t HAL_MODULE_INFO_SYM = {
    common: {	// 初始化父結構hw_module_t成員
        tag: HARDWARE_MODULE_TAG,
	    version_major: 1,
	    version_minor: 0,
	    id: LED_HARDWARE_MODULE_ID,
	    name: "led HAL Stub",
	    author: "farsight",
	    methods: &led_module_methods,
	}, 
	// 擴充套件屬性放在這兒
};

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

struct led_module_t {
	struct hw_module_t common;
};
結構體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型別的,其宣告程式碼如下:
static struct hw_module_methods_t led_module_methods = {
    open: led_device_open  
};

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

static int led_device_open(const struct hw_module_t* module, const char* name,
	struct hw_device_t** device)
{
	struct led_device_t *led_device;
	LOGI("%s E ", __func__);
	led_device = (struct led_device_t *)malloc(sizeof(*led_device));
	memset(led_device, 0, sizeof(*led_device)); 

	// init hw_device_t
	led_device->common.tag= HARDWARE_DEVICE_TAG;
	led_device->common.version = 0;
	led_device->common.module= module;
	led_device->common.close = led_device_close; 

	// init operation interface
	led_device->set_on= led_set_on;
	led_device->set_off= led_set_off;
	led_device->get_led_count = led_getcount;
	*device= (struct hw_device_t *)led_device;

	if((fd=open("/dev/leds",O_RDWR))==-1)
	{
		LOGI("open error");
		return -1;
	}else
	LOGI("open ok\n");

	return 0;
}

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的關係:

struct led_device_t {
	struct hw_device_t common;   // led_devict_t的父結構,它裡面只封裝了close方法
	// 下面三個函式指標是子結構led_device_t對父結構hw_device_t的擴充套件,可以理解為子類擴充套件了父類增加了三個方法
	int (*getcount_led)(struct led_device_t *dev);
	int (*set_on)(struct led_device_t *dev);
	int (*set_off)(struct led_device_t *dev);
};

用UML類圖來表示:

由類圖可知,led_device_t擴充套件了三個介面:seton(), setoff(),get_led_count()。

那麼剩下的工作就是實現子結構中新擴充套件的三個介面了:
static int led_getcount(struct led_control_device_t*dev)
{
         LOGI("led_getcount");
         return 4;
}
 
static int led_set_on(struct led_control_device_t *dev)
{   
         LOGI("led_set_on");
         ioctl(fd,GPG3DAT2_ON,NULL);
         return 0;
}
 
static int led_set_off(struct led_control_device_t*dev)
{
         LOGI("led_set_off");
         ioctl(fd,GPG3DAT2_OFF,NULL);
         return 0;
}

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

總結一下:

我們有一個硬體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函式的實現:

@hardware/libhardware/hardware.c

static const char *variant_keys[] = {
	“ro.hardware”,
	“ro.product.board”,
	“ro.board.platform”,
	“ro.arch”
};
// 由上面定義的字串陣列可知,HAL_VARIANT_KEYS_COUNT的值為4
struct constint HAL_VARIANT_KEYS_COUNT = (sizeof(variant_keys)/sizeof(variant_keys[0]));

int hw_get_module(const char *id, const struct hw_module_t **module){
	// 呼叫3個引數的hw_get_module_by_class函式
return hw_get_module_by_class(id, NULL, module);
}

int hw_get_module_by_class(const char *class_id, const char *inst, 
const struct hw_module_t **module){
	int status;
	int i;
	// 宣告一個hw_module_t指標變數hmi
	const struct hw_module_t *hmi = NULL;
	char prop[PATH_MAX};
	char path[PATH_MAX];
	char name[PATH_MAX];
	// 由前面呼叫函式可知,inst = NULL,執行else部分,將硬體id名拷貝到name數組裡
	if(inst)
		snprintf(name, PATH_MAX, “%s.%s”, class_id, inst);
	else
		strlcpy(name, class_id, PATH_MAX);
	// i 迴圈5次
	for(i=0; i<HAL_VARIANT_KEYS_COUNT+1; i++){
		if(i<HAL_VARIANT_KEYS_COUNT){
			// 從系統屬性裡依次查詢前面定義的4個屬性的值,找其中一個後,執行後面程式碼,找不到,進入else部分執行
			if(property_get(variant_keys[i], prop, NULL) == 0){
				continue;
			}
			// 找到一個屬性值prop後,拼寫path的值為:/vendor/lib/hw/硬體id名.prop.so
			snprintf(path, sizeof(path), “%s/%s.%s.so”,
				HAL_LIBRARY_PATH2, name, prop);
			if(access(path, R_OK) ==0) break;	// 如果path指向有效的庫檔案,退出for迴圈
			// 如果vendor/lib/hw目錄下沒有庫檔案,查詢/system/lib/hw目錄下有沒有:硬體id名.prop.so的庫檔案
			snprintf(path, sizeof(path), “%s/%s.%s.so”,
				HAL_LIBRARY_PATH1, name, prop);
			If(access(path, R_OK) == 0) break;
		} else {
			// 如果4個系統屬性都沒有定義,則使用預設的庫名:/system/lib/hw/硬體id名.default.so
			snprintf(path, sizeof(path), “%s/%s.default.so”,
				HAL_LIBRARY_PATH1, name);
			If(access(path, R_OK) == 0) break;
		}
	}
	status = -ENOENT;
	if(i<HAL_VARIANT_KEYS_COUNT+1){
		status = load(class_id, path, module);	// 難道是要載入前面查詢到的so庫??
	}
	return status;
}

static int load(const char *id, counst char *path, const struct hw_module_t **pHmi){
	void *handle;
	struct hw_module_t * hmi;
	// 通過dlopen開啟so庫
	handle = dlopen(path, RTLD_NOW);
	// sym的值為”HMI”,這個名字還有印象嗎?
	const char * sym = HAL_MODULE_INFO_SYM_AS_STR;
	// 通過dlsym從開啟的庫裡查詢”HMI”這個符號,如果在so程式碼裡有定義的函式名或變數名為HMI,dlsym返回其地址hmi,將該地址轉化成hw_module_t型別,即,硬體物件,這招夠狠,“殺雞取卵”
	hmi = (struct hw_module_t *)dlsym(handle, sym);	
	// 判斷找到的硬體物件的id是否和要查詢的id名一致,不一致出錯退出
// 取了卵還要驗證下是不是自己要的“卵”
	if(strcmp(id, hmi->) != 0){
		// 出錯退出處理
	}
	// 將庫的控制代碼儲存到hmi硬體物件的dso成員裡
	hmi->dso = handle;
	// 將硬體物件地址送給load函式者,最終將硬體物件返回到了hw_get_module的呼叫者
	*pHmi = hmi;
	// 成功返回
}

通過上面程式碼的註釋分析可知,硬體物件宣告的結構體程式碼被編譯成了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的知識吧。