--- 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); ``` > ![](https://i.imgur.com/DDx40Ra.png) * 完整程式 (我放置在 `<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` 子目錄 > ![](https://hackmd.io/_uploads/HJIMdjJ62.png) 找到剛剛編寫的 Kconfig 設定,並選擇將 `hello` 驅動一起編入核心 > ![](https://i.imgur.com/UpvEljH.png) :::danger * 該 hello 驅動的 `Kconfig` 檔案,核心預設是無法找到的,所以我們必須手動修改核心的 drvices#`Kconfig` 檔案 ```shell= vim ./drvices/kconfig ``` > ![](https://hackmd.io/_uploads/rkXdcj1pn.png) :::info * 最終編譯出來的結果 > ![](https://hackmd.io/_uploads/rkWZ2j16h.png) ::: ::: <!-- 1. **修改驅動核心 `Kconfig` 檔案**:這樣才能真正在 `make menuconfig` 看到我們要的 hello 驅動 ```shell= ## drivers/Kconfig ... 省略部分 source "drivers/hello/Kconfig" endmenu ``` > ![](https://i.imgur.com/UpvEljH.png) 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) ``` > ![](https://i.imgur.com/8LLEEkh.png) * 使用指令指定核心,並開啟模擬器 ```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 ``` > ![](https://i.imgur.com/pfiQUvg.png) 2. **驗證 `/proc` 資料夾下是否有 hello 驅動裝置的映射**,並 **對 hello 裝置 val 進行修改** ```shell= cat /proc/hello # 寫入 '7' echo '7' > /proc/hello cat /proc/hello ``` > ![](https://i.imgur.com/p6rjc2S.png) 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 ``` > ![](https://i.imgur.com/DrUjf0n.png) <!-- //## 驗證 Android dev 裝置 --> ## 本地 Linux so 開發 - 開啟 hello 驅動 :::info 由於 HAL 層運作在使用者空間,所以以下使用 Android Open Source 開發(不是核心唷!),版本為 **`android-10.0._r1` 分支** > 我們先不開發 HAL,先開發普通 Linux so,並用他來開啟 hello 驅動 ::: * 在 **`<android source>/external` 資料夾** 下建立一個 `hello` 資料夾,並在內部創建檔案 > ![image](https://hackmd.io/_uploads/B1EXmbvP6.png) :::success * 使用 Android source 內置的 Prebuild Header 來編譯,就不用特別去下載預編譯檔案(也就是使用 Android source 內的交叉編譯器) > ![image](https://hackmd.io/_uploads/BJVUNbww6.png) ::: 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 ``` > ![](https://i.imgur.com/1Ih2uID.png) ### 測試 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 ``` > ![](https://i.imgur.com/1u9wScx.png) ## 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 ``` * 模塊編譯結果 > ![](https://i.imgur.com/RCg7cTm.png) * 實際輸出 `hello.default.so` 到 `/system/lib64/hw/` 目錄下 > ![](https://i.imgur.com/dl4iI7K.png) * system.img 編譯結果 > ![](https://i.imgur.com/YfDxjnW.png) ## 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", ... ], ... } ``` > ![](https://hackmd.io/_uploads/rJ17vcyph.png) 3. **編譯 AIDL 檔案**: 透過 `mmm` 命令,模組編譯 `./frameworks/base`,自動產生跨進程通訊界面 ```shell= mmm -j$(nproc) ./frameworks/base ``` > ![](https://i.imgur.com/IX3vT0J.png) * 這裡簡單的說明一下 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/ ``` > ![](https://i.imgur.com/NsayJTr.png) ### 實現 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/ ``` > ![](https://i.imgur.com/V6MSxAc.png) ### 向 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 ``` > ![](https://i.imgur.com/WkHLJtz.png) > > ![](https://i.imgur.com/gKLC7ig.png) ## 應用實現 直接在 Android source 目錄 `./packages/experimental/Hello`,創建一個應用,用它來訪問 `/dev/hello` 裝置 目錄結構如下 > ![](https://i.imgur.com/RBkt3Tq.png) ### 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 ``` > ![](https://i.imgur.com/SmoGmys.png) * 啟動模擬器 ```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 系統`