Binder學習筆記(十二)—— binder_transaction(...)都幹了什麼?

binder_open(...)都幹了什麼?

在回答binder_transaction(...)以前,還有一些基礎設施要去探究,好比binder_open(...),binder_mmap(...),這些調用是在打開設備文件/dev/binder以後必須完成的程式化操做,而在它們內部須要作一些數據結構的準備。首先來看binder_open(...)
kernel/drivers/staging/android/binder.c:2979node

static int binder_open(struct inode *nodp, struct file *filp)
{
    struct binder_proc *proc;
    ......
    proc = kzalloc(sizeof(*proc), GFP_KERNEL); // 建立binder_proc結構體
    ......
    get_task_struct(current);
    proc->tsk = current;
    INIT_LIST_HEAD(&proc->todo);  // 初始化鏈表頭
    init_waitqueue_head(&proc->wait);   
    proc->default_priority = task_nice(current);

    ......
    // 將proc_node串入全局鏈表binder_procs中
    hlist_add_head(&proc->proc_node, &binder_procs); 
    proc->pid = current->group_leader->pid;
    INIT_LIST_HEAD(&proc->delivered_death);
    filp->private_data = proc;

    ......
    return 0;
}

binder_open(...)生成並初始化binder_proc結構體以下:
binder_open(...)初始化的binder_proc結構體linux

struct binder_proc

struct binder_proc描述一個「正在使用Binder進程間通訊機制」的進程,它的定義參見kernel/goldfish/drivers/staging/android/binder.c:286android

struct binder_proc {
    // 進程打開設備文件/dev/binder時,Binder驅動會爲它建立一個binder_proc結構體,並將它
    // 保存在全局hash列表中,proc_node是該hash列表的節點。
    struct hlist_node proc_node;

    // 每一個使用了Binder機制的進程都有一個Binder線程池,用來處理進程間通訊請求。threads以
    // 線程ID做爲key來組織進程的Binder線程池。進程能夠調用ioctl將線程註冊到Binder驅動中
    // 當沒有足夠的空閒線程處理進程間通訊請求時,驅動能夠要求進程註冊更多的線程到Binder線程
    // 池中
    struct rb_root threads; 

    struct rb_root nodes;           // 組織Binder實體對象,它以成員ptr做爲key
    struct rb_root refs_by_desc;    // 組織Binder引用對象,它以成員desc做爲key
    struct rb_root refs_by_node;    // 組織Binder引用對象,它以成員node做爲key
    int pid;                        // 指向進程組ID
    struct vm_area_struct *vma;     // 內核緩衝區的用戶空間地址,供應用程序使用
    struct mm_struct *vma_vm_mm;
    struct task_struct *tsk;        // 指向進程任務控制塊
    struct files_struct *files;     // 指向進程打開文件結構體數組

    // 一個hash表,保存進程能夠延遲執行的工做項,這些延遲工做有三種類型
    // BINDER_DEFERRED_PUT_FILES、BINDER_DEFERRED_FLUSH、BINDER_DEFERRED_RELEASE
    // 驅動爲進程分配內核緩衝區時,會爲該緩衝區建立一個文件描述符,進程能夠經過該描述符將該內
    // 核緩衝區映射到本身的地址空間。當進程再也不須要使用Binder機制時,就會通知驅動關閉該文件
    // 描述符並釋放以前所分配的內核緩衝區。因爲這不是一個立刻就要完成的操做,所以驅動會建立一
    // 個BINDER_DEFERRED_PUT_FILES類型的工做來延遲執行;
    // Binder線程池中的空閒Binder線程是睡眠在一個等待隊列中的,進程能夠經過調用函數flush
    // 來喚醒這些線程,以便它們能夠檢查進程是否有新的工做項須要處理。此時驅動會建立一個
    // BINDER_DEFERRED_FLUSH類型的工做項,以便延遲執行喚醒空閒Binder線程的操做;
    // 當進程再也不使用Binder機制時,會調用函數close關閉文件/dev/binder,此時驅動會釋放之
    // 前爲它分配的資源,因爲資源釋放是個比較耗時的操做,驅動會建立一個
    // BINDER_DEFERRED_RELEASE類型的事務來延遲執行
    struct hlist_node deferred_work_node;

    int deferred_work;              // 描述該延遲工做項的具體類型
    void *buffer;                   // 內核緩衝區的內核空間地址,供驅動程序使用
    ptrdiff_t user_buffer_offset;   // vma和buffer之間的差值

    // buffer指向一塊大的內核緩衝區,驅動程序爲方便管理,將它劃分紅若干小塊,這些小塊的內核緩
    // 衝區用binder_buffer描述保存在列表中,按地址從小到大排列。buffers指向該列表的頭部。
    struct list_head buffers; 

    struct rb_root free_buffers;      // buffers中的小塊有的正在使用,被保存在此紅黑樹
    struct rb_root allocated_buffers;   // buffers中的空閒小塊被保存在此紅黑樹
    size_t free_async_space;            // 當前可用的保存異步事務數據的內核緩衝區的大小

    struct page **pages;    // buffer和vma都是虛擬地址,它們對應的物理頁面保存在pages
                            // 中,這是一個數組,每一個元素指向一個物理頁面
    size_t buffer_size;     // 進程調用mmap將它映射到進程地址空間,其實是請求驅動爲它
                            // 分配一塊內核緩衝區,緩衝區大小保存在該成員中
    uint32_t buffer_free;   // 空閒內核緩衝區的大小
    struct list_head todo;  // 當進程接收到一個進程間通訊請求時,Binder驅動就將該請求封
                            // 裝成一個工做項,而且加入到進程的待處理工做向隊列中,該隊列
                            // 使用成員變量todo來描述。
    wait_queue_head_t wait; // 線程池中空閒Binder線程會睡眠在由該成員所描述的等待隊列中
                            // 當宿主進程的待處理工做項隊列增長新工做項後,驅動會喚醒這
                            // 些線程,以便處理新的工做項
    struct binder_stats stats;  // 用來統計進程數據

    // 當進程所引用的Service組件死亡時,驅動會向該進程發送一個死亡通知。這個正在發出的通知被
    // 封裝成一個類型爲BINDER_WORK_DEAD_BINDER或BINDER_WORK_DEAD_BINDER_AND_CLEAR
    // 的工做項,並保存在由該成員描述的隊列中刪除
    struct list_head delivered_death;  

    int max_threads;        // 驅動程序最多能夠主動請求進程註冊的線程數
    int requested_threads;
    int requested_threads_started;
    int ready_threads;      // 進程當前的空閒Binder線程數
    long default_priority;  // 進程的優先級,當線程處理一個工做項時,線程優先級可能被
                            // 設置爲宿主進程的優先級
    struct dentry *debugfs_entry;
};

binder_proc中的鏈表

在binder_proc內部有若干個list_head類型的字段,用來把binder_proc串到不一樣的鏈表中去。通常寫鏈表的作法是在鏈表節點結構體中追加業務邏輯字段,順着鏈表的prev、next指針到達指定節點,而後再訪問業務邏輯字段:
通常的鏈表作法
在Linux代碼中則經常反過來,先定義業務邏輯的結構體,在其內部嵌入鏈表字段list_head,順着該字段遍歷鏈表,在每一個節點上根據該字段與所在結構體的偏移量找到所在結構體,訪問業務邏輯字段:
Linux中經常使用的鏈表作法
這樣作的好處是讓業務邏輯和鏈表邏輯分離,Linux還定義了宏用於操做鏈表,以及根據鏈表字段找到所在結構體。如binder_proc結構體內部盛放多個list_head,表示把該結構體串入了不一樣的鏈表。
具體技巧可參見《Linux內核設計與實現》第6章。git

INIT_LIST_HEAD(&proc->todo)

回到binder_open(...),除了直接字段賦值,須要解釋的是幾個鏈表字段的處理。
INIT_LIST_HEAD(&proc->todo)用於將todo的next、prev指針指向本身,該宏的定義在kernel/goldfish/include/linux/lish.t:24github

static inline void INIT_LIST_HEAD(struct list_head *list)
{
    list->next = list;
    list->prev = list;
}

init_waitqueue_head(&proc->wait)

init_waitqueue_head(&proc->wait)這個宏定義在kernel/goldfish/include/linux/wait.h:81數組

#define init_waitqueue_head(q)              \
    do {                        \
        static struct lock_class_key __key; \
                            \
        __init_waitqueue_head((q), #q, &__key); \
    } while (0)

__init_waitqueue_head(...)定義在kernel/goldfish/kernel/wait.c:13,主要完成了對task_list字段的初始化:安全

void __init_waitqueue_head(wait_queue_head_t *q, const char *name, struct lock_class_key *key)
// q=(&proc->todo)
{
    spin_lock_init(&q->lock);
    lockdep_set_class_and_name(&q->lock, key, name);
    INIT_LIST_HEAD(&q->task_list);  // 爲何使用符號->來提領task_list呢?
}

說到底仍是初始化proc->wait->task_list字段。不過有點奇怪task_list是wait內的結構體,而不是結構體指針,爲何對task_list的提領使用符號->呢?cookie

struct binder_proc {
    ......
    wait_queue_head_t wait;
    ......
};

kernel/goldfish/include/linux/wait.h:53數據結構

struct __wait_queue_head {
    spinlock_t lock;
    struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;

hlist_add_head(&proc->proc_node, &binder_procs)

hlist_add_head(&proc->proc_node, &binder_procs)將proc->proc_node節點串到全局鏈表binder_procs的頭部,其定義在kernel/goldfish/include/linux/list.h:610app

static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h)
{
    struct hlist_node *first = h->first;
    n->next = first;
    if (first)
        first->pprev = &n->next;
    h->first = n;
    n->pprev = &h->first;
}

kernel/goldfish/include/linux/types.h:233

struct hlist_head {
    struct hlist_node *first;
};

struct hlist_node {
    struct hlist_node *next, **pprev;
};

將n插入到h

插入後的結果
綜上所述,binder_open(...)組織的數據結構proc爲:
binder_open(...)組織的proc數據結構圖

binder_mmap(...)都幹了什麼?

接下來就是binder_mmap(...),當進程打開/dev/binder以後,必須調用mmap(...)函數把該文件映射到進程的地址空間。
kernel/goldfish/drivers/staging/android/binder.c:2883

static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
{
    int ret;
    struct vm_struct *area; // area描述內核地址空間;vma描述用戶地址空間
    struct binder_proc *proc = filp->private_data;
    const char *failure_string;
    struct binder_buffer *buffer;

    ......
    vma->vm_flags = (vma->vm_flags | VM_DONTCOPY) & ~VM_MAYWRITE;

    ......
    // 在內核地址空間分配
    area = get_vm_area(vma->vm_end - vma->vm_start, VM_IOREMAP);
    ......
    proc->buffer = area->addr;
    proc->user_buffer_offset = vma->vm_start - (uintptr_t)proc->buffer;
    mutex_unlock(&binder_mmap_lock);
......
    // 建立物理頁面結構體指針數組
    proc->pages = kzalloc(sizeof(proc->pages[0]) * ((vma->vm_end - vma->vm_start) / PAGE_SIZE), GFP_KERNEL);
    ......
    proc->buffer_size = vma->vm_end - vma->vm_start;

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

    // 分配物理頁面,並將之同時映射到用戶和內核地址空間
    if (binder_update_page_range(proc, 1, proc->buffer, proc->buffer + PAGE_SIZE, vma)) {
        ret = -ENOMEM;
        failure_string = "alloc small buf";
        goto err_alloc_small_buf_failed;
    }
    buffer = proc->buffer;
    INIT_LIST_HEAD(&proc->buffers);
    list_add(&buffer->entry, &proc->buffers); // 把entry串到buffers鏈表中
    buffer->free = 1;
    binder_insert_free_buffer(proc, buffer);
    proc->free_async_space = proc->buffer_size / 2;
    barrier();
    proc->files = get_files_struct(proc->tsk);
    proc->vma = vma;
    proc->vma_vm_mm = vma->vm_mm;

    /*printk(KERN_INFO "binder_mmap: %d %lx-%lx maps %p\n",
         proc->pid, vma->vm_start, vma->vm_end, proc->buffer);*/
    return 0;

err_alloc_small_buf_failed:
    kfree(proc->pages);
    proc->pages = NULL;
err_alloc_pages_failed:
    mutex_lock(&binder_mmap_lock);
    vfree(proc->buffer);
    proc->buffer = NULL;
err_get_vm_area_failed:
err_already_mapped:
    mutex_unlock(&binder_mmap_lock);
err_bad_arg:
    printk(KERN_ERR "binder_mmap: %d %lx-%lx %s failed %d\n",
           proc->pid, vma->vm_start, vma->vm_end, failure_string, ret);
    return ret;
}

到第28行調用binder_update_page_range(...)以前,binder_mmap(...)在內核地址空間申請了struct vm_struct area,並完成部分紅員的初始化,以下:
到28行爲止binder_mmap(...)構造的數據結構

binder_update_page_range(...)作了什麼

kernel/goldfish/drivers/staging/android/binder.c:627

static int binder_update_page_range(struct binder_proc *proc, int allocate,
                    void *start, void *end,
                    struct vm_area_struct *vma)
{
    void *page_addr;
    unsigned long user_page_addr;
    struct vm_struct tmp_area;
    struct page **page;
    struct mm_struct *mm;

    ... ...

    if (vma)
        mm = NULL;
    else
        mm = get_task_mm(proc->tsk);

    if (mm) {
        down_write(&mm->mmap_sem);
        vma = proc->vma;
        ... ...
    }

    if (allocate == 0) 
        goto free_range;    // 執行釋放邏輯

    ... ...

    // 遍歷全部頁面
    for (page_addr = start; page_addr < end; page_addr += PAGE_SIZE) {
        int ret;
        struct page **page_array_ptr;
        page = &proc->pages[(page_addr - proc->buffer) / PAGE_SIZE];

        BUG_ON(*page);
        *page = alloc_page(GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO);
        ... ...
        // 映射內核地址空間
        tmp_area.addr = page_addr;
        tmp_area.size = PAGE_SIZE + PAGE_SIZE /* guard page? */;
        page_array_ptr = page;
        ret = map_vm_area(&tmp_area, PAGE_KERNEL, &page_array_ptr);
        ... ...

        // 映射用戶地址空間
        user_page_addr =
            (uintptr_t)page_addr + proc->user_buffer_offset;
        ret = vm_insert_page(vma, user_page_addr, page[0]);
        ... ...
    }
    if (mm) {
        up_write(&mm->mmap_sem);
        mmput(mm);
    }
    return 0;

free_range:
    for (page_addr = end - PAGE_SIZE; page_addr >= start;
         page_addr -= PAGE_SIZE) {
        page = &proc->pages[(page_addr - proc->buffer) / PAGE_SIZE];
        // 解除物理頁面在用戶地址空間和內核地址空間的映射
        if (vma)
            zap_page_range(vma, (uintptr_t)page_addr +
                proc->user_buffer_offset, PAGE_SIZE, NULL);
err_vm_insert_page_failed:
        unmap_kernel_range((unsigned long)page_addr, PAGE_SIZE);
err_map_kernel_failed:
        __free_page(*page);     // 釋放物理頁面
        *page = NULL;
err_alloc_page_failed:
        ;
    }
err_no_vma:
    if (mm) {
        up_write(&mm->mmap_sem);
        mmput(mm);
    }
    return -ENOMEM;
}

struct binder_buffer

以後在binder_mmap(...)第34行,buffer的類型是struct binder_buffer*,該結構體用來描述一個內核緩衝區,該緩衝區用於在進程間傳輸數據。
kernel/goldfish/drivers/staging/android/binder.c:263

struct binder_buffer {
    // 每個使用Binder機制的進程在Binder驅動中都有一個內核緩衝區列表,用來保存Binder驅動
    // 程序爲它分配的內核緩衝區,entry是該列表的一個節點
    struct list_head entry; /* free and allocated entries by address */ 

    // 進程使用兩個紅黑樹分別保存使用中以及空閒的內核緩衝區。若是空閒,free=1,
    //rb_node就是空閒內核緩衝區紅黑樹中的節點,不然是使用中內核緩衝區紅黑樹中的節點
    struct rb_node rb_node; /* free entry by size or allocated entry */ 
                         /* by address */                       

    unsigned free:1;
    // Service處理完成該事務後,若發現allow_user_free爲1,會請求驅動程序釋放該內核緩衝區
    unsigned allow_user_free:1;         
    unsigned async_transaction:1;           // 與該內核緩衝區關聯的是一個異步事務
    unsigned debug_id:29;

    struct binder_transaction *transaction; // 內核緩衝區正交給哪一個事務使用
    struct binder_node *target_node;        // 內核緩衝區正交給哪一個Binder實體對象使用
    size_t data_size;
    size_t offsets_size;

    // 保存通訊數據,分兩種類型:普通數據、Binder對象。驅動程序不關心普通數據,但必須知道里面
    // 的Binder對象,由於要根據它們來維護內核中Binder實體對象和Binder引用對象的生命週期。
    uint8_t data[0];                        
};

list_add(&buffer->entry, &proc->buffers)

初始化完proc->buffers以後,第36行執行一個list_add(...),該函數定義見kernel/goldfish/include/linux/list.h:37~60

static inline void __list_add(struct list_head *new,
                  struct list_head *prev,
                  struct list_head *next)
{
    next->prev = new;
    new->next = next;
    new->prev = prev;
    prev->next = new;
}
... ...
static inline void list_add(struct list_head *new, struct list_head *head)
{
    __list_add(new, head, head->next);
}

運算過程以下圖:
list_add操做過程
因而到binder_mmap(...)第37行爲止,binder_mmap(...)構造的數據結構以下:
到37行爲止binder_mmap(...)構造的數據結構

函數binder_insert_free_buffer(...)

kernel/goldfish/drivers/statging/android/binder.c:545

static void binder_insert_free_buffer(struct binder_proc *proc,
                      struct binder_buffer *new_buffer)
{   // new_buffer就是以前分配的buffer,被轉型成了binder_buffer
    struct rb_node **p = &proc->free_buffers.rb_node;
    struct rb_node *parent = NULL;
    struct binder_buffer *buffer;
    size_t buffer_size;
    size_t new_buffer_size;
    ... ...
    // 計算binder_buffer中data部分的大小
    new_buffer_size = binder_buffer_size(proc, new_buffer);

    ... ...
    // 根據new_buffer的大小,找到在proc->free_buffers紅黑樹中的正確位置,並插入
    while (*p) {
        parent = *p;
        buffer = rb_entry(parent, struct binder_buffer, rb_node);
        BUG_ON(!buffer->free);

        buffer_size = binder_buffer_size(proc, 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, &proc->free_buffers);
}

因而到binder_mmap(...)結束,這個binder_proc結構體就被作成了這樣:
binder_mmap(...)調用完成後構造的binder_proc結構體

從服務端addService觸發的binder_transaction(...)

從native層的調用過程參見binder學習筆記(十)—— 穿越到驅動層。咱們以addService爲例深刻到binder_transaction(...)內部,
傳入的binder_transaction_data輸入參數爲:addService組織的請求數據

kernel/goldfish/drivers/staging/android/binder.c:1402

static void binder_transaction(struct binder_proc *proc,
                   struct binder_thread *thread,
                   struct binder_transaction_data *tr, int reply)
{   // reply=(cmd==BC_REPLY)即false,flags=TF_ACCEPT_FDS
    // proc和thread表示當前進程對應的數據結構
    struct binder_transaction *t;
    struct binder_work *tcomplete;
    size_t *offp, *off_end;
    struct binder_proc *target_proc;
    struct binder_thread *target_thread = NULL;
    struct binder_node *target_node = NULL;
    struct list_head *target_list;
    wait_queue_head_t *target_wait;
    struct binder_transaction *in_reply_to = NULL;
    struct binder_transaction_log_entry *e;
    uint32_t return_error;

    ......

    if (reply) {
        ......
    } else {
        if (tr->target.handle) {  // tr->target.handle!=0
            ......
        } else {
            // target_node表示binder請求要發送到的節點,此處即
            // service manager對應的節點
            target_node = binder_context_mgr_node; 
            ......
        }
        ......
        target_proc = target_node->proc; // 獲得目標進程的binder_proc
        ......
        // 獲得目標線程tr->flags=TF_ACCEPT_FDS
        // thread未被操做過,故transaction_stack爲0
        if (!(tr->flags & TF_ONE_WAY) && thread->transaction_stack) {
            struct binder_transaction *tmp;
            tmp = thread->transaction_stack;
            ... ...
            while (tmp) {
                if (tmp->from && tmp->from->proc == target_proc)
                    target_thread = tmp->from;
                tmp = tmp->from_parent;
            }
        }
    }
    if (target_thread) {
        e->to_thread = target_thread->pid;
        target_list = &target_thread->todo;
        target_wait = &target_thread->wait;
    } else { // 走這裏
        target_list = &target_proc->todo;
        target_wait = &target_proc->wait;
    }
    ......
    t = kzalloc(sizeof(*t), GFP_KERNEL);  // 建立binder_transaction節點
    ......
    tcomplete = kzalloc(sizeof(*tcomplete), GFP_KERNEL);//建立一個binder_work節點
    ......
    // 這裏豈不是爲真?thread來自binder_ioctl(...)中的binder_get_thread(proc)
    // 返回proc的當前線程
    if (!reply && !(tr->flags & TF_ONE_WAY)) 
        t->from = thread;
    else
        t->from = NULL;
    t->sender_euid = proc->tsk->cred->euid; // 源線程用戶id
    t->to_proc = target_proc;               // 負責處理該事務的進程,sm
    t->to_thread = target_thread;           // 負責處理該事務的線程
    t->code = tr->code;                     // ADD_SERVICE_TRANSACTION
    t->flags = tr->flags;                   // TF_ACCEPT_FDS
    t->priority = task_nice(current);       // 源線程優先級
    ... ...
    t->buffer = binder_alloc_buf(target_proc, tr->data_size,
        tr->offsets_size, !reply && (t->flags & TF_ONE_WAY));
    ......
    t->buffer->allow_user_free = 0;// Service處理完該事務後,驅動不會釋放該內核緩衝區
    t->buffer->debug_id = t->debug_id;
    t->buffer->transaction = t; // 緩衝區正交給哪一個事務使用
    t->buffer->target_node = target_node;   // 緩衝區正交給哪一個Binder實體對象使用
    ......
    if (target_node)
        binder_inc_node(target_node, 1, 0, NULL);
    // 分析所傳數據中的全部binder對象,若是是binder實體,在紅黑樹中添加相應的節點。
    // 首先,從用戶態獲取所傳輸的數據,以及數據裏的binder對象偏移信息。
    offp = (size_t *)(t->buffer->data + ALIGN(tr->data_size, sizeof(void *)));
    // 將服務端傳來的Parcel的數據部分拷貝到內核空間
    if (copy_from_user(t->buffer->data, tr->data.ptr.buffer, tr->data_size)) {
        ......
    }
    // 將服務端傳來的Parcel的偏移數組拷貝到內核空間
    if (copy_from_user(offp, tr->data.ptr.offsets, tr->offsets_size)) {
        ......
    }
    ......
    off_end = (void *)offp + tr->offsets_size;
    // 遍歷每一個flat_binder_object信息,建立必要的紅黑樹節點
    for (; offp < off_end; offp++) {
        struct flat_binder_object *fp;
        ......
        fp = (struct flat_binder_object *)(t->buffer->data + *offp);
        switch (fp->type) {
        case BINDER_TYPE_BINDER:
        case BINDER_TYPE_WEAK_BINDER: { // 若是是binder實體
            struct binder_ref *ref;
            // fp->binder是BnTestService::getWeakRefs(),BnTestService的影子對象
            // binder_get_node(...)在proc->nodes.rb_node中找fp->binder,若是沒有
            // 找到,則在該紅黑樹中爲fp->binder建立節點
            struct binder_node *node = binder_get_node(proc, fp->binder);
            if (node == NULL) {
                node = binder_new_node(proc, fp->binder, fp->cookie);
                ......
                node->min_priority = fp->flags & FLAT_BINDER_FLAG_PRIORITY_MASK;
                node->accept_fds = !!(fp->flags & FLAT_BINDER_FLAG_ACCEPTS_FDS);
            }
            ......
            // 必要時,會在目標進程的binder_proc中建立對應的binder_ref紅黑樹節點
            ref = binder_get_ref_for_node(target_proc, node);
            ......
            if (fp->type == BINDER_TYPE_BINDER)
                fp->type = BINDER_TYPE_HANDLE;
            else
                fp->type = BINDER_TYPE_WEAK_HANDLE;
            // 修改所傳數據中的flat_binder_object信息,由於遠端的binder實體到
            // 了目標端就變爲binder代理了,因此要記錄下binder句柄了。
            fp->handle = ref->desc;
            binder_inc_ref(ref, fp->type == BINDER_TYPE_HANDLE,
                       &thread->todo);
            ......
        } break;
        case BINDER_TYPE_HANDLE:
        case BINDER_TYPE_WEAK_HANDLE: { 
            // 對flat_binder_object作必要的修改,好比將BINDER_TYPE_HANDLE改成
            // BINDER_TYPE_BINDER
            struct binder_ref *ref = binder_get_ref(proc, fp->handle);
            ......
            if (ref->node->proc == target_proc) {
                if (fp->type == BINDER_TYPE_HANDLE)
                    fp->type = BINDER_TYPE_BINDER;
                else
                    fp->type = BINDER_TYPE_WEAK_BINDER;
                fp->binder = ref->node->ptr;
                fp->cookie = ref->node->cookie;
                binder_inc_node(ref->node, fp->type == BINDER_TYPE_BINDER, 0, NULL);
                trace_binder_transaction_ref_to_node(t, ref);
                ... ...
            } else {
                struct binder_ref *new_ref;
                new_ref = binder_get_ref_for_node(target_proc, ref->node);
                if (new_ref == NULL) {
                    return_error = BR_FAILED_REPLY;
                    goto err_binder_get_ref_for_node_failed;
                }
                fp->handle = new_ref->desc;
                binder_inc_ref(new_ref, fp->type == BINDER_TYPE_HANDLE, NULL);
                trace_binder_transaction_ref_to_ref(t, ref,
                                    new_ref);
                ... ...
            }
        } break;

        case BINDER_TYPE_FD: {
            int target_fd;
            struct file *file;
            ... ...

            file = fget(fp->handle);
            ... ...
            target_fd = task_get_unused_fd_flags(target_proc, O_CLOEXEC);
            ... ...
            task_fd_install(target_proc, target_fd, file);
            trace_binder_transaction_fd(t, fp->handle, target_fd);
            binder_debug(BINDER_DEBUG_TRANSACTION,
                     "        fd %ld -> %d\n", fp->handle, target_fd);
            /* TODO: fput? */
            fp->handle = target_fd;
        } break;

        default:
            ... ...
            return_error = BR_FAILED_REPLY;
            goto err_bad_object_type;
        }
    }
    if (reply) {
        ......
    } else if (!(t->flags & TF_ONE_WAY)) {
        ... ...
        t->need_reply = 1;
        t->from_parent = thread->transaction_stack;
        thread->transaction_stack = t;
    } else {
        ......
        if (target_node->has_async_transaction) {
            target_list = &target_node->async_todo;
            target_wait = NULL;
        } else
            target_node->has_async_transaction = 1;
    }
    t->work.type = BINDER_WORK_TRANSACTION;
    // 把binder_transaction節點插入target_list(即目標todo隊列)
    list_add_tail(&t->work.entry, target_list);
    tcomplete->type = BINDER_WORK_TRANSACTION_COMPLETE;
    list_add_tail(&tcomplete->entry, &thread->todo);
    if (target_wait) // 傳輸動做完畢,如今能夠喚醒系統中其它相關線程,wake up!
        wake_up_interruptible(target_wait);
    return;

err_get_unused_fd_failed:
err_fget_failed:
err_fd_not_allowed:
err_binder_get_ref_for_node_failed:
err_binder_get_ref_failed:
err_binder_new_node_failed:
err_bad_object_type:
err_bad_offset:
err_copy_data_failed:
    trace_binder_transaction_failed_buffer_release(t->buffer);
    binder_transaction_buffer_release(target_proc, t->buffer, offp);
    t->buffer->transaction = NULL;
    binder_free_buf(target_proc, t->buffer);
err_binder_alloc_buf_failed:
    kfree(tcomplete);
    binder_stats_deleted(BINDER_STAT_TRANSACTION_COMPLETE);
err_alloc_tcomplete_failed:
    kfree(t);
    binder_stats_deleted(BINDER_STAT_TRANSACTION);
err_alloc_t_failed:
err_bad_call_stack:
err_empty_call_stack:
err_dead_binder:
err_invalid_target_handle:
err_no_context_mgr_node:
    binder_debug(BINDER_DEBUG_FAILED_TRANSACTION,
             "binder: %d:%d transaction failed %d, size %zd-%zd\n",
             proc->pid, thread->pid, return_error,
             tr->data_size, tr->offsets_size);

    {
        struct binder_transaction_log_entry *fe;
        fe = binder_transaction_log_add(&binder_transaction_log_failed);
        *fe = *e;
    }

    BUG_ON(thread->return_error != BR_OK);
    if (in_reply_to) {
        thread->return_error = BR_TRANSACTION_COMPLETE;
        binder_send_failed_reply(in_reply_to, return_error);
    } else
        thread->return_error = return_error;
}

struct binder_transaction

在函數binder_transaction(...)第53行建立告終構體binder_transaction,該結構體用來描述進程間通訊過程,即事務。其定義在kernel/goldfish/drivers/staging/android/binder.c:346

struct binder_transaction {
    int debug_id;

    // 當驅動爲目標進程或線程建立一個事務時,就會將該成員的type置爲
    // BINDER_WORK_TRANSACTION,並將它添加到目標進程或線程的todo隊列,等待處理
    struct binder_work work;

    struct binder_thread *from;         // 發起事務的線程

    // 事務所依賴的另一個事務以及目標線程下一個要處理的事務
    struct binder_transaction *from_parent; 

    struct binder_proc *to_proc;        // 負責處理該事務的進程
    struct binder_thread *to_thread;    // 負責處理該事務的線程
    struct binder_transaction *to_parent;
    unsigned need_reply:1;              // 同步事務爲1須要等待對方回覆;異步爲0
    /* unsigned is_dead:1; */   /* not used at the moment */

    // 指向驅動爲該事務分配的內核緩衝區,保存了進程間通訊數據
    struct binder_buffer *buffer;   

    unsigned int    code;   // 直接從進程間通訊數據中拷貝過來
    unsigned int    flags;  // 直接從進程間通訊數據中拷貝過來
    long    priority;       // 源線程優先級

    // 線程在處理事務時,驅動會修改它的優先級以知足源線程和目標Service組建的要求。在修改之
    // 前,會將它原來的線程優先級保存在該成員中,以便線程處理完該事務後能夠恢復原來的優先級
    long    saved_priority; 
    uid_t   sender_euid;    // 源線程用戶ID
};

struct binder_work

在binder_transaction(...)第55行建立了struct binder_work,該結構體用於描述待處理的工做項,其定義在kernel/goldfish/drivers/staging/android/binder.c:205

struct binder_work {        
    struct list_head entry; // 用來將該結構體嵌入到一個宿主結構中
    // 描述工做項的類型,根據取值,Binder驅動程序就能夠判斷出一個binder_work結構體嵌入到
    // 什麼類型的宿主結構中
    enum {
        BINDER_WORK_TRANSACTION = 1,
        BINDER_WORK_TRANSACTION_COMPLETE,
        BINDER_WORK_NODE,
        BINDER_WORK_DEAD_BINDER,
        BINDER_WORK_DEAD_BINDER_AND_CLEAR,
        BINDER_WORK_CLEAR_DEATH_NOTIFICATION,
    } type;             
};

到binder_transaction(...)第92行爲止,它構造的數據結構以下。此時用戶控件的部分數據被拷貝到了內核空間,內核空間中binder_transaction的buffer是從proc->free_buffers中摘取下來的,爲了不圖片過大,此處的細節暫不展示了。摘取下的buffer的數據部分用於暫存從用戶空間拷貝來的數據。
到binder_transaction(...)第92行位置,構造的數據結構

struct binder_node

從94行開始,逐個遍歷t->buffer.data中的binder objects,在for循環中,fp指向當前的binder object。若是fp->type是BINDER_TYPE_BINDER或BINDER_TYPE_WEAK_BINDER,#104先從proc->nodes.rb_node中查找有沒有fp->binder,若是沒有則調用binder_new_node(...)在proc->nodes.rb_node中建立此節點。接下來先看看struct binder_node,kernel/goldfish/drivers/staging/android/binder.c:217,它用來描述一個Binder實體對象,每個Service組件在驅動層都對應一個binder_node,用來描述在內核中的狀態:

struct binder_node {            
    int debug_id;               // 幫助調試用的

    // 當Binder實體對象的引用計數由0變爲1或由1變爲0時,Binder驅動程序就會請求相應的
    // Service組件增長或減小其引用計數。Binder驅動程序就會將「該引用計數修改」封裝成一個類
    // 型爲一個類型爲binder_node的工做項,即將成員work的值置爲BINDER_WORK_NODE,並將
    // 它添加到相應進程的todo隊列中等待處理
    struct binder_work work;

    union {
        struct rb_node rb_node;
        struct hlist_node dead_node;
    };

    // 指向宿主進程,宿主進程使用一個紅黑樹來維護它內部全部Binder實體對象,而每個
    // Binder實體對象的成員變量rb_node就正好是這個紅黑樹的一個節點。若是Binder實體對象
    // 的宿主進程已經死亡,那麼該Binder實體對象就會經過它的成員變量dead_node保存在一個全
    // 局的hash列表中。
    struct binder_proc *proc;

    // 一個Binder實體對象可能會同時被多個Client組件引用,所以Binder驅動使用結構體
    // binder_ref來描述這些引用關係,而且將引用了同一個Binder實體對象的全部引用都保存在
    // 一個hash列表中。這個hash列表經過Binder實體對象的refs成員來描述,而Binder驅動通
    // 過refs就能夠知道有哪些Client組件引用了同一個Binder實體對象
    struct hlist_head refs;

    int internal_strong_refs;       // 描述Bidner實體對象的強引用計數
    int local_weak_refs;            // 描述Binder實體對象的弱引用計數
    int local_strong_refs;          // 描述Bidner實體對象的強引用計數

    void __user *ptr;       // 描述用戶空間中的Service組件,指向Service的影子對象
    void __user *cookie;    // 描述用戶空間中的Service組件的地址,指向Service的地址

     // 當Binder實體對象請求Service執行某個操做時,會增長該Service的強/弱引用計數,
     // has_strong_ref和has_weak_ref被置1;
     // 當Service完成Binder所請求的操做後,會遞減該Service的強/弱引用計數,has_strong_ref和has_weak_ref被置0;
     // Binder實體在請求Service增/減強/弱引用計數的過程當中,會將
     // pending_strong_ref或pending_weak_ref置1;
     // 當Service完成增/減強/弱引用計數以後,會將這兩個成員變量置爲0。
    unsigned has_strong_ref:1;     
    unsigned pending_strong_ref:1;
    unsigned has_weak_ref:1;
    unsigned pending_weak_ref:1;

    // 描述Binder對象是否正在處理一個異步事務。Binder驅動程序將一個事務保存在todo隊列中
    // 表示將由該線程來處理該事務。每一個事務都關聯着一個Binder實體對象,要求與該Binder實
    // 體對象對應的Service組件在指定線程中處理該事務。然而,當Binder驅動發現一個事務是異
    // 步事務時,就會將它保存在目標Binder實體對象的一個異步事務隊列中,這個異步事務隊列就
    // 是由成員變量async_todo來描述的。異步事務的定義是那些單向的進程間通訊請求,即不需
    // 要等待應答的進程間通訊請求,與此相對的是同步事務。由於不須要等待應答,Binder驅動就
    // 認爲異步事務優先級低於同步事務,具體表現爲在同一時刻,一個Binder實體對象的全部異步
    // 事務最多隻有一個會獲得處理,其他的都等待在異步事務隊列中,而同步事務無此限制。
    unsigned has_async_transaction:1;

    // 描述Binder實體對象是否能夠接收包含有文件描述符的進程間通訊數據。1表示能夠接收,0表
    // 示禁止接收。當一個進程向另外一個進程發送的數據中包含有文件描述符時,Binder驅動程序就
    // 會自動在目標進程中打開一個相同的文件。基於安全性考慮,Binder程序就要經過該變量防止
    // 源進程在目標進程中打開文件。
    unsigned accept_fds:1;

    // 表示Binder實體對象在處理來自Client進程的請求時,他所要求的處理線程(即Server進程
    // 中的一個線程)應具有的最小線程優先級。
    unsigned min_priority:8;       
    struct list_head async_todo;
};

接下來的binder_new_node(proc, fp->binder, fp->cookie)將申請一個struct binder_node,在初始化中,將該節點掛到proc->nodes.rb_node中,並初始化部分紅員,數據結構圖以下:
在case BINDER_TYPE_BINDER和case BINDER_TYPE_WEAK_BINDER中建立的struct binder_node

struct binder_ref

struct binder_ref用來描述一個Binder引用對象,當客戶端使用Binder實體時,在客戶端保存的就是對該實體的引用,該結構體用來描述引用對象在內核中的狀態。kernel/goldfish/drivers/staging/android/binder.c:246

struct binder_ref {
    /* Lookups needed: */
    /*   node + proc => ref (transaction) */
    /*   desc + proc => ref (transaction, inc/dec ref) */
    /*   node => refs + procs (proc exit) */
    int debug_id;

    // 宿主進程使用兩個紅黑樹來保存它內部全部Binder引用對象,分別以句柄值和對應的Binder
    // 實體對象地址來做爲關鍵字保存這些引用對象,這兩個rb_node_xxxx正是紅黑樹中的節點
    struct rb_node rb_node_desc;    
    struct rb_node rb_node_node;

    // 每一個Binder實體對象都有一個hash表保存引用了它的Binder引用對象,這些引用對象的成員
    // node_entry就是該hash表的節點
    struct hlist_node node_entry;   
    struct binder_proc *proc;   // 宿主進程
    struct binder_node *node;   // 描述Binder引用對象所引用的Binder實體對象

    // 描述Binder引用對象的句柄值,驅動經過該句柄找到對應的Binder引用對象,而後再根據該
    // Binder引用對象的成員node找到對應的Binder實體對象,而後就能夠經過該實體對象找到要
    // 訪問的Service組件了。一個Binder引用對象的句柄值僅在進程範圍內惟一,所以在兩個不一樣
    // 進程中,同一個句柄可能表明不一樣的Service組件
    uint32_t desc;

    int strong;                 // 描述Binder引用對象的強/弱引用計數
    int weak;

    // 指向一個Service組件的死亡接收通知。當Client進程向Binder驅動程序註冊一個它所引用
    // 的Service組件死亡接收通知時,Binder驅動程序會建立一個binder_ref_death結構體,然
    // 後保存在該成員變量death中
    struct binder_ref_death *death; 
};

接下來看binder_get_ref_for_node(target_proc, node)。須要注意,前面建立binder_node的時候,是爲proc建立的,proc是在調用binder_open(...)時建立,用來描述「使用(打開)該binder的進程」,proc就藏在binder文件的文件描述符的私有數據中;而此處(第150行)參數使用的是target_proc,它表示當前的binder請求發向的目標進程,在本上下文中就是handle爲0的service manager,即binder_context_mgr_node。
kernel/goldfish/drivers/staging/android/binder.c:1107

static struct binder_ref *binder_get_ref_for_node(struct binder_proc *proc,
                          struct binder_node *node)
{
    struct rb_node *n;
    struct rb_node **p = &proc->refs_by_node.rb_node;
    struct rb_node *parent = NULL;
    struct binder_ref *ref, *new_ref;
    // 在target_proc中查找node,若是找不到就建立
    while (*p) {
        parent = *p;
        ref = rb_entry(parent, struct binder_ref, rb_node_node);

        if (node < ref->node)
            p = &(*p)->rb_left;
        else if (node > ref->node)
            p = &(*p)->rb_right;
        else
            return ref;
    }
    new_ref = kzalloc(sizeof(*ref), GFP_KERNEL);
    ... ...
    binder_stats_created(BINDER_STAT_REF);
    new_ref->debug_id = ++binder_last_id;
    new_ref->proc = proc;
    new_ref->node = node;
    rb_link_node(&new_ref->rb_node_node, parent, p);
    rb_insert_color(&new_ref->rb_node_node, &proc->refs_by_node);

    // 遍歷target_proc的binder_ref,找到最大的desc,加1後賦給new_ref->desc
    new_ref->desc = (node == binder_context_mgr_node) ? 0 : 1;
    for (n = rb_first(&proc->refs_by_desc); n != NULL; n = rb_next(n)) {
        ref = rb_entry(n, struct binder_ref, rb_node_desc);
        if (ref->desc > new_ref->desc)
            break;
        new_ref->desc = ref->desc + 1;
    }

    // 將new_ref插入到target_proc->refs_by_desc.rb_node中
    p = &proc->refs_by_desc.rb_node;
    while (*p) {
        parent = *p;
        ref = rb_entry(parent, struct binder_ref, rb_node_desc);

        if (new_ref->desc < ref->desc)
            p = &(*p)->rb_left;
        else if (new_ref->desc > ref->desc)
            p = &(*p)->rb_right;
        else
            BUG();
    }
    rb_link_node(&new_ref->rb_node_desc, parent, p);
    rb_insert_color(&new_ref->rb_node_desc, &proc->refs_by_desc);
    if (node) {
        hlist_add_head(&new_ref->node_entry, &node->refs);
        ... ...
    } 
    ... ...
    return new_ref;
}

因而,在binder_transaction(...)函數第114行完成調用binder_get_ref_for_node(target_proc, node)以後,數據結構圖爲:
在binder_transaction(...)函數中爲target_proc建立完binder_ref以後的數據結構

接下來在函數binder_transaction(...)中還有幾個關鍵操做,見第116行,若是fp->type爲BINDER_TYPE_BINDER,就改成BINDER_TYPE_HANDLE,而後把fp->handle改成ref->desc,接下來的binder_ref_ref(ref, fp->type==BINDER_TYPE_HANDLE, &thread->todo)定義在kernel/goldfish/drivers/staging/android/binder.c:1200

static int binder_inc_ref(struct binder_ref *ref, int strong,
              struct list_head *target_list)
{   // strong = (fp->type==BINDER_TYPE_HANDLE)即爲1
    // target_list = &thread->todo
    int ret;
    if (strong) {
        if (ref->strong == 0) {
            // ref->node->internal_strong_ref++,成功返回0
            ret = binder_inc_node(ref->node, 1, 1, target_list);
            if (ret)
                return ret;
        }
        ref->strong++;
    } else {
        if (ref->weak == 0) {
            ret = binder_inc_node(ref->node, 0, 1, target_list);
            if (ret)
                return ret;
        }
        ref->weak++;
    }
    return 0;
}

接下來跳出case後還有對t的成員need_reply、from_parent、t->work.type的處理,並將t插入到target_list即target_proc或target_thread的todo隊列中,爾後返回。此時的數據結構圖爲:
binder_transaction(...)完成時的數據結構

到此爲止,終於完成了binder_transaction(...)的分析,知道怎麼回事,但內心有不少個「爲何」。並且把前面的學習筆記串聯起來,隱約以爲能感應到一些曙光了,本節的篇幅太長了,這些曙光留待下一節一塊兒領略吧。

相關文章
相關標籤/搜索