---
title: 'HAL 硬體驅動 - 開發範例'
disqus: kyleAlien
---
HAL 硬體驅動 - 開發範例
===
## OverView of Content
由於驅動開發與核心是強耦合關係… 所以先了解當前範例使用的核心源碼、Android 源碼版本
:::info
**使用 ^1.^ Android Kernel 為 `android-goldfish-4.14-dev.150` 分支**、**^2.^ Android source `android-10.0._r1` 分支**
:::
[TOC]
## 驅動開發 Device
簡單的開發一個 `hello` **虛擬硬體裝置**,可以對這個虛擬硬體裝置做「寫入」、「存取」的動作
:::success
驅動是在 Kernel 空間運行,所以使用的 API 都需要使用 Kernel 版本的 API… 像是 `printk`、`kmalloc`
:::
### 虛擬字節驅動裝置
* **`hello` 虛擬驅動實現**
* **`hello.h` 檔案**:定義該裝置使用到的結構
```c=
// drivers/hello/hello.h
#ifndef _HELLO_ANDROID_H_
#define _HELLO_ANDROID_H_
#include <linux/cdev.h>
#include <linux/semaphore.h>
#define HELLO_DEVICE_NODE_NAME "hello"
#define HELLO_DEVICE_FILE_NAME "hello"
#define HELLO_DEVICE_PROC_NAME "hello"
#define HELLO_DEVICE_CLASS_NAME "hello"
struct hello_android_dev {
int val;
// 宣告一個信號量成員,避免 Mutli Thread 問題
struct semaphore sem;
// 字節裝置
struct cdev dev;
};
```
* **`hello.c` 檔案**:這裡分為幾個小步驟
1. **創建 `/dev/hello`**:
實現檔案操作結構 (`file_operations`) 分別是 `open`、`release`、`read`、`write` ...等等對 `/dev` 的基礎操作
```c=
// drivers/hello/hello.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/proc_fs.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/proc_fs.h>
#include <linux/slab.h>
#include "hello.h"
static int hello_open(struct inode* inode, struct file* filp);
static int hello_release(struct inode* inode, struct file* filp);
static ssize_t hello_read(struct file* filp, char __user *buf, size_t count, loff_t* f_pos);
static ssize_t hello_write(struct file* filp, const char __user *buf, size_t count, loff_t* f_pos);
// 定義檔案操作的結構 (最後會使用)
static struct file_operations hello_fops = {
.owner = THIS_MODULE,
.open = hello_open,
.release = hello_release,
.read = hello_read,
.write = hello_write,
};
// 開啟 hello 裝置
static int hello_open(struct inode* inode, struct file* filp) {
struct hello_android_dev* dev;
dev = container_of(inode->i_cdev, struct hello_android_dev, dev);
filp->private_data = dev;
return 0;
}
// 釋放 hello 裝置
static int hello_release(struct inode* inode, struct file* filp) {
return 0;
}
// 讀取 hello 裝置
static ssize_t hello_read(struct file* filp, char __user *buf, size_t count, loff_t* f_pos) {
ssize_t err = 0;
struct hello_android_dev* dev = filp->private_data;
printk(KERN_ALERT"hello_read: %lu\n", count);
if(down_interruptible(&(dev->sem))) {
return -ERESTARTSYS;
}
if(count < sizeof(dev->val)) {
goto out;
}
if(copy_to_user(buf, &(dev->val), sizeof(dev->val))) {
err = -EFAULT;
goto out;
}
err = sizeof(dev->val);
out:
up(&(dev->sem));
return err;
}
// 寫入 hello 裝置
static ssize_t hello_write(struct file* filp, const char __user *buf, size_t count, loff_t* f_pos) {
struct hello_android_dev* dev = filp->private_data;
ssize_t err = 0;
printk(KERN_ALERT"hello_write: %lu\n", count);
if(down_interruptible(&(dev->sem))) {
return -ERESTARTSYS;
}
if(count != sizeof(dev->val)) {
goto out;
}
if(copy_from_user(&(dev->val), buf, count)) {
err = -EFAULT;
goto out;
}
err = sizeof(dev->val);
out:
up(&(dev->sem));
return err;
}
static int __hello_setup_dev(struct hello_android_dev* dev) {
int err;
// 創建 dev
dev_t devno = MKDEV(hello_major, hello_minor);
memset(dev, 0, sizeof(struct hello_android_dev));
// 初始化字節裝置
cdev_init(&(dev->dev), &hello_fops);
dev->dev.owner = THIS_MODULE;
dev->dev.ops = &hello_fops;
// 添加字節裝置
err = cdev_add(&(dev->dev),devno, 1);
if(err) {
return err;
}
// 初始化信號量
sema_init(&(dev->sem), 1);
dev->val = 0;
printk(KERN_ALERT"__hello_setup_dev\n");
return 0;
}
```
2. **定義 `devfs` 檔案系統**:
**`devfs` 是「檔案系統介面」**,**透過這個硬體介面可以存取虛擬硬體裝置 hello 的數值**;順便定義虛擬裝置的操作 (`set`、`get`),之後會用到
```c=
// drivers/hello/hello.c
... 省略部分
static ssize_t hello_val_show(struct device* dev, struct device_attribute* attr, char* buf);
static ssize_t hello_val_store(struct device* dev, struct device_attribute* attr, const char* buf, size_t count);
// 設定 File System 屬性
static DEVICE_ATTR(val, S_IRUGO | S_IWUSR, hello_val_show, hello_val_store);
static ssize_t __hello_get_val(struct hello_android_dev* dev, char* buf) {
int val = 0;
printk(KERN_ALERT"__hello_get_val\n");
if(down_interruptible(&(dev->sem))) { // 取得信號量使用權
return -ERESTARTSYS;
}
val = dev->val;
up(&(dev->sem)); // 釋放訊號量
return snprintf(buf, PAGE_SIZE, "%d\n", val);
}
static ssize_t __hello_set_val(struct hello_android_dev* dev, const char* buf, size_t count) {
int val = 0;
val = simple_strtol(buf, NULL, 10);
printk(KERN_ALERT"__hello_set_val: %lu\n", val);
if(down_interruptible(&(dev->sem))) { // 取得信號量使用權
return -ERESTARTSYS;
}
dev->val = val;
up(&(dev->sem)); // 釋放訊號量
return count;
}
// devfs 顯示
static ssize_t hello_val_show(struct device* dev, struct device_attribute* attr, char* buf) {
struct hello_android_dev* hdev = (struct hello_android_dev*)dev_get_drvdata(dev);
return __hello_get_val(hdev, buf);
}
// devfs 寫入
static ssize_t hello_val_store(struct device* dev, struct device_attribute* attr, const char* buf, size_t count) {
struct hello_android_dev* hdev = (struct hello_android_dev*)dev_get_drvdata(dev);
return __hello_set_val(hdev, buf, count);
}
```
3. **定義 `/proc/hello` 檔案**:
hello 虛擬硬體裝置在啟動時也順 帶在 `/proc` 資料夾底下創建一個對應操作文件,之後可以透過 `/proc/hello` 操作文件
```c=
// drivers/hello/hello.c
... 省略部分
static ssize_t hello_proc_read(struct file* filp, char __user *buff, size_t count, loff_t* f_pos) {
printk(KERN_ALERT"Test count: %lu, filp->f_pos: %lu, f_pos: %lu.\n", count, filp->f_pos, *f_pos);
if(*f_pos > 0) {
return 0;
}
*f_pos = __hello_get_val(hello_dev, buff);
return *f_pos;
}
static ssize_t hello_proc_write(struct file* filp, const char __user *buff, size_t count, loff_t* f_pos) {
int err = 0;
char* page = NULL;
if(count > PAGE_SIZE) {
printk(KERN_ALERT"The buff is too large: %lu.\n", count);
return -EFAULT;
}
page = (char*)__get_free_page(GFP_KERNEL);
if(!page) {
printk(KERN_ALERT"Failed to alloc page.\n");
return -ENOMEM;
}
if(copy_from_user(page, buff, count)) {
printk(KERN_ALERT"Failed to copy buff from user.\n");
err = -EFAULT;
goto out;
}
err = __hello_set_val(hello_dev, page, count);
out:
free_page((unsigned long)page);
return err;
}
// 對 /proc 檔案操作時的處理函數
static struct file_operations hello_proc = {
.owner = THIS_MODULE,
.open = hello_open,
.read = hello_proc_read,
.write = hello_proc_write,
};
// 創建 /proc 目錄下的檔案
static void hello_create_proc(void) {
struct proc_dir_entry* entry;
entry = proc_create_data(HELLO_DEVICE_PROC_NAME,
S_IFREG | S_IRUGO,
NULL,
&hello_proc,
NULL);
}
// 移除 /proc 目錄下的檔案
static void hello_remove_proc(void) {
remove_proc_entry(HELLO_DEVICE_PROC_NAME, NULL);
}
```
4. **註冊 `hello` 虛擬硬體(字節)裝置**:
**透過 `module_init`、`module_exit` 宏**,來註冊該裝置;在啟動函數中就包括了 `/dev`、`/proc` 檔案創建… 釋放函數中就包括釋放驅動記憶體
```c=
// drivers/hello/hello.c
... 省略部分
static int hello_major = 0;
static int hello_minor = 0;
static struct class* hello_class = NULL;
static struct hello_android_dev* hello_dev = NULL;
static int __init hello_init(void){
int err = -1;
dev_t dev = 0;
struct device* temp = NULL;
printk(KERN_ALERT"Initializing hello device.\n");
// 分配 dev
err = alloc_chrdev_region(&dev, 0, 1, HELLO_DEVICE_NODE_NAME);
if(err < 0) {
printk(KERN_ALERT"Failed to alloc char dev region.\n");
goto fail;
}
// 設備主號
hello_major = MAJOR(dev);
// 設備次號
hello_minor = MINOR(dev);
// 動態分配給設備空間
hello_dev = kmalloc(sizeof(struct hello_android_dev), GFP_KERNEL);
if(!hello_dev) {
err = -ENOMEM;
printk(KERN_ALERT"Failed to alloc hello_dev.\n");
goto unregister;
}
// 初始化 dev
err = __hello_setup_dev(hello_dev);
if(err) {
printk(KERN_ALERT"Failed to setup dev: %d.\n", err);
goto cleanup;
}
// 創建對應的 class
hello_class = class_create(THIS_MODULE, HELLO_DEVICE_CLASS_NAME);
if(IS_ERR(hello_class)) {
err = PTR_ERR(hello_class);
printk(KERN_ALERT"Failed to create hello class.\n");
goto destroy_cdev;
}
temp = device_create(hello_class, NULL, dev, "%s", HELLO_DEVICE_FILE_NAME);
if(IS_ERR(temp)) {
err = PTR_ERR(temp);
printk(KERN_ALERT"Failed to create hello device.");
goto destroy_class;
}
err = device_create_file(temp, &dev_attr_val);
if(err < 0) {
printk(KERN_ALERT"Failed to create attribute val.");
goto destroy_device;
}
dev_set_drvdata(temp, hello_dev);
// 創建 proc
hello_create_proc();
printk(KERN_ALERT"Succedded to initialize hello device.\n");
return 0;
destroy_device:
device_destroy(hello_class, dev);
destroy_class:
class_destroy(hello_class);
destroy_cdev:
cdev_del(&(hello_dev->dev));
cleanup:
kfree(hello_dev);
unregister:
unregister_chrdev_region(MKDEV(hello_major, hello_minor), 1);
fail:
return err;
}
static void __exit hello_exit(void) {
dev_t devno = MKDEV(hello_major, hello_minor);
printk(KERN_ALERT"Destroy hello device.\n");
hello_remove_proc();
if(hello_class) {
device_destroy(hello_class, MKDEV(hello_major, hello_minor));
class_destroy(hello_class);
}
if(hello_dev) {
cdev_del(&(hello_dev->dev));
kfree(hello_dev);
}
unregister_chrdev_region(devno, 1);
}
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("First Android Driver");
// module 初始化
module_init(hello_init);
// module 釋放
module_exit(hello_exit);
```
> 
* 完整程式 (我放置在 `<kernel\>/drivers/hello` 資料夾)
```c=
// drivers/hello/hello.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/proc_fs.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/proc_fs.h>
#include <linux/slab.h>
#include "hello.h"
// device 編號
static int hello_major = 0;
static int hello_minor = 0;
static struct class* hello_class = NULL;
static struct hello_android_dev* hello_dev = NULL;
static int hello_open(struct inode* inode, struct file* filp);
static int hello_release(struct inode* inode, struct file* filp);
static ssize_t hello_read(struct file* filp, char __user *buf, size_t count, loff_t* f_pos);
static ssize_t hello_write(struct file* filp, const char __user *buf, size_t count, loff_t* f_pos);
static struct file_operations hello_fops = {
.owner = THIS_MODULE,
.open = hello_open,
.release = hello_release,
.read = hello_read,
.write = hello_write,
};
static ssize_t hello_val_show(struct device* dev, struct device_attribute* attr, char* buf);
static ssize_t hello_val_store(struct device* dev, struct device_attribute* attr, const char* buf, size_t count);
static DEVICE_ATTR(val, S_IRUGO | S_IWUSR, hello_val_show, hello_val_store);
static int hello_open(struct inode* inode, struct file* filp) {
struct hello_android_dev* dev;
dev = container_of(inode->i_cdev, struct hello_android_dev, dev);
filp->private_data = dev;
return 0;
}
static int hello_release(struct inode* inode, struct file* filp) {
return 0;
}
static ssize_t hello_read(struct file* filp, char __user *buf, size_t count, loff_t* f_pos) {
ssize_t err = 0;
struct hello_android_dev* dev = filp->private_data;
printk(KERN_ALERT"hello_read: %lu\n", count);
if(down_interruptible(&(dev->sem))) {
return -ERESTARTSYS;
}
if(count < sizeof(dev->val)) {
goto out;
}
if(copy_to_user(buf, &(dev->val), sizeof(dev->val))) {
err = -EFAULT;
goto out;
}
err = sizeof(dev->val);
out:
up(&(dev->sem));
return err;
}
static ssize_t hello_write(struct file* filp, const char __user *buf, size_t count, loff_t* f_pos) {
struct hello_android_dev* dev = filp->private_data;
ssize_t err = 0;
printk(KERN_ALERT"hello_write: %lu\n", count);
if(down_interruptible(&(dev->sem))) {
return -ERESTARTSYS;
}
if(count != sizeof(dev->val)) {
goto out;
}
if(copy_from_user(&(dev->val), buf, count)) {
err = -EFAULT;
goto out;
}
err = sizeof(dev->val);
out:
up(&(dev->sem));
return err;
}
static ssize_t __hello_get_val(struct hello_android_dev* dev, char* buf) {
int val = 0;
printk(KERN_ALERT"__hello_get_val\n");
if(down_interruptible(&(dev->sem))) {
return -ERESTARTSYS;
}
val = dev->val;
up(&(dev->sem));
return snprintf(buf, PAGE_SIZE, "%d\n", val);
}
static ssize_t __hello_set_val(struct hello_android_dev* dev, const char* buf, size_t count) {
int val = 0;
val = simple_strtol(buf, NULL, 10);
printk(KERN_ALERT"__hello_set_val: %lu\n", val);
if(down_interruptible(&(dev->sem))) {
return -ERESTARTSYS;
}
dev->val = val;
up(&(dev->sem));
return count;
}
static ssize_t hello_val_show(struct device* dev, struct device_attribute* attr, char* buf) {
struct hello_android_dev* hdev = (struct hello_android_dev*)dev_get_drvdata(dev);
return __hello_get_val(hdev, buf);
}
static ssize_t hello_val_store(struct device* dev, struct device_attribute* attr, const char* buf, size_t count) {
struct hello_android_dev* hdev = (struct hello_android_dev*)dev_get_drvdata(dev);
return __hello_set_val(hdev, buf, count);
}
static ssize_t hello_proc_read(struct file* filp, char __user *buff, size_t count, loff_t* f_pos) {
printk(KERN_ALERT"Test count: %lu, filp->f_pos: %lu, f_pos: %lu.\n", count, filp->f_pos, *f_pos);
if(*f_pos > 0) {
return 0;
}
*f_pos = __hello_get_val(hello_dev, buff);
return *f_pos;
}
static ssize_t hello_proc_write(struct file* filp, const char __user *buff, size_t count, loff_t* f_pos) {
int err = 0;
char* page = NULL;
if(count > PAGE_SIZE) {
printk(KERN_ALERT"The buff is too large: %lu.\n", count);
return -EFAULT;
}
page = (char*)__get_free_page(GFP_KERNEL);
if(!page) {
printk(KERN_ALERT"Failed to alloc page.\n");
return -ENOMEM;
}
if(copy_from_user(page, buff, count)) {
printk(KERN_ALERT"Failed to copy buff from user.\n");
err = -EFAULT;
goto out;
}
err = __hello_set_val(hello_dev, page, count);
out:
free_page((unsigned long)page);
return err;
}
static struct file_operations hello_proc = {
.owner = THIS_MODULE,
.open = hello_open,
.read = hello_proc_read,
.write = hello_proc_write,
};
static void hello_create_proc(void) {
struct proc_dir_entry* entry;
entry = proc_create_data(HELLO_DEVICE_PROC_NAME,
S_IFREG | S_IRUGO,
NULL,
&hello_proc,
NULL);
}
static void hello_remove_proc(void) {
remove_proc_entry(HELLO_DEVICE_PROC_NAME, NULL);
}
static int __hello_setup_dev(struct hello_android_dev* dev) {
int err;
dev_t devno = MKDEV(hello_major, hello_minor);
memset(dev, 0, sizeof(struct hello_android_dev));
cdev_init(&(dev->dev), &hello_fops);
dev->dev.owner = THIS_MODULE;
dev->dev.ops = &hello_fops;
err = cdev_add(&(dev->dev),devno, 1);
if(err) {
return err;
}
sema_init(&(dev->sem), 1);
dev->val = 0;
printk(KERN_ALERT"__hello_setup_dev\n");
return 0;
}
static int __init hello_init(void){
int err = -1;
dev_t dev = 0;
struct device* temp = NULL;
printk(KERN_ALERT"Initializing hello device.\n");
err = alloc_chrdev_region(&dev, 0, 1, HELLO_DEVICE_NODE_NAME);
if(err < 0) {
printk(KERN_ALERT"Failed to alloc char dev region.\n");
goto fail;
}
hello_major = MAJOR(dev);
hello_minor = MINOR(dev);
hello_dev = kmalloc(sizeof(struct hello_android_dev), GFP_KERNEL);
if(!hello_dev) {
err = -ENOMEM;
printk(KERN_ALERT"Failed to alloc hello_dev.\n");
goto unregister;
}
err = __hello_setup_dev(hello_dev);
if(err) {
printk(KERN_ALERT"Failed to setup dev: %d.\n", err);
goto cleanup;
}
hello_class = class_create(THIS_MODULE, HELLO_DEVICE_CLASS_NAME);
if(IS_ERR(hello_class)) {
err = PTR_ERR(hello_class);
printk(KERN_ALERT"Failed to create hello class.\n");
goto destroy_cdev;
}
temp = device_create(hello_class, NULL, dev, "%s", HELLO_DEVICE_FILE_NAME);
if(IS_ERR(temp)) {
err = PTR_ERR(temp);
printk(KERN_ALERT"Failed to create hello device.");
goto destroy_class;
}
err = device_create_file(temp, &dev_attr_val);
if(err < 0) {
printk(KERN_ALERT"Failed to create attribute val.");
goto destroy_device;
}
dev_set_drvdata(temp, hello_dev);
hello_create_proc();
printk(KERN_ALERT"Succedded to initialize hello device.\n");
return 0;
destroy_device:
device_destroy(hello_class, dev);
destroy_class:
class_destroy(hello_class);
destroy_cdev:
cdev_del(&(hello_dev->dev));
cleanup:
kfree(hello_dev);
unregister:
unregister_chrdev_region(MKDEV(hello_major, hello_minor), 1);
fail:
return err;
}
static void __exit hello_exit(void) {
dev_t devno = MKDEV(hello_major, hello_minor);
printk(KERN_ALERT"Destroy hello device.\n");
hello_remove_proc();
if(hello_class) {
device_destroy(hello_class, MKDEV(hello_major, hello_minor));
class_destroy(hello_class);
}
if(hello_dev) {
cdev_del(&(hello_dev->dev));
kfree(hello_dev);
}
unregister_chrdev_region(devno, 1);
}
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("First Android Driver");
module_init(hello_init);
module_exit(hello_exit);
```
### 編譯驅動 - 設定核心 Makefile 編譯檔
> 我們可以動代安裝驅動,也可以將驅動編譯進核心當中… 以下使用將驅動編譯進核心內
* **Makefile 檔案**:
編譯 hello 字節驅動的設定檔案
> `CONFIG_HELLO` 是一個變數(等等會使用),它的值取決於 hello 的編譯方式 (y、m、其他)
>
> * `obj-m` 將驅動作為模塊編譯
> * `obj-y` 將驅動編譯進核心
```shell=
## drivers/hello/Makefile
obj-$(CONFIG_HELLO) += hello.o
```
* **Kconfig 檔案**:
該檔案定義驅動程式 hello 在 `make menuconfig` 上的顯示、預設狀態,但若要真的顯示則須修改上層的 Kconfig
| 使用字元 | menuconfig 顯示符號 | 說明 |
| -------- | -------- | -------- |
| y | `[*]` | 一起編譯入核心 |
| m | `[m]` | 用 Module 的方式編譯 |
| 任意其他字元 | `[ ]` | 不編譯 |
```shell=
## drivers/hello/Kconfig
config HELLO
## menuconfig 上面顯示的名稱
tristate "First Android Driver"
## 預設加入核心編譯
default y
## 幫助說明
help
This is the first android driver.
```
完成編寫 `Kconfig` 後,到 Kernel root 資料夾,執行以下命令
```shell=
make menuconfig
```
再進入 `Device Drivers` 子目錄
> 
找到剛剛編寫的 Kconfig 設定,並選擇將 `hello` 驅動一起編入核心
> 
:::danger
* 該 hello 驅動的 `Kconfig` 檔案,核心預設是無法找到的,所以我們必須手動修改核心的 drvices#`Kconfig` 檔案
```shell=
vim ./drvices/kconfig
```
> 
:::info
* 最終編譯出來的結果
> 
:::
:::
<!-- 1. **修改驅動核心 `Kconfig` 檔案**:這樣才能真正在 `make menuconfig` 看到我們要的 hello 驅動
```shell=
## drivers/Kconfig
... 省略部分
source "drivers/hello/Kconfig"
endmenu
```
> 
2. **修改驅動核心 Makefile 檔案**:核心 Kconfig 會先編譯 `drivers/Makefile` 最後才能找到目標的 `drivers/hello/Makefile` 檔案
```shell=
## drivers/Makefile
+obj-$(CONFIG_HELLO) += hello/
``` -->
### 重新編譯核心 - 驗證 hello 驅動
:::info
**先使用指令 `make menuconfig` 來確認開啟,我們的 `First Android Driver` 裝置**
:::
* 核心編譯 (切換到 <kernel root\>),並使用以下指令就可以編譯出新的 bzImage 核心
```shell=
make -j$(nproc --all)
```
> 
* 使用指令指定核心,並開啟模擬器
```shell=
## 使用 `-kerenl` 指定
emulator -verbose \
-show-kernel \
-no-cache \
-no-snapshot \
-kernel ~/Desktop/android_kernel/goldfish/arch/x86_64/boot/bzImage
```
:::info
* 你也可以指定模擬器(這裡就不說明如何創建模擬器)
:::
1. **驗證 `/dev` 資料夾下是否有 hello 驅動裝置**
```shell=
adb root && adb shell
ls /dev/hello
```
> 
2. **驗證 `/proc` 資料夾下是否有 hello 驅動裝置的映射**,並 **對 hello 裝置 val 進行修改**
```shell=
cat /proc/hello
# 寫入 '7'
echo '7' > /proc/hello
cat /proc/hello
```
> 
3. **驗證** `/sys/class/hello/hello` 是否有創建,並進行修改、讀取
```shell=
cat /sys/class/hello/hello/val
echo '1' > /sys/class/hello/hello/val
cat /sys/class/hello/hello/val
```
> 
<!-- //## 驗證 Android dev 裝置 -->
## 本地 Linux so 開發 - 開啟 hello 驅動
:::info
由於 HAL 層運作在使用者空間,所以以下使用 Android Open Source 開發(不是核心唷!),版本為 **`android-10.0._r1` 分支**
> 我們先不開發 HAL,先開發普通 Linux so,並用他來開啟 hello 驅動
:::
* 在 **`<android source>/external` 資料夾** 下建立一個 `hello` 資料夾,並在內部創建檔案
> 
:::success
* 使用 Android source 內置的 Prebuild Header 來編譯,就不用特別去下載預編譯檔案(也就是使用 Android source 內的交叉編譯器)
> 
:::
1. **創建 `hello.c` 檔案**:
透過 Linux 提供的 write、read 方法來讀寫 `/dev/hello` 虛擬裝置
```c=
// external/hello/hello.c
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#define HELLO_DEV "/dev/hello"
int main(int argc, char** argv) {
(void)argc;
(void)argv;
int fd = -1;
int val = 0;
// 開啟 hello 裝置
fd = open(HELLO_DEV, O_RDWR);
if(fd == -1) {
printf("Failed to open device %s\n.", HELLO_DEV);
return -1;
}
int res = read(fd, &val, sizeof(val));
if(res != -1) {
printf("Read success, val: %d\n", val);
} else {
printf("Read fail.\n");
}
val = 5;
printf("Write value %d to %s.\n\n", val, HELLO_DEV);
res = write(fd, &val, sizeof(val));
if(res != -1) {
printf("Write success\n");
} else {
printf("Write fail.\n");
}
printf("Read value again:\n");
res = read(fd, &val, sizeof(val));
if(res != -1) {
printf("Read success: %d\n", val);
} else {
printf("Read fail.\n");
}
close(fd);
return 0;
}
```
2. **創建 Android.mk**:編譯 `hello.c` 的規則
```shell=
# external/hello/Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
# 編譯條件(模組、編譯進核心… 都編)
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE := hello
LOCAL_SRC_FILES := $(call all-subdir-c-files)
include $(BUILD_EXECUTABLE)
```
:::info
* `include $(BUILD_EXECUTABLE)` 代表可執行模組
:::
### 編譯 hello 驅動
* 使用(`mmm`)編譯上述檔案為 os 檔,最終可以看到 os 輸出在 `out/target/product/generic_x86_64/system/bin/hello` 目錄中
> 請先切換到 android source 目錄下再執行以下命令
```shell=
## 單獨編譯模塊
mmm -j$(nproc --all) ./external/hello/
## 單獨創建 system.img
make -j$(nproc --all) snod
```
> 
### 測試 hello 驅動
* **測試讀取、寫入 `/dev/hello` 虛擬裝置**:以下我們來驗證我們剛剛寫的 `.so` 檔是否可以正常運作
1. 透過 adb 將 so 動態連結檔案 push 到裝置上
> 請用 adb root 模式進入 android 虛擬機
```shell=
# push hello 檔案到 /data 目錄下
adb push ./out/target/product/generic_x86_64/system/bin/hello /data/
```
2. 運行 hello 檔案
```shell=
adb root && adb shell
## 進入模擬器
/data/hello
```
> 
## Android HAL Module
開發完驅動程式後,接著要 **開發 `Android HAL`驅動模組**
:::info
* **開發完後的 HAL 模塊可以被 Android 系統自動裝載,不須開發人員手動裝載**(這同時也看出了框架的特性… 也就是 Android HAL 有「**約定的規則**」)
:::
### 自訂 HAL Module - hello 模塊
1. **標頭 `hello.h` 檔案**:
定義硬體、模組 ID,並宣告一個硬體結構 (**符合規定以 `hw_device_t` 開頭**)、模組結構 (**符合規定以 `hw_module_t` 開頭**)
> 資料夾 `./hardware/libhardware/include/hardware/` 下,創建 `hello.h` 檔案
* **操作 Android HAL 結構**:操作的自訂的結構須以 `hw_device_t` 開頭,並且 **可在該結構下定義操作驅動的方法**
```c=
struct hello_device_t {
// 符合規定以 `hw_device_t` 開頭
struct hw_device_t common;
int fd;
int (*set_val)(struct hello_device_t* dev, int val);
int (*get_val)(struct hello_device_t* dev, int* val);
};
```
* **設定 Android HAL 訊息**:設定 Android HAL 訊息資料須以 `hw_module_t` 開頭
```c=
struct hello_module_t {
// 符合規定以 `hw_module_t` 開頭
struct hw_module_t common;
};
```
完整程式如下
```c=
// hello.h
#ifndef __HELLO_INTERFACE_H
#define __HELLO_INTERFACE_H
#include <hardware/hardware.h>
#define HELLO_HARDWARE_MODULE_ID "hello"
#define HELLO_HARDWARE_DEVICE_ID "hello"
struct hello_module_t {
// 符合規定以 `hw_module_t` 開頭
struct hw_module_t common;
};
struct hello_device_t {
// 符合規定以 `hw_device_t` 開頭
struct hw_device_t common;
int fd;
int (*set_val)(struct hello_device_t* dev, int val);
int (*get_val)(struct hello_device_t* dev, int* val);
};
#endif
```
:::info
* 之後可透過 `HELLO_HARDWARE_MODULE_ID` 取得驅動服務
:::
2. **源碼 `hello.cpp` 檔案**:
> 資料夾 `./hardware/libhardware/modules/hello/` 下,創建 `hello.cpp` 檔案
* **定義 HAL 驅動入口**:
**^1.^ 抽象模組規定「`HAL_MODULE_INFO_SYM`」符號**,並 **指定 `.common` 成員 ^2.^ `.tag` 必須使用「`HARDWARE_MODULE_TAG`」**
```c=
struct hello_module_t HAL_MODULE_INFO_SYM = {
.common =
{
.tag = HARDWARE_MODULE_TAG,
.version_major = 1,
.version_minor = 0,
.id = HELLO_HARDWARE_MODULE_ID, // 定義 Module ID
.name = MODULE_NAME,
.author = MODULE_AUTHOR,
.methods = &hello_module_methods, // 定義開啟硬體方法
},
};
```
:::success
* **`HAL_MODULE_INFO_SYM` 相當於 HAL 驅動的入口!**(就像 Main 函數一般)
:::
* **定義 `hw_module_methods_t` 結構**:用來開啟硬體裝置
```c=
static struct hw_module_methods_t hello_module_methods = {
// 真正開啟方法
.open = hello_device_open
};
```
完整程式如下
```c=
// hello.cpp
#include <hardware/hardware.h>
#include <hardware/hello.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
#include <cstring>
#include <cutils/log.h>
#include <cutils/atomic.h>
#define DEVICE_NAME "/dev/hello"
#define MODULE_NAME "Hello"
#define MODULE_AUTHOR "alien123@gmail.com"
static int hello_device_open(const struct hw_module_t* module, const char* id, struct hw_device_t** device);
static int hello_device_close(struct hw_device_t* device);
static int hello_get_val(struct hello_device_t* dev, int* val);
static int hello_set_val(struct hello_device_t* dev, int val);
static struct hw_module_methods_t hello_module_methods = {
// 真正開啟方法
.open = hello_device_open
};
struct hello_module_t HAL_MODULE_INFO_SYM = {
.common =
{
.tag = HARDWARE_MODULE_TAG,
.version_major = 1,
.version_minor = 0,
.id = HELLO_HARDWARE_MODULE_ID, // 定義 Module ID
.name = MODULE_NAME,
.author = MODULE_AUTHOR,
.methods = &hello_module_methods, // 定義開啟硬體方法
},
};
```
* **定義 HAL 函數的各個實做(包括 `open`, `close`... 等方法)**
1. **開啟**(賦予到 `hw_module_methods_t`#`.open`)、**關閉硬體的方法**(賦予給 `hw_device_t`#`.common.close`)
```c=
// hello.cpp
// 開啟 /dev/hello 裝置的方法
static int hello_device_open(const struct hw_module_t* module, const char* id, struct hw_device_t** device) {
if(strcmp(id, HELLO_HARDWARE_DEVICE_ID)) {
return -EFAULT;
}
struct hello_device_t* dev;
int size = sizeof(hello_device_t);
// 動態分配記憶體
dev = (struct hello_device_t*) malloc(size);
if(!dev) {
printf("Failed to alloc space for hello_device_t\n");
}
memset(dev, 0, size);
// `hw_module_t` tag 必須定義為 HARDWARE_DEVICE_TAG
dev->common.tag = HARDWARE_DEVICE_TAG;
dev->common.version = 0;
// 強制轉型 struct
dev->common.module = (hw_module_t*) module;
dev->common.close = hello_device_close;
// 設定函數指標
dev->set_val = hello_set_val;
dev->get_val = hello_get_val;
// 開啟 hello 裝置
if((dev->fd = open(DEVICE_NAME, O_RDWR)) == -1) {
printf("Failed to open device file /dev/hello\n");
free(dev);
return -EFAULT;
}
*device = &(dev->common); // 賦予 device
printf("Open device success\n");
return 0;
}
// 透過 fd 來關閉 /dev/hello 裝置
static int hello_device_close(struct hw_device_t* device) {
struct hello_device_t* dev = (struct hello_device_t*) device;
if(dev) {
close(dev->fd);
free(dev);
}
return 0;
}
```
2. **定義 HAL module 對於 `/dev/hello` 虛擬裝置的讀寫**
```c=
// hello.cpp
static int hello_get_val(struct hello_device_t* dev, int* val) {
if(!dev) {
printf("Null dev ptr");
return -EFAULT;
}
if(!val) {
printf("Null val ptr");
return -EFAULT;
}
read(dev->fd, val, sizeof(*val));
printf("Get value %d from device file /dev/hello.\n", *val);
return 0;
}
static int hello_set_val(struct hello_device_t* dev, int val) {
if(!dev) {
printf("Null dev ptr");
return -EFAULT;
}
printf("Set value %d to device file /dev/hello.\n", val);
write(dev->fd, &val, sizeof(val));
return 0;
}
```
---
* 完整程式如下
```c=
// hello.cpp
#include <hardware/hardware.h>
#include <hardware/hello.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
#include <cstring>
#include <cutils/log.h>
#include <cutils/atomic.h>
#define DEVICE_NAME "/dev/hello"
#define MODULE_NAME "Hello"
#define MODULE_AUTHOR "alien123@gmail.com"
static int hello_device_open(const struct hw_module_t* module, const char* id, struct hw_device_t** device);
static int hello_device_close(struct hw_device_t* device);
static int hello_get_val(struct hello_device_t* dev, int* val);
static int hello_set_val(struct hello_device_t* dev, int val);
static struct hw_module_methods_t hello_module_methods = {
.open = hello_device_open
};
struct hello_module_t HAL_MODULE_INFO_SYM = {
.common =
{
.tag = HARDWARE_MODULE_TAG,
.version_major = 1,
.version_minor = 0,
.id = HELLO_HARDWARE_MODULE_ID,
.name = MODULE_NAME,
.author = MODULE_AUTHOR,
.methods = &hello_module_methods,
},
};
static int hello_device_open(const struct hw_module_t* module, const char* id, struct hw_device_t** device) {
if(strcmp(id, HELLO_HARDWARE_DEVICE_ID)) {
return -EFAULT;
}
struct hello_device_t* dev;
int size = sizeof(hello_device_t);
dev = (struct hello_device_t*) malloc(size);
if(!dev) {
printf("Failed to alloc space for hello_device_t\n");
}
memset(dev, 0, size);
dev->common.tag = HARDWARE_DEVICE_TAG;
dev->common.version = 0;
dev->common.module = (hw_module_t*) module;
dev->common.close = hello_device_close;
dev->set_val = hello_set_val;
dev->get_val = hello_get_val;
if((dev->fd = open(DEVICE_NAME, O_RDWR)) == -1) {
printf("Failed to open device file /dev/hello\n");
free(dev);
return -EFAULT;
}
*device = &(dev->common);
printf("Open device success\n");
return 0;
}
static int hello_device_close(struct hw_device_t* device) {
struct hello_device_t* dev = (struct hello_device_t*) device;
if(dev) {
close(dev->fd);
free(dev);
}
return 0;
}
static int hello_get_val(struct hello_device_t* dev, int* val) {
if(!dev) {
printf("Null dev ptr");
return -EFAULT;
}
if(!val) {
printf("Null val ptr");
return -EFAULT;
}
read(dev->fd, val, sizeof(*val));
printf("Get value %d from device file /dev/hello.\n", *val);
return 0;
}
static int hello_set_val(struct hello_device_t* dev, int val) {
if(!dev) {
printf("Null dev ptr");
return -EFAULT;
}
printf("Set value %d to device file /dev/hello.\n", val);
write(dev->fd, &val, sizeof(val));
return 0;
}
```
### 編譯 HAL module - hello
* **Android.mk**:定義 HAL module 的編譯規則
> 路徑 `./hardware/libhardware/modules/hello/`
```shell=
## Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAG := optional
LOCAL_PRELINK_MODULE := false
LOCAL_MODULE_RELATIVE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw
LOCAL_SHARED_LIBRARIES := liblog
LOCAL_SRC_FILES := hello.cpp
LOCAL_MODULE := hello.default
include $(BUILD_SHARED_LIBRARY)
```
* **編譯 Android HAL**
```shell=
## 單獨編譯 指定 模塊
mmm -j$(nproc --all) ./hardware/libhardware/modules/hello/
make -j$(nproc --all) snod
```
* 模塊編譯結果
> 
* 實際輸出 `hello.default.so` 到 `/system/lib64/hw/` 目錄下
> 
* system.img 編譯結果
> 
## JNI 存取硬體
接下來我們要開發一個服務,該服務類似於 AMS、PMS 可以讓使用者從 ServiceManager 取得
* 由於硬體抽象層模組是 C++ 開發,如果要使用就要開發
1. Java 層(應用層):作為 BinderServer 服務端
2. JNI 層(銜接 Native 層):用來讓 Java 訪問 C++ 層數據
3. 添加到 ServiceManager 註冊,這樣才可以讓其他 App 使用
### AIDL 通訊 - IHelloService.aidl
1. **創建 AIDL 檔案**:
在 `./frameworks/base/core/java/android/os` 目錄下創建一個 `IHelloService.aidl` 檔案,並添加要使用的接口 (這邊簡單添加一個 setter & getter)
```java=
// IHelloService.aidl
package android.os;
interface IHelloService {
void setVal(int val);
int getVal();
}
```
2. **將 AIDL 檔案加入到 `Android.bp` 檔案**:
切換到 `./frameworks/base` 目錄下,編輯 Android.bp 檔案,把剛剛新增的 aidl 檔案加入 `srcs` Array 中(之後執行編譯時,就會透過 `aidl` 工具產生對應 Java 檔)
```shell=
## Android.bp
java_defaults {
name: "framework-defaults",
installable: true,
srcs: [
...
"core/java/android/os/IHelloService.aidl",
...
],
...
}
```
> 
3. **編譯 AIDL 檔案**:
透過 `mmm` 命令,模組編譯 `./frameworks/base`,自動產生跨進程通訊界面
```shell=
mmm -j$(nproc) ./frameworks/base
```
> 
* 這裡簡單的說明一下 AIDL 如何產生檔案,與 Binder 服務有啥關係
1. IHelloService 實作一個統一介面 `IInterface`,讓 BinderService、BinderClient 有相同介面
2. IHelloService.Stub 繼承於 Binder 並實作 IHelloService 介面;**讓服務端使用,也會從 Stub 類中接收到其他進程的訊息**
3. IHelloService.Proxy 實作 IHelloService 介面 (這樣保證了 Service & Client 有相同接口);**Proxy 代表了 Client 端代理訪問 Service**
:::success
* `IHelloService`、`IHelloService.Stub`、`IHelloService.Proxy` 是什麼?
由 Android 編譯期間,透過 Android AIDL 工具,幫我們分析 AIDL 檔案,並產生相對應的 Java、C++ 檔
> AIDL 工具可在 SDK 包的 `build-tools` 目錄底下找到
:::
:::spoiler 經過編譯後系統自動生程 IHelloService 的 Java 檔案
```java=
/*
* This file is auto-generated. DO NOT MODIFY.
*/
package android.os;
public interface IHelloService extends android.os.IInterface
{
...
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements android.os.IHelloService
{
private static final java.lang.String DESCRIPTOR = "android.os.IHelloService";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
public static android.os.IHelloService asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof android.os.IHelloService))) {
return ((android.os.IHelloService)iin);
}
return new android.os.IHelloService.Stub.Proxy(obj);
}
@Override public android.os.IBinder asBinder()
{
return this;
}
/** @hide */
public static java.lang.String getDefaultTransactionName(int transactionCode)
{
switch (transactionCode)
{
case TRANSACTION_setVal:
{
return "setVal";
}
case TRANSACTION_getVal:
{
return "getVal";
}
default:
{
return null;
}
}
}
/** @hide */
public java.lang.String getTransactionName(int transactionCode)
{
return this.getDefaultTransactionName(transactionCode);
}
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
java.lang.String descriptor = DESCRIPTOR;
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(descriptor);
return true;
}
case TRANSACTION_setVal:
{
data.enforceInterface(descriptor);
int _arg0;
_arg0 = data.readInt();
this.setVal(_arg0);
reply.writeNoException();
return true;
}
case TRANSACTION_getVal:
{
data.enforceInterface(descriptor);
int _result = this.getVal();
reply.writeNoException();
reply.writeInt(_result);
return true;
}
default:
{
return super.onTransact(code, data, reply, flags);
}
}
}
private static class Proxy implements android.os.IHelloService
{
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote)
{
mRemote = remote;
}
@Override public android.os.IBinder asBinder()
{
return mRemote;
}
public java.lang.String getInterfaceDescriptor()
{
return DESCRIPTOR;
}
@Override public void setVal(int val) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(val);
boolean _status = mRemote.transact(Stub.TRANSACTION_setVal, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
getDefaultImpl().setVal(val);
return;
}
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
@Override public int getVal() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
int _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
boolean _status = mRemote.transact(Stub.TRANSACTION_getVal, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
return getDefaultImpl().getVal();
}
_reply.readException();
_result = _reply.readInt();
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
public static android.os.IHelloService sDefaultImpl;
}
static final int TRANSACTION_setVal = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_getVal = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
public static boolean setDefaultImpl(android.os.IHelloService impl) {
if (Stub.Proxy.sDefaultImpl == null && impl != null) {
Stub.Proxy.sDefaultImpl = impl;
return true;
}
return false;
}
public static android.os.IHelloService getDefaultImpl() {
return Stub.Proxy.sDefaultImpl;
}
}
public void setVal(int val) throws android.os.RemoteException;
public int getVal() throws android.os.RemoteException;
}
```
:::
### 實現 Java 調用 JNI 函數 - HelloService
:::info
這裡就是實現 Binder Server 的地方 (實際服務)
:::
* 在 `./frameworks/base/services/core/java/com/android/server/` 目錄下新增 `IHelloService.java` 檔案,並實作 `IHelloService.Stub` 接口
```java=
package com.android.server;
import android.os.IHelloService;
public class HelloService extends IHelloService.Stub {
// 用來儲存硬體控制的地址
private static final long HW_PTR;
static {
HW_PTR = init_native();
if(HW_PTR == -1) {
System.out.println("Failed to init hello service.\n");
}
}
@Override
public void setVal(int val) {
if(HW_PTR == -1) {
System.out.println("hello service haven't init.\n");
return;
}
setVal_native(HW_PTR, val);
}
@Override
public int getVal() {
if(HW_PTR == -1) {
System.out.println("hello service haven't init.\n");
return -1;
}
return getVal_native(HW_PTR);
}
// JNI 函數
private static native long init_native();
private static native void setVal_native(long ptr, int val);
private static native int getVal_native(long ptr);
}
```
* 嘗試編譯 `IHelloService.java` 檔案
```shell=
mmm -j$(nproc) ./frameworks/base/services/core/java/
```
> 
### 實現 JNI 方法
1. **撰寫 JNI(`.cpp` 檔):** 其中包括開啟、操作驅動
在 `./frameworks/base/services/core/jni` 目錄下新增 `com_android_server_HelloService.cpp` 檔案,並實作 `HelloService` 需要的 JNI 方法
> 透過 `<hardware/hardware.h>` 庫提供的 `hw_get_module` 來驅動 Android HAL 系統加載,幫我們自動加載 `.so` 庫
:::success
* 由於經過 Android 編譯的 HAL 驅動是屬於「系統路徑」,所以需要使用 `<>` 尖?。:括號
:::
```cpp=
// com_android_server_HelloService.cpp
#define LOG_TAG "HelloServiceJNI"
#include "jni.h"
#include "android_runtime/AndroidRuntime.h"
#include <nativehelper/JNIHelp.h>
#include <utils/misc.h>
#include <utils/Log.h>
#include <hardware/hardware.h>
// 自己定義的驅動
#include <hardware/hello.h>
#include <stdio.h>
namespace android {
static void hello_setVal(JNIEnv* env, jobject clazz, jlong ptr, jint value) {
hello_device_t* device = (hello_device_t*) ptr;
if(!device) {
printf("Device hello is not open\n");
return;
}
int val = value;
printf("Set value %d to device hello.\n", val);
device->set_val(device, val);
}
static jint hello_getVal(JNIEnv* env, jobject clazz, jlong ptr) {
hello_device_t* device = (hello_device_t*) ptr;
if(!device) {
printf("Device hello is not open.\n");
return 0;
}
int val = 0;
device->get_val(device, &val);
return val;
}
static inline int hello_device_open(const hw_module_t* module, struct hello_device_t** device) {
return module->methods->open(
module,
HELLO_HARDWARE_DEVICE_ID,
(struct hw_device_t**) device);
}
static jlong hello_init(JNIEnv* env, jclass clazz) {
hello_module_t* module;
hello_device_t* device;
printf("Init HAL stub hello.\n");
// 1. 透過 id 取得 so 硬體模組
if(hw_get_module(HELLO_HARDWARE_MODULE_ID, (const struct hw_module_t**) &module) != 0) {
printf("Load hello so fail.\n");
return -1;
}
// 2. 開啟模組
if(hello_device_open(&(module->common), &device) != 0) {
printf("Open hello dev fail.\n");
return -1;
}
printf("Open hello dev success.\n");
return 0;
}
// 對應 Java & JNI 實作的方法
static const JNINativeMethod method_table[] = {
{ "init_native", "()J", (void*) hello_init },
{ "setVal_native", "(II)V", (void*) hello_setVal},
{ "getVal_native", "(I)I", (void*) hello_getVal},
};
// 動態註冊 JNI 方法 (尚未完成,等等會呼叫該方法)
int register_android_server_HelloService(JNIEnv* env) {
return jniRegisterNativeMethods(env,
// JNI 庫的位置
"com/android/server/HelloService",
method_table,
NELEM(method_table));
}
}
```
2. **動態註冊 JNI**
在 `./frameworks/base/services/core/jni/onload.cpp` 路徑下,**修改 `onload.cpp` 檔案,將註冊函數 `register_android_server_HelloService` 寫入**
```cpp=
// onload.cpp
namespace android {
...
int register_android_server_HelloService(JNIEnv* env);
}
extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
{
JNIEnv* env = NULL;
jint result = -1;
// 獲取 JNIEnv 結構的指標(獲取後會放置到 env 地址中)
// 並且必須指定 J2SE1.4 及中上版本
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
ALOGE("GetEnv failed!");
return result;
}
ALOG_ASSERT(env, "Could not retrieve the env!");
...
// 完成動態註冊
register_android_server_HelloService(env);
// 返回 JNI_VERSION_1_4,代表只運行在 JDK 1.4 以及以上版本的 Java 程序
// 才可能調用當前的 JNI
return JNI_VERSION_1_4;
}
```
3. 使用 mmm 嘗試編譯 jni,確認是否成功
```shell=
mmm -j$(nproc) ./frameworks/base/services/core/jni/
```
> 
### 向 ServiceManager 註冊服務
* 直接修改 ServiceManager 檔案,將 **HelloService** 加進系統服務
> Path: `./frameworks/base/services/java/com/android/server/SystemServer.java`
```java=
// SystemServer.java
private void startOtherServices() {
...
// 添加服務,之後要取用服務時,
ServiceManager.addService("hello", new HelloService());
}
```
* 用 mmm 模組編譯,並重新編譯 `system.img`
```shell=
mmm -j$(nproc) ./frameworks/base/services/java/
make -j$(nproc) snod
```
> 
>
> 
## 應用實現
直接在 Android source 目錄 `./packages/experimental/Hello`,創建一個應用,用它來訪問 `/dev/hello` 裝置
目錄結構如下
> 
### App 實作
* 以下只會列出重點檔案
1. AndroidManifest.xml
```xml=
<!-- AndroidManifest.xml -->
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.example.hello">
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:versionCode="1"
android:versionName="1.0">
<activity
android:name=".Hello"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
```
2. Hello.java
```java=
package com.example.hello;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.IHelloService;
import android.util.Log;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
public class Hello extends Activity {
private static final String TAG = Hello.class.getSimpleName();
private IHelloService helloService;
private EditText editText;
private Button readBtn, writeBtn, clearBtn;
@SuppressLint("WrongConstant")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
try {
helloService = IHelloService.Stub
.asInterface((IBinder) getSystemService("hello"));
} catch (Exception e) {
Toast.makeText(this, "Open hello device fail.", Toast.LENGTH_SHORT).show();
}
initView();
initEvent();
}
private void initView() {
editText = findViewById(R.id.editText);
readBtn = findViewById(R.id.readBtn);
writeBtn = findViewById(R.id.writeBtn);
clearBtn = findViewById(R.id.clearBtn);
}
private void initEvent() {
readBtn.setOnClickListener(view -> {
try {
helloService.getVal();
} catch (RemoteException e) {
Log.e(TAG, "Fail of getVal");
}
});
writeBtn.setOnClickListener(view -> {
try {
helloService.setVal(Integer.parseInt(editText.getText().toString()));
} catch (RemoteException e) {
Log.e(TAG, "Fail of setVal");
}
});
clearBtn.setOnClickListener(view -> editText.setText(""));
}
}
```
3. Android.mk
```shell=
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_PACKAGE_NAME := Hello
LOCAL_SDK_VERSION := current
LOCAL_PRIVILEGED_MODULE := true
include $(BUILD_PACKAGE)
```
* 用 mmm 模組編譯,並重新編譯 `system.img`
```shell=
mmm -j$(nproc) ./packages/experimental/Hello/
make -j$(nproc) snod
```
> 
* 啟動模擬器
```shell=
emulator -verbose \
-show-kernel \
-no-cache \
-no-snapshot \
## 指定 system.img
-system /media/alien/480G/android_source_12/out/target/product/generic_x86_64/system.img \
## 指定 Kernel
-kernel ~/Desktop/android_kernel/goldfish/arch/x86_64/boot/bzImage \
-avd Pixel_3_XL_API_29 ## 指定模擬器
```
## Appendix & FAQ
:::info
:::
###### tags: `Android 系統`