--- title: 'Binder - Kernel 驅動分析' disqus: kyleAlien --- Binder - Kernel 驅動分析 === ## OverView of Content 請配合 [**Binder 驅動結構**](https://hackmd.io/TtT34BAwRP2sZ3vALt4Drg?view) 一起看 [TOC] ## Binder 概述 - Misc device * 我們需要先知道 Binder 是一個 **雜項(miscellaneous)驅動**,簡單來說 Binder 是一個虛擬文件,它會直接存在內存(`dev` 中 ) :::info * `/dev` 目錄下就是一個真實的設備 ? Binder 並不是一個真實的硬體設備,Binder 驅動運行於內核,**屬於虛擬驅動設備** ::: * **Misc (Miscellaneous) 雜項驅動的 ++主設設備號統一為 10++,次設備號是每個設備獨有的**,驅動程式可以透過 MISC_DYNAMIC_MINOR 來由系統動態分配次設備號 ### Binder 初始化 - binder_init * Binder 的進入點是 [**binder**](https://cs.android.com/android/kernel/superproject/+/common-android-mainline:common/drivers/android/binder.c)#**device_initcall** 函數,在系統初始化時會呼叫 `binder_init` 函數 ```c= // binder.c device_initcall(binder_init); ``` * **binder_init** 分析:根據檔案設定創建相關的 binder 檔案 ```c= // common/drivers/android/binder.c static struct dentry *binder_debugfs_dir_entry_root; static int __init binder_init(void) { int ret; char *device_name, *device_tmp struct binder_device *device; struct hlist_node *tmp; char *device_names = NULL; ... 省略部分 // 創建 debug 目錄 binder_debugfs_dir_entry_root = debugfs_create_dir("binder", NULL); if (binder_debugfs_dir_entry_root) { const struct binder_debugfs_entry *db_entry; binder_for_each_debugfs_entry(db_entry) debugfs_create_file(db_entry->name, db_entry->mode, binder_debugfs_dir_entry_root, db_entry->data, db_entry->fops); // 在 proc 目錄下創建一個 binder 資料夾 binder_debugfs_dir_entry_proc = debugfs_create_dir("proc", binder_debugfs_dir_entry_root); } // 1. 判斷 Kconfig 檔案 if (!IS_ENABLED(CONFIG_ANDROID_BINDERFS) && strcmp(binder_devices_param, "") != 0) { // 讀取 Kconfig 的參數 // kstrdup 為現有字符串分配空間並複制 device_names = kstrdup(binder_devices_param, GFP_KERNEL); if (!device_names) { ret = -ENOMEM; goto err_alloc_device_names_failed; } device_tmp = device_names; while ((device_name = strsep(&device_tmp, ","))) { // @ 追蹤 init_binder_device 函數 ret = init_binder_device(device_name); // 初始化設備 if (ret) goto err_init_binder_device_failed; } } ret = init_binderfs(); if (ret) goto err_init_binder_device_failed; return ret; ... 省略錯誤 } ``` 1. [**KConfig 檔案**](https://cs.android.com/android/kernel/superproject/+/common-android-mainline:common/drivers/android/Kconfig):如果有 `ANDROID_BINDER_DEVICES` 標示,按照 `ANDROID_BINDER_DEVICES` 設定的參數在 `dev` 下創建對應檔案 ```shell= ## KConfig config ANDROID_BINDER_DEVICES string "Android Binder devices" depends on ANDROID_BINDER_IPC default "binder,hwbinder,vndbinder" ### 預設創建 3 個檔案 ``` > ![](https://i.imgur.com/LfypTuM.png) 2. **init_binder_device 函數**:動態創建 `binder_device` 結構,並且設定 `binder_device` 的 name、minor、fos ... 等等資料 :::info * **file_operations 結構** 該結構定義了多個 **函數指針**,讓 File 操作行為 (`mmap`、`open`、`release`... 等等) 與真正執行的函數區分開來 ::: ```c= // binder.c // 真正執行的 filesystem 操作函數 const struct file_operations binder_fops = { .owner = THIS_MODULE, .poll = binder_poll, .unlocked_ioctl = binder_ioctl, .compat_ioctl = compat_ptr_ioctl, .mmap = binder_mmap, .open = binder_open, .flush = binder_flush, .release = binder_release, }; /** * name 目前傳入 3 個 name - binder - hwbinder - vndbinder */ static int __init init_binder_device(const char *name) { int ret; struct binder_device *binder_device; // 動態分配 `binder_device` 結構 binder_device = kzalloc(sizeof(*binder_device), GFP_KERNEL); if (!binder_device) return -ENOMEM; // 註冊 binder 的操作函數指針 binder_device->miscdev.fops = &binder_fops; binder_device->miscdev.minor = MISC_DYNAMIC_MINOR; // 次設備號 255 // 設定資料夾 /dev/<name> 名稱 binder_device->miscdev.name = name; refcount_set(&binder_device->ref, 1); // 先設定當前 binder 驅動的 manager 為 INVALID_UID // 之後有設定 Binder Manager 就會將其設定進去 binder_device->context.binder_context_mgr_uid = INVALID_UID; binder_device->context.name = name; // 互斥鎖初始化 mutex_init(&binder_device->context.context_mgr_node_lock); // 註冊 misc 設備 // @ 追蹤 misc_register 函數 ret = misc_register(&binder_device->miscdev); if (ret < 0) { kfree(binder_device); return ret; } // 將當前 binder_device 串接到 binder_devices 列表前面 hlist_add_head(&binder_device->hlist, &binder_devices); return ret; } ``` > ![](https://i.imgur.com/Go1v9lz.png) :::info * **miscdevice 函數**:實作在 [**misc.c**](https://cs.android.com/android/kernel/superproject/+/common-android-mainline:common/drivers/char/misc.c;l=173?q=misc.c&ss=android%2Fkernel%2Fsuperproject):該函數的主要功能是 **動態註冊 misc device 號,在 `/dev` 目錄下創建檔案 !** ```c= // miscdevice.h #define MISC_DYNAMIC_MINOR 255 extern int misc_register(struct miscdevice *misc); ``` ::: * Binder File (System Call) 對應處理的函數 | File 函數指針 | Binder 實作函數 | | -------- | -------- | | poll | binder_poll | | unlocked_ioctl | binder_ioctl | | compat_ioctl | compat_ptr_ioctl | | mmap | binder_mmap | | open | binder_open | | flush | binder_flush | | release | binder_release | ```c= // binder.c // 真正執行的 filesystem 操作函數 const struct file_operations binder_fops = { .owner = THIS_MODULE, .poll = binder_poll, .unlocked_ioctl = binder_ioctl, .compat_ioctl = compat_ptr_ioctl, .mmap = binder_mmap, .open = binder_open, .flush = binder_flush, .release = binder_release, }; ``` :::success * Binder 較為重要的函數 1. **binder_open**:打開 `/dev/binder` 設備,取得映射值 2. **binder_mmap**:讓當前 proc 取得 `/dev/binder` 的 **內存映射**,**映射到當前 proc 的記憶體中** 3. **binder_ioctl**:讓使用者 BinderThread 控制 Service ::: ### [misc](https://cs.android.com/android/kernel/superproject/+/common-android-mainline:common/drivers/char/misc.c) 雜項註冊 * 在上面我們有看到在 `init_binder_device` 函數中,使用 `misc_register` 做雜項硬體註冊到 Android 系統中 :::success * 目前是打算在 `/dev` 目錄下創建三個 binder 相關檔案:`binder`、`hwbinder`、`vndbinder` ::: 1. 可以動態註冊的驅動裝置數量為 `DYNAMIC_MINORS`,也就是 128 個 (`0 ~ 127`),每個標號是否已經被註冊過,都記錄在 `misc_minors` 陣列中 :::info * `misc_minors` 列表中,已分配設定為 1,未分配則為 0 ::: 2. 透過 `find_first_zero_bit` 函數找到第一個未分配的空間 3. **`MKDEV` 創建對應的 dev 檔案**,並註冊到檔案系統中 4. misc 註冊成功後串接到 misc_list 列表 ```c= // common/drivers/char/misc.c static DEFINE_MUTEX(misc_mtx); #define DYNAMIC_MINORS 128 /* like dynamic majors */ int misc_register(struct miscdevice *misc) { dev_t dev; int err = 0; // 是否是動態註冊 bool is_dynamic = (misc->minor == MISC_DYNAMIC_MINOR); // 動態註冊就是 255 // 雜項設備列表頭 INIT_LIST_HEAD(&misc->list); // 取得互斥鎖 mutex_lock(&misc_mtx); if (is_dynamic) { // 找到第一個尚未使用的編號 int i = find_first_zero_bit(misc_minors, DYNAMIC_MINORS); if (i >= DYNAMIC_MINORS) { // 無法動態註冊 (滿) err = -EBUSY; goto out; } misc->minor = DYNAMIC_MINORS - i - 1; // 設定為以使用 set_bit(i, misc_minors); } else { struct miscdevice *c; // 判斷所以雜項設備 list_for_each_entry(c, &misc_list, list) { // 是否已經註冊將要註冊的 misc 號碼 if (c->minor == misc->minor) { err = -EBUSY; goto out; } } } // 創建 dev dev = MKDEV(MISC_MAJOR, misc->minor); // MISC_MAJOR 是 10 // 將記錄檔案裝置 misc 註冊到系統中 misc->this_device = device_create_with_groups(misc_class, misc->parent, dev, misc, misc->groups, "%s", misc->name); // 失敗處理 if (IS_ERR(misc->this_device)) { if (is_dynamic) { int i = DYNAMIC_MINORS - misc->minor - 1; if (i < DYNAMIC_MINORS && i >= 0) clear_bit(i, misc_minors); misc->minor = MISC_DYNAMIC_MINOR; } err = PTR_ERR(misc->this_device); goto out; } // 註冊成功後串接到 misc_list 列表 list_add(&misc->list, &misc_list); out: // 釋放互斥鎖 mutex_unlock(&misc_mtx); return err; } ``` ## binder_open 概述 * **binder_open 這個函數主要是在做以下幾件事** 1. **創建 `binder_proc` 實體**(管理數據的記錄器):可以 adb shell 在 `/proc` 看到相對應的數據 ```c= // /drivers/staging/android/binder.h // 管理數據的紀錄體 (proc 進程空間) struct binder_proc { struct hlist_node proc_node; struct rb_root threads; struct rb_root nodes; struct rb_root refs_by_desc; struct rb_root refs_by_node; int pid; ... wait_queue_head_t freeze_wait; struct list_head todo; ... }; ``` 2. 初始化 proc 結構 (放進當前進程訊息) 3. **添加到 `binder_procs` 鏈表中**(`binder_procs` 是 Kernel space 的全局變量) 4. 將當前進程 (呼叫者的進程) **`proc` 保存到 Binder 的 `filp->private_data`** (將創建該文件的 proc 存到 `/dev/binder` 中) > ![](https://i.imgur.com/E1QxJc2.png) :::success * **不同 App 應用都會透過 `binder_open` 會開起相同的 `/dev/binder` 文件**,打開 Binder 設備後取得偏移值,這個偏移值會存在 `mProcess->mDriverFD` * `binder_open` 返回的偏移值 就是在該進程中虛擬記憶體的位置,請參考 [**Linux memory 管理**](https://hackmd.io/ptxTEwCzQBuxv61dGL_lvA?view#%E8%A8%98%E6%86%B6%E9%AB%94%E7%AE%A1%E7%90%86) ::: ### binder_open 函數分析 1. **`binder_open` 開啟 `/dev/binder` 節點(驅動層在 binder_open 函數實現)**:並在系統中的 [**proc 目錄**](https://hackmd.io/MtOap5UaR7KcJpdTisc75g?view#%E8%B3%87%E6%96%99%E5%A4%BE%E4%BB%8B%E7%B4%B9) (虛擬目錄,用於系統內存的映射) 下產生各種管理訊息 ```c= // /drivers/staging/android/binder.c static int binder_open(struct inode *nodp, struct file *filp) { // binder_proc 就是管理數據的紀錄體(每個進程都有獨立一個) struct binder_proc *proc; ... // 分配 binder_proc 結構一個記憶體空間 proc = kzalloc(sizeof(*proc), GFP_KERNEL); if (proc == NULL) return -ENOMEM; ... return 0; } ``` 2. **初始化 proc 操作** : 對 `binder_proc` 結構賦予值,並初始化 proc 列表 ```c= // /drivers/staging/android/binder.c static int binder_open(struct inode *nodp, struct file *filp) { // binder_proc 就是管理數據的紀錄體(每個進程都有獨立一個) struct binder_proc *proc; ... // 初始化操作 get_task_struct(current); proc->tsk = current->group_leader; // 工作控制區塊 ... // 初始化 Linked 列表 INIT_LIST_HEAD(&proc->todo); // todo Link init_waitqueue_head(&proc->wait); // wait Link proc->default_priority = task_nice(current); ... return 0; } ``` 3. 創建同步鎖,將 `proc` 訊息加入 Binder 全局管理鏈表 (**binder_procs**) binder_procs 的初始化如下 ```c= // /drivers/staging/android/binder.c static HLIST_HEAD(binder_procs); ``` > ![](https://i.imgur.com/tBkZIVu.png) * **指定 `proc` 給 `filp->private_data`**(filp 就是 `/dev/binder` 文件映射到應用進程的代表),**未來 Binder 驅動就可以透過 `filp->private_data` 找到對應的應用** > ![](https://i.imgur.com/kW5qxSo.png) ```c= // /drivers/staging/android/binder.c static int binder_open(struct inode *nodp, struct file *filp) { // binder_proc 就是管理數據的紀錄體(每個進程都有獨立一個) struct binder_proc *proc; ... // 獲取鎖 binder_lock(__func__); binder_stats_created(BINDER_STAT_PROC); // 計數 + 1 // 將創建出的 proc 結構 加入到 binder_procs 的 header hlist_add_head(&proc->proc_node, &binder_procs); // 進程 pid proc->pid = current->group_leader->pid; INIT_LIST_HEAD(&proc->delivered_death); // 將這個 proc 與 filp 串起來,這樣下次就可以通過 filp 找到該 proc // 最終會透過 private_data 取得 proc 資料 filp->private_data = proc; // 重點 !! // 解開鎖 binder_unlock(__func__); return 0; } ``` > ![](https://i.imgur.com/RM4seb6.png) ### binder_proc 結構關係 * **`binder_proc` 是一個進程在 Kernel space 的代表**:它還會與 `binder_node`、`binder_thread`、`binder_ref`、`binder_buffer` 結構會有關係 :::info binder_proc 會透過 **紅黑數** 儲存多個結構 ::: ```c= // binder_internal.h struct binder_proc { ... // binder_thread struct rb_root threads; // binder_node struct rb_root nodes; // binder_ref struct rb_root refs_by_desc; struct rb_root refs_by_node; ... 省略部份 struct binder_alloc alloc; ... 省略部份 }; ``` > ![](https://i.imgur.com/JgxveLN.png) ## binder_mmap 概述 binder_mmap 函數中會申請一塊物理內存,然後將 User space & Kernel space 對應到同一個內存上,這樣只需要一次複製就可以把數據傳道目標進程 ### binder_mmap 分析 * 在說明 Binder 驅動層的 `binder_mmap` 函數之前,先介紹幾個重要的數據結構,並且以下分析會將函數拆分說明 | Struct 名 | 說明 | | -------- | -------- | | **vm_area_struct** \*vma | 使用者空間的描述;該應用程序使用的 **虛擬內存**,其中 `vma->vm_start`、`vma->vm_end` 代表了該內存的起點 & 終點。**`vm_area_struct` 是 Binder 驅動中對使用者虛擬內存的描述** | | **vm_struct** \*area | 核心空間;**App 程序中,對核心虛擬內存的描述** | | **binder_proc** \*proc | binder 驅動分配的數據結構(`binder_open` 中有用到),儲存 **當前進程相關訊息(內存分配、線程管理)** | :::info * **為何要分 `vm_area_struct`、`vm_struct`** ? 主要是因為 PC 對硬體的設計,讓其有 MMU 機制,產生了虛擬記憶體的概念 ! > ![](https://i.imgur.com/hUXTsaC.png) ::: binder_mmap 流程圖 > ![](https://i.imgur.com/PbEcX82.png) * 分析 **binder_mmap 函數** 1. 判斷 `vm_area_struct` (APP 進程) 標誌是否禁止了 mmap (**`FORBIDDEN_MMAP_FLAGS`**) ```java= // binder.c // filp 就是 binder 文件 // vm_area_struct 是 Binder 空間對 APP 端的虛擬空間描述 (使用者空間) static int binder_mmap(struct file *filp, struct vm_area_struct *vma) { // private_data 是在 open 時設定的 ! // (biner_open 創建虛擬設備~ Do u remember~ 你是否記得~ ) struct binder_proc *proc = filp->private_data; ... 省略部分 // 判斷是否有禁止 Flag ! if (vma->vm_flags & FORBIDDEN_MMAP_FLAGS) { ... 錯誤訊息 return -EPERM; } vma->vm_ops = &binder_vm_ops; // 操作函數指針 vma->vm_private_data = proc; // @ 主要 binder_alloc_mmap_handler 函數處理,接下來主要分析該函數 return binder_alloc_mmap_handler(&proc->alloc, vma); } ``` > ![](https://i.imgur.com/0mfTCrR.png) 2. 判斷 mmap 是否已經有分配 (判斷 `buffer_size` 指針是否為空),若已分配則不繼續往下執行 :::info * 從這裡可以看出,**每個 APP 進程只能 mmap 一次** ::: ```java= // drivers/android/binder_alloc.c // alloc: Kernel 管理 proc 的紀錄體 // vma: Binder 空間對 APP 端的虛擬空間描述 (使用者空間) int binder_alloc_mmap_handler(struct binder_alloc *alloc, struct vm_area_struct *vma) { int ret; const char *failure_string; // 錯誤訊息 struct binder_buffer *buffer; // 線程鎖 mutex_lock(&binder_alloc_mmap_lock); // 判斷是否有 mmap 分配 if (alloc->buffer_size) { ret = -EBUSY; failure_string = "already mapped"; // 若該進程已經分配 mmap 過則不可再分配 goto err_already_mapped; } ... mutex_unlock(&binder_alloc_mmap_lock); return 0; ...省略部分 (錯誤處理) } ``` > ![](https://i.imgur.com/EDi8CQj.png) 3. Binder 驅動會判斷 `App 進程` 申請的內存大小 - **==最大 4M==** - 若超出就修正為 4M (**這 4M 是分配在核心的緩衝區**) > 檢查呼叫 mmap 的 app 進程申請的空間 ```java= // drivers/android/binder_alloc.c // alloc: Kernel 管理 proc 的紀錄體 // vma: Binder 空間對 APP 端的虛擬空間描述 (使用者空間) int binder_alloc_mmap_handler(struct binder_alloc *alloc, struct vm_area_struct *vma) { int ret; const char *failure_string; // 錯誤訊息 struct binder_buffer *buffer; ... // 修正 Binder 內存大小,最大 4M alloc->buffer_size = min_t(unsigned long, vma->vm_end - vma->vm_start, SZ_4M); // 解開線程鎖 mutex_unlock(&binder_alloc_mmap_lock); ... return 0; ...省略部分 (錯誤處理) } ``` :::info * 應用進程對 MMAP 真正能申請的內存大小是多少 ? 4M 是 Binder 驅動定義的 APP 應用層定義的是 1M - 8K = (0.992M),eg. Intent 就不能超過 1M 大小 > `Parcel.cpp` 有限制 ::: 4. 初始化進程 `proc` 的參數 (buffer、pages...),用 `kzalloc` 分配了 pages 數組空間 (申請物理頁面),**先幫進程申請一頁 (4KB) 的內存,當需要更多空間時才會繼續申請更大的空間** * 將在進程的 Binder 空間儲存的 App 虛擬位址 (`vma->vm_start`),指向 Kernel (`alloc->buffer`) 位置 * 在 Kernel 分配 `alloc->pages` 空間 ```java= // drivers/android/binder_alloc.c // alloc: Kernel 管理 proc 的紀錄體 // vma: Binder 空間對 APP 端的虛擬空間描述 (使用者空間) int binder_alloc_mmap_handler(struct binder_alloc *alloc, struct vm_area_struct *vma) { int ret; const char *failure_string; // 錯誤訊息 struct binder_buffer *buffer; ... // 初始化 proc 中 alloc->buffer 的參數 // 將應用進程 (vma) 開始位置 (vm_start) 放置到 buffer alloc->buffer = (void __user *)vma->vm_start; // 分配 page 空間 alloc->pages = kcalloc(alloc->buffer_size / PAGE_SIZE, sizeof(alloc->pages[0]), // 一頁空間 GFP_KERNEL); // 判斷是否申請成功 if (alloc->pages == NULL) { ret = -ENOMEM; failure_string = "alloc page array"; goto err_alloc_pages_failed; } return 0; ...省略部分 (錯誤處理) } ``` :::info * 開一頁 4KB 物理內存夠用嗎 ? 這是 Linxu 的核心分頁機制,先開一頁 Page,當有需要時會 **動態拓展**,以免浪費物理記憶體空間 ::: > ![](https://i.imgur.com/hmruuL2.png) 5. 使用 `kcalloc` 申請 `binder_buffer` 結構空間 (這個 buffer 在內核空間) ```c= // drivers/android/binder_alloc.h struct binder_buffer { struct list_head entry; /* free and allocated entries by address */ struct rb_node rb_node; /* free entry by size or allocated entry */ /* by address */ unsigned free:1; unsigned clear_on_free:1; unsigned allow_user_free:1; unsigned async_transaction:1; unsigned oneway_spam_suspect:1; unsigned debug_id:27; struct binder_transaction *transaction; struct binder_node *target_node; size_t data_size; size_t offsets_size; size_t extra_buffers_size; void __user *user_data; int pid; }; // -------------------------------------------------------- // drivers/android/binder_alloc.c // alloc: Kernel 管理 proc 的紀錄體 // vma: Binder 空間對 APP 端的虛擬空間描述 (使用者空間) int binder_alloc_mmap_handler(struct binder_alloc *alloc, struct vm_area_struct *vma) { int ret; const char *failure_string; // 錯誤訊息 struct binder_buffer *buffer; ... // 申請 binder_buffer 空間 buffer = kzalloc(sizeof(*buffer), GFP_KERNEL); if (!buffer) { ret = -ENOMEM; failure_string = "alloc buffer struct"; goto err_alloc_buf_struct_failed; } ... return 0; ...省略部分 (錯誤處理) } ``` > ![](https://i.imgur.com/4z9Y22d.png) 6. 在分配結束 `binder_buffer` 空間完成後,映射到 `binder_alloc` (Kernel Space) 中,並將創建好的 buffer 添加到 proc 中 (未來就可以找到這個空間做使用) > 這樣將來在寫入資料時就會直接映射到 User 空間 ```c= // drivers/android/binder_alloc.c // alloc: Kernel 管理 proc 的紀錄體 // vma: Binder 空間對 APP 端的虛擬空間描述 int binder_alloc_mmap_handler(struct binder_alloc *alloc, struct vm_area_struct *vma) { int ret; const char *failure_string; // 錯誤訊息 struct binder_buffer *buffer; ... 省略部分 // 用戶空間的 Buffer 指定給 Binder->user_date,該空間就可以給用戶寫資料 buffer->user_data = alloc->buffer; // 把 buffer->entry 存到 alloc->buffers 中 list_add(&buffer->entry, &alloc->buffers); // 1 => 代表該內存可用 buffer->free = 1; // 計算得出空間內存的大小 binder_insert_free_buffer(alloc, buffer); // 進行 Buffer 分配 & 排序 // 2 => 代表異步空間 alloc->free_async_space = alloc->buffer_size / 2; // 用戶空間設定為分配好的 alloc 空間 binder_alloc_set_vma(alloc, vma); // 添加原子計數 mmgrab(alloc->vma_vm_mm); return 0; ...省略部分 (錯誤處理) } ``` :::info * 在這邊也可以看到異步空間只占一半,避免過度佔據同步處理的優先度 ::: > ![](https://i.imgur.com/jqQQ1Ay.png) * 將 binder_buffer 添加到該進程的列表 `alloc->buffers` 中 ```c= list_add(&buffer->entry, &alloc->buffers); ``` * 將 binder_buffer 添加到該進程的 free 列表 (`binder_insert_free_buffer`) ```c= static void binder_insert_free_buffer(struct binder_alloc *alloc, struct binder_buffer *new_buffer) { struct rb_node **p = &alloc->free_buffers.rb_node; struct rb_node *parent = NULL; struct binder_buffer *buffer; size_t buffer_size; size_t new_buffer_size; BUG_ON(!new_buffer->free); // 計算新 buffer 空間大小 new_buffer_size = binder_alloc_buffer_size(alloc, new_buffer); ... // 計算紅黑數插入位置 while (*p) { parent = *p; buffer = rb_entry(parent, struct binder_buffer, rb_node); BUG_ON(!buffer->free); buffer_size = binder_alloc_buffer_size(alloc, buffer); if (new_buffer_size < buffer_size) p = &parent->rb_left; else p = &parent->rb_right; } // 插入紅黑樹 rb_link_node(&new_buffer->rb_node, parent, p); rb_insert_color(&new_buffer->rb_node, &alloc->free_buffers); } ``` * 到這一步就完成了 `Binder 文件` 被讀取到 Kernel space,並在內核建立 `binder buffer` 再設定映射關係,最終把 `binder buffer` 存到 proc 的 `alloc->buffers` 中 ### adb - 驗證 binder mmap * 首先用 adb root 進入手機裝置,查看 `system_server` 服務的 pid(system_server 同樣也有開啟 `/dev/binder` 文件,並用 mmap 將該文件映射到自己的進程中) ```shell= adb shell ps -A | grep system_server ``` system server 目前 pid 就是 `1920` > ![](https://i.imgur.com/lXNw5u1.png) * 在 `proc` 中查看 pid 的 maps 映射位置 :::success `proc/<pid\>/maps` 存有真實 addr. 對應 虛擬 addr. ::: ```shell= cat /proc/1920/maps | grep /dev/binder ``` > ![](https://i.imgur.com/KMtrIS4.png) ## Binder 核心 Buffer ### 分配核心 Buffer * 當一個應用對 Binder 發起 `BC_TRANSACTION`、`BC_REPLY` 命令時,Binder 驅動就會將使用者資料 **複製** 到 Binder 驅動中,之後再傳遞給目標進程; 這時就需要 Binder 驅動分配一塊核心空間給儲存複製進來的資料 :::info * Buffer 是分配在目標 proc 的核心空間 ::: > ![](https://i.imgur.com/agkV8lG.png) * 在 Binder 驅動中就是 [**binder_alloc**](https://cs.android.com/android/kernel/superproject/+/common-android-mainline:common/drivers/android/binder_alloc.c)#`binder_alloc_new_buf` 函數負責 ```c= // binder_alloc.c struct binder_buffer *binder_alloc_new_buf(struct binder_alloc *alloc, size_t data_size, size_t offsets_size, size_t extra_buffers_size, int is_async, int pid) { struct binder_buffer *buffer; // 分配時必須使用互斥鎖鎖住 mutex_lock(&alloc->mutex); // @ 查看 binder_alloc_new_buf_locked buffer = binder_alloc_new_buf_locked(alloc, data_size, offsets_size, extra_buffers_size, is_async, pid); // 解開互斥鎖 mutex_unlock(&alloc->mutex); return buffer; } ``` * 分析 `binder_alloc_new_buf_locked` 函數 1. 取得目標 alloc 並判斷大小是否溢位,最終決定這次要分配的緩衝區大小賦予 size 變數 :::info 在分配核心核心空間時,要多分配一個 `binder_buffer` 結構來描述個核心緩衝區 ::: ```c= static struct binder_buffer *binder_alloc_new_buf_locked( struct binder_alloc *alloc, // 可看作 "目標" 進程 proc size_t data_size, // 資料緩衝的大小 size_t offsets_size, // 偏移陣列緩衝區大小 size_t extra_buffers_size, // 額外資料大小 int is_async, // 是否是異步交易 int pid) { struct rb_node *n = alloc->free_buffers.rb_node; struct binder_buffer *buffer; size_t buffer_size; struct rb_node *best_fit = NULL; void __user *has_page_addr; void __user *end_page_addr; size_t size, data_offsets_size; int ret; mmap_read_lock(alloc->vma_vm_mm); // 查找使用者空間分配的 alloc if (!binder_alloc_get_vma(alloc)) { ... 省略錯誤 return ERR_PTR(-ESRCH); } mmap_read_unlock(alloc->vma_vm_mm); // 計算要分配的核心大小空間 data_offsets_size = ALIGN(data_size, sizeof(void *)) + ALIGN(offsets_size, sizeof(void *)); // 判斷是否溢位 if (data_offsets_size < data_size || data_offsets_size < offsets_size) { ... 省略錯誤 return ERR_PTR(-EINVAL); } // 再計算額外資料大小,並判斷是否溢位 size = data_offsets_size + ALIGN(extra_buffers_size, sizeof(void *)); if (size < data_offsets_size || size < extra_buffers_size) { ... 省略錯誤 return ERR_PTR(-EINVAL); } // 判斷目標進程可接受的非同步的大小 // 並加上 `binder_buffer`(用於描述) 大小判斷溢位 if (is_async && alloc->free_async_space < size + sizeof(struct binder_buffer)) { ... 省略錯誤 return ERR_PTR(-ENOSPC); } /* Pad 0-size buffers so they get assigned unique addresses */ size = max(size, sizeof(void *)); ... ``` 2. 找尋目標進程的紅黑數的核心空間有沒有適當的位置,並有以下幾種狀況 * 找不到合適位置,返回錯誤 * 找不到合適位置,但有一塊更大個區塊可以使用 (待分配) ```c= static struct binder_buffer *binder_alloc_new_buf_locked( struct binder_alloc *alloc, // 可看作 "目標" 進程 proc size_t data_size, // 資料緩衝的大小 size_t offsets_size, // 偏移陣列緩衝區大小 size_t extra_buffers_size, // 額外資料大小 int is_async, // 是否是異步交易 int pid) { struct rb_node *n = alloc->free_buffers.rb_node; struct binder_buffer *buffer; size_t buffer_size; struct rb_node *best_fit = NULL; void __user *has_page_addr; void __user *end_page_addr; size_t size, data_offsets_size; int ret; ... 決定 Size // 紅黑樹中找合適的位置 while (n) { buffer = rb_entry(n, struct binder_buffer, rb_node); BUG_ON(!buffer->free); buffer_size = binder_alloc_buffer_size(alloc, buffer); if (size < buffer_size) { best_fit = n; n = n->rb_left; } else if (size > buffer_size) n = n->rb_right; else { best_fit = n; break; } } // 判斷是否有找到合適位置 if (best_fit == NULL) { ... 省略錯誤處理 return ERR_PTR(-ENOSPC); } if (n == NULL) { // 判沒有合適位置,但 ! 找到一塊較大的核心空間 buffer = rb_entry(best_fit, struct binder_buffer, rb_node); buffer_size = binder_alloc_buffer_size(alloc, buffer); } ... } ``` 3. 計算 Buffer 結束位置 & 使用者要求的結束位置,再決定最後要分配的記憶體位置 (`end_page_addr`),之後呼叫 `binder_update_page_range` 分配實體位置 ```c= static struct binder_buffer *binder_alloc_new_buf_locked( struct binder_alloc *alloc, // 可看作 "目標" 進程 proc size_t data_size, // 資料緩衝的大小 size_t offsets_size, // 偏移陣列緩衝區大小 size_t extra_buffers_size, // 額外資料大小 int is_async, // 是否是異步交易 int pid) { struct rb_node *n = alloc->free_buffers.rb_node; struct binder_buffer *buffer; size_t buffer_size; struct rb_node *best_fit = NULL; void __user *has_page_addr; void __user *end_page_addr; size_t size, data_offsets_size; int ret; ... 決定 Size ... 在目標核心空間找到相對適合的位置 // 計算 Page 的結束位置 has_page_addr = (void __user *) (((uintptr_t)buffer->user_data + buffer_size) & PAGE_MASK); WARN_ON(n && buffer_size != size); // 計算使用者要求的核心大小 end_page_addr = (void __user *)PAGE_ALIGN((uintptr_t)buffer->user_data + size); // 如果使用者要求過大 if (end_page_addr > has_page_addr) // 修正為使用者要求的大小 end_page_addr = has_page_addr; // 分配實體空間 // @ 之後分析 binder_update_page_range ret = binder_update_page_range(alloc, 1, (void __user *) PAGE_ALIGN((uintptr_t)buffer->user_data), end_page_addr); if (ret) return ERR_PTR(ret); ... return buffer; ... } ``` 4. 最後再做一些準備就完成分配:判斷是否需要將多出的空間加入目標進城的 free buffer、將 buffer 加入目標進程使用中的紅黑樹... 等等 ```c= static struct binder_buffer *binder_alloc_new_buf_locked( struct binder_alloc *alloc, // 可看作 "目標" 進程 proc size_t data_size, // 資料緩衝的大小 size_t offsets_size, // 偏移陣列緩衝區大小 size_t extra_buffers_size, // 額外資料大小 int is_async, // 是否是異步交易 int pid) { struct rb_node *n = alloc->free_buffers.rb_node; struct binder_buffer *buffer; size_t buffer_size; struct rb_node *best_fit = NULL; void __user *has_page_addr; void __user *end_page_addr; size_t size, data_offsets_size; int ret; ... 決定 Size ... 在目標核心空間找到相對適合的位置 ... 分配地址 // 判斷是否有多餘的空間 if (buffer_size != size) { struct binder_buffer *new_buffer; // 分配空間 new_buffer = kzalloc(sizeof(*buffer), GFP_KERNEL); if (!new_buffer) { pr_err("%s: %d failed to alloc new buffer struct\n", __func__, alloc->pid); goto err_alloc_buf_struct_failed; } // 賦予使用者空間位置 new_buffer->user_data = (u8 __user *)buffer->user_data + size; // 添加進紅黑數 list_add(&new_buffer->entry, &buffer->entry); // 標註可用 new_buffer->free = 1; // 添加進 alloc 中的 free 紅黑樹 binder_insert_free_buffer(alloc, new_buffer); } // 從 free buffer 中移除 rb_erase(best_fit, &alloc->free_buffers); // 標記已在使用 buffer->free = 0; buffer->allow_user_free = 0; // 添加進使用中的樹 binder_insert_allocated_buffer_locked(alloc, buffer); ... buffer->data_size = data_size; buffer->offsets_size = offsets_size; buffer->async_transaction = is_async; buffer->extra_buffers_size = extra_buffers_size; buffer->pid = pid; buffer->oneway_spam_suspect = false; if (is_async) { // 異步則縮減空間 alloc->free_async_space -= size + sizeof(struct binder_buffer); ... } return buffer; ... } ``` ### 釋放核心 Buffer * 當 Binder 處理完一個命令時,Binder 驅動發出 `BR_TRANSACTION`、`BR_REPLY` 命令之後,**應用方就會使用 ++`BC_FREE_BUFFER` 命令++,讓 Binder 驅動釋放對應的核心緩衝**; :::info * 要釋放的 Buffer 在目標 proc 的核心空間 ::: > ![](https://i.imgur.com/9Rs117P.png) * 在 Binder 驅動中就是 binder_alloc#`binder_alloc_new_buf` 函數負責 ```c= void binder_alloc_free_buf(struct binder_alloc *alloc, struct binder_buffer *buffer) { if (buffer->clear_on_free) { // 如果有標記 clear_on_free,則呼叫 binder_alloc_clear_buf binder_alloc_clear_buf(alloc, buffer); buffer->clear_on_free = false; } mutex_lock(&alloc->mutex); // @ 分析 binder_free_buf_locked binder_free_buf_locked(alloc, buffer); mutex_unlock(&alloc->mutex); } ``` * 分析 `binder_free_buf_locked` 函數 ```c= // binder_alloc.c static void binder_free_buf_locked(struct binder_alloc *alloc, struct binder_buffer *buffer) { size_t size, buffer_size; // 計算緩衝區大小 buffer_size = binder_alloc_buffer_size(alloc, buffer); size = ALIGN(buffer->data_size, sizeof(void *)) + // 計算 資料大小 ALIGN(buffer->offsets_size, sizeof(void *)) + // 計算 資料偏移 ALIGN(buffer->extra_buffers_size, sizeof(void *));// 計算 額外資料 ... if (buffer->async_transaction) { alloc->free_async_space += buffer_size + sizeof(struct binder_buffer); ... } // 更新 Page 資料 binder_update_page_range(alloc, 0, // 0 代表釋放 (void __user *)PAGE_ALIGN((uintptr_t)buffer->user_data), (void __user *)(((uintptr_t) buffer->user_data + buffer_size) & PAGE_MASK)); rb_erase(&buffer->rb_node, &alloc->allocated_buffers); // 設定為可用 buffer->free = 1; // 進行合併 // 判斷是否是最後一個 if (!list_is_last(&buffer->entry, &alloc->buffers)) { struct binder_buffer *next = binder_buffer_next(buffer); if (next->free) { rb_erase(&next->rb_node, &alloc->free_buffers); binder_delete_free_buffer(alloc, next); } } if (alloc->buffers.next != &buffer->entry) { struct binder_buffer *prev = binder_buffer_prev(buffer); if (prev->free) { binder_delete_free_buffer(alloc, buffer); rb_erase(&prev->rb_node, &alloc->free_buffers); buffer = prev; } } // 將合併完的緩衝區,放回 Proc 中的 alloc 中的紅黑樹中 binder_insert_free_buffer(alloc, buffer); } ``` ### 查詢核心 Buffer * 雖說使用者可以傳送 `BC_FREE_BUFFER` 指令就可以讓 Binder 驅動釋放目標 Proc 中的 Buffer 空間,但是要釋放哪一個空間 ? 這是應用程式指定,才能找到對應的 `binder_buffer` 結構 * Binder 驅動 `binder_alloc_prepare_to_free` 函數來負責取得指定 `binder_buffer` ```c= // binder_alloc.c struct binder_buffer *binder_alloc_prepare_to_free(struct binder_alloc *alloc, uintptr_t user_ptr) { struct binder_buffer *buffer; // 互斥鎖保護 mutex_lock(&alloc->mutex); // @ 分析 binder_alloc_prepare_to_free_locked buffer = binder_alloc_prepare_to_free_locked(alloc, user_ptr); mutex_unlock(&alloc->mutex); return buffer; } ``` * 分析 `binder_alloc_prepare_to_free_locked` 函數: :::info * Binder 驅動將資料傳給目標 Proc 時,它只是把資料緩衝區的 **使用者空間地址** 船給目標 Proc ::: ```c= // binder_alloc.c static struct binder_buffer *binder_alloc_prepare_to_free_locked( struct binder_alloc *alloc, uintptr_t user_ptr) // 使用者空間,指向了 binder_buffer#data 成員 { struct rb_node *n = alloc->allocated_buffers.rb_node; struct binder_buffer *buffer; void __user *uptr; uptr = (void __user *)user_ptr; // 搜尋目標 Proc 的黑樹 while (n) { buffer = rb_entry(n, struct binder_buffer, rb_node); BUG_ON(buffer->free); if (uptr < buffer->user_data) n = n->rb_left; else if (uptr > buffer->user_data) n = n->rb_right; else { // 判斷該 Buffer 是否已經被釋放 if (!buffer->allow_user_free) return ERR_PTR(-EPERM); buffer->allow_user_free = 0; return buffer; } } return NULL; } ``` ## binder_ioctl 概述 如果要對 Binder 讀寫的話都要執行 binder_ioctl 函數,Framework 層會以 IPCThreadState 類來呼叫 binder 驅動 ```c= // IPCThreadState.cpp status_t IPCThreadState::talkWithDriver(bool doReceive) // doReceive = true { ... 省略部份 status_t err; do { ... log 訊息 #if defined(__ANDROID__) // 呼叫 Kernel#binder_ioctl 函數 if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0) err = NO_ERROR; else err = -errno; ... } while (err == -EINTR); return err; } ``` * IPCThreadState 透過 ioctl 呼叫 Kernel#binder_ioctl 函數,這裡接收的參數就是呼叫者進程的訊息,參數是相互對應 | User space 參數 | BinderDriver 參數 | 說明 | | -------- | -------- | -------- | | `mProcess->mDriverFD` | \*filp | 呼叫者進程在 binder 文件的偏移 FD | | `BINDER_WRITE_READ` | cmd | 對 BinderDriver 的控制協議命令 | | `&bwr` | arg | 其結構是 `binder_write_read` | ```cpp= // binder.c static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { int ret; // 從 Binder 文件中取出 proc 訊息 struct binder_proc *proc = filp->private_data; struct binder_thread *thread; unsigned int size = _IOC_SIZE(cmd); void __user *ubuf = (void __user *)arg; ... 省略部份 // 取得進入 Kernel space 的 proc thread thread = binder_get_thread(proc); if (thread == NULL) { ret = -ENOMEM; goto err; }; switch (cmd) { case BINDER_WRITE_READ: // @ 分析 binder_ioctl_write_read ret = binder_ioctl_write_read(filp, cmd, arg, thread); if (ret) goto err; break; ... 省略其他 case } ... 省略錯誤處理 return ret; } ``` 目前進入 Kernel Space 後,帶入的命令如下圖,首先處理第一個命令 `BINDER_WRITE_READ` (對 binder 的控制協議命令) > ![](https://i.imgur.com/bvDIn4V.png) ### 讀、寫 Binder 驅動 - binder_ioctl_write_read * 讀取、寫入 Binder 驅動主要的指令是 `BINDER_WRITE_READ`,在這之中會在針對 Read、Write 處理做不同的處理 | ioctl 行為 | BinderDriver 處理函數 | 含意 | | -------- | -------- | - | | read | binder_thread_read | User 有資料要傳給 BinderDriver | | write | binder_thread_write | BinderDriver 有資料要傳給 User | ```cpp= // binder.c static int binder_ioctl_write_read(struct file *filp, unsigned int cmd, unsigned long arg, struct binder_thread *thread) { int ret = 0; struct binder_proc *proc = filp->private_data; unsigned int size = _IOC_SIZE(cmd); void __user *ubuf = (void __user *)arg; struct binder_write_read bwr; ... 省略部份 // 將使用者空間數據 (ubuf) 複製到 bwr 結構中 if (copy_from_user(&bwr, ubuf, sizeof(bwr))) { ret = -EFAULT; goto out; } // User 有資料要傳給 BinderDriver if (bwr.write_size > 0) { ret = binder_thread_write(proc, thread, bwr.write_buffer, // 目前內部指令是 BC_ENTER_LOOPER bwr.write_size, &bwr.write_consumed); ... 省略錯誤處理 } // BinderDriver 有資料要傳給 User if (bwr.read_size > 0) { ret = binder_thread_read(proc, thread, bwr.read_buffer, bwr.read_size, &bwr.read_consumed, filp->f_flags & O_NONBLOCK); ... 省略錯誤處理 } ... debug 訊息 // 將內核空間數據 (bwr) 複製到使用者空間(ubuf) if (copy_to_user(ubuf, &bwr, sizeof(bwr))) { ret = -EFAULT; goto out; } out: return ret; } ``` 流程圖 > ![](https://i.imgur.com/dLjM9cM.png) ### Binder 處理 User 寫入 - binder_thread_write * 前面已經介紹過如何進入 Kernel space 與 [**binder**](https://elixir.bootlin.com/linux/latest/source/drivers/android/binder.c) 通訊,這裡重點介紹 `binder_thread_write 函數` 在 Kernel space 的運作流程、重點 :::success - 當然前提是 User 有在 `binder_write_read` 結構中 寫入資料; **當前寫入的是 `BC_TRANSACTION` 命令** > ![](https://i.imgur.com/zsx4s88.png) ::: ```c= // binder.c static int binder_thread_write( struct binder_proc *proc, // 調用者 - 進程 struct binder_thread *thread, // 調用者 - 線程 binder_uintptr_t binder_buffer, // bwr.write_buffer size_t size, // bwr.write_size binder_size_t *consumed) // bwr.write_consumed { uint32_t cmd; // buffer = cmd + 對應數據 // 取得調用者的 buffer address void __user *buffer = (void __user *)(uintptr_t)binder_buffer; // 1. 設定開頭、結尾的指針 void __user *ptr = buffer + *consumed; // 跳過已處理過得部份,取得開始部份 void __user *end = buffer + size; // 指向 buffer 結尾 while (ptr < end && thread->return_error == BR_OK) { // 2. 獲取 cmd if (get_user_preempt_disabled(cmd, (uint32_t __user *)ptr)) return -EFAULT; // 取出指令後移動指針,讓指針移動到數據開頭 ptr += sizeof(uint32_t); // 3. 統計數據 if (_IOC_NR(cmd) < ARRAY_SIZE(binder_stats.bc)) { binder_stats.bc[_IOC_NR(cmd)]++; proc->stats.bc[_IOC_NR(cmd)]++; thread->stats.bc[_IOC_NR(cmd)]++; } // 判斷 cmd switch (cmd) { ... case BC_TRANSACTION: case BC_REPLY: { struct binder_transaction_data tr; // 4. 從 User space 獲取數據存到 tr 結構 (User 預計給 BinderDriver 的資料) if (copy_from_user(&tr, ptr, sizeof(tr))) return -EFAULT; // 移動指針到下一個指令 ptr += sizeof(tr); // 5. 接著繼續分析 binder_transaction() 執行具體命令 // @ 追蹤 binder_transaction binder_transaction(proc, thread, &tr, cmd == BC_REPLY); break; } } } } ``` 1. 這裡重點是處理 User 傳送的 `BC_TRANSACTION` 命令 2. 將 User 的 `binder_write_read` 結構 copy 到 Kernel space (BinderDriver) 中的 `binder_transaction_data` 結構中 > ![](https://i.imgur.com/Yzs1Vu4.png) ## Binder 內部傳送資料 - binder_transaction :::danger 目前是以 **Client 方的角度** 來看 BinderDriver ::: 這裡會分為兩個階段分析 | 函數 | 功能 | 其他 | | -------- | -------- | - | | binder_transaction | 處要要傳送的資料 (`binder_transaction`) | 同時負責 `reply` & `transact` 功能 | | binder_proc_transaction | 將處理好的資料傳給目標進程 | | [**binder**](https://cs.android.com/android/kernel/superproject/+/common-android-mainline:common/drivers/android/binder.c)#binder_transaction 函數:該函數同時負責 `reply` & `transact` 功能,而目前是 `BC_TRANSACTION`,所以關注 transact 功能,由於該函數較長 > 以下會分階段說明 ### Binder 傳送 - 找到目標 binder_node 1. **判斷 ==handle 標示==,獲取 `binder_node`**:透過 handle 標示,**取得對應的目標 `binder_node`、`target_proc` 數據結構**,如果找不到目標 `binder_node` 則會錯誤 :::info * ServiceManager 的 handle 數值就固定是 0 * 這邊取的 binder_node 在 `/dev/binder` 文件中的 Service 節點,要傳遞給 Client 的也是這個節點訊息 ::: ```c= // binder.c static void binder_transaction(struct binder_proc *proc, // 呼叫進程 proc struct binder_thread *thread, // 呼叫進程 thread struct binder_transaction_data *tr, // 數據 int reply, // 目前為 0 binder_size_t extra_buffers_size) { int ret; // 要傳輸的資料 struct binder_transaction *t; struct binder_work *w; struct binder_work *tcomplete; ... 省略部份 // 目標 proc, thread struct binder_proc *target_proc = NULL; struct binder_thread *target_thread = NULL; // 目標 binder 節點 struct binder_node *target_node = NULL; // 回覆的數據 struct binder_transaction *in_reply_to = NULL; struct binder_transaction_log_entry *e; // log 訊息 ... 省略部份 // 當前進程的上下文 struct binder_context *context = proc->context; ... 省略部份 // 使用者空間 const void __user *user_buffer = (const void __user *) (uintptr_t)tr->data.ptr.buffer ... 省略 log 訊息 if (reply) { ... 先關注 transact 功能 } else { // handle 值代表 "控制碼" // ++只有 ServiceManager handle 為 0++ if (tr->target.handle) { struct binder_ref *ref; // 鎖住當前 proc binder_proc_lock(proc); // 查看是否有符合 target.handle 的 binder_ref ref = binder_get_ref_olocked(proc, tr->target.handle, true); if (ref) { // 透過 binder_ref 有辦法取得 binder_node target_node = binder_get_node_refs_for_txn( ref->node, &target_proc, &return_error); } else { ... 省略錯誤資訊 } // 解鎖 binder_proc_unlock(proc); } else { // 只有 ServiceManager 進入這 mutex_lock(&context->context_mgr_node_lock); // 不用尋找 Node // 直接指定 ServiceManager 進程的 node target_node = context->binder_context_mgr_node; // 取得對於 node 的 ref 引用 if (target_node) target_node = binder_get_node_refs_for_txn( target_node, &target_proc, &return_error); else return_error = BR_DEAD_REPLY; mutex_unlock(&context->context_mgr_node_lock); if (target_node && target_proc->pid == proc->pid) { // ServiceManager 呼叫自己 ... 省略 } } if (!target_node) { ... 找不到 binder_node goto err_dead_binder; } ... 後面接續分析 } } ``` :::info * **`binder_get_ref_olocked` 函數**:透過 Handle 搜尋 binder_ref ```c= // binder.c static struct binder_ref *binder_get_ref_olocked( struct binder_proc *proc // 當前 proc u32 desc, // tr->target.handle (控制碼) bool need_strong_ref) { struct rb_node *n = proc->refs_by_desc.rb_node; struct binder_ref *ref; while (n) { // 當前進程在 紅黑數的節點 ref = rb_entry(n, struct binder_ref, rb_node_desc); if (desc < ref->data.desc) { n = n->rb_left; } else if (desc > ref->data.desc) { n = n->rb_right; } else if (need_strong_ref && !ref->data.strong) { binder_user_error("tried to use weak ref as strong ref\n"); return NULL; } else { return ref; } } return NULL; } ``` ::: > ![](https://i.imgur.com/0cuSjTl.png) 2. **分配新的 `binder_transaction` 結構空間**:取得空的 `binder_work`、分配 `binder_transaction` 結構空間 ```c= // binder.c static void binder_transaction(struct binder_proc *proc, // 呼叫進程 proc struct binder_thread *thread, // 呼叫進程 thread struct binder_transaction_data *tr, // 數據 int reply, binder_size_t extra_buffers_size) { int ret; // 要傳輸的資料 struct binder_transaction *t; struct binder_work *w; struct binder_work *tcomplete; ... 省略部份 // 目標 proc, thread struct binder_proc *target_proc = NULL; struct binder_thread *target_thread = NULL; // 目標 binder 節點 struct binder_node *target_node = NULL; // 回覆的數據 struct binder_transaction *in_reply_to = NULL; struct binder_transaction_log_entry *e; // log 訊息 ... 省略部份 // 當前進程的上下文 struct binder_context *context = proc->context; ... 省略部份 // 使用者空間 const void __user *user_buffer = (const void __user *) (uintptr_t)tr->data.ptr.buffer ... 省略 log 訊息 // 取得一個空的 binder_work w = list_first_entry_or_null(&thread->todo, struct binder_work, entry); // 分配 binder_transaction 空間 t = kzalloc(sizeof(*t), GFP_KERNEL); if (t == NULL) { ...省略 goto err_alloc_t_failed; } ... } ``` > ![](https://i.imgur.com/B6LQeHh.png) ### Binder 傳送 - 找到目標 binder_thread 1. **鎖定 target thread**:從呼叫者 thread#`transaction_stack` 鎖定要傳輸的 target thread,**目的是找到 目標 BinderServer (目前是 ServiceManager) 中最適合傳遞任務的 BinderThread** ```c= // binder.c static void binder_transaction(struct binder_proc *proc, // 呼叫進程 proc struct binder_thread *thread, // 呼叫進程 thread struct binder_transaction_data *tr, // 數據 int reply, binder_size_t extra_buffers_size) { int ret; // 要傳輸的資料 struct binder_transaction *t; struct binder_work *w; struct binder_work *tcomplete; ... 省略部份 // 目標 proc, thread struct binder_proc *target_proc = NULL; struct binder_thread *target_thread = NULL; // 目標 binder 節點 struct binder_node *target_node = NULL; // 回覆的數據 struct binder_transaction *in_reply_to = NULL; struct binder_transaction_log_entry *e; // log 訊息 ... 省略部份 // 當前進程的上下文 struct binder_context *context = proc->context; ... 省略部份 // 使用者空間 const void __user *user_buffer = (const void __user *) (uintptr_t)tr->data.ptr.buffer ... 省略 log 訊息 // 查看呼叫者 thread 是否有 stack (任務相互依賴) // 以下目標是找到 User 中最適合傳遞任務的 BinderThread if (!(tr->flags & TF_ONE_WAY) && thread->transaction_stack) { struct binder_transaction *tmp; tmp = thread->transaction_stack; // 來源 thread stack if (tmp->to_thread != thread) { ... bad transaction stack goto err_bad_call_stack; } while (tmp) { struct binder_thread *from; spin_lock(&tmp->lock); from = tmp->from; if (from && from->proc == target_proc) { // 判斷來源 thread atomic_inc(&from->tmp_ref); target_thread = from; // 找到目標 thread spin_unlock(&tmp->lock); break; } spin_unlock(&tmp->lock); tmp = tmp->from_parent; } } ... } ``` :::info * 這裡可以看到單向傳遞 `TF_ONE_WAY` 是不用找到 Server 的最佳 thread,因為 thread 間的任務不會相互依賴 ::: > ![](https://i.imgur.com/Pey6DvA.png) 2. **分配 `binder_work` 空間**:分配一個 `tcomplete`,之後會封裝成一個 `BINDER_WORK_TRANSACTION_COMPLETE` 並添加到上一步找到的 User thread 的 todo 列表中 > 這樣使用者就可以知道該命令已傳送 ```c= // binder.c static void binder_transaction(struct binder_proc *proc, // 呼叫進程 proc struct binder_thread *thread, // 呼叫進程 thread struct binder_transaction_data *tr, // 數據 int reply, binder_size_t extra_buffers_size) { int ret; // 要傳輸的資料 struct binder_transaction *t; struct binder_work *w; struct binder_work *tcomplete; ... 省略部份 // 目標 proc, thread struct binder_proc *target_proc = NULL; struct binder_thread *target_thread = NULL; // 目標 binder 節點 struct binder_node *target_node = NULL; // 回覆的數據 struct binder_transaction *in_reply_to = NULL; struct binder_transaction_log_entry *e; // log 訊息 ... 省略部份 // 當前進程的上下文 struct binder_context *context = proc->context; ... 省略部份 // 使用者空間 const void __user *user_buffer = (const void __user *) (uintptr_t)tr->data.ptr.buffer ... 省略 log 訊息 tcomplete = kzalloc(sizeof(*tcomplete), GFP_KERNEL); if (tcomplete == NULL) { ...省略 goto err_alloc_tcomplete_failed; } } ``` > ![](https://i.imgur.com/MHEd0cd.png) ### Binder 傳送 - 填充要傳遞的數據 1. **填充 `binder_transaction` 數據**:在上面步驟有創建出一個 binder_transaction,現在就在對其填充數據,其中包括 code(給目標 Service 的指令)、thread & proc 的來源紀錄、priority 優先性... 等等 :::info * `binder_transaction` 結構是 BinderDriver 獨用 ::: 後面也會將它 (t) 封裝成一個 `BINDER_WORK_TRANSACTION` 命令,並添加到目標 todo 列表 :::warning * 其中會對 `binder_transaction#buffer` 成員創建一個 `binder_buffer` 緩衝結構 > `binder_alloc_new_buf` 函數 * 並先將使用者 User space 的資料複製到 `binder_transaction#buffer` 中 > `binder_alloc_copy_user_to_buffer` 函數 注意:分配的空間是在 ServiceManager 中分配 ! ::: ```c= // binder.c static void binder_transaction(struct binder_proc *proc, // 呼叫進程 proc struct binder_thread *thread, // 呼叫進程 thread struct binder_transaction_data *tr, // 數據 int reply, binder_size_t extra_buffers_size) { int ret; // 要傳輸的資料 struct binder_transaction *t; struct binder_work *w; struct binder_work *tcomplete; ... 省略部份 // 目標 proc, thread struct binder_proc *target_proc = NULL; struct binder_thread *target_thread = NULL; // 目標 binder 節點 struct binder_node *target_node = NULL; // 回覆的數據 struct binder_transaction *in_reply_to = NULL; struct binder_transaction_log_entry *e; // log 訊息 ... 省略部份 // 當前進程的上下文 struct binder_context *context = proc->context; ... 省略部份 // 使用者空間 const void __user *user_buffer = (const void __user *) (uintptr_t)tr->data.ptr.buffer ... 省略 debug, log 訊息 if (!reply && !(tr->flags & TF_ONE_WAY)) t->from = thread; // 紀錄發送者 thread 到 binder_transaction#from else t->from = NULL; // 紀錄各種訊息到 binder_transaction t->sender_euid = task_euid(proc->tsk); t->to_proc = target_proc; t->to_thread = target_thread; t->code = tr->code; // 給目標進的的命令 (code) t->flags = tr->flags; t->priority = task_nice(current); ... 省略 security // buffer: 為了完成這次 transaction 所申請的內存, // 創建一塊 buffer 指向 binder_transaction#buffer // 注意:分配的空間是在 ServiceManager 中分配 ! t->buffer = binder_alloc_new_buf(&target_proc->alloc, tr->data_size, tr->offsets_size, extra_buffers_size, !reply && (t->flags & TF_ONE_WAY), current->tgid); ... 省略 buffer 錯誤、security // 添加 buffer 訊息 t->buffer->debug_id = t->debug_id; t->buffer->transaction = t; t->buffer->target_node = target_node; t->buffer->clear_on_free = !!(t->flags & TF_CLEAR_BUF); // 將 User space 資料複製到 buffer if (binder_alloc_copy_user_to_buffer( &target_proc->alloc, t->buffer, ALIGN(tr->data_size, sizeof(void *)), (const void __user *) (uintptr_t)tr->data.ptr.offsets, tr->offsets_size)) { ... 省略錯誤 goto err_copy_data_failed; } ... 省略對齊檢查 IS_ALIGNED ... } ``` > ![](https://i.imgur.com/fYoIUPr.png) 對應 Binder 通訊模型:就是先在 Kernel space 分配 binder_buffer 空間,再將使用者資料複製進 binder_buffer 空間 (下圖的內核緩衝區) 開闢的 `binder_buffer` 是屬於 ServiceManager 空間 > ![](https://i.imgur.com/cl0yGOs.png) ### Binder 傳送 - 處理 User 傳入的 BinderServer 1. **複製資料、對象**:for 迴圈處理 User 傳入的 Binder 物件資料;這邊假設是 ServiceManager#addService 的話,就是 **BinderDriver 第一次接觸到該服務,那它就會創建一個 `binder_node` 與之對應** 1. 將 binder_transaction#buf 資料複製到 目標 proc (目前是 ServiceManager) 的 alloc 區塊 2. 取得 User 傳入的資料 (目前是 Service 的相關數據) 並存入 `binder_object` 3. 針對 `binder_object` 類型轉換 binder 資料 ```c= // binder.c static void binder_transaction(struct binder_proc *proc, // 呼叫進程 proc struct binder_thread *thread, // 呼叫進程 thread struct binder_transaction_data *tr, // 數據 int reply, binder_size_t extra_buffers_size) { int ret; // 要傳輸的資料 struct binder_transaction *t; struct binder_work *w; struct binder_work *tcomplete; ... 省略部份 // 目標 proc, thread struct binder_proc *target_proc = NULL; struct binder_thread *target_thread = NULL; // 目標 binder 節點 struct binder_node *target_node = NULL; // 回覆的數據 struct binder_transaction *in_reply_to = NULL; struct binder_transaction_log_entry *e; // log 訊息 ... 省略部份 // 當前進程的上下文 struct binder_context *context = proc->context; ... 省略部份 // 使用者空間 const void __user *user_buffer = (const void __user *) (uintptr_t)tr->data.ptr.buffer ... 省略 log 訊息 // 簡單來說這個 for 迴圈的功能是 // 將使用者傳入的 BBinder (存在 Parcel 中的 cookie 服務) 存下來 for (buffer_offset = off_start_offset; buffer_offset < off_end_offset; buffer_offset += sizeof(binder_size_t)) { struct binder_object_header *hdr; size_t object_size; // 創建 binder_object 結構,可以先看成 flat_binder_object struct binder_object object; binder_size_t object_offset; binder_size_t copy_size; // 1. 從 buffer 緩存空間複製數據到目標 alloc 空間 if (binder_alloc_copy_from_buffer(&target_proc->alloc, &object_offset, t->buffer, buffer_offset, sizeof(object_offset))) { ... 錯誤處理 goto err_bad_offset; } ... copy size 檢查 // 2. 取得 User 傳入的資料並存入 `binder_object` object_size = binder_get_object(target_proc, user_buffer, t->buffer, object_offset, &object); if (object_size == 0 || object_offset < off_min) { ... 錯誤處理 goto err_bad_offset; } // 為下一個物件設定篇一量(因為當前 Service 已佔有一定的空間) user_offset = object_offset + object_size; hdr = &object.hdr; off_min = object_offset + object_size; // 3. 查看 Binder 物件類型,依照物件類型處理 switch (hdr->type) { // 使用 ServiceManager 添加服務時,大多是 BINDER_TYPE_BINDER 類型 case BINDER_TYPE_BINDER: case BINDER_TYPE_WEAK_BINDER: { struct flat_binder_object *fp; // 找到目標 Service 在資料中的偏移 fp = to_flat_binder_object(hdr); // BINDER_TYPE_BINDER 轉 HANDLE // @ 追蹤 binder_translate_binder 函數 ret = binder_translate_binder(fp, t, thread); if (ret < 0 || // 將 fp 資料複製進 buffer binder_alloc_copy_to_buffer(&target_proc->alloc, t->buffer, // 複製進這 object_offset, fp, sizeof(*fp))) { ... 錯誤處理 goto err_translate_failed; } } break; case BINDER_TYPE_HANDLE: case BINDER_TYPE_WEAK_HANDLE: { struct flat_binder_object *fp; fp = to_flat_binder_object(hdr); ret = binder_translate_handle(fp, t, thread); if (ret < 0 || binder_alloc_copy_to_buffer(&target_proc->alloc, t->buffer, object_offset, fp, sizeof(*fp))) { ... 錯誤處理 goto err_translate_failed; } } break; ... 省略部份 case default: ... 錯誤訊息 goto err_bad_object_type; } } // 完成 obj 對象處理,複製緩衝區的其餘部分複製到 buffer if (binder_alloc_copy_user_to_buffer( &target_proc->alloc, t->buffer, user_offset, user_buffer + user_offset, tr->data_size - user_offset)) { ... 複製資料失敗 goto err_copy_data_failed; } ... } ``` :::info * **傳遞 IBinder 對象是真的對象 ? NO !** 1. 當 Server 把 Binder 實體傳給 Client 時,`flat_binder_object` 的 type 是 `BINDER_TYPE_BINDER`(代表該服務在自身進程中,**在自身進程中的地址是虛擬地址,對 目標進程 (ServiceManager) 是完全無效的!**), 2. 必須透過 kernel 把 type 轉為 `BINDER_TYPE_HANDLE`,為 目標進程 (ServiceManager) 創建內核引用 `binder_ref`,並將引用存入 handle 中 (控制碼) 3. 最終將 handle (控制碼) 填入 `binder_transaction_data` 中 ```c= // binder.c static int binder_translate_binder( struct flat_binder_object *fp, struct binder_transaction *t, struct binder_thread *thread) { struct binder_node *node; struct binder_proc *proc = thread->proc; struct binder_proc *target_proc = t->to_proc; struct binder_ref_data rdata; int ret = 0; // 取得進程中的 node 節點 node = binder_get_node(proc, fp->binder); if (!node) { // 如果沒有則創建 node 節點 // 以照目前的狀況來說就是找不到 node,就會創建一個 node node = binder_new_node(proc, fp); if (!node) return -ENOMEM; } ... 省略部份 // 將對應的 binder_ref 加入 binder_proc // 並增加 ref 計數 ret = binder_inc_ref_for_node(target_proc, node, fp->hdr.type == BINDER_TYPE_BINDER, &thread->todo, &rdata); if (ret) goto done; // BINDER_TYPE_BINDER 轉 HANDLE 並存入 type if (fp->hdr.type == BINDER_TYPE_BINDER) fp->hdr.type = BINDER_TYPE_HANDLE; // 描述改為遠端 Service else fp->hdr.type = BINDER_TYPE_WEAK_HANDLE; fp->binder = 0; fp->handle = rdata.desc; fp->cookie = 0; ... done: binder_put_node(node); return ret; } ``` ::: 2. 調整 `binder_transaction`#work#type 為 `BINDER_WORK_TRANSACTION` ```c= // binder.c static void binder_transaction(struct binder_proc *proc, // 呼叫進程 proc struct binder_thread *thread, // 呼叫進程 thread struct binder_transaction_data *tr, // 數據 int reply, binder_size_t extra_buffers_size) { int ret; // 要傳輸的資料 struct binder_transaction *t; struct binder_work *w; struct binder_work *tcomplete; ... // 設定 tcomplete 的 work.type (要給 User 的 BinderThread 處理) if (t->buffer->oneway_spam_suspect) tcomplete->type = BINDER_WORK_TRANSACTION_ONEWAY_SPAM_SUSPECT; else tcomplete->type = BINDER_WORK_TRANSACTION_COMPLETE; // 在 ServiceManager 接收後,binder_thread_read 時判斷 t->work.type = BINDER_WORK_TRANSACTION; } ``` > ![](https://i.imgur.com/HrNPCvE.png) ### Binder 傳送 - 添加任務到目標 & 喚醒 1. **傳送 binder 資料到目標 proc**:**重點是 `binder_proc_transaction` 函數**(之後分析) ```c= // binder.c static void binder_transaction(struct binder_proc *proc, // 呼叫進程 proc struct binder_thread *thread, // 呼叫進程 thread struct binder_transaction_data *tr, // 數據 int reply, binder_size_t extra_buffers_size) { int ret; // 要傳輸的資料 struct binder_transaction *t; struct binder_work *w; struct binder_work *tcomplete; ... 省略部份 // 目標 proc, thread struct binder_proc *target_proc = NULL; struct binder_thread *target_thread = NULL; // 目標 binder 節點 struct binder_node *target_node = NULL; // 回覆的數據 struct binder_transaction *in_reply_to = NULL; struct binder_transaction_log_entry *e; // log 訊息 ... 省略部份 // 當前進程的上下文 struct binder_context *context = proc->context; ... 省略部份 // 使用者空間 const void __user *user_buffer = (const void __user *) (uintptr_t)tr->data.ptr.buffer ... 省略 log 訊息 if (reply) { ... 先關注 transact 功能 } else if (!(t->flags & TF_ONE_WAY)) { // 並非單向寫入 ... // 設定必須 reply 為 1 t->need_reply = 1; t->from_parent = thread->transaction_stack; // 設定定來 thread stack // 將交易 t 存到來源 thread (就是 User 的 thread) thread->transaction_stack = t; binder_inner_proc_unlock(proc); // @ 追蹤 binder_proc_transaction 函數 return_error = binder_proc_transaction(t, target_proc, target_thread); if (return_error) { ... 錯誤處理 goto err_dead_proc_or_thread; } } else { // 單向 binder_enqueue_thread_work(thread, tcomplete); // @ 追蹤 binder_proc_transaction 函數 return_error = binder_proc_transaction(t, target_proc, NULL); if (return_error) goto err_dead_proc_or_thread; } ... } ``` > ![](https://i.imgur.com/tsh6VRK.png) * 分析 [**binder**](https://cs.android.com/android/kernel/superproject/+/common-android-mainline:common/drivers/android/binder.c)#**binder_proc_transaction** 函數:前面的過程可以算是整理要傳輸的資料,**到 `binder_proc_transaction` 才是真正傳輸資料的部份** ```c= // binder.c static int binder_proc_transaction(struct binder_transaction *t, // 要傳輸的數據 struct binder_proc *proc, // 目標 proc struct binder_thread *thread) // 目標 thread { struct binder_node *node = t->buffer->target_node; bool oneway = !!(t->flags & TF_ONE_WAY); bool pending_async = false; binder_node_lock(node); // 鎖住目標 binder_node if (oneway) { if (node->has_async_transaction) pending_async = true; else node->has_async_transaction = true; } binder_inner_proc_lock(proc); // 鎖住目標 proc // 將任務串接到目標 if (thread) { binder_transaction_priority(thread, t, node); binder_enqueue_thread_work_ilocked(thread, &t->work); } else if (!pending_async) { binder_enqueue_work_ilocked(&t->work, &proc->todo); } else { ... binder_enqueue_work_ilocked(&t->work, &node->async_todo); } ... 省略部份 if (!pending_async) // @ 追蹤 binder_wakeup_thread_ilocked 函數 binder_wakeup_thread_ilocked(proc, thread, !oneway /* sync */); proc->outstanding_txns++; // 解除 node, proc 的鎖 binder_inner_proc_unlock(proc); binder_node_unlock(node); return 0; } // onway = sync // onway true 的話 sync true static void binder_wakeup_thread_ilocked(struct binder_proc *proc, struct binder_thread *thread, bool sync) { assert_spin_locked(&proc->inner_lock); // 喚醒目標 thread if (thread) { trace_android_vh_binder_wakeup_ilocked(thread->task, sync, proc); if (sync) // 同步 wake_up_interruptible_sync(&thread->wait); else // 異步 wake_up_interruptible(&thread->wait); return; } ... } ``` 2. **移除暫存結構** ```c= // binder.c static void binder_transaction(struct binder_proc *proc, // 呼叫進程 proc struct binder_thread *thread, // 呼叫進程 thread struct binder_transaction_data *tr, // 數據 int reply, binder_size_t extra_buffers_size) { ... 省略部份 // 移除暫時 binder_thread if (target_thread) binder_thread_dec_tmpref(target_thread); // 移除暫時 binder_proc binder_proc_dec_tmpref(target_proc); // 移除暫時 binder_node if (target_node) binder_dec_node_tmpref(target_node); /* * write barrier to synchronize with initialization * of log entry */ smp_wmb(); WRITE_ONCE(e->debug_id_done, t_debug_id); return; ... 省略各種錯誤處理 tag } ``` > ![](https://i.imgur.com/yifFu0X.png) ## Binder 讀取資料 - binder_thread_read :::info 目前我們假設是 **ServiceManager 進程在處理 Binder 訊息** ::: 前面已經介紹過如何進入 Kernel space 與 Binder 通訊,這裡重點介紹 `binder_thread_read 函數` 在 Kernel space 的運作流程、重點 :::success - 當然前提是 User 有在 `binder_write_read` 結構中 寫入資料; 當前寫入的是 `BC_TRANSACTION` 命令 > ![](https://i.imgur.com/zsx4s88.png) ::: ### Binder 讀取 - 判斷是否有任務 1. **判斷 `binder_thread` 是否有任務**:如果 `binder_thread` 沒有任務則會進行休眠(並 **把 BinderThread 切換成 Waiting 狀態**),直到有任務才會被喚醒 :::info * 像是 ServiceManager 剛被創建出來時就沒有任務,所以會進行休眠 ::: ```c= // binder.c static int binder_thread_read(struct binder_proc *proc, // Server 的 Proc struct binder_thread *thread, // Server 的 Thread binder_uintptr_t binder_buffer, // IPCThreadState 的 mIn 數據空間 size_t size, binder_size_t *consumed, int non_block) { // 使用者空間的指針 void __user *buffer = (void __user *)(uintptr_t)binder_buffer; // 取得開頭結尾指針 void __user *ptr = buffer + *consumed; // 數據的起點 void __user *end = buffer + size; // 數據的終點 int ret = 0; int wait_for_proc_work; ... retry: // 判斷是否有任務,若沒有任務則需要休眠 // eg. 像是 service_manager 剛開始就沒有寫,因為沒有任務 wait_for_proc_work = binder_available_for_proc_work_ilocked(thread); // 加上 thread Waiting 狀態 thread->looper |= BINDER_LOOPER_STATE_WAITING; ... trace if (wait_for_proc_work) { // 判斷 Binder thread 狀態... 是否已註冊、是否開始 if (!(thread->looper & (BINDER_LOOPER_STATE_REGISTERED | BINDER_LOOPER_STATE_ENTERED))) { ... 省略錯誤訊息 } ... } // 看看服務是否是堵塞;像是 ServiceManager 就是堵塞 if (non_block) { if (!binder_has_work(thread, wait_for_proc_work)) ret = -EAGAIN; } else { ret = binder_wait_for_work(thread, wait_for_proc_work); } // 移除 Thread Waiting 狀態 thread->looper &= ~BINDER_LOOPER_STATE_WAITING; if (ret) return ret; ... } ``` > ![](https://i.imgur.com/zRnULNw.png) ### Binder 讀取 - 取出任務 1. **取得任務 header**:進入 while 循環處理資料,判斷 `binder_thread`、`binder_proc` 的 todo 列表是否有任務 (先 thread 再 proc) :::info BinderDriver 會將 Client 的任務加入目標 Proc 的 todo 列表;優先處理 thread,之後才處理 proc ::: ```c= // binder.c static int binder_thread_read( struct binder_proc *proc, struct binder_thread *thread, binder_uintptr_t binder_buffer, // IPCThreadState 的 mIn 數據空間 size_t size, binder_size_t *consumed, int non_block) { // 使用者空間的指針 void __user *buffer = (void __user *)(uintptr_t)binder_buffer; // 取得開頭結尾指針 void __user *ptr = buffer + *consumed; // 數據的起點 void __user *end = buffer + size; // 數據的終點 int ret = 0; int wait_for_proc_work; ... while (1) { uint32_t cmd; // 創建 binder_transaction_data_secctx struct binder_transaction_data_secctx tr; // 取 tr#transaction_data struct binder_transaction_data *trd = &tr.transaction_data; // 當前需要執行的 binder_work struct binder_work *w = NULL; // 任務 header struct list_head *list = NULL; // 要對外傳輸的訊息 struct binder_transaction *t = NULL; struct binder_thread *t_from; size_t trsize = sizeof(*trd); // 判斷 thread#todo 列表是否為空 if (!binder_worklist_empty_ilocked(&thread->todo)) // 在 binder_thread_write 中,有把 binder_work 添加到目標 Server 的 thread->todo 列表中 // 所以這裡會讀取到數據,並不為空 list = &thread->todo; // 判斷 proc#todo 列表 else if (!binder_worklist_empty_ilocked(&proc->todo) && wait_for_proc_work) list = &proc->todo; else { ... } ... } } ``` > ![](https://i.imgur.com/cs2bgMo.png) ### Binder 讀取 - 判斷任務類型 1. **判斷 binder_work#type**:根據判斷 `binder_work#type` 來決定要 BinderDriver 要回覆的 BR_xxx 命令 > * `BINDER_WORK_TRANSACTION` (BinderDrive 傳送數據) > * `BINDER_WORK_TRANSACTION_COMPLETE`(BinderDrive 處理完成的數據) 這邊假設我們的 type 是 `BINDER_WORK_TRANSACTION`,也就是 BinderDriver 要求交易 ```c= // binder.c static int binder_thread_read( struct binder_proc *proc, struct binder_thread *thread, binder_uintptr_t binder_buffer, // IPCThreadState 的 mIn 數據空間 size_t size, binder_size_t *consumed, int non_block) { // 使用者空間的指針 void __user *buffer = (void __user *)(uintptr_t)binder_buffer; // 取得開頭結尾指針 void __user *ptr = buffer + *consumed; // 數據的起點 void __user *end = buffer + size; // 數據的終點 int ret = 0; int wait_for_proc_work; ... while (1) { uint32_t cmd; // 創建 binder_transaction_data_secctx struct binder_transaction_data_secctx tr; // 取 tr#transaction_data struct binder_transaction_data *trd = &tr.transaction_data; // 當前需要執行的 binder_work struct binder_work *w = NULL; // 任務 header struct list_head *list = NULL; // 要對外傳輸的訊息 struct binder_transaction *t = NULL; struct binder_thread *t_from; size_t trsize = sizeof(*trd); ... // 判斷 binder_work#type switch (w->type) { case BINDER_WORK_TRANSACTION: { binder_inner_proc_unlock(proc); // 從 binder_work 中取得 binder_transaction t = container_of(w, struct binder_transaction, work); } break; // app 喚醒 service_manager 後, // service_manager 接收到的 `BINDER_WORK_TRANSACTION_COMPLETE` case BINDER_WORK_TRANSACTION_COMPLETE: case BINDER_WORK_TRANSACTION_ONEWAY_SPAM_SUSPECT: { // 設定 將要回覆的指令 cmd if (proc->oneway_spam_detection_enabled && w->type == BINDER_WORK_TRANSACTION_ONEWAY_SPAM_SUSPECT) cmd = BR_ONEWAY_SPAM_SUSPECT; else cmd = BR_TRANSACTION_COMPLETE; // 釋放 binder_work kfree(w); ... if (put_user(cmd, (uint32_t __user *)ptr)) return -EFAULT; // 移動該指標,跳過指令 ptr += sizeof(uint32_t); ... } break; // 透過 ServiceManager 註冊 Server 服務時會調用 case BINDER_WORK_NODE: { ... int has_weak_ref; int has_strong_ref; void __user *orig_ptr = ptr; // Server 的計數 // 強計數 strong = node->internal_strong_refs || node->local_strong_refs; // 弱計數 weak = !hlist_empty(&node->refs) || node->local_weak_refs || node->tmp_refs || strong; // 是否有強引用 has_strong_ref = node->has_strong_ref; // 是否有弱引用 has_weak_ref = node->has_weak_ref; // 有弱引用,但沒有 ref if (weak && !has_weak_ref) { node->has_weak_ref = 1; // 設為 1 node->pending_weak_ref = 1; node->local_weak_refs++; } // 有強引用,但沒有 ref if (strong && !has_strong_ref) { node->has_strong_ref = 1; // 設為 1 node->pending_strong_ref = 1; node->local_strong_refs++; } if (!strong && has_strong_ref) node->has_strong_ref = 0; // 設為 0 (清除) if (!weak && has_weak_ref) node->has_weak_ref = 0; // 設為 0 (清除) if (!weak && !strong) { ... 釋放 server node } else { ... } if (weak && !has_weak_ref) // @ 查看 binder_put_node_cmd ret = binder_put_node_cmd( proc, thread, &ptr, node_ptr, node_cookie, node_debug_id, BR_INCREFS, "BR_INCREFS"); ... 省略部分 if (ret) return ret; } break; } } } ``` > ![](https://i.imgur.com/KeojQBJ.png) ### Binder 讀取 - 取得 Node 1. **判斷 binder_node**:從接收到的資料 t#buffer 中取得 `target_node` (目前的 `target_node` 是 ServiceManager,也就是 `binder_context_mgr_node`) ```c= // binder.c static int binder_thread_read( struct binder_proc *proc, struct binder_thread *thread, binder_uintptr_t binder_buffer, // IPCThreadState 的 mIn 數據空間 size_t size, binder_size_t *consumed, int non_block) { // 使用者空間的指針 void __user *buffer = (void __user *)(uintptr_t)binder_buffer; // 取得開頭結尾指針 void __user *ptr = buffer + *consumed; // 數據的起點 void __user *end = buffer + size; // 數據的終點 int ret = 0; int wait_for_proc_work; ... while (1) { uint32_t cmd; // 創建 binder_transaction_data_secctx struct binder_transaction_data_secctx tr; // 取 tr#transaction_data struct binder_transaction_data *trd = &tr.transaction_data; // 當前需要執行的 binder_work struct binder_work *w = NULL; // 任務 header struct list_head *list = NULL; // 要對外傳輸的訊息 struct binder_transaction *t = NULL; struct binder_thread *t_from; size_t trsize = sizeof(*trd); ... // 從接收到的資料 t#buffer 中取得 target_node if (t->buffer->target_node) { struct binder_node *target_node = t->buffer->target_node; trd->target.ptr = target_node->ptr; trd->cookie = target_node->cookie; binder_transaction_priority(thread, t, target_node); // 切換 cmd 改為 BR_TRANSACTION (ServiceManager 之後要回傳) cmd = BR_TRANSACTION; } else { ...無法取得 binder_node } ... } } ``` > ![](https://i.imgur.com/MZgg1s5.png) ### Binder 讀取 - 將資料複製給目標 Thread 1. **填充剩餘的 `binder_transaction_data` 數據**:其中就包括要傳遞給 Server 的命令(code) > 當前的目標是 ServiceManager > code 是 ADD_SERVICE_TRANSACTION ```c= // binder.c static int binder_thread_read(struct binder_proc *proc, struct binder_thread *thread, binder_uintptr_t binder_buffer, // IPCThreadState 的 mIn 數據空間 size_t size, binder_size_t *consumed, int non_block) { // 使用者空間的指針 void __user *buffer = (void __user *)(uintptr_t)binder_buffer; // 取得開頭結尾指針 void __user *ptr = buffer + *consumed; // 數據的起點 void __user *end = buffer + size; // 數據的終點 int ret = 0; int wait_for_proc_work; ... while (1) { uint32_t cmd; struct binder_transaction_data_secctx tr; struct binder_transaction_data *trd = &tr.transaction_data; struct binder_work *w = NULL; struct list_head *list = NULL; struct binder_transaction *t = NULL; struct binder_thread *t_from; size_t trsize = sizeof(*trd); ... // Client 給 Server 的指令 trd->code = t->code; trd->flags = t->flags; // 來源 ID,可做安全性檢查 trd->sender_euid = from_kuid(current_user_ns(), t->sender_euid); t_from = binder_get_txn_from(t); if (t_from) { struct task_struct *sender = t_from->proc->tsk; trd->sender_pid = task_tgid_nr_ns(sender, task_active_pid_ns(current)); ... trace } else { trd->sender_pid = 0; } ... 省略部份 // 將要傳輸的大小指向 t 數據區域大小 trd->data_size = t->buffer->data_size; // Offset 區域大小 trd->offsets_size = t->buffer->offsets_size; // 將要傳輸的數據指向 t 數據區域 trd->data.ptr.buffer = (uintptr_t)t->buffer->user_data; // 將要傳輸的偏移指向 t Offset 區域 trd->data.ptr.offsets = trd->data.ptr.buffer + ALIGN(t->buffer->data_size, sizeof(void *)); tr.secctx = t->security_ctx; if (t->security_ctx) { cmd = BR_TRANSACTION_SEC_CTX; trsize = sizeof(tr); } // 將指令 cmd 複製到 User space 中 // cmd 為 BR_TRANSACTION if (put_user(cmd, (uint32_t __user *)ptr)) { ... 失敗處理 return -EFAULT; } ptr += sizeof(uint32_t); // 將指令 `binder_transaction_data` 複製到 User space // (ServiceManager 的 BinderThread) 中 if (copy_to_user(ptr, &tr, trsize)) { ... 複製失敗 return -EFAULT; } ptr += trsize; ... 省略部份 // 設定 buffer 為可以用 t->buffer->allow_user_free = 1; if (cmd != BR_REPLY && !(t->flags & TF_ONE_WAY)) { binder_inner_proc_lock(thread->proc); // 挑選最佳處理的 thread t->to_parent = thread->transaction_stack; t->to_thread = thread; thread->transaction_stack = t; binder_inner_proc_unlock(thread->proc); } else { // @ 追蹤 binder_free_transaction 函數 binder_free_transaction(t); } break; } done: ... 省略部份 return 0; } ``` > ![](https://i.imgur.com/vXdz3ME.png) * **binder_free_transaction 函數**:處理 `binder_transaction` 資料(處理完畢後也會用 free 釋放),如果目標進程在休眠也會在這個時候喚醒 > `binder_transaction` 結構是 Kernal Space 獨用 ```c= static void binder_free_transaction(struct binder_transaction *t) { struct binder_proc *target_proc = t->to_proc; if (target_proc) { ... 省略部份 // 如果 target_proc 在休眠則喚醒 target_proc if (!target_proc->outstanding_txns && target_proc->is_frozen) wake_up_interruptible_all(&target_proc->freeze_wait); if (t->buffer) t->buffer->transaction = NULL; binder_inner_proc_unlock(target_proc); } ... kfree(t); binder_stats_deleted(BINDER_STAT_TRANSACTION); } ``` > ![](https://i.imgur.com/L3tv3DD.png) :::info * 到這一步後 BinderDriver 的 Read 就處理完了 ::: ## Appendix & FAQ :::info ::: ###### tags: `Binder`