---
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 個檔案
```
> 
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;
}
```
> 
:::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` 中)
> 
:::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);
```
> 
* **指定 `proc` 給 `filp->private_data`**(filp 就是 `/dev/binder` 文件映射到應用進程的代表),**未來 Binder 驅動就可以透過 `filp->private_data` 找到對應的應用**
> 
```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;
}
```
> 
### 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;
... 省略部份
};
```
> 
## 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 機制,產生了虛擬記憶體的概念 !
> 
:::
binder_mmap 流程圖
> 
* 分析 **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);
}
```
> 
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;
...省略部分 (錯誤處理)
}
```
> 
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,當有需要時會 **動態拓展**,以免浪費物理記憶體空間
:::
> 
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;
...省略部分 (錯誤處理)
}
```
> 
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
* 在這邊也可以看到異步空間只占一半,避免過度佔據同步處理的優先度
:::
> 
* 將 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`
> 
* 在 `proc` 中查看 pid 的 maps 映射位置
:::success
`proc/<pid\>/maps` 存有真實 addr. 對應 虛擬 addr.
:::
```shell=
cat /proc/1920/maps | grep /dev/binder
```
> 
## Binder 核心 Buffer
### 分配核心 Buffer
* 當一個應用對 Binder 發起 `BC_TRANSACTION`、`BC_REPLY` 命令時,Binder 驅動就會將使用者資料 **複製** 到 Binder 驅動中,之後再傳遞給目標進程;
這時就需要 Binder 驅動分配一塊核心空間給儲存複製進來的資料
:::info
* Buffer 是分配在目標 proc 的核心空間
:::
> 
* 在 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 的核心空間
:::
> 
* 在 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 的控制協議命令)
> 
### 讀、寫 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;
}
```
流程圖
> 
### 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` 命令**
> 
:::
```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` 結構中
> 
## 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;
}
```
:::
> 
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;
}
...
}
```
> 
### 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 間的任務不會相互依賴
:::
> 
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;
}
}
```
> 
### 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
...
}
```
> 
對應 Binder 通訊模型:就是先在 Kernel space 分配 binder_buffer 空間,再將使用者資料複製進 binder_buffer 空間 (下圖的內核緩衝區)
開闢的 `binder_buffer` 是屬於 ServiceManager 空間
> 
### 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;
}
```
> 
### 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;
}
...
}
```
> 
* 分析 [**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
}
```
> 
## Binder 讀取資料 - binder_thread_read
:::info
目前我們假設是 **ServiceManager 進程在處理 Binder 訊息**
:::
前面已經介紹過如何進入 Kernel space 與 Binder 通訊,這裡重點介紹 `binder_thread_read 函數` 在 Kernel space 的運作流程、重點
:::success
- 當然前提是 User 有在 `binder_write_read` 結構中 寫入資料; 當前寫入的是 `BC_TRANSACTION` 命令
> 
:::
### 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;
...
}
```
> 
### 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 {
...
}
...
}
}
```
> 
### 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;
}
}
}
```
> 
### 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
}
...
}
}
```
> 
### 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;
}
```
> 
* **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);
}
```
> 
:::info
* 到這一步後 BinderDriver 的 Read 就處理完了
:::
## Appendix & FAQ
:::info
:::
###### tags: `Binder`