Android HAL 層框架分析以及程式碼示例
本文拜讀參考自羅升楊老師的 《Android系統原始碼情景分析》
本文程式碼實驗平臺為 Android7.1
一 硬體抽象層概述
二 開發Android硬體驅動程式
三 開發Android硬體抽象層模組
- 3.1 硬體抽象層模組檔案 命名規範
- 3.2 硬體抽象層模組結構體 以及 硬體抽象層裝置結構體 定義規範
- 3.3 編寫硬體抽象層模組介面
四 為Android硬體抽象層編寫JNI方法供硬體服務程式呼叫
- 4.1 JNI實現
- 4.2 宣告JNI註冊方法
- 4.3 新增JNI方法程式碼
五 開發Android硬體訪問服務
- 5.1 定義硬體訪問服務介面
- 5.2 實現硬體訪問服務 最後 啟動硬體訪問服務
六 開發Android應用程式來使用硬體訪問服務
一 硬體抽象層概述
Android系統的硬體抽象層(Hardware Abstract Layer, HAL)執行在使用者空間中,它向下遮蔽硬體驅動模組的實現細節,向上提供硬體訪問服務。通過硬體抽象層,Android系統分為兩層來支援硬體裝置,其中一層實現在使用者空間(User Space),另外一層是現在核心空間(Kernel Space)。傳統的Linux系統把對硬體的支援完全是現在在核心空間,即把對硬體的支援完全實現在硬體驅動模組中。
問題:Android系統為什麼要把對硬體的支援劃分為兩層來實現呢?把硬體抽象層和核心驅動整合在一起放在核心空間不可行嗎?從技術實現的角度來看,是可以的,然而從商業的角度來看,把對硬體的支援邏輯都放在核心空間,可能會損害廠家的利益。我們知道Linux核心原始碼是遵循GPL協議的,如果我們在Android系統所使用的Linux核心中新增或者修改了程式碼,那麼就必須將其公開,所以,如果Android系統想Linux系統一樣,把對硬體的支援完全完全是現在linux硬體驅動中,那麼就是說這些實現是開源的,相當於暴露的硬體的實現細節和引數,損傷了廠商的利益。因此,Android才會想到把對硬體的支援分成硬體抽象層和核心驅動層,核心驅動層只提供簡單的訪問硬體邏輯,例如讀寫硬體暫存器的通道,至於從硬體中讀到了什麼值或者寫了什麼值到硬體中的邏輯,都放在硬體抽象層中去了,這樣就可以把商業祕密隱藏起來了。也正是由於這個分層的原因,Android被踢出了Linux核心主線程式碼樹中。
先來一張程式碼概覽圖鎮樓:
二 開發Android硬體驅動程式
為了方便描述,我們將為一個虛擬的字元硬體裝置開發去驅動程式,這個虛擬的字元硬體裝置只有一個暫存器,它的大小為4個位元組,可讀可寫,由於這個字元裝置是虛擬的,而且只有一個暫存器,因此我們將它命名為 fake register,並且將其對應的驅動名稱命名為 freg。
具體驅動程式 :
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/ioctl.h>
#include <linux/mm.h>
#include <asm/uaccess.h>
#include <linux/blkdev.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/major.h>
#include <linux/errno.h>
#include <linux/module.h>
#include <linux/seq_file.h>
#include <linux/kobject.h>
#include <linux/kobj_map.h>
#include <linux/cdev.h>
#include <linux/mutex.h>
#include <linux/backing-dev.h>
#include <linux/tty.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/hrtimer.h>
#include <linux/i2c.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/async.h>
#include <linux/irq.h>
#include <linux/workqueue.h>
#include <linux/proc_fs.h>
#include <linux/input/mt.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/slab.h>
#include <linux/device.h>
int CDRIVER_MAJOR=0;
int CDRIVER_MINOR=0;
static struct class *freg_class = NULL;
static ssize_t freg_read(struct file *filp, char *buffer, size_t count, loff_t *ppos);
static ssize_t freg_write(struct file* filp, const char __user *buf, size_t count, loff_t* f_pos);
static int freg_open(struct inode *inode,struct file *filp);
static int freg_release(struct inode* inode, struct file* filp);
static ssize_t freg_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf);
static ssize_t freg_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t n);
struct Freg_struct{
//struct semaphore sem;//訊號量
struct mutex mutex;//互斥鎖
struct cdev freg_cdev;
char val;
};
struct Freg_struct *freg_struct = NULL; /*裝置結構體指標*/
static struct kobject *freg_kobj;
struct Freg_Attribute0 {
struct attribute attr;
ssize_t (*show)(struct kobject *kobj, struct kobj_attribute *attr,
char *buf);
ssize_t (*store)(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t n);
};
static struct Freg_Attribute0 freg_attribute[] = {
//__ATTR(device name, S_IRUGO | S_IWUSR, hdmiin_test_show, hdmiin_test_store),
__ATTR(freg_dev, 0660, freg_show, freg_store),
};
static ssize_t freg_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
{
int val = 0;
struct Freg_struct dev;
mutex_lock(&dev.mutex);
val = dev.val;
mutex_unlock(&dev.mutex);
return snprintf(buf, PAGE_SIZE, "%d\n", val);
}
static ssize_t freg_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t n)
{
struct Freg_struct dev;
mutex_lock(&dev.mutex);
dev.val = *buf;
mutex_unlock(&dev.mutex);
return 0;
}
static ssize_t freg_read(struct file *filp, char *buffer, size_t count, loff_t *ppos)
{
int ret;
struct Freg_struct* freg_str = filp->private_data;
mutex_lock(&freg_str->mutex);
ret=copy_to_user(buffer, (char *)&freg_str->val, sizeof(freg_str->val));
if(ret<0)
{
printk("ret =%d \n",ret);
return ret;
}
mutex_unlock(&freg_str->mutex);
return 0;
}
static ssize_t freg_write(struct file* filp, const char __user *buf, size_t count, loff_t* f_pos)
{
struct Freg_struct* freg_str = filp->private_data;
int err;
mutex_lock(&freg_str->mutex);
err=copy_from_user(&(freg_str->val), buf, count);
if(err<0)
{
printk("ret =%d \n",err);
return err;
}
mutex_unlock(&freg_str->mutex);
return 0;
}
static int freg_open(struct inode *inode,struct file *filp)
{
struct Freg_struct* freg_str;
//對於一個字元裝置檔案, 其inode->i_cdev 指向字元驅動物件cdev,已知 inode->i_cdev(freg_cdev)字元裝置地址,以及名稱freg_cdev,返回 型別為 Freg_struct 的結構的首地址
// 需要注意的是 container_of 第三個引數是一個普通變數,並非指標。所以本程式中的字元裝置的 struct cdev 定義的是一個普通的 cdev變數,
// 而初始化cdev 的方式也必須是靜態方法,因為動態方式使用的是 cdev 的指標變數。
freg_str = container_of(inode->i_cdev, struct Freg_struct, freg_cdev);
//將自定義裝置結構體儲存在檔案指標的私有資料中,以便後面讀寫的時候用
filp->private_data = freg_str;
return 0;
}
static int freg_release(struct inode* inode, struct file* filp)
{
return 0;
}
struct file_operations simple_fops={
.owner=THIS_MODULE,
.open=freg_open,
.read=freg_read,
.write=freg_write,
.release = freg_release,
};
static int hello_init(void)
{
int ret;
dev_t freg_dev_t;
printk("start freg_init2\n" );
if(CDRIVER_MAJOR!=0){
//靜態申請裝置編號
freg_dev_t = MKDEV(CDRIVER_MAJOR,CDRIVER_MINOR);
ret = register_chrdev_region(freg_dev_t,1,"freg_chrdev");
}else{
//動態分配裝置號
ret = alloc_chrdev_region(&freg_dev_t,0,1,"freg_chrdev");
CDRIVER_MAJOR = MAJOR(freg_dev_t);
}
if(ret < 0){
printk(KERN_ERR"cannot get major %d \n",CDRIVER_MAJOR);
printk(KERN_INFO "cannot get major1\n");
return ret;
}
//申請裝置結構體記憶體
freg_struct = kmalloc(sizeof(struct Freg_struct), GFP_KERNEL);
if(!freg_struct){
printk(KERN_INFO "freg_struct kmalloc failed \n");
}
//賦值申請記憶體空間全部為0
memset(freg_struct, 0, sizeof(struct Freg_struct));
/*
//動態申請、初始化 cdev
freg_struct->freg_cdev = cdev_alloc();
freg_struct->freg_cdev->ops=&simple_fops;
freg_struct->freg_cdev->owner=THIS_MODULE;
*/
printk(KERN_INFO "init cdev start\n");
/*初始化 註冊 cdev*/
cdev_init(&freg_struct->freg_cdev, &simple_fops);//靜態申請cdev
freg_struct->freg_cdev.owner = THIS_MODULE;
freg_struct->freg_cdev.ops = &simple_fops;
ret = cdev_add(&freg_struct->freg_cdev,freg_dev_t,1);
if(ret)
{
printk(KERN_INFO "cdev_add err!\n");
}
// 在/sys/class/目錄下建立裝置類別目錄 freg
freg_class = class_create(THIS_MODULE, "freg");
// 在/dev/目錄和/sys/class/freg 目錄下分別建立裝置檔案 freg
device_create(freg_class, NULL, freg_dev_t,NULL,"freg");
//freg_kobj = kobject_create_and_add("freg_kobj", NULL); //在 sys下生成 freg_kobj屬性目錄
freg_kobj = kobject_create_and_add("freg_kobj", kernel_kobj);//在 /sys/kernel下生成 freg_kobj屬性目錄
ret = sysfs_create_file(freg_kobj, &freg_attribute[0].attr);
mutex_init(&freg_struct->mutex);
return 0;
}
static void freg_exit(void)
{
dev_t freg_dev_t;
freg_dev_t=MKDEV(CDRIVER_MAJOR,0);
printk( KERN_DEBUG "Module skeleton exit\n" );
device_destroy(freg_class, freg_dev_t);
class_destroy(freg_class);
cdev_del(&freg_struct->freg_cdev);
unregister_chrdev_region(MKDEV(CDRIVER_MAJOR,0),1);
}
module_init(hello_init);
module_exit(freg_exit);
MODULE_LICENSE("GPL");
測試程式:
external\freg\freg.c
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <sys/mman.h>
#define DEVICE_NAME "/dev/freg"
int main()
{
int val = 0;
int fd = -1;
fd = open(DEVICE_NAME, O_RDWR);
if (fd == -1)
{
printf("Failed to open device %s.\n", DEVICE_NAME);
return -1;
}
printf("Read original value:\n");
read(fd, &val, sizeof(val));
printf("%d.\n\n", val);
val = 5;
printf("Write value %d to %s.\n\n", val, DEVICE_NAME);
write(fd, &val, sizeof(val));
printf("Read the value again:\n");
read(fd, &val, sizeof(val));
printf("%d.\n\n", val);
close(fd);
return 0;
}
external\freg\Android.mk
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
LOCAL_MODULE_TAGS := debug
LOCAL_SRC_FILES := freg.c
LOCAL_MODULE := freg
LOCAL_SYSTEM_SHARED_LIBRARIES := libc
include $(BUILD_EXECUTABLE)
三 開發Android硬體抽象層模組
Android系統為硬體抽象層中的模組介面定義了編寫規範,Android系統的硬體抽象層以模組的形式來管理各個硬體訪問介面,每一個硬體模組都對應有一個動態連結庫檔案。這些動態連結庫檔案的命名符合一定的命名規範,同時在系統內部,每個硬體抽象層模組都使用結構體 hw_module_t 來描述,而硬體裝置則結構體 hw_device_t 來描述。下面分別介紹硬體抽象層模組檔案的命名規範以及結構體 hw_module_t 和 hw_device_t的定義。
在上面我們完成了核心的驅動程式,我們可以通過核心生成的 /dev 目錄下的裝置節點來連線 Android系統硬體抽象層和Linux底層驅動層。
3.1 硬體抽象層模組檔案 命名規範
硬體抽象層模組檔案 命名規範在 hardware/libhardware/hardware.c檔案中
hardware/libhardware/hardware.c
/**
* There are a set of variant filename for modules. The form of the filename
* is "<MODULE_ID>.variant.so" so for the led module the Dream variants
* of base "ro.product.board", "ro.board.platform" and "ro.arch" would be:
*
* led.trout.so
* led.msm7k.so
* led.ARMV6.so
* led.default.so
*/
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"
};
硬體抽象層模組檔案的命名規範為 “<MODULE_ID>.variant.so”,其中 MODULE_ID 表示模組的ID,variant表示系統的四個屬性,ro.hardware, ro.product.board, ro.board.platform,ro.arch 之一。系統在載入硬體抽象層模組時,依次按照 ro.hardware、ro.product.board、ro.board.platform、ro.arch的順序來取他們的屬性值。如果其中一個屬性值存在,那麼就把它的值作為variant值,然後再檢查對應的檔案是否存在,如果存在,那麼就找到要載入的硬體抽象層模組了,否則就繼續查詢下一個系統屬性,如果這四個系統屬性都不存在,或者對應於這四個系統屬性硬體抽象層模組檔案都不存在,那麼就是用 <MODULE_ID>.default.so來作為要載入的硬體抽象層模組檔案的名稱。
注意:
系統屬性 ro.hardware : 表示在系統啟動時,由init程式負責設定的。它首先會讀取 /proc/cmdline檔案,檢查裡面有沒有一個名稱為 androidboot.hardware的屬性,如果有,就把他作為屬性 ro.hardware的值,否則,就將 /proc/cpuinfo檔案的內容讀取出來,並且將裡面的硬體資訊解析出來,,即將 Hardware欄位的內容作為屬性 ro.hardware的值。
系統屬性 ro.product.board、ro.board.platform、ro.arch 是從 /system/build.prop檔案讀取出來的
3.2 硬體抽象層模組結構體 以及 硬體抽象層裝置結構體 定義規範
結構體 hw_module_t 和 hw_device_t 以及相關的其他結構體定義在檔案 hardware/libhardware/include/hardware/hardware.h中。
#define HARDWARE_MODULE_TAG MAKE_TAG_CONSTANT('H', 'W', 'M', 'T')
#define HARDWARE_DEVICE_TAG MAKE_TAG_CONSTANT('H', 'W', 'D', 'T')
struct hw_module_t;
struct hw_module_methods_t;
struct hw_device_t;
typedef struct hw_module_t {
/** tag must be initialized to HARDWARE_MODULE_TAG */
uint32_t tag;
uint16_t module_api_version;
#define version_major module_api_version
uint16_t hal_api_version;
#define version_minor hal_api_version
/** Identifier of module */
const char *id;
/** Name of this module */
const char *name;
/** Author/owner/implementor of the module */
const char *author;
/** Modules methods */
struct hw_module_methods_t* methods;
/** module's dso */
void* dso;
#ifdef __LP64__
uint64_t reserved[32-7];
#else
/** padding to 128 bytes, reserved for future use */
uint32_t reserved[32-7];
#endif
} hw_module_t;
typedef struct hw_module_methods_t {
/** Open a specific device */
int (*open)(const struct hw_module_t* module, const char* id,
struct hw_device_t** device);
} hw_module_methods_t;
typedef struct hw_device_t {
/** tag must be initialized to HARDWARE_DEVICE_TAG */
uint32_t tag;
uint32_t version;
/** reference to the module this device belongs to */
struct hw_module_t* module;
/** padding reserved for future use */
#ifdef __LP64__
uint64_t reserved[12];
#else
uint32_t reserved[12];
#endif
/** Close this device */
int (*close)(struct hw_device_t* device);
} hw_device_t;
#define HAL_MODULE_INFO_SYM HMI
struct hw_module_t 注意:
-
1.硬體抽象層中的中的每一個模組都必須自定義一個硬體抽象層模組結構體,而且它的第一個成員變數的型別必須是 hw_module_t。
-
2.硬體抽象層中的每一個模組都必須存在一個匯出符號 HAL_MODULE_INFO_SYM ,即"HMI",他指向一個自定義的硬體抽象層模組結構體。
-
3.結構體 hw_module_t的成員變數tag的值必須設定為 HARDWARE_MODULE_TAG ,用來標誌這是一個硬體抽象層模組結構體。
-
4.結構體 hw_module_t的成員變數dso用來儲存載入硬體抽象層模組後得到的控制程式碼值。因為每一個硬體抽象層模組都對應有一個動態連結庫檔案,載入硬體抽象層模組的過程實際就是載入與其對應的動態連結庫檔案的過程。在呼叫 dlclose 函式來解除安裝這個硬體抽象層模組時,需要用到這個控制程式碼值。
-
5.結構體 hw_module_t的成員變數 methods定義了一個硬體抽象層模組的操作方法列表,型別為 hw_module_methods_t。
struct hw_module_methods_t 注意:
*1. 結構體 hw_module_methods_t 只有一個成員變數,一個函式指標,用來打來硬體抽象層模組中的硬體裝置,引數module表示要開啟的硬體裝置所在的模組,id表示要開啟的硬體裝置的id,引數device是一個輸出引數,用來描述一個已經開啟的硬體裝置。由於一個硬體抽象層模組可能包含多個硬體裝置,因此,在呼叫 hw_module_methods_t的成員變數open來開啟一個硬體裝置時,我們需要指定一個id,硬體抽象層模組中的硬體裝置使用結構體 he_device_t來描述。
struct hw_device_t 注意:
-
1.硬體抽象層模組中的每一個硬體裝置都必須自定義一個硬體裝置結構體,而且他的第一個成員變數的型別必須是 hw_device_t。
-
2.結構體 hw_device_t的成員變數 tag的值必須設定為 HARDWARE_DEVICE_TAG,用來標誌這是一個硬體抽象層中的硬體裝置結構體
-
3.成員變數 close 函式指標用來關閉一個硬體裝置
-
4.硬體抽象層中的硬體裝置是由其所在的模組提供的介面來開啟的,而關閉則是由硬體裝置本身提供介面來完成。
3.3 編寫硬體抽象層模組介面
每一個硬體抽象層模組在核心中都有一個對應的驅動程式,硬體抽象層模組就是通過這些驅動程式來訪問硬體裝置的。硬體抽象層中模組介面原始檔一般儲存在 hardware/libhardware目錄中,我們將虛擬硬體裝置freg在硬體抽象層中的模組名稱定義為 freg,目錄結構如下:
hardware/libhardware
--------include
------------hardware
----------------freg.h
hardware/libhardware/Modules
--------freg
---------------freg.cpp
---------------Android.mk
它由三個檔案組成,其中freg.h 和 freg.cpp 是原始碼檔案,而Android.mk是模組的編譯指令碼檔案,三個檔案內容如下:
hardware/libhardware/include/hardware/freg.h
freg.h 內容如下
#ifndef ANDROID_FREF_INTERFACE_H
#define ANDROID_FREF_INTERFACE_H
#include <hardware/hardware.h>
__BEGIN_DECLS
//定義模組ID
#define FREG_HARDWARE_MODULE_ID "freg"
//定義裝置ID
#define FREG_HARDWARE_DEVICE_ID "freg"
//自定義模組結構體
struct freg_module_t{
struct hw_module_t common;
};
//自定義裝置結構體
struct freg_device_t{
struct hw_device_t common;
int fd;
int(*set_val)(struct freg_device_t* dev, int val);
int(*get_val)(struct freg_device_t* dev, int* val);
};
__END_DECLS
#endif
這個檔案中的常量和結構體都是按照硬體抽象層模組編寫規範來定義的,巨集 FREG_HARDWARE_MODULE_ID 和 FREG_HARDWARE_DEVICE_ID 分別代表模組ID和裝置ID,結構體freg_module_t用來描述自定義的模組結構體,他的第一個成員變數的型別是hw_module_t。 結構體freg_device_t是自定義的裝置結構體,他的第一個成員變數的型別是hw_device_t 用來描述我們的虛擬硬體裝置,其中的fd代表檔案描述符,表示需要開啟的/dev/xxx 裝置結點。其餘兩個函式指標用來讀寫 /dev/xxx 裝置。
hardware/libhardware/Modules/freg/freg.cpp
freg.cpp 內容如下
#define LOG_TAG "FregHALStub"
#include <hardware/hardware.h>
#include <hardware/freg.h>
#include <fcntl.h>
#include <errno.h>
#include <cutils/log.h>
#include <cutils/atomic.h>
#include <stdlib.h>
#include <string.h>
#define DEVICE_NAME "/dev/freg"
#define MODULE_NAME "Freg"
#define MODULE_AUTHOR "MHR"
//裝置開啟和關閉介面
static int freg_device_open(const struct hw_module_t* module, const char* name, struct hw_device_t** device);
static int freg_device_close(struct hw_device_t* device);
//裝置讀寫介面
static int freg_set_val(struct freg_device_t* dev, int val);
static int freg_get_val(struct freg_device_t* dev, int* val);
//定義模組操作方式結構體變數
static struct hw_module_methods_t freg_module_methods = {
open: freg_device_open
};
//定義模組結構體變數
struct freg_module_t HAL_MODULE_INFO_SYM = {
common: {
tag: HARDWARE_MODULE_TAG,
version_major: 1,
version_minor: 0,
id: FREG_HARDWARE_MODULE_ID,
name: MODULE_NAME,
author: MODULE_AUTHOR,
methods: &freg_module_methods,
}
};
//虛擬硬體裝置freg的開啟和關閉分別由 freg_device_open、freg_device_close來實現
static int freg_device_open(const struct hw_module_t* module, const char* id, struct hw_device_t** device)
{
struct freg_device_t* dev;
dev = (struct freg_device_t*)malloc(sizeof(struct freg_device_t));
if(!dev) {
ALOGI("%s Freg Stub: failed to alloc space", __func__);
return -EFAULT;
}
memset(dev, 0, sizeof(struct freg_device_t));
dev->common.tag = HARDWARE_DEVICE_TAG;
dev->common.version = 0;
dev->common.module = (hw_module_t*)module;
dev->common.close = freg_device_close;
dev->set_val = freg_set_val;
dev->get_val = freg_get_val;
if((dev->fd = open(DEVICE_NAME, O_RDWR)) == -1){
ALOGI("%s Freg Stub: open /dev/freg Failed.", __func__);
free(dev);
}
*device = &(dev->common);
ALOGI("%s Freg Stub: open /dev/freg successfully.", __func__);
return 0;
}
static int freg_device_close(struct hw_device_t* device)
{
struct freg_device_t* freg_device = (struct freg_device_t*)device;
if(freg_device) {
close(freg_device->fd);
free(freg_device);
}
return 0;
}
//虛擬硬體裝置 freg的讀寫函式
static int freg_set_val(struct freg_device_t* dev, int val) {
write(dev->fd, &val, sizeof(val));
return 0;
}
static int freg_get_val(struct freg_device_t* dev, int* val) {
read(dev->fd, val, sizeof(*val));
return 0;
}
freg.cpp 程式碼分析:
//定義模組結構體變數
struct freg_modules_t HAL_MODULE_INFO_SYM = {
common: {
tag: HARDWARE_MODULE_TAG,
version_major: 1,
version_minor: 0,
id: FREG_HARDWARE_MODULE_ID,
name: MODULE_NAME,
author: MODULE_AUTHOR,
methods: &freg_module_methods,
}
};
這段程式碼中, HAL_MODULE_INFO_SYM 是模組變數,按照硬體抽象層的編寫規範,每一個硬體抽象層模組必須匯出一個名稱為 HAL_MODULE_INFO_SYM 的符號,它指向一個自定義的硬體抽象層模組結構體,而且它的第一個型別為 hw_module_t 的成員變數tag值必須設定為 HARDWARE_MODULE_TAG . 除此之外,還初始化了這個硬體抽象層模組結構體的版本號, ID,名稱,作者,操作放大列表等。所以,這裡,例項變數名必須為 HAL_MODULE_INFO_SYM ,tag也必須為 HARDWARE_MODULE_TAG ,這是Android硬體抽象層規範規定的。
static int freg_device_open(const struct hw_module_t* module, const char* id, struct hw_device_t** device)
static int freg_device_close(struct hw_device_t* device)
前面提到,一個硬體抽象層模組可能包含多個硬體裝置,而這些硬體裝置的開啟操作都是由函freg_device_open來完成的,因此,函式freg_device_open會根據傳遞進來的引數id來判斷要開啟哪一個硬體裝置。在本利硬體抽象層模組freg中,只有一個虛擬硬體裝置freg,它使用 freg_device_t來描述,因此,函式 freg_device_open 發現引數id與虛擬硬體裝置freg的ID值匹配以後,就會分配一個 freg_device_t結構體,並對其成員變數進行初始化,按照硬體抽象層模組的編寫規範,硬體抽象層中的硬體裝置標籤 (dev->common.tag)必須設定為HARDWARE_DEVICE_TAG,除此之外,我們還將虛擬硬體裝置freg的關閉函式設定為 freg_device_close。
hardware/libhardware/Modules/freg/Android.mk
Android.mk 內容如下
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_PRELINK_MODULE := false
LOCAL_SHARED_LIBRARIES := liblog
LOCAL_MODULE_RELATIVE_PATH := hw
LOCAL_SRC_FILES := freg.cpp
LOCAL_MODULE := freg.default
include $(BUILD_SHARED_LIBRARY)
這個是硬體抽象層模組freg的編譯指令碼檔案,include $(BUILD_SHARED_LIBRARY) 表示要將該硬體模組抽象層編譯成一個動態連結庫檔案, 名稱為 freg.default,並且儲存在 $(TARGET_OUT_SHARED_LIBRARIES)/hw (out/target/product/generic/system/lib/hw )目錄下。LOCAL_MODULE的定義規則,freg後面跟有default,freg.default能夠保證我們的模組總能被硬象抽象層載入到。
mmm hardware/libhardware/modules/
生成 out/target/product/m282a/obj/lib/freg.default.so
注意:
我們將硬體抽象層模組 freg 對應的檔名稱定義為 freg.default,編譯成功後,系統會自動在後面加字尾.so, 於是就得到了一個 freg.default.so檔案。根據硬體抽象層模組檔案的命名規範,當我們要載入硬體抽象層模組freg時,只需要指定它的ID值,即 “freg”,系統會根據一定的規則成功的找到要載入啊 freg.default.so檔案。硬體抽象層模組 freg 都準備好後,就可以編譯打包了,最終會在 out/target/product/generic/system/lib/hw 下得到 freg.default.so檔案。硬體抽象層模組檔案實際上是一個動態連結庫檔案,即so檔案。另外為了能直接編譯,需要在 build\target\product\embedded.mk 中新增 : freg.default
才能編譯我們所新增的 hardware模組。最後在 out/target/product/generic/system/lib/hw 下生成 freg.default.so
至此, 雖然我們在Android系統為我們自己的硬體增加了一個硬體抽象層模組,但是現在Java應用程式還不能訪問到我們的硬體。我們還必須編寫JNI方法和在Android的Application Frameworks層增加API介面,才能讓上層Application訪問我們的硬體。
四 為Android硬體抽象層編寫JNI方法供硬體服務程式呼叫
4.1 JNI實現
在Android系統中,通常把硬體訪問服務的JNI方法實現在frameworks\base\services\core\jni 目錄中,因此,我們把實現了硬體訪問服務FregService的JNI方法 com_android_service_FregService.cpp 放在該目錄下,com_android_server字首表示的是包名 , 表示硬體服務FregService是放在frameworks/base/services/java目錄下的com/android/server目錄的, 即存在一個命令為com.android.server.FregService的類 內如如下:
#define LOG_TAG "FregServiceJNI"
#include "jni.h"
#include "JNIHelp.h"
#include "android_runtime/AndroidRuntime.h"
#include <utils/misc.h>
#include <utils/Log.h>
#include <hardware/hardware.h>
#include <hardware/freg.h>
#include <stdio.h>
namespace android
{
//硬體抽象層中定義的 硬體結構體
struct freg_device_t* freg_device = NULL;
//通過硬體抽象層定義的硬體訪問介面設定硬體暫存器val的值
static void freg_setVal(JNIEnv* env, jobject clazz, jint value) {
int val = value;
LOGI("Freg JNI: set value %d to device.", val);
if(!freg_device){
LOGI("Freg JNI: device is not open.");
return;
}
freg_device->set_val(freg_device, val);
}
//通過硬體抽象層定義的硬體訪問介面讀取硬體暫存器val的值
static jint freg_getVal(JNIEnv* env, jobject clazz){
int val = 0;
if(!freg_device){
LOGE("Device freg is not oprn.");
return 0;
}
freg_device->get_val(freg_device,&val);
LOGE("Freg JNI: Get value %d from device freg."val);
return val;
}
//通過硬體抽象層定義的硬體模組開啟介面開啟硬體裝置
static inline int freg_device_open(const hw_module_t* module, struct freg_device_t** device)
{
return module->methods->open(module, FREG_HARDWARE_MODULE_ID, (struct hw_device_t**)device);
}
//通過硬體模組ID來載入指定的硬體抽象層模組並開啟硬體
static jint freg_init(JNIEnv* env, jclass clazz){
freg_module_t* module;
LOGI(Initializing HAL stub freg......);
//載入硬體抽象層模組 freg
if(hw_get_module(FREG_HARDWARE_MODULE_ID,(const struct hw_module_t**)&module) == 0) {
LOGI("Freg JNI : freg Stub found.");
//開啟虛擬硬體裝置freg
if(freg_device_open(&(module->common),&freg_device) ==0) {
LOGI("Freg JNI :freg Device found");
return 0;
}
LOGI("Failed to open device freg");
return -1;
}
LOGI("Failed to get HAL stub freg");
return -1;
}
//Java 本地介面方法表
static const JNINativeMethod method_table[] = {
{"init_native", "()Z", (void*)freg_init},
{"setVal_native", "(I)V", (void*)freg_setVal},
{"getVal_native", "()I", (void*)freg_getVal},
};
//註冊java本地介面方法
int register_android_server_FregService(JNIEnv *env) {
return jniRegisterNativeMethods(env, "com/android/server/FregService", method_table, NELEM(method_table));
}
};
在函式 freg_init 中,首先通過Android硬體抽象層提供的 hw_module_get 函式來載入模組ID為 FREG_HARDWARE_MODULE_ID 的硬體抽象層模組,其中, FREG_HARDWARE_MODULE_ID 是在<hardware/freg.h>中定義的。Android硬體抽象層會根據 FREG_HARDWARE_MODULE_ID 的值在Android系統的/system/lib/hw目錄中找到相應的模組,然後載入起來,並且返回hw_module_t介面給呼叫者使用。
需要注意到是,在呼叫 freg_setVal 和 freg_getVal這兩個JNI方法之前,呼叫者首先要呼叫JNI方法 freg_init 開啟虛擬硬體裝置,以便可以獲得一個 freg_device_t介面。檔案中定義的 JNI方法表 method_table,分別將函式 freg_init、freg_setVal、freg_getVal的JNI方法註冊為 init_native setVal_native getVal_native。檔案最後呼叫了 jniRegisterNativeMethods()函式把JNI方法表註冊到JAVA虛擬機器中,在 jniRegisterNativeMethods 函式中,第二個引數的值必須對應FregService所在的包的路徑,即 com.android.server.FregService。以便提供給硬體訪問服務 FregService 使用。
4.2 宣告JNI註冊方法
增加 register_android_server_FregService()函式的宣告和呼叫。
frameworks\base\services\core\jni\onload.cpp
namespace android {
...
...
int register_android_server_FregService(JNIEnv *env);
};
using namespace android;
extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */){
...
register_android_server_FregService(env);
}
4.3 新增JNI方法程式碼
最後到 frameworks\base\services\core\jni 目錄中,開啟裡面的Android.mK檔案,修改變數LOCALSRC_FILS的值。
frameworks\base\services\core\jni\Android.mK
LOCAL_SRC_FILES += \
......
com_android_server_FregService.cpp \
onload.cpp
最後執行
mmm frameworks/base/services/core/jni
最後得到的 libandroid_service.so檔案就包含 init_native、setVal_native、getVal_native 這三個JNI方法了。至此 硬體訪問服務FregService的實現就完成了。下面介紹如何在系統程式System中啟動它。
五 開發Android硬體訪問服務
在前面,我們介紹瞭如何為Android系統的硬體編寫驅動程式,包括如何在Linux核心空間實現核心驅動程式和在使用者空間實現硬體抽象層介面。實現這兩者的目的是為了向更上一層提供硬體訪問介面,即為Android的Application Frameworks層提供硬體服務。那麼到此為止,在開發好硬體抽象層模組之後,我們還需要在應用程式框架層中實現一個硬體訪問服務,硬體訪問服務通過硬體抽象層模組來為應用程式提供硬體讀寫操作介面,由於硬體抽象層模組是使用 C++/C 語言開發的,而應用程式框架中的硬體訪問服務是使用JAVA語言開發的,那麼,Java介面如何去訪問C介面呢?眾所周知,Java提供了JNI方法呼叫,因此,硬體訪問服務必須通過JAVA本地介面(Java Native Interface JNI)來呼叫硬體抽象層模組的介面。Android系統的硬體訪問服務通常執行在系統程式System中,而使用這些硬體訪問服務的應用程式執行在其他的程式中,即應用程式需要通過程式間通訊機制來訪問這些硬體訪問服務,Android系統提供了一種高效的程式間通訊機制–Binder程式間通訊機制,應用程式就是通過它來訪問執行在系統程式System中的硬體訪問服務的。Binder程式間通訊機制要求要求提供服務的一方必須實現一個具有跨程式訪問能力的服務介面,以便使用服務的一方可以通過這個服務介面來訪問它。因此,在實現硬體訪問服務之前,我們首先要定義它的服務介面。
所以綜上所述,我們在此處要實現以下2個任務
1.定義硬體訪問服務介面
2.實現硬體訪問服務 最後 啟動硬體訪問服務
5.1 定義硬體訪問服務介面
Android 系統提供了一種描述語言來定義具有跨程式訪問能力的服務介面,這種描述語言稱為 Android 介面描述語言(Android Interface Definition Language, AIDL)。以AIDL定義的服務介面檔案是以aidl為字尾的,在編譯時,編譯系統會將他們轉換成Java檔案,然後對他們進行編譯,此處我們使用AIDL來定義硬體訪問服務介面 IFregService。在Android系統中,通常把硬體訪問服務介面定義在 framework/base/core/java/android/os 目錄中,因此,我們把定義了硬體訪問服務介面 IFregService 的檔案 IFregService.aidl也儲存在這個目錄中,內容如下:
framework/base/core/java/android/os/IFregService.aidl
package android.os;
//注: 註釋中需要新增{@hide},不然會出現編譯問題
/** @hide */
interface IFregService {
void setVal(int val);
int getVal();
}
IFregService服務介面之定義了兩個成員函式,setVal用來往虛擬硬體裝置freg寫,另一個是讀。
由於伺服器介面 IFregService 是使用AIDL語言描述的,因此,我們需要將其新增到編譯指令碼檔案中,這樣編譯系統才能將其轉換為Java檔案,然後再對它進行編譯,進入framework/base目錄中,開啟裡面的 Andriod.mk檔案,修改 LOCAL_SRC_FILES 變數的值,增加IFregService.aidl原始檔
frameworks/base\ Andriod.mk
LOCAL_SRC_FILES += \
.....
voip/java/android/net/sip/ISipService.aidl \
...
core/java/android/os/IFregService.aidl
修改這個編譯指令碼之後,可以使用mmm命令對硬體訪問服務介面IFregService進行編譯了,
mmm frameworks/base
這樣,就會根據 IFregService.aidl 生成相應的IFregService.Stub介面
5.2 實現硬體訪問服務 最後 啟動硬體訪問服務
在Android系統中,通常把硬體訪問服務實現在 frameworks/base/services/java/com/android/server 中,因此,我們把實現了硬體訪問服務 FregService的FregService.java檔案也儲存在這個目錄中,內容如下:
frameworks/base/services/java/com/android/server/FregService.java
package com.android.server;
import android.content.Context;
import android.os.IFregService;
import android.util.Slog;
/**
* @hide
*/
// 定義類FregService 繼承自 IFregService.Stub
public class FregService extends IFregService.Stub{
private static final String TAG = "FregService";//final關鍵字定義常量,就使得他在被定義後無法再對此進行賦值
FregService(){
init_native();
}
public void setVal(int val){
setVal_native(val);
}
public int getVal(){
return getVal_native();
}
private static native int init_native();
private static native void setVal_native(int val);
private static native int getVal_native();
};
硬體訪問服務FregService繼承了IFregService.Stub類,並且實現了IFregService介面的成員函式setVal和getVal,其中,成員函式setVal通過呼叫JNI方法來寫虛擬硬體裝置freg,而成員函式getVal呼叫JNI方法getVal_native來讀虛擬硬體裝置。
前面提到,Android系統的硬體訪問服務通常是在系統程式System中啟動的,而系統程式System是在開機時自動啟動,因此,將硬體訪問服務執行在系統程式System中,就可以實現開機時自動啟動,此處我們將硬體訪問服務 FregService執行在系統程式System中,因此,進入到 frameworks/base/service/java/com/android/server目錄下,開啟裡面的 SystemService.java 檔案,修改ServiceThread類的成員函式 run的實現
public void run() {
Slog.i(TAG, "Making services ready");
......
try {
Slog.i(TAG,"Freg Service");
ServiceManager.addService("freg", new FregService());
} catch(Throwable e) {
Slog.i(TAG,"Failure starting Freg Service", e);
}
}
系統程式System在啟動時,會建立一個 ServiceThread 執行緒來啟動系統中的關鍵服務,其中就包括一些硬體訪問服務,在ServiceThread類的成員函式run中,首先建立一個 FregService例項,然後把他註冊到 Service Manager中,Service Manager是Android系統的Binder程式間通訊機制的一個重要角色,他負責管理系統中的服務物件,註冊到 Service Manager 中的服務物件都有一個對應的名稱,使用這些服務的Client程式就是通過這些名稱來向 Service Manager 請求他們的Binder代理物件介面的,以便可以訪問他們所提供的服務,硬體訪問服務 FregService 註冊到 Service Manager之後,他的啟動過程就完成了。
最後執行
mmm frameworks/base/services/java/
編譯後得到的 service.jar 檔案就包含有硬體訪問服務FregService,並且在系統啟動時,將他執行在系統程式System中。
五 開發Android應用程式來使用硬體訪問服務
。。。。。。待續。。。。。。
相關文章
- android HAL層程式碼Android
- Android Framework 音訊子系統(12)HAL層分析AndroidFramework音訊
- 一個android 的HAL示例中遇到的坑。Android
- 層次分析法模型原理以及程式碼實現模型
- 視窗程式框架示例程式碼框架
- 【Camera專題】Qcom-Camera驅動框架淺析(Hal層->Driver層)框架
- Android硬體抽象層(HAL)模組編寫規範Android抽象
- beego框架程式碼分析Go框架
- Android UsbDeviceManager 程式碼分析Androiddev
- Flutter之Android層面原始碼分析(一)FlutterAndroid原始碼
- Android Studio 藍芽 示例程式碼(轉)Android藍芽
- Android 相對佈局RelativeLayout 程式碼示例Android
- 【Camera專題】你應該熟悉的Camera驅動框架一(Hal層->kernel層)框架
- android studio 除錯 framework 層程式碼Android除錯Framework
- Android Compose 入門,深入底層原始碼分析Android原始碼
- React Native 0.55.4 Android 原始碼分析(Java層原始碼解析)React NativeAndroid原始碼Java
- Android Binder實現示例(C/C++層)AndroidC++
- Android Hook框架Xposed原理與原始碼分析AndroidHook框架原始碼
- Android 外掛化框架 DynamicLoadApk 原始碼分析Android框架APK原始碼
- 使用GDB除錯Android Native 層程式碼除錯Android
- 影片聊天原始碼,同步、非同步示例程式碼分析原始碼非同步
- HAL 硬體抽象層介紹抽象
- 【Flink】Flink 底層RPC框架分析RPC框架
- Android示例應用:開源框架Glide的使用Android框架IDE
- 排序程式碼示例排序
- RabbitMQ 程式碼示例MQ
- werkzeug原始碼分析——從官網的示例程式碼開始原始碼
- 搶紅包案例分析以及程式碼實現
- Android 記憶體快取框架 LruCache 的原始碼分析Android記憶體快取框架原始碼
- RxCache 整合 Android 的持久層框架 greenDAO、RoomAndroid框架OOM
- AI框架中圖層IR的分析AI框架
- Java NIO 程式碼示例Java
- java SPI 程式碼示例Java
- 搶紅包案例分析以及程式碼實現(三)
- 搶紅包案例分析以及程式碼實現(二)
- 搶紅包案例分析以及程式碼實現(四)
- Android Thermal HAL 降龍十八掌Android
- Android 常用換膚方式以及原理分析Android