Android10.0 Binder通信原理(五)-Binder驅動分析

摘要:本節主要來講解Android10.0 Binder的驅動層分析

閱讀本文大約需要花費35分鐘。

文章首發微信公衆號:IngresGe

專注於Android系統級源碼分析,Android的平臺設計,歡迎關注我,謝謝!

[Android取經之路] 的源碼都基於Android-Q(10.0) 進行分析

[Android取經之路] 系列文章:

《系統啓動篇》

  1. Android系統架構
  2. Android是怎麼啓動的
  3. Android 10.0系統啓動之init進程
  4. Android10.0系統啓動之Zygote進程
  5. Android 10.0 系統啓動之SystemServer進程
  6. Android 10.0 系統服務之ActivityMnagerService
  7. Android10.0系統啓動之Launcher(桌面)啓動流程
  8. Android10.0應用進程創建過程以及Zygote的fork流程
  9. Android 10.0 PackageManagerService(一)工作原理及啓動流程
  10. Android 10.0 PackageManagerService(二)權限掃描
  11. Android 10.0 PackageManagerService(三)APK掃描
  12. Android 10.0 PackageManagerService(四)APK安裝流程

《日誌系統篇》

  1. Android10.0 日誌系統分析(一)-logd、logcat 指令說明、分類和屬性
  2. Android10.0 日誌系統分析(二)-logd、logcat架構分析及日誌系統初始化
  3. Android10.0 日誌系統分析(三)-logd、logcat讀寫日誌源碼分析
  4. Android10.0 日誌系統分析(四)-selinux、kernel日誌在logd中的實現​

《Binder通信原理》

  1. Android10.0 Binder通信原理(一)Binder、HwBinder、VndBinder概要
  2. Android10.0 Binder通信原理(二)-Binder入門篇
  3. Android10.0 Binder通信原理(三)-ServiceManager篇
  4. Android10.0 Binder通信原理(四)-Native-C\C++實例分析
  5. Android10.0 Binder通信原理(五)-Binder驅動分析
  6. Android10.0 Binder通信原理(六)-Binder數據如何完成定向打擊
  7. Android10.0 Binder通信原理(七)-Framework binder示例
  8. Android10.0 Binder通信原理(八)-Framework層分析
  9. Android10.0 Binder通信原理(九)-AIDL Binder示例
  10. Android10.0 Binder通信原理(十)-AIDL原理分析-Proxy-Stub設計模式​​​​​​​​​​​​​​
  11. Android10.0 Binder通信原理(十一)-Binder總結

1.概述

在Android中,用戶空間的應用程序都可以看做是一個獨立的進程,進程間存在隔離,進程不能互相訪問數據,如果需要訪問就需要藉助內核。

每個應用程序都有它自己獨立的內存空間,若不同的應用程序之間涉及到通信,需要通過內核進行中轉,因爲需要用到內核的copy_from_user()和copy_to_user()等函數。因此在Binder的通信中,也引入了Binder內核驅動,用來提供數據中轉。

Binder驅動就是一個多個進程之間的中樞神經,支撐起了Android中進程間通信,它內部的設計,與應用程序進程中的業務,不存在任何耦合關係,只負責實現進程間數據通信。

Binder驅動的核心是維護一個binder_proc類型的鏈表。裏面記錄了包括ServiceManager在內的所有Client信息,當Client去請求得到某個Service時,Binder驅動就去binder_proc中查找相應的Service返回給Client,同時增加當前Service的引用個數。

 

2.Binder架構

在整個Binder通信流程中,Binder驅動肩負了載體的作用,承上啓下,使用戶的數據可以順暢交互。

 

3.Binder中重要的數據結構

 

4.核心內容

用戶態的程序調用Kernel層驅動是需要陷入內核態,進行系統調用(syscall),比如打開Binder驅動方法的調用鏈爲:open-> __open() -> binder_open()。open()爲用戶空間的方法,__open()便是系統調用中相應的處理方法,通過查找,對應調用到內核binder驅動的binder_open()方法,至於其他的從用戶態陷入內核態的流程也基本一致。

幾個重要方法說明:

  • binder_init:初始化字符設備 "/dev/binder","/dev/hwbinder","/dev/vndbinder";
  • binder_open:打開驅動設備;
  • binder_mmap:申請內存空間;
  • binder_ioctl:執行相應的ioctl操作;

 

4.1 初始化 binder_init()

內核初始化時,會調用到device_initcall()進行初始化,從而啓動binder_init。

binder_init()主要負責註冊misc設備,通過調用misc_register()來實現。

在Android8.0之後,現在Binder驅動有三個:/dev/binder; /dev/hwbinder; /dev/vndbinder.

device_initcall(binder_init);
static HLIST_HEAD(binder_devices);

static int __init binder_init(void)
{
       int ret;
       char *device_name, *device_names, *device_tmp;
       struct binder_device *device;
       struct hlist_node *tmp;

       ret = binder_alloc_shrinker_init();
       if (ret)
               return ret;

       atomic_set(&binder_transaction_log.cur, ~0U);
       atomic_set(&binder_transaction_log_failed.cur, ~0U);

    //在debugfs文件系統中創建一個目錄,返回值是指向dentry的指針
    //在手機對應的目錄:/sys/kernel/debug/binder,裏面創建了幾個文件,用來記錄binder操作過程中的信息和日誌:
    //failed_transaction_log、state、stats、transaction_log、transactions
       binder_debugfs_dir_entry_root = debugfs_create_dir("binder", NULL);
       if (binder_debugfs_dir_entry_root)
        //創建目錄:/sys/kernel/debug/binder/proc
               binder_debugfs_dir_entry_proc = debugfs_create_dir("proc",
                                                binder_debugfs_dir_entry_root);
    ...

       device_names = kzalloc(strlen(binder_devices_param) + 1, GFP_KERNEL);
       if (!device_names) {
               ret = -ENOMEM;
               goto err_alloc_device_names_failed;
       }
       strcpy(device_names, binder_devices_param);

       device_tmp = device_names;
   //Android8.0 中引入了hwbinder,vndbinder,所以現在有三個binder,分別需要創建三個binder device:
   // /dev/binder、/dev/hwbinder、/dev/vndbinder
   //循環註冊binder 的三個設備:/dev/binder、/dev/hwbinder、/dev/vndbinder
       while ((device_name = strsep(&device_tmp, ","))) {
               ret = init_binder_device(device_name);
               if (ret)
                       goto err_init_binder_device_failed;
       }

       return ret;

err_init_binder_device_failed:
       hlist_for_each_entry_safe(device, tmp, &binder_devices, hlist) {
               misc_deregister(&device->miscdev);
               hlist_del(&device->hlist);
               kfree(device);
       }

       kfree(device_names);

err_alloc_device_names_failed:
       debugfs_remove_recursive(binder_debugfs_dir_entry_root);

       return ret;
}

static int __init init_binder_device(const char *name)
{
       int ret;
       struct binder_device *binder_device;

    //申請內存空間,
       binder_device = kzalloc(sizeof(*binder_device), GFP_KERNEL);
       if (!binder_device)
               return -ENOMEM;

       binder_device->miscdev.fops = &binder_fops;
       binder_device->miscdev.minor = MISC_DYNAMIC_MINOR;
       binder_device->miscdev.name = name;

       binder_device->context.binder_context_mgr_uid = INVALID_UID;
       binder_device->context.name = name;
       mutex_init(&binder_device->context.context_mgr_node_lock);

       ret = misc_register(&binder_device->miscdev);
       if (ret < 0) {
               kfree(binder_device);
               return ret;
       }

       hlist_add_head(&binder_device->hlist, &binder_devices);

       return ret;
}

Android8.0及之後的Binder域如下圖所示:

 

4.1.1 註冊設備的幾個重要結構體

binder的device包含了一個哈希鏈表,一個misc設備結構,一個binder context,結構如下:

struct binder_device {
       struct hlist_node hlist;
       struct miscdevice miscdev;  //misc 設備
       struct binder_context context;  //context
};

misc 設備結構中,我們主要關注name,fops,結構如下:

struct miscdevice  {
       int minor;                          //次設備號 動態分配 MISC_DYNAMIC_MINOR
       const char *name;                   //設備名          
                                           //"/dev/binder、/dev/hwbinder、/dev/vndbinder"
       const struct file_operations *fops; //設備的文件操作結構,這是file_operations結構
       struct list_head list;
       struct device *parent;
       struct device *this_device;
       const struct attribute_group **groups;
       const char *nodename;
       umode_t mode;
};

在獲取了一些設備編號後,我們還沒有將任何驅動程序操作連接到這些編號,file_operations結構就是用來建立這種連接的。

binder_fops主要包含了Binder的一些操作方法配置,例如open、mmap、ioctl,結構如下:

static const struct file_operations binder_fops = {
       .owner = THIS_MODULE,
       .poll = binder_poll,
       .unlocked_ioctl = binder_ioctl,
       .compat_ioctl = binder_ioctl,
       .mmap = binder_mmap,
       .open = binder_open,
       .flush = binder_flush,
       .release = binder_release,
};

 

4.2 binder_open

當我們在Native C\C++層通過系統調用open()來打開binder驅動時,驅動層會根據設備文件的主設備號,找到相應的設備驅動程序,然後讀取這個數據結構相應的函數指針,接着把控制權交給該函數。

因此,open()到驅動層,就會調用binder_open()

binder_open()主要負責打開驅動設備,創建binder_proc對象,並把當前進程等信息保存到binder_proc對象,該對象管理IPC所需的各種信息並擁有其他結構體的根結構體;再把binder_proc對象保存到文件指針filp,以及把binder_proc加入到全局鏈表binder_procs。

 

binder_open()職責如下

  1. 首先創建了binder_proc結構體實例proc
  2. 接着開始初始化一系列成員:tsk, todo, default_priority, pid, delivered_death等。
  3. 更新了統計數據:binder_proc的創建個數加1
  4. 緊接着將初始化好的proc,存放到文件指針filp->private_data中,以便於在之後的mmap、ioctl中獲取。
  5. 將binder_proc鏈入binder_procs哈希鏈表中;
  6. 最後查看是否創建的了/sys/kernel/debug/binde/proc/目錄,有的話再創建一個/sys/kernel/debug/binde/proc/pid文件,用來記錄binder_proc的狀態
static HLIST_HEAD(binder_procs);
static int binder_open(struct inode *nodp, struct file *filp)
{
       struct binder_proc *proc;           // binder進程
       struct binder_device *binder_dev;   // binder device

       binder_debug(BINDER_DEBUG_OPEN_CLOSE, "%s: %d:%d\n", __func__,
                    current->group_leader->pid, current->pid);

       proc = kzalloc(sizeof(*proc), GFP_KERNEL);  // 爲binder_proc結構體在分配kernel內存空間
       if (proc == NULL)
               return -ENOMEM;
       spin_lock_init(&proc->inner_lock);
       spin_lock_init(&proc->outer_lock);
       atomic_set(&proc->tmp_ref, 0);
       get_task_struct(current->group_leader);//增加線程引用計數
       proc->tsk = current->group_leader;     //將當前線程的task保存到binder進程的tsk
       mutex_init(&proc->files_lock);
       INIT_LIST_HEAD(&proc->todo);                 //初始化todo隊列,用於存放待處理的請求(server端)
    //配置binder優先級
       if (binder_supported_policy(current->policy)) {
               proc->default_priority.sched_policy = current->policy;
               proc->default_priority.prio = current->normal_prio;
       } else {
               proc->default_priority.sched_policy = SCHED_NORMAL;
               proc->default_priority.prio = NICE_TO_PRIO(0);
       }

       binder_dev = container_of(filp->private_data, struct binder_device,
                                 miscdev);
       proc->context = &binder_dev->context;   //拿到binder device的context,傳給binder_proc 
       binder_alloc_init(&proc->alloc);

       binder_stats_created(BINDER_STAT_PROC); //類型爲BINDER_STAT_PROC對象的創建個數加1
       proc->pid = current->group_leader->pid; //記錄當前進程的pid
       INIT_LIST_HEAD(&proc->delivered_death);
       INIT_LIST_HEAD(&proc->waiting_threads);
       filp->private_data = proc;  //將binder_proc存放在filp的private_data域,以便於在之後的mmap、ioctl中獲取

       mutex_lock(&binder_procs_lock);
       hlist_add_head(&proc->proc_node, &binder_procs);    //將proc_node節點添加到binder_procs爲表頭的隊列
       mutex_unlock(&binder_procs_lock);

    // 如果/sys/kernel/debug/binder/proc 目錄存在,在該目錄中創建相應pid對應的文件,名稱爲pid,用來記錄binder_proc的狀態
       if (binder_debugfs_dir_entry_proc) {
               char strbuf[11];

               snprintf(strbuf, sizeof(strbuf), "%u", proc->pid);
               proc->debugfs_entry = debugfs_create_file(strbuf, 0444,
                       binder_debugfs_dir_entry_proc,
                       (void *)(unsigned long)proc->pid,
                       &binder_proc_fops);
       }

       return 0;
}

4.2.1 重要結構 binder_proc

binder_proc 與應用層的binder實體一一對應,每個進程調用open()打開binder驅動都會創建該結構體,用於管理IPC所需的各種信息。

其中有4個紅黑樹threads、nodes、refs_by_desc、refs_by_node, 在一個進程中,有多少“被其他進程進行跨進程調用的”binder實體,就會在該進程對應的nodes樹中生成多少個紅黑樹節點。另一方面,一個進程要訪問多少其他進程的binder實體,則必須在其refs_by_desc樹中擁有對應的引用節點。

struct binder_proc {
       struct hlist_node proc_node;    //進程節點
       struct rb_root threads;         //記錄執行傳輸動作的線程信息, binder_thread紅黑樹的根節點
       struct rb_root nodes;           //用於記錄binder實體  ,binder_node紅黑樹的根節點,它是Server在Binder驅動中的體現
       struct rb_root refs_by_desc;    //記錄binder引用, 便於快速查找,binder_ref紅黑樹的根節點(以handle爲key),它是Client在Binder驅動中的體現
       struct rb_root refs_by_node;    //記錄binder引用, 便於快速查找,binder_ref紅黑樹的根節點(以ptr爲key),它是Client在Binder驅動中的體現
       struct list_head waiting_threads;
       int pid;    //相應進程id
       struct task_struct *tsk;    //相應進程的task結構體
       struct files_struct *files; //相應進程的文件結構體
       struct mutex files_lock;
       struct hlist_node deferred_work_node;
       int deferred_work;
       bool is_dead;

       struct list_head todo;      //進程將要做的事
       struct binder_stats stats;  //binder統計信息
       struct list_head delivered_death;   //已分發的死亡通知
       int max_threads;        //最大線程數
       int requested_threads;  //請求的線程數
       int requested_threads_started;  //已啓動的請求線程數
       atomic_t tmp_ref;
       struct binder_priority default_priority;    //默認優先級
       struct dentry *debugfs_entry;
       struct binder_alloc alloc;
       struct binder_context *context;
       spinlock_t inner_lock;
       spinlock_t outer_lock;
};

binder_procs哈希鏈表, 存儲了所有open() binder驅動的進程對象,如下圖所示:

 

4.3 binder_mmap

主要功能:首先在內核虛擬地址空間,申請一塊與用戶虛擬內存相同大小的內存;然後再申請page物理內存,

再將同一塊物理內存分別映射到內核虛擬地址空間和用戶虛擬內存空間,從而實現了用戶空間的Buffer和內核空間的Buffer同步操作的功能。

 

參數:

filp: 文件描述符

vma: 用戶虛擬內存空間

 

流程:

  1. filp->private_data保存了我們open設備時創建的binder_proc信息;
  2. 爲用戶進程分配一塊內核空間作爲緩衝區;
  3. 把分配的緩衝區指針存放到binder_proc的buffer字段;
  4. 分配pages空間;
  5. 在內核分配一塊同樣頁數的內核空間,並把它的物理內存和前面爲用戶進程分配的內存地址關聯;
  6. 將剛纔分配的內存塊加入用戶進程內存鏈表;
static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
{
       int ret;
       struct binder_proc *proc = filp->private_data; //private_data保存了我們open設備時創建的binder_proc信息
       const char *failure_string;

       if (proc->tsk != current->group_leader)
               return -EINVAL;

       //vma->vm_end, vma->vm_start 指向要 映射的用戶空間地址, map size 不允許 大於 4M
       if ((vma->vm_end - vma->vm_start) > SZ_4M)
               vma->vm_end = vma->vm_start + SZ_4M;
       ...
       //mmap 的 buffer 禁止用戶進行寫操作。mmap 只是爲了分配內核空間,傳遞數據通過 ioctl()
       if (vma->vm_flags & FORBIDDEN_MMAP_FLAGS) {
               ret = -EPERM;
               failure_string = "bad vm_flags";
               goto err_bad_arg;
       }

       // 將 VM_DONTCOP 置起,禁止 拷貝,禁止 寫操作
       vma->vm_flags |= VM_DONTCOPY | VM_MIXEDMAP;
       vma->vm_flags &= ~VM_MAYWRITE;

       vma->vm_ops = &binder_vm_ops;
       vma->vm_private_data = proc;

       // 再次完善 binder buffer allocator
       ret = binder_alloc_mmap_handler(&proc->alloc, vma);
       if (ret)
               return ret;
       mutex_lock(&proc->files_lock);  //同步鎖
       proc->files = get_files_struct(current);
       mutex_unlock(&proc->files_lock);    //釋放鎖
       return 0;

err_bad_arg:
       pr_err("%s: %d %lx-%lx %s failed %d\n", __func__,
              proc->pid, vma->vm_start, vma->vm_end, failure_string, ret);
       return ret;
}


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傳輸數據時,都會先從Binder內存緩存區中分配一個binder_buffer來存儲傳輸數據

       mutex_lock(&binder_alloc_mmap_lock);    //同步鎖
       if (alloc->buffer) {        // 不需要重複mmap
               ret = -EBUSY;
               failure_string = "already mapped";
               goto err_already_mapped;
       }

       alloc->buffer = (void __user *)vma->vm_start; //指向用戶進程內核虛擬空間的 start地址
       mutex_unlock(&binder_alloc_mmap_lock);               //釋放鎖

   //分配物理頁的指針數組,數組大小爲vma的等效page個數
       alloc->pages = kzalloc(sizeof(alloc->pages[0]) *
                                  ((vma->vm_end - vma->vm_start) / PAGE_SIZE),
                              GFP_KERNEL);
       if (alloc->pages == NULL) {
               ret = -ENOMEM;
               failure_string = "alloc page array";
               goto err_alloc_pages_failed;
       }
       alloc->buffer_size = vma->vm_end - vma->vm_start;

       buffer = kzalloc(sizeof(*buffer), GFP_KERNEL);  //申請一個binder_buffer的內存
       if (!buffer) {
               ret = -ENOMEM;
               failure_string = "alloc buffer struct";
               goto err_alloc_buf_struct_failed;
       }

       buffer->user_data = alloc->buffer;                 //指向用戶進程內核虛擬空間的 start地址,即爲當前進程mmap的內核空間地址
       list_add(&buffer->entry, &alloc->buffers);  //將binder_buffer地址 加入到所屬進程的buffers隊列
       buffer->free = 1;
       binder_insert_free_buffer(alloc, buffer);   //將 當前 buffer 加入到 紅黑樹 alloc->free_buffers 中,表示當前 buffer 是空閒buffer
       alloc->free_async_space = alloc->buffer_size / 2; // 將 異步事務 的空間大小設置爲 整個空間的一半
       barrier();
       alloc->vma = vma;
       alloc->vma_vm_mm = vma->vm_mm;
       /* Same as mmgrab() in later kernel versions */
       atomic_inc(&alloc->vma_vm_mm->mm_count);

       return 0;

err_alloc_buf_struct_failed:
       kfree(alloc->pages);
       alloc->pages = NULL;
err_alloc_pages_failed:
       mutex_lock(&binder_alloc_mmap_lock);
       alloc->buffer = NULL;
err_already_mapped:
       mutex_unlock(&binder_alloc_mmap_lock);
       pr_err("%s: %d %lx-%lx %s failed %d\n", __func__,
              alloc->pid, vma->vm_start, vma->vm_end, failure_string, ret);
       return ret;
}

 

4.3.1 重要結構 binder_buffer

每一次Binder傳輸數據時,都會先從Binder內存緩存區中分配一個binder_buffer來存儲傳輸數據。

struct binder_buffer {
       struct list_head entry; //buffer實體的地址
       struct rb_node rb_node; //buffer實體的地址
       unsigned free:1;            //標記是否是空閒buffer,佔位1bit
       unsigned allow_user_free:1;  //是否允許用戶釋放,佔位1bit
       unsigned async_transaction:1;//佔位1bit
       unsigned debug_id:29;          //佔位29bit

       struct binder_transaction *transaction; //該緩存區的需要處理的事務

       struct binder_node *target_node; //該緩存區所需處理的Binder實體
       size_t data_size;          //數據大小
       size_t offsets_size;      //數據偏移量
       size_t extra_buffers_size;
       void __user *user_data;   //用戶數據
};

 

4.3.3 內存分配情況

ServiceManager啓動後,會通過系統調用mmap向內核空間申請128K的內存,用戶進程會通過mmap向內核申請(1M-8K)的內存空間。

這裏用戶空間mmap (1M-8K)的空間,爲什麼要減去8K,而不是直接用1M?

 Android的git commit記錄:

Modify the binder to request 1M - 2 pages instead of 1M. The backing store in the kernel requires a guard page, so 1M allocations fragment memory very badly. Subtracting a couple of pages so that they fit in a power of two allows the kernel to make more efficient use of its virtual address space.

大致的意思是:kernel的“backing store”需要一個保護頁,這使得1M用來分配碎片內存時變得很差,所以這裏減去兩頁來提高效率,因爲減去一頁就變成了奇數。

 

系統定義:BINDER_VM_SIZE ((1 * 1024 * 1024) - sysconf(_SC_PAGE_SIZE) * 2)   = (1M- sysconf(_SC_PAGE_SIZE) * 2)        

這裏的8K,其實就是兩個PAGE的SIZE, 物理內存的劃分是按PAGE(頁)來劃分的,一般情況下,一個Page的大小爲4K。

內核會增加一個guard page,再加上內核本身的guard page,正好是兩個page的大小,減去後,就是用戶空間可用的大小。        

在內存分配這塊,還要分爲32位和64位,32位的系統很好區分,虛擬內存爲4G,用戶空間從低地址開始佔用3G,內核空間佔用剩餘的1G。

ARM32內存佔用分配:

但隨着現在的硬件發展越來越迅速,應用程序的運算也越來越複雜,佔用空間越來越大,原有的4G虛擬內存已經不能滿足用戶的需求,因此,現在的Android基本都是用64位的內存機制。

理論上講,64位的地址總線可以支持高達16EB(2^64)的內存。AMD64架構支持52位(4PB)的地址總線和48位(256TB)的虛擬地址空間。在linux arm64中,如果頁的大小爲4KB,使用3級頁錶轉換或者4級頁錶轉換,用戶空間和內核空間都支持有39bit(512GB)或者48bit(256TB)大小的虛擬地址空間。

2^64 次方太大了,Linux 內核只採用了 64 bits 的一部分(開啓 CONFIG_ARM64_64K_PAGES 時使用 42 bits,頁大小是 4K 時使用 39 bits),該文假設使用的頁大小是 4K(VA_BITS = 39)

ARM64 有足夠的虛擬地址,用戶空間和內核空間可以有各自的 2^39 = 512GB 的虛擬地址。

ARM64內存佔用分配:

用戶地址空間(服務端-數據接收端)和內核地址空間都映射到同一塊物理地址空間。

Client(數據發送端)先從自己的用戶進程空間把IPC數據通過copy_from_user()拷貝到內核空間。而Server端(數據接收端)與內核共享數據(mmap到同一塊物理內存),不再需要拷貝數據,而是通過內存地址空間的偏移量,即可獲悉內存地址,整個過程只發生一次內存拷貝。

 圖片來源於Gityuan

 

4.4 binder_ioctl

binder_ioctl()函數負責在兩個進程間收發IPC數據和IPC reply數據,Native C\C++ 層傳入不同的cmd和數據,根據cmd的值,進行相應的處理並返回

參數:

filp:文件描述符

cmd:ioctl命令

arg:數據類型

 

ioctl命令說明:


static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
       int ret;
   //filp->private_data 在open()binder驅動時,保存了一個創建的binder_proc,即是此時調用進程的binder_proc.
       struct binder_proc *proc = filp->private_data;
       //binder線程
       struct binder_thread *thread;
       unsigned int size = _IOC_SIZE(cmd);
       void __user *ubuf = (void __user *)arg;

       binder_selftest_alloc(&proc->alloc);

       trace_binder_ioctl(cmd, arg);
       //進入休眠狀態,直到中斷喚醒
       ret = wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2);
       if (ret)
               goto err_unlocked;

       //獲取binder線程信息,如果是第一次調用ioctl(),則會爲該進程創建一個線程
       thread = binder_get_thread(proc);
       if (thread == NULL) {
               ret = -ENOMEM;
               goto err;
       }

       switch (cmd) {
       //binder的讀寫操作,使用評論較高
       case BINDER_WRITE_READ: 
               ret = binder_ioctl_write_read(filp, cmd, arg, thread);
               if (ret)
                       goto err;
               break;
   //設置Binder線程最大個數
       case BINDER_SET_MAX_THREADS: { 
               int max_threads;

               if (copy_from_user(&max_threads, ubuf,
                                  sizeof(max_threads))) {
                       ret = -EINVAL;
                       goto err;
               }
               binder_inner_proc_lock(proc);
               proc->max_threads = max_threads;
               binder_inner_proc_unlock(proc);
               break;
       }
   //設置Service Manager節點,帶flag參數, servicemanager進程成爲上下文管理者
       case BINDER_SET_CONTEXT_MGR_EXT: { 
               struct flat_binder_object fbo;

               if (copy_from_user(&fbo, ubuf, sizeof(fbo))) {
                       ret = -EINVAL;
                       goto err;
               }
               ret = binder_ioctl_set_ctx_mgr(filp, &fbo);
               if (ret)
                       goto err;
               break;
       }
   //設置Service Manager節點,不帶flag參數, servicemanager進程成爲上下文管理者
       case BINDER_SET_CONTEXT_MGR:
               ret = binder_ioctl_set_ctx_mgr(filp, NULL);
               if (ret)
                       goto err;
               break;
       ...
   //獲取Binder版本信息
       case BINDER_VERSION: {
               struct binder_version __user *ver = ubuf;

               if (size != sizeof(struct binder_version)) {
                       ret = -EINVAL;
                       goto err;
               }
               if (put_user(BINDER_CURRENT_PROTOCOL_VERSION,
                            &ver->protocol_version)) {
                       ret = -EINVAL;
                       goto err;
               }
               break;
       }
       ...
       default:
               ret = -EINVAL;
               goto err;
       }
       ret = 0;
err:
       if (thread)
               thread->looper_need_return = false;
       wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2);
       if (ret && ret != -ERESTARTSYS)
               pr_info("%d:%d ioctl %x %lx returned %d\n", proc->pid, current->pid, cmd, arg, ret);
err_unlocked:
       trace_binder_ioctl_done(ret);
       return ret;
}

 

4.4.1 獲取binder線程

方法:binder_get_thread()

作用:從當前進程中獲取線程信息,如果當前進程中沒有線程信息,那麼創建一個線程,把proc指向當前進程,並進行線程初始化

static struct binder_thread *binder_get_thread(struct binder_proc *proc)
{
       struct binder_thread *thread;
       struct binder_thread *new_thread;

       binder_inner_proc_lock(proc);
       //從當前進程中獲取線程
       thread = binder_get_thread_ilocked(proc, NULL);
       binder_inner_proc_unlock(proc);
       if (!thread) {
              //如果當前進程中沒有線程,那麼創建一個
               new_thread = kzalloc(sizeof(*thread), GFP_KERNEL);
               if (new_thread == NULL)
                       return NULL;
               binder_inner_proc_lock(proc);
               thread = binder_get_thread_ilocked(proc, new_thread);
               binder_inner_proc_unlock(proc);
               if (thread != new_thread)
                       kfree(new_thread);
       }
       return thread;
}

binder_get_thread_ilocked()流程:

  1. 先遍歷threads節點的紅黑樹鏈表;
  2. 如果沒有查找到,則分配一個struct binder_thread長度的空間;
  3. 初始化等待隊列頭節點和thread的todo鏈表;
  4. 將該線程插入到進程的threads節點;
static struct binder_thread *binder_get_thread_ilocked(
               struct binder_proc *proc, struct binder_thread *new_thread)
{
       struct binder_thread *thread = NULL;
       struct rb_node *parent = NULL;
       struct rb_node **p = &proc->threads.rb_node;

       //根據當前進程的pid,從binder_proc中查找相應的binder_thread
       while (*p) {
               parent = *p;
               thread = rb_entry(parent, struct binder_thread, rb_node);

               if (current->pid < thread->pid)
                       p = &(*p)->rb_left;
               else if (current->pid > thread->pid)
                       p = &(*p)->rb_right;
               else
                       return thread;
       }
       if (!new_thread)
               return NULL;
       //若當前進程中沒有線程信息,那麼創建一個新的線程,並進行相應的初始化操作
       thread = new_thread;
       binder_stats_created(BINDER_STAT_THREAD);
       thread->proc = proc;                //線程的proc指向當前進程
       thread->pid = current->pid; //線程pid爲當前進程的pid
       get_task_struct(current);
       thread->task = current;
       atomic_set(&thread->tmp_ref, 0);
       init_waitqueue_head(&thread->wait);
       INIT_LIST_HEAD(&thread->todo); //初始化等待隊列頭節點和thread的todo鏈表
       //把線程節點加入到proc的 threads紅黑樹中,平衡紅黑樹
       rb_link_node(&thread->rb_node, parent, p);
       rb_insert_color(&thread->rb_node, &proc->threads);
       thread->looper_need_return = true;
       thread->return_error.work.type = BINDER_WORK_RETURN_ERROR;
       thread->return_error.cmd = BR_OK;
       thread->reply_error.work.type = BINDER_WORK_RETURN_ERROR;
       thread->reply_error.cmd = BR_OK;
       INIT_LIST_HEAD(&new_thread->waiting_thread_node);
       return thread;
}

 

4.4.1.1 重要結構 binder_thread

binder_thread結構體代表當前binder操作所在的線程

struct binder_thread {
       struct binder_proc *proc;   //線程所屬的進程
       struct rb_node rb_node;         //紅黑樹節點
       struct list_head waiting_thread_node;
       int pid;                          //線程pid
       int looper;               //looper的狀態
       bool looper_need_return;  
       struct binder_transaction *transaction_stack;   //線程正在處理的事務
       struct list_head todo;                   //將要處理的鏈表
       bool process_todo;
       struct binder_error return_error;   //write失敗後,返回的錯誤碼
       struct binder_error reply_error;
       wait_queue_head_t wait;                 //等待隊列的隊頭
       struct binder_stats stats;          //binder線程的統計信息
       atomic_t tmp_ref;
       bool is_dead;
       struct task_struct *task;
};

4.4.2 ServiceManager守護進程設置

方法:binder_ioctl_set_ctx_mgr()

ServiceManager、HwServiceManager、VNDServiceManager,在Native C層通過ioctl()發送BINDER_SET_CONTEXT_MGR_EXT 命令,讓自身成爲上下文管理者,即各自的守護進程。

binder_ioctl_set_ctx_mgr()處理如下:

  1. open()binder驅動時得到的filp->private_data,存入binder_proc,代表當前進程的信息

  2. 檢查當前進程是否具註冊Context Manager的SELinux安全權限

  3. 進行uid檢查,線程只能註冊自己,且只能有一個線程設置爲Context Manager

  4. 設置當前線程euid作爲ServiceManager的uid

  5. 創建一個binder實體binder_node,並加入到當前進程的nodes紅黑樹中,我們這裏可以是ServiceManager

  6. 把新創建的binder_node,賦值給當前進程的binder_context_mgr_node,這樣該進程就成爲了上下文的管理者,這是一個約定的過程

static int binder_ioctl_set_ctx_mgr(struct file *filp,
                                   struct flat_binder_object *fbo)
{
       int ret = 0;
       //filp->private_data 在open()binder驅動時,保存了一個創建的binder_proc,即是此時調用進程的binder_proc.
       struct binder_proc *proc = filp->private_data;
       //獲得當前進程的context
       struct binder_context *context = proc->context;
       struct binder_node *new_node;
       kuid_t curr_euid = current_euid();

       mutex_lock(&context->context_mgr_node_lock);

   //保證只創建一次mgr_node對象
       if (context->binder_context_mgr_node) {
               pr_err("BINDER_SET_CONTEXT_MGR already set\n");
               ret = -EBUSY;
               goto out;
       }

   //檢查當前進程是否具註冊Context Manager的SEAndroid安全權限
       ret = security_binder_set_context_mgr(proc->tsk);
       if (ret < 0)
               goto out;
   //檢查已的uid是否有效
       if (uid_valid(context->binder_context_mgr_uid)) {
               //uid有效但是與當前運行線程的效用戶ID不相等,則出錯。
               //即線程只能註冊自己,且只能有一個線程設置爲Context Manager
               if (!uid_eq(context->binder_context_mgr_uid, curr_euid)) {
                       pr_err("BINDER_SET_CONTEXT_MGR bad uid %d != %d\n",
                              from_kuid(&init_user_ns, curr_euid),
                              from_kuid(&init_user_ns,
                                        context->binder_context_mgr_uid));
                       ret = -EPERM;
                       goto out;
               }
       } else {
               //設置當前線程euid作爲ServiceManager的uid
               context->binder_context_mgr_uid = curr_euid;
       }

       //創建binder實體,並加入到當前進程的nodes紅黑樹中,我們這裏可以是ServiceManager
       new_node = binder_new_node(proc, fbo);
       if (!new_node) {
               ret = -ENOMEM;
               goto out;
       }
       binder_node_lock(new_node);
       //更新new_node的相關強弱引用計數
       new_node->local_weak_refs++;
       new_node->local_strong_refs++;
       new_node->has_strong_ref = 1;
       new_node->has_weak_ref = 1;
   //new_node 賦值給進程的上下文管理節點,作爲上下文管理者
       context->binder_context_mgr_node = new_node;
       binder_node_unlock(new_node);
       binder_put_node(new_node);
out:
       mutex_unlock(&context->context_mgr_node_lock);
       return ret;
}

4.4.2.1 重要結構 binder_node

binder_node代表binder實體

struct binder_node {
       int debug_id;   //節點創建時分配,具有全局唯一性,用於調試使用
       spinlock_t lock;
       struct binder_work work;
       union {
               struct rb_node rb_node;          //binder節點正常使用,union
               struct hlist_node dead_node;//binder節點已銷燬,union
       };
       struct binder_proc *proc;   //binder所在的進程
       struct hlist_head refs;     //所有指向該節點的binder引用隊列
       int internal_strong_refs;
       int local_weak_refs;
       int local_strong_refs;
       int tmp_refs;
       binder_uintptr_t ptr;     //指向用戶空間binder_node的指針,對應flat_binder_object.binder
       binder_uintptr_t cookie;   //數據,對應flat_binder_object.cookie
       struct {
               /*
                * bitfield elements protected by
                * proc inner_lock
                */
               u8 has_strong_ref:1;
               u8 pending_strong_ref:1;
               u8 has_weak_ref:1;
               u8 pending_weak_ref:1;
       };
       struct {
               /*
                * invariant after initialization
                */
               u8 sched_policy:2;
               u8 inherit_rt:1;
               u8 accept_fds:1;
               u8 txn_security_ctx:1;
               u8 min_priority;
       };
       bool has_async_transaction;
       struct list_head async_todo;    //異步todo隊列
};

 

4.4.3 Binder讀寫操作

方法:binder_ioctl_write_read()

作用:根據從用戶空間傳來的binder_write_read數據進行判斷,是否進行讀寫操作, 這也是binder數據交互的核心入口。

流程如下:

  1. 如果write_size大於0,表示用戶進程有數據發送到驅動,則調用binder_thread_write()發送數據,binder_thread_write()中有錯誤發生,則read_consumed設爲0,表示kernel沒有數據返回給進程;
  2. 如果read_size大於0, 表示進程用戶態地址空間希望有數據返回給它,則調用binder_thread_read()進行處理,讀取完後,如果proc->todo鏈表不爲空,則喚醒在proc->wait等待隊列上的進程,如果binder_thread_read返回小於0,可能處理一半就中斷了,需要將bwr拷貝回進程的用戶態地址;
  3. 處理成功的情況,也需要將bwr拷貝回進程的用戶態地址空間

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;

       if (size != sizeof(struct binder_write_read)) {
               ret = -EINVAL;
               goto out;
       }
       if (copy_from_user(&bwr, ubuf, sizeof(bwr))) {
               ret = -EFAULT;
               goto out;
       }
       ...
       if (bwr.write_size > 0) {
               //write_size大於0,表示用戶進程有數據發送到驅動,則調用binder_thread_write發送數據
               ret = binder_thread_write(proc, thread,
                                         bwr.write_buffer,
                                         bwr.write_size,
                                         &bwr.write_consumed);
               trace_binder_write_done(ret);
               if (ret < 0) {
                       //binder_thread_write中有錯誤發生,則read_consumed設爲0,表示kernel沒有數據返回給進程
                       bwr.read_consumed = 0;
                       //將bwr返回給用戶態調用者,bwr在binder_thread_write中會被修改
                       if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
                               ret = -EFAULT;
                       goto out;
               }
       }
       //read_size大於0, 表示進程用戶態地址空間希望有數據返回給它,則調用binder_thread_read進行處理
       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);
               trace_binder_read_done(ret);
               binder_inner_proc_lock(proc);
               //讀取完後,如果proc->todo鏈表不爲空,則喚醒在proc->wait等待隊列上的進程
               if (!binder_worklist_empty_ilocked(&proc->todo))
                       binder_wakeup_proc_ilocked(proc);
               binder_inner_proc_unlock(proc);
               if (ret < 0) {
                       //如果binder_thread_read返回小於0,可能處理一半就中斷了,需要將bwr拷貝回進程的用戶態地址
                       if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
                               ret = -EFAULT;
                       goto out;
               }
       }
       ...
   //處理成功的情況,也需要將bwr拷貝回進程的用戶態地址空間
       if (copy_to_user(ubuf, &bwr, sizeof(bwr))) {
               ret = -EFAULT;
               goto out;
       }
out:
       return ret;
}

 

4.4.3.1 重要結構 binder_write_read

binder的讀寫結構, 記錄了binder中讀和寫的數據信息

struct binder_write_read {
       binder_size_t       write_size;     //要寫入的字節數,write_buffer的總字節數
       binder_size_t       write_consumed; //驅動程序佔用的字節數,write_buffer已消費的字節數
       binder_uintptr_t    write_buffer;   //寫緩衝數據的指針
       binder_size_t       read_size;      //要讀的字節數,read_buffer的總字節數
       binder_size_t       read_consumed;  //驅動程序佔用的字節數,read_buffer已消費的字節數
       binder_uintptr_t    read_buffer;    //讀緩存數據的指針
};

binder_thread_write() 和 binder_thread_read()的邏輯太冗長,加起來有1千多行,放到下一節的《Binder數據定向打擊》 進行單獨分析

 

6.紅黑樹分析

6.1 爲什麼binder驅動中要用到紅黑樹

其實我也不知道爲什麼,採用紅黑樹,說明我們要存儲數據,要複合插入、修改、刪除、查找等操作。既然不知道爲什麼,那就找幾個複合類型的數據結構做個比較:
1)數組

優點:內存大小固定,操作流程簡單,查找數據方便;

缺點:內存要麼很大,會浪費內存,要麼很小,數據存儲空間不夠,刪除或插入數據比較麻煩;

 

2)鏈表

優點:在內存中可以存在任何地方,不要求連續,不指定大小,擴展方便。鏈表大小不用定義,數據隨意增刪,增加數據和刪除數據很容易;

缺點:查找數據時效率低,因爲不具隨機訪問性,所以訪問某個位置的數據都要從第一個數據開始訪問,然後根據第一個數據保存的下一個數據的地址找到第二個數據,以此類推。要找到第三個人,必須從第一個人開始問起;

 

3)二叉樹

 特點:左子樹的節點值比父親節點小,而右子樹的節點值比父親節點大

 優點:快速查找,不需要爲二叉樹預先分配固定的空間,所的元素在樹中是排序好的

 缺點:極端情況下,時間複雜度會由O(logn)變成O(n),即變成了一種鏈式結構

 

4)平衡二叉樹

特點:具有二叉樹的全部特性,每個節點的左子樹和右子樹的高度差至多等於1 

優點:具有二叉樹的所有優點,並解決了二叉樹的 鏈式極端情況,時間複雜度保持在O(logn)

缺點:每次進行插入/刪除節點的時候,幾乎都會破壞平衡樹的規則(每個節點的左子樹和右子樹的高度差至多等於1 ),每次都需要調整,使得性能大打折扣

 

5)紅黑樹

特點:具有二叉樹的特點;根節點是黑色的;葉子節點不存數據;任何相鄰的節點都不能同時爲紅色;每個節點,從該節點到達其可達的葉子節點是所路徑,都包含相同數目的黑色節點

優點:具有二叉樹所有特點,與平衡樹不同的是,紅黑樹在插入、刪除等操作,不會像平衡樹那樣,頻繁着破壞紅黑樹的規則,所以不需要頻繁着調整,減少性能消耗;

缺點:代碼複雜,查找效率比平衡二叉樹低

 

通過上面幾個存儲的數據結構,當我們需要頻繁對數據進行插入、修改、刪除、查找時,我們的選擇如下

平衡二叉樹\紅黑樹 > 二叉樹 > 鏈表 > 數組

其中紅黑樹雖然查找效率比平衡二叉樹低,但是減少了性能消耗,這在內核中尤爲重要,因此binder驅動中使用了紅黑樹。

當然上面的流程也只是我的推測,也有可能谷歌工程師開發時就喜歡用紅黑樹呢,當然這是題外話,不影響我們繼續擼代碼。

 

6.2 binder_proc 中的4棵紅黑樹

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;
    ...
}

threads 執行傳輸動作的線程信息, nodes 記錄binder實體,refs_by_desc 和refs_by_node 記錄binder代理, 便於快速查找

以nodes樹爲例,每個binder_node中都有一個rb_node節點,rb_node 又分爲左子樹和右子樹,如圖所示:

truct rb_root {
    struct rb_node *rb_node;
};

struct rb_node {
    unsigned long  __rb_parent_color;
    struct rb_node *rb_right;
    struct rb_node *rb_left;
} __attribute__((aligned(sizeof(long))));

 

6.3 Binder 中紅黑樹的轉換

在Binder驅動中,會爲每個Client創建對應的Binder引用,即會爲每個Client創建binder_ref對象;會爲每一個Server都創建一個Binder實體,即會爲每個Server都創建一個binder_node對象

 "Binder實體"和"Binder引用"可以很好的將Server和Client關聯起來:因爲Binder實體和Binder引用分別是Server和Client在Binder驅動中的體現。Client獲取到Server對象後,"Binder引用所引用的Biner實體(即binder_ref.node)" 會指向 "Server對應的Biner實體";同樣的,Server被某個Client引用之後,"Server對應的Binder實體的引用列表(即,binder_node.refs)" 會包含 "Client對應的Binder引用"。

 

7.Binder協議碼

BC碼--Binder響應碼:

 

BR碼--Binder回覆碼:

代碼路徑:

\kernel\msm-4.9\drivers\android\binder.c

\kernel\msm-4.9\drivers\android\binder_alloc.c

github 中 LineageOS Kernel4.9代碼下載地址:

https://github.com/LineageOS/android_kernel_google_msm-4.9

 

我的微信公衆號:IngresGe

 

參考:

《Binder機制情景分析之深入驅動》

《Binder系列1—Binder Driver初探》

《Binder(傳輸機制篇_上)》

《Binder中的數據結構》

《linux內核中的紅黑樹代碼解析》

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章