Binder驅動之傳輸事件

Binder驅動中每一次傳輸都代表一個傳輸事件,通過binder_transaction來描述。

struct binder_thread {
    struct binder_proc *proc; /* 線程所屬的而進程 */
    struct rb_node rb_node; /* 紅黑樹節點,插入到binder_proc->threads中 */
    int pid; /* 線程PID */
    int looper; /* 線程looper狀態,用上面的枚舉描述 */
    struct binder_transaction *transaction_stack; /* Binder傳輸棧 */
    struct list_head todo; /* Binder線程todo隊列 */
    uint32_t return_error; /* 寫失敗時的返回錯誤碼 */
    uint32_t return_error2; /* 寫失敗時的返回錯誤碼2 */
    wait_queue_head_t wait; /* Binder線程等待隊列 */
    struct binder_stats stats; /* Binder線程統計信息 */
};

struct binder_transaction {
    int debug_id; /* 全局ID,用於調試 */
    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; /* 是否需要回復 */
    /* unsigned is_dead:1; */   /* not used at the moment */

    struct binder_buffer *buffer; /* 傳輸數據的buffer */
    unsigned int    code; /* 傳輸的命令碼 */
    unsigned int    flags; /* 傳輸標識,例如TF_ONE_WAY */
    long    priority; /* 傳輸優先級 */
    long    saved_priority; /* 傳輸缺省優先級 */
    kuid_t  sender_euid; /* 發送端的uid */
}

Binder傳輸時通過Binder線程爲主體進行交互的,所以Binder線程中會保存Binder傳輸事件,在binder_thread中使用transaction_stack做爲一種棧的形式來記錄所有的傳輸事件。transaction_stack保存着當前正在進行的傳輸事件,採取壓棧的方式保存,所以棧頂爲最新的傳輸,棧底爲最早的傳輸。這種方式也表現了線程中傳輸事件的依賴關係,一個傳輸事件必須等待上一個棧的傳輸完成才能進行。

  • 線程transaction_stack中記錄着當前線程正在進行的傳輸事件。
  • 發送中的傳輸事件通過from_parent(BC_TRANSACTION)入棧,接收中的傳輸事件通過to_thread(BR_TRANSACTION)進行入棧。
  • 通過from_parent入棧的傳輸事件,from爲當前當前Binder線程。通過to_parent入棧的傳輸事件,to_thread爲當前Binder線程。
  • 系統中所有進行中的傳輸事件都存在各Binder線程的棧中,傳輸完成會出棧。傳輸事件通過from_parent形成一個鏈表,用來表明當前傳輸事件的上一個傳輸事件。這個鏈表可以用來對傳輸事件溯源,鏈表的頭就是當前傳輸關聯的第一個傳輸事件。

簡單的傳輸

通過一個簡單的傳輸來分析傳輸事件的流程。

  • Binder進程Proc A的線程Thread A1向Binder進程Proc B的線程Thread B1發起IPC通信。
  • A1發送命令BC_TRANSACTION到Binder驅動。
  • Binder驅動發送命令BR_TRANSACTION給B1。
  • B1完成處理後回覆BC_REPLY給Binder驅動。
  • Binder驅動再將BR_REPLY發送給A1.

BC_TRANSACTION

A1發起的傳輸事件(暫且稱爲T1_tr)在BC_TRANSACTION過程中通過from_parent入站到A1的transaction_stack中。

static void binder_transaction(struct binder_proc *proc,
                   struct binder_thread *thread,
                   struct binder_transaction_data *tr, int reply)
{
    ......
    // 分配binder transaction
    t = kzalloc(sizeof(*t), GFP_KERNEL);
    ......
    // 分配binder_work用於處理傳輸完成
    tcomplete = kzalloc(sizeof(*tcomplete), GFP_KERNEL);
    ......
    // 同步的非reply傳輸,設置當前線程爲from
    if (!reply && !(tr->flags & TF_ONE_WAY))
        t->from = thread;
    else
        t->from = NULL;
    t->sender_euid = proc->tsk->cred->euid;
    // 設置傳輸的目標進程和線程
    t->to_proc = target_proc;
    t->to_thread = target_thread;
    t->code = tr->code;
    t->flags = tr->flags;
    t->priority = task_nice(current);
    ......
    if (reply) {
        ......
    } else if (!(t->flags & TF_ONE_WAY)) {
        // 當前線程的傳輸入棧
        t->need_reply = 1;
        t->from_parent = thread->transaction_stack;
        thread->transaction_stack = t;
    } else {
    ......
    // 將傳輸添加到目標隊列中
    t->work.type = BINDER_WORK_TRANSACTION;
    list_add_tail(&t->work.entry, target_list);
    // 將傳輸完成添加到當前線程todo隊列中
    tcomplete->type = BINDER_WORK_TRANSACTION_COMPLETE;
    list_add_tail(&tcomplete->entry, &thread->todo);
    // 喚醒目標線程或進程
    if (target_wait)
        wake_up_interruptible(target_wait);
    return;
    ......
}

可以看到,在傳輸發起時先分配內存給T1_tr,之後對T1_tr的數據進行賦值。然後入棧,將T1_tr的from_parent指向A1的transaction_stack,在將transaction_stack指向T1_tr。如果A1中已有進行中的傳輸,則表明原有的傳輸依賴T1_tr完成才能繼續。在這個簡單的例子中,之前沒有傳輸發生,所以from_parent=null。入棧完成後需要將T1_tr掛到B1線程的todo隊列中,已便B1可以獲取到T1_tr,這時通過T1_tr的work完成的。

BR_TRANSACTION

在BR_TRANSACTION中,會將T1_tr入棧到B1的transaction_stack中。

static int binder_thread_read(struct binder_proc *proc,
                  struct binder_thread *thread,
                  binder_uintptr_t binder_buffer, size_t size,
                  binder_size_t *consumed, int non_block)
{
    ......
    while (1) {
        uint32_t cmd; 
        struct binder_transaction_data tr;
        struct binder_work *w;
        struct binder_transaction *t = NULL;
        
        // 獲取todo工作隊列
        if (!list_empty(&thread->todo))
            w = list_first_entry(&thread->todo, struct binder_work, entry);
        else if (!list_empty(&proc->todo) && wait_for_proc_work)
            w = list_first_entry(&proc->todo, struct binder_work, entry);
        else {
        ......
        switch (w->type) {
        // binder_transaction()將工作BINDER_WORK_TRANSACTION加入隊列後喚醒目標進程
        case BINDER_WORK_TRANSACTION: {
            // 通過work中獲取binder_transaction
            t = container_of(w, struct binder_transaction, work);
        } break;
        ......
        // 從隊列中移除當前工作事件
        list_del(&t->work.entry);
        t->buffer->allow_user_free = 1;
        if (cmd == BR_TRANSACTION && !(t->flags & TF_ONE_WAY)) {
            // 同步傳輸時,命令爲BR_TRANSACTION的情況下,將工作事件入棧
            t->to_parent = thread->transaction_stack;
            t->to_thread = thread;
            thread->transaction_stack = t;
        } else {
        ......
    }
    ......
}

BR_TRANSACTION處理傳輸事件的大致流程就是:B1獲取todo隊列進行處理,通過工作任務獲得到傳輸事件T1_tr,然後將T1_tr通過to_parent入棧到B1的transaction_stack中。

BC_REPLY

T1_tr的出棧是通過BC_REPLY完成的,同時在BC_REPLY過程中會創建新的傳輸事件(暫且稱爲T1_re)用於Reply。T1_re時沒有入棧動作的,因爲Reply時不需要後續處理的。

static void binder_pop_transaction(struct binder_thread *target_thread,
                   struct binder_transaction *t)
{
    // 通過from_parent來出棧,用於傳輸發起端出棧
    if (target_thread) {
        target_thread->transaction_stack =
            target_thread->transaction_stack->from_parent;
        t->from = NULL;
    }
    t->need_reply = 0;
    if (t->buffer)
        t->buffer->transaction = NULL;
    kfree(t);
    // 刪除TRANSACTION狀態
    binder_stats_deleted(BINDER_STAT_TRANSACTION);
}
......
static void binder_transaction(struct binder_proc *proc,
                   struct binder_thread *thread,
                   struct binder_transaction_data *tr, int reply)
{
    ......
    if (reply) {
        // 從當前線程中出棧
        in_reply_to = thread->transaction_stack;
        ......
        thread->transaction_stack = in_reply_to->to_parent;
        // 目標線程爲發起端線程
        target_thread = in_reply_to->from;
        ......
        target_proc = target_thread->proc;
    } else {
        ......
    }
    if (target_thread) {
        e->to_thread = target_thread->pid;
        target_list = &target_thread->todo;
        target_wait = &target_thread->wait;
    } else {
        ......
    }
    ......
    // reply傳輸的from爲空
    if (!reply && !(tr->flags & TF_ONE_WAY))
        t->from = thread;
    else
        t->from = NULL;
    ......
    if (reply) {
        // 從目標線程中出棧
        binder_pop_transaction(target_thread, in_reply_to);
    } else if (!(t->flags & TF_ONE_WAY)) {
        ......
    } else {
        ......
    }
    t->work.type = BINDER_WORK_TRANSACTION;
    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_interruptible(target_wait);
    return;
    ......
}

BC_REPLY是由B1發起的,所以當前線程的T1_tr出棧是通過to_parent完成。其目標線程A1是由統一的出棧函數binder_pop_transaction,通過from_parent完成。出棧完成後將T1_tr釋放掉。在BC_REPLY過程中,新建的傳輸事件t就是之前說的T1_re,用於傳輸Reply事件。可以看到T1_re並沒有入棧。
T1_re的釋放是在BR_REPLY中完的,這裏不再詳細分析。所有傳輸事件完成時都會釋放相應的事件。

傳輸流程

根據上面的源碼分析,可以將傳輸事件的變化用下圖表示。
biner_transaction.png

 進程間的反覆調用

通過上面對簡單傳輸的分析,可以清晰的看清傳輸事件的流程。接下分析兩個進程間的反覆調用,看看Binder事件是如何傳輸的。
首先Proc A向Proc B發起IPC傳輸T1,這時跟上面的例子相同,是Thread A1調用到Thread B1。B1收到BR_TRANSACTION後,在處理的過程中向Proc A發起IPC傳輸T2,這時Porc A是使用哪個線程來處理T2的?如果Proc A在這個處理過程中又向Proc B發起IPC傳輸T3,那麼將會發生什麼?看看源碼中是如何處理這種情況的。

static void binder_transaction(struct binder_proc *proc,
                   struct binder_thread *thread,
                   struct binder_transaction_data *tr, int reply)
{
    ......
    if (reply) {
    ......
    } else {
        ......
        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;
            }
        }
    }
    ......
}

尋找Binder對端線程的核心代碼就是上面這段。如果當前線程傳輸棧不爲空,則表明當前線程還有未完成的傳輸。然後沿着from_parent尋找是否有來自對端進程的傳輸,如果有就複用這個傳輸的線程來處理這個新發起的傳輸。這中複用線程的方式時合理的,因爲from_parent這條鏈表記錄了傳輸調用的流程,鏈表內的傳輸有依賴關係,鏈表中的一個節點上的傳輸依賴上一個節點傳輸的完成。所以鏈表中的節點放在同一線程中處理不會產生影響,並且可以節約線程。
在我們的例子中,T1的進程A就是T2的對端進程,所以T2將使用A1做爲目標線程。然後A1向進程B發起T3傳輸時,B1同樣會被選做目標線程。用一個圖來表示可能會更清晰些。

biner_transaction2.png

  1. A1->B1:A1->transaction=T1,B1->transaction=T1,T1->from->proc=A。
  2. B1->A1:A1->transaction=T2,B1->transaction=T2,T2->from->proc=B。T2->from_parent=T1。
  3. A1->B1:A1->transaction=T3,B1->transaction=T3,T3->from->proc=A。T3->from_parent=T2。

多進程的調用

兩個進程間調用的例子給人一個錯覺,好像from_parent與to_parent記錄着同一個傳輸,實際上並不是這樣。看一個多進程調用的例子加深理解一下。

biner_transaction3.png

  1. A1->B1:T1入棧A1和B1,T1->from->proc=A。
  2. B1->C1:T2入棧B1和C1,T2->from->proc=B,T2->from_parent=T1。
  3. C1->A1:T3入棧C1和A1,T3->from->proc=C,T3->from_parent=T2,T3->to_parent=T1。

這裏的關鍵點時傳輸T3時,C1是如何找到A1的。尋找流程還是上面那段代碼,沿着from_parent最終找到T1,T1->from=A1,決定了T3會從C1調用到A1。

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