---
# System prepended metadata

title: Binder - Kernel 驅動分析(v2)
tags: [Binder]

---

---
title: 'Binder - Kernel 驅動分析(v2)'
disqus: kyleAlien
---

Binder - Kernel 驅動分析(v2)
===

## OverView of Content

請配合 [**Binder 驅動結構**](https://hackmd.io/TtT34BAwRP2sZ3vALt4Drg?view) 一起看

這裡著重介紹分析 Kernal 層… BinderKernal 比較複雜，但主要就是從 KernalSpace 角度看 Binder IPC，同時也會接觸到 UserSpace

```shell!
APP / Framework
      │
      │  Java Binder API
      ▼
Binder Framework (Java)
      │
      │ JNI
      ▼
Binder Native (C++)    
      │
      │ ioctl()
      ▼
Binder Driver (Kernel)    // 目前的位置
```
[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`      | 用於 `epoll/poll` 等待事件；當有 `BR_*` 回傳或可讀資料時喚醒 thread            |
    | `unlocked_ioctl`(IPC 核心通道) | `binder_ioctl`     | Binder IPC 核心入口；處理 `BC_*` 命令（如 `BC_TRANSACTION`、`BC_REPLY`） |
    | `compat_ioctl`(IPC 核心通道)   | `compat_ptr_ioctl` | 32-bit process 在 64-bit kernel 上的 ioctl 相容層轉換               |
    | `mmap`(建立生命週期)           | `binder_mmap`      | 建立該 process 的 Binder transaction buffer pool（mmap 區）        |
    | `open`(建立生命週期)           | `binder_open`      | 建立 `binder_proc`，初始化 `binder_alloc`、thread pool 結構          |
    | `flush`(清理)          | `binder_flush`     | 清除 thread 尚未處理的待辦工作（通常在 close 前）                            |
    | `release`(建立生命週期)        | `binder_release`   | 關閉 `/dev/binder` 時釋放 `binder_proc` 與相關資源                    |


    ```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` 設備，再 Binder 驅動中初始化 `binder_proc`

        2. **`binder_mmap`**：讓當前 proc 取得 `/dev/binder` 的 **內存映射區**，**映射 (Mapping) 到當前 proc 的記憶體中**
        
        3. **`binder_ioctl`**：Binder IPC 核心入口；處理 `BC_*` 命令（如 `BC_TRANSACTION`、`BC_REPLY`）
    :::

### [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;
            ...
        };
        ```
        > ![image](https://hackmd.io/_uploads/B1JMNCmtWe.png)
    
    2. 初始化 `binder_proc` 結構 (放進當前進程訊息；也就是 Caller 再 Binder 驅動中的訊息)
    
    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` 返回的 FD，也就是 Caller 進程內部的檔案編號，協助 Caller 進程之後可以訪問 Binder 驅動
        
        > 正確點的描述是：讓這個進程能透過 VFS 找到對應的 file 物件，間接找到 Binder 驅動

        ```mermaid
        flowchart LR

        subgraph Process
            FD["binder_open<br>fd = 5"]
        end

        subgraph Kernel
            FILE["struct file"]
            PROC["binder_proc"]
            DRIVER["binder_ioctl()"]
        end

        FD --> FILE
        FILE -->|private_data| PROC
        FILE -->|f_op| DRIVER
        ```
    :::

### 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 驅動會為每個 `open("/dev/binder")` 的進程建立一個 binder_proc，並把它加入全域 `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` 會為「Caller 進程」建立一塊由 Binder 驅動管理的 transaction buffer pool，這塊物理頁會同時映射（Mapping）到該進程的 UserSpace 與 KernelSpace

當其他進程呼叫它時，驅動會將資料複製到這塊 mmap 區域中，讓接收端可以直接從自己的 mmap 區讀取資料

:::warning
但這段過程仍是兩次複製，因為這是一種 Mapping 的過程，而不像是 DMA 這種機制
:::

### binder_mmap 分析

* 在說明 Binder 驅動層的 `binder_mmap` 函數之前，先介紹幾個重要的數據結構，並且以下分析會將函數拆分說明（以下是站在 **Binder 驅動的角度對 Caller 進程的描述**）

    | Struct 名 | 說明 |
    | -------- | -------- |
    | **vm_area_struct** \*vma | 使用者空間的描述；該應用程序使用的 **虛擬內存**，其中 `vma->vm_start`、`vma->vm_end` 代表了 Caller 內存的起點 ＆ 終點<br>**`vm_area_struct` 是 Binder 驅動中對使用者虛擬內存的描述** |
    | **vm_struct** \*area | Kernel 自己的「非連續實體頁面，但連續虛擬地址」記憶體區段（站在使用者的角度紀錄自身） |
    | **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 一次**

            之所以只能映射一次是因為 binder 的這塊 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 大小
        :::
    
    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 中 （未來就可以找到這個空間做使用）
        
        
        > 這樣將來在寫入資料時就會映射 Mapping 到 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 添加到 Caller 進程的列表 `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);
            }
            ```

:::success
* 到這一步就完成了 `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 映射位置

    ```shell=
    cat /proc/1920/maps | grep /dev/binder
    ```
    下圖的虛擬地址資訊對應到 vm_area_struct 裡的 vm_start(`7411da99d000`) / vm_end(`7411da9b000`)
    > ![](https://i.imgur.com/KMtrIS4.png)

## Binder 核心 Buffer


### 分配核心 Buffer

* 當一個應用對 Binder 發起 `BC_TRANSACTION`、`BC_REPLY` 命令時，Binder 驅動就會將使用者資料 **複製** 到 Binder 驅動中，之後再傳遞給目標進程；

    這時就需要 Binder 驅動分配一塊核心空間給儲存複製進來的資料

    分配的是「目標進程的 binder mmap pool 裡的一段 buffer」（user VA 區段），並由驅動確保對應頁面(backing pages)已建立/映射，讓資料可以被寫入
    
    > 因為 `binder_buffer->user_data` 是 `void __user *`，它指向的是 目標進程 UserSpace 的 mmap 區域
    
    :::info
    * Buffer 是分配在目標 proc 的核心空間
        
        Buffer 是從「目標進程的 `binder_alloc` 所管理的 mmap pool」切出來的，其地址是目標 proc 的 UserSpace VA（user_data），但由 kernel 控制其 backing pages
    :::
    > ![](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（Native） 層會以 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) 複製到 Caller 使用者空間（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. 從 UserSpace 獲取數據存到 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. 將 Caller 的 `binder_write_read` 結構複製到 Kernel space (BinderDriver) 中的 `binder_transaction_data` 結構中
        > ![](https://i.imgur.com/Yzs1Vu4.png)

## Binder 內部傳送資料 - binder_transaction 

:::danger
目前是以 **Caller 的角度** 來看 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` 則會錯誤
    
    > `binder_node` 簡單來說就是 Kernel 中「代表一個 Binder 實體物件（通常是 Server 端 BBinder）」的節點
    :::info
    * ServiceManager 的 handle 數值就固定是 0（就像是 DNS 的概念）

    * 這邊取的 `binder_node` 在 `/dev/binder` 文件中的 Server 節點，要傳遞給 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 功能，BC_REPLY 才會進來這裡

        } else {
            // handle 值代表 "控制碼"
            // ++只有 ServiceManager handle 為 0++
            if (tr->target.handle) {        // 進入到這裡代表並非 ServiceManager 服務
                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**：主要在做 reply routing / call-chain 的對齊：
    
    也就是「如果這個呼叫是嵌套的同步呼叫，盡量把新的交易送到同一條關聯鏈上的 thread」，避免亂跳 thread 造成死鎖/違反同步語意

    ```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 (任務相互依賴)
        // 在 target_proc 裡找「曾經和這個 thread 有關聯的對端 thread」
        if (!(tr->flags & TF_ONE_WAY) && thread->transaction_stack) {
            struct binder_transaction *tmp;

            tmp = thread->transaction_stack;    // 來源 thread stack
            if (tmp->to_thread != thread) {    // 自己呼叫自己
                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
    * 什麼時候會產生 `transaction_stack`？
        
        通常發生在 Thread 正在處理一個同步來電（`BR_TRANSACTION`），並且還沒有回覆（`BC_REPLY`），或是巢狀同步呼叫
        
        ```mermaid
        sequenceDiagram
            participant A as Client A (thread)
            participant K as Binder Driver
            participant B as Server B (binder thread)

            A->>K: BC_TRANSACTION (sync)
            K->>B: BR_TRANSACTION
            Note over B: push transaction to B.transaction_stack

            B->>K: BC_REPLY
            K->>A: BR_REPLY
            Note over B: pop transaction from B.transaction_stack
        ```
        

    * 這裡可以看到單向傳遞 `TF_ONE_WAY` 是不用找到 Server 的最佳 thread，因為 thread 間的任務不會相互依賴
        
        oneway 不需要維持 reply chain（因為沒有 reply），所以通常不需要沿 transaction_stack 找對應 thread
        
        > 但它仍然需要把 work 放進 target_proc（或 node 的 async queue）讓某個 thread 來處理
    :::
    > ![](https://i.imgur.com/Pey6DvA.png)


2. **分配 `binder_work` 空間**：分配一個 `tcomplete`，之後會封裝成一個 `BINDER_WORK_TRANSACTION_COMPLETE` 並添加到上一步找到的 User thread 的 todo 列表中（也就是 `thread->todo`）

    > 這樣使用者就可以知道該命令已傳送
    > 
    > 它的作用是讓 UserSpace 的 libbinder 在 `binder_thread_read()` 收到 `BR_TRANSACTION_COMPLETE` 之類事件，知道「driver 已經接手/排隊完成」，但不代表對方已處理

    ```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` 結構是 Binder 驅動獨用
    :::

    後面也會將它 (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 通訊模型：就是先在 KernelSpace 分配 binder_buffer 空間，再將使用者資料複製進 binder_buffer 空間 (下圖的內核緩衝區)

    開闢的 `binder_buffer` 是屬於 ServiceManager 空間
    > ![](https://i.imgur.com/cl0yGOs.png)


### Binder 傳送 - 處理 User 傳入的 BinderServer

1. **複製資料、物件**：for 迴圈處理 User 傳入的 Binder 物件資料；這邊假設是 ServiceManager#addService 的話，就是 **Binder 驅動第一次接觸到該服務，那它就會創建一個 `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
            // 前面處理 object 時，驅動會「只 copy 物件 header/內容並改寫」，剩下沒碰的 payload 才一次 copy 補齊
            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) 是完全無效的！**）
                
                > handler 是目標進程的 `binder_ref` 紅黑樹上的數值，而不是具體服務 `binder_node` 上的數值
                > 舉例：A 進程要取得 B 服務，所以訪問 ServiceManager 後，A 進程取得的 Handler 數值就是 ServiceManager 中的 `binder_ref` 的數值
                
            2. 必須透過 kernel 把 type 轉為 `BINDER_TYPE_HANDLE`，為 目標進程 (ServiceManager) 創建內核引用 `binder_ref`，並將引用存入 handle 中 (控制碼)

                > 發起方 Caller 呼叫時會判斷並做出這個轉換

            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)
                        // 描述改為遠端 Service（轉換處）
                        fp->hdr.type = BINDER_TYPE_HANDLE;
                    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);
                // 寶任務塞到目標執行緒的 todo 列表
                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);
                
                // 喚醒 thread->wait，把正在等待事做的 Thread 叫醒
                if (sync)
                    // 同步交易有立刻喚醒、減少延遲的特性
                    wake_up_interruptible_sync(&thread->wait);
                else
                    // 異步交易，屬於ㄧ般喚醒
                    wake_up_interruptible(&thread->wait);
                return;
            }
            ...
        }
        ```

2. **移除臨時結構**：這段「移除暫時 `binder_*`」其實是在…

    * 釋放臨時引用（`tmp_ref`），避免 leak

    * 也確保在這段 transaction 建立/排隊/喚醒過程中，目標物件不會被釋放造成 UAF（`Use-After-Free`，也就是以釋放的目標仍被使用）

    * 最後用 memory barrier 確保 log 記錄一致

    ```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 進程被 Caller 在 Binder 驅動中喚醒，然後以 ServiceManager 的角度處理 Binder 訊息**
:::

前面已經介紹過如何進入 KernelSpace 與 Binder 通訊，這裡重點介紹 `binder_thread_read 函數` 在 Kernel space 的運作流程、重點

:::success
- 當然前提是 User 有在 `binder_write_read` 結構中 寫入資料； 當前寫入的是 `BC_TRANSACTION` 命令

    > 下圖是 Caller 原先對於 Binder 驅動寫入的命令
    > ![](https://i.imgur.com/zsx4s88.png)
:::

### Binder 讀取 - 判斷是否有任務

1. **判斷 `binder_thread` 是否有任務**：
    
    如果 `binder_thread` 沒有任務則會進行休眠（**把 BinderThread 切換成 Waiting 狀態**），直到有任務才會被喚醒，這也就是為什麼我們上面說 Caller 在 binder write 到最後要把目標喚醒的原因

    :::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
    Binder 驅動會將 Client 的任務加入目標進程的 `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` 來決定要 Binder 驅動要回覆的 `BR_*` 命令
 
    > * `BINDER_WORK_TRANSACTION` (Binder 驅動傳送數據，給目標進程 transaction)
    > * `BINDER_WORK_TRANSACTION_COMPLETE`（Binder 驅動處理完成的數據，回應給 Caller Thread，用來表示驅動已經處理完這次 Write）
    
    這邊假設我們的 type 是 `BINDER_WORK_TRANSACTION`，也就是 Binder 驅動要求交易
    
    :::warning
    * `BINDER_WORK_TRANSACTION` 並不是 Caller 發送給 Binder 驅動的命令，他是 Binder 驅動內部的工作類型，表示的是「**有一筆 transaction 要交給目標進程處理**」

        ✅ 一筆 transaction ⇒ 通常一個 BINDER_WORK_TRANSACTION（入隊一次）
        ❌ 不是「找到 proc/thread 就各發一次」
    :::
    ```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
                // 會組出 BR_TRANSACTION（或 BR_REPLY）+ binder_transaction_data 寫回 userspace
                // 讓目標 thread 回到 userspace 去執行 onTransact()
                t = container_of(w, struct binder_transaction, work);
            } break;

            // Binder 驅動在完成 BINDER_WORK_TRANSACTION 後，會回應給 Caller 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;

            // 處理 node 強/弱引用狀態變化
            // 像是透過 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

> 以下的解釋語境是在「對 service manager 添加服務」

1. **判斷 binder_node**：從接收到的資料 t#buffer 中取得 `target_node`
    
    此處 `target_node` 是「這筆 transaction 的目標本地 binder 實體」；若交易是呼叫 ServiceManager 的 context manager 介面，`target_node` 會是 `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
            // Binder 驅動準備寫回 ServiceManager UserSpace Buffer 的數據
            struct binder_transaction_data *trd = &tr.transaction_data;

            // 當前需要執行的 binder_work
            struct binder_work *w = NULL;
            // 任務 header
            struct list_head *list = NULL;

            // 別人要傳給 ServiceManager 的訊息
            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;

                // 把目標 target_node 放到 service manager buffer
                trd->target.ptr = target_node->ptr;
                trd->cookie =  target_node->cookie;
                binder_transaction_priority(thread, t, target_node);

                // 切換 cmd 改為 BR_TRANSACTION (Binder 驅動之後要回傳)
                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);

            ...

            // Caller 給 Server 的指令(Caller 就是 t)
            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 複製到 SM 的 UserSpace 中
            // cmd 為 BR_TRANSACTION
            if (put_user(cmd, (uint32_t __user *)ptr)) {
                ... 失敗處理
                return -EFAULT;
            }

            ptr += sizeof(uint32_t);

            // 將指令 `binder_transaction_data`(tr) 複製到 SM 的 UserSpace 
            // (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;
    }
    ```

    ```mermaid
    sequenceDiagram
        participant Caller
        participant Driver as Binder Driver
        participant SM as ServiceManager

        Caller->>Driver: BC_TRANSACTION
        Note over Driver: 建立 binder_transaction<br>translate objects<br>分配 buffer

        Driver-->>Caller: BR_TRANSACTION_COMPLETE
        Note over Caller: 表示 driver 已接受這筆 transaction

        Driver->>SM: BINDER_WORK_TRANSACTION
        Note over SM: enqueue 到 thread->todo

        Driver-->>SM: BR_TRANSACTION
        Note over SM: ServiceManager binder thread 被喚醒<br>onTransact()

        SM->>Driver: BC_REPLY
        Note over SM: 回傳結果

        Driver-->>Caller: BR_REPLY
        Note over Caller: 收到 reply
    ```
    
    > ![](https://i.imgur.com/vXdz3ME.png)

    * **binder_free_transaction 函數**：處理 `binder_transaction` 資料（處理完畢後也會用 free 釋放），如果目標進程在休眠也會在這個時候喚醒

        > `binder_transaction`  結構是 KernalSpace 獨用
        ```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
    * 到這一步後 Binder 驅動的 Read 就處理完了
    :::

## Appendix & FAQ

:::info
:::

###### tags: `Binder`
