Android筆記 - Binder之處理註冊Service組件請求

上篇文章以 MediaPlayerService 爲例,分析了 Service 通過 Binder 驅動發送註冊請求給 servicemanager 的過程。本文在此基礎上分析 servicemanager 如何處理註冊請求,以及如何反饋處理結果給 MediaPlayerService。

1. servicemanager 被喚醒

在上一篇文章 Binder之請求註冊Service組件 中,分析到 binder_transaction 函數會創建一個待處理事務 t(事務類型是 BINDER_WORK_TRANSACTION),並將其添加到 servicemanager 進程的待處理工作隊列 target_list 中,然後喚醒睡眠中的 servicemanager 進程,如下所示:

代碼路徑:linux/drivers/staging/android/binder.c

static void binder_transaction(struct binder_proc *proc,
                   struct binder_thread *thread,
                   struct binder_transaction_data *tr, int reply)
{
    ......
    list_add_tail(&t->work.entry, target_list);
    ....
    if (target_wait)
        wake_up_interruptible(target_wait);
    ......
}

Binder之守護進程servicemanager 這篇文章中,講到 servicemanager 進程通過 ioctl 系統調用檢查 Binder 驅動是否有進程間通信請求需要它來處理。如果沒有請求需要處理,那麼 servicemanager 進程會在 binder_thread_read 函數中調用 wait_event_freezable_exclusive 進入睡眠等待狀態。

代碼路徑:linux/drivers/staging/android/binder.c

static int binder_thread_read(struct binder_proc *proc,
                  struct binder_thread *thread,
                  void  __user *buffer, int size,
                  signed long *consumed, int non_block)
    ....

    if (wait_for_proc_work) {
        ......
        if (non_block) {
            if (!binder_has_proc_work(proc, thread))
                ret = -EAGAIN;
        } else
            ret = wait_event_freezable_exclusive(proc->wait, binder_has_proc_work(proc, thread));
    } else {
        ......
    }

當 servicemanager 進程被喚醒時,會通過 binder_has_proc_work 函數來檢查是否有新的請求需要處理,也就是檢查當前進程的待處理工作隊列 todo 是否爲空,如下所示:

代碼路徑:linux/drivers/staging/android/binder.c

static int binder_has_proc_work(struct binder_proc *proc,
                struct binder_thread *thread)
{
    return !list_empty(&proc->todo) ||
        (thread->looper & BINDER_LOOPER_STATE_NEED_RETURN);
}

如果檢測到待處理工作隊列 todo 不爲空,因此退出睡眠狀態,接下來處理待處理工作隊列中的進程間通信請求。

2. Binder 驅動處理待處理事務 - servicemanager 內核空間

servicemanager 退出睡眠後,繼續執行 binder_thread_read 函數,將待處理事務從 Binder 驅動轉發到 servicemanager 用戶空間。過程如下所示:

代碼路徑:linux/drivers/staging/android/binder.c

static int binder_thread_read(struct binder_proc *proc,
                  struct binder_thread *thread,
                  void  __user *buffer, int size,
                  signed long *consumed, int non_block)
{
    ......
    while (1) {
        uint32_t cmd;
        struct binder_transaction_data tr;
        struct binder_work *w;
        struct binder_transaction *t = NULL;

        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);                         [1]
        else {
            if (ptr - buffer == 4 && !(thread->looper & BINDER_LOOPER_STATE_NEED_RETURN))
                goto retry;
            break;
        }

        if (end - ptr < sizeof(tr) + 4)
            break;

        switch (w->type) {
        case BINDER_WORK_TRANSACTION: {
            t = container_of(w, struct binder_transaction, work);                                 [2]
        } break;
        ......
        }

        if (!t)
            continue;

        BUG_ON(t->buffer == NULL);
        if (t->buffer->target_node) {
            struct binder_node *target_node = t->buffer->target_node;
            tr.target.ptr = target_node->ptr;                                                    [3]
            tr.cookie =  target_node->cookie;                                                    [4]
            t->saved_priority = task_nice(current);
            if (t->priority < target_node->min_priority &&
                !(t->flags & TF_ONE_WAY))
                binder_set_nice(t->priority);
            else if (!(t->flags & TF_ONE_WAY) ||
                 t->saved_priority > target_node->min_priority)
                binder_set_nice(target_node->min_priority);
            cmd = BR_TRANSACTION;
        } else {
            ......
        }
        tr.code = t->code;
        tr.flags = t->flags;
        tr.sender_euid = t->sender_euid;

        if (t->from) {
            struct task_struct *sender = t->from->proc->tsk;
            tr.sender_pid = task_tgid_nr_ns(sender,                                               [5]
                            current->nsproxy->pid_ns);
        } else {
            tr.sender_pid = 0;
        }

        tr.data_size = t->buffer->data_size;
        tr.offsets_size = t->buffer->offsets_size;
        tr.data.ptr.buffer = (void *)t->buffer->data +
                    proc->user_buffer_offset;                                                     [6]
        tr.data.ptr.offsets = tr.data.ptr.buffer +
                    ALIGN(t->buffer->data_size, sizeof(void *));                                  [7]

        if (put_user(cmd, (uint32_t __user *)ptr))                                                [8]
            return -EFAULT;
        ptr += sizeof(uint32_t);
        if (copy_to_user(ptr, &tr, sizeof(tr)))                                                   [9]
            return -EFAULT;
        ptr += sizeof(tr);

        ......
        list_del(&t->work.entry);
        t->buffer->allow_user_free = 1;
        if (cmd == BR_TRANSACTION && !(t->flags & TF_ONE_WAY)) {
            t->to_parent = thread->transaction_stack;
            t->to_thread = thread;
            thread->transaction_stack = t;                                                       [10]
        } else {
            ......
        }
        break;
    }

    ......
    return 0;
}

整個過程實際上是初始化描述事務數據的結構體 binder_transaction_data,並通過 copy_to_user 函數將其傳遞給 servicemanager 用戶空間。

[1] 從待處理工作隊列 todo 中取出一個工作項 w。
[2] 由於工作項類型爲 BINDER_WORK_TRANSACTION,獲取待處理事務 t 作爲當前正在處理的事務。
[3] 由於 target_node 爲 binder_context_mgr_node,所以 target_node->ptr 值爲 NULL。
[4] 由於 target_node 爲 binder_context_mgr_node,所以 target_node->cookie 值爲 NULL。
[5] 將事務發起方的 pid 保存在 tr.sender_pid 中。
[6] 將通信數據 buffer 在內核空間的地址加上一個固定差值 proc->user_buffer_offset,得到通信數據 buffer 在 servicemanager 用戶空間的地址。
[7] 通過通信數據 buffer 在 servicemanager 用戶空間的地址,得到偏移數組在 servicemanager 用戶空間的地址。

拷貝有深拷貝和淺拷貝之分:深拷貝需要重新分配內存資源,並且將內容完整的拷貝到分配好的內存空間;淺拷貝只是複製指向同一內存空間的地址,不需要分配新的內存資源。上述過程通過淺拷貝,實現了內存在內核空間和用戶空間之間的共享,詳細過程可以參考 Binder之守護進程servicemanager 中的打開和映射 Binder 設備文件小節。

[8] 調用 put_user 系統函數將協議 BR_TRANSACTION 拷貝回 servicemanager 用戶空間。
[9] 調用 copy_to_user 系統函數將初始化好的結構體 binder_transaction_data 拷貝回 servicemanager 用戶空間。
[10] 將當前處理事務 t 添加到 thread->transaction_stack 中,下面流程中還會用到這個事務。

3. servicemanager 處理註冊請求 - servicemanager 用戶空間

binder_thread_read 函數執行完返回到 binder_ioctl 函數,然後 ioctl 系統調用返回到 binder_loop 函數中,也就是從 Binder 驅動重新回到了 servicemanager 用戶空間。接下來調用 binder_parse 函數解析從 Binder 驅動程序拷貝回來的 binder_transaction_data 結構體,將解析得到的 MediaPlayerService 名稱以及句柄值保存到結構體 svcinfo,然後將結構體 svcinfo 添加到 svclist 鏈表中。詳細過程請參考 Binder之守護進程servicemanager 中的 servicemanager 如何提供服務小節。

當 Client 進程需要獲取某個 Service 的代理對象時,servicemanager 根據服務名稱遍歷 svclist 鏈表,然後將對應的句柄值返回給 Client 進程,最後 Client 進程根據句柄值獲得 Service 的代理對象。

servicemanager 處理完註冊請求後,繼續調用 binder_send_reply 函數返回處理結果。binder_send_reply 函數定義如下:

代碼路徑:frameworks/native/cmds/servicemanager/binder.c

void binder_send_reply(struct binder_state *bs,
                       struct binder_io *reply,
                       void *buffer_to_free,
                       int status)
{
    struct {
        uint32_t cmd_free;
        void *buffer;
        uint32_t cmd_reply;
        struct binder_txn txn;
    } __attribute__((packed)) data;                                                               [1]

    data.cmd_free = BC_FREE_BUFFER;                                                               [2]
    data.buffer = buffer_to_free;
    data.cmd_reply = BC_REPLY;                                                                    [3]
    data.txn.target = 0;
    data.txn.cookie = 0;
    data.txn.code = 0;
    if (status) {
        data.txn.flags = TF_STATUS_CODE;
        data.txn.data_size = sizeof(int);
        data.txn.offs_size = 0;
        data.txn.data = &status;
        data.txn.offs = 0;
    } else {
        data.txn.flags = 0;
        data.txn.data_size = reply->data - reply->data0;
        data.txn.offs_size = ((char*) reply->offs) - ((char*) reply->offs0);
        data.txn.data = reply->data0;
        data.txn.offs = reply->offs0;
    }
    binder_write(bs, &data, sizeof(data));                                                        [4]
}

[1] 定義結構體 data 用於保存返回給 Binder 驅動的內容。其中變量 cmd_free 用於保存釋放內存的協議,指針 buffer 保存需要釋放的內存地址。變量 cmd_reply 用於保存返回協議,結構體 binder_txn 用於保存請求處理返回結果。
[2] 釋放內存協議爲 BC_FREE_BUFFER。註冊請求處理完後,需要釋放之前 Binder 驅動在 binder_transaction 函數中分配的用於保存通信數據的內存。
[3] 返回協議爲 BC_REPLY。
[4] 調用 binder_write 函數將結構體 data 傳遞給 Binder 驅動,binder_write 函數內部也是通過系統調用 ioctl(bs->fd, BINDER_WRITE_READ, &bwr) 來與 Binder 驅動進行交互。

4. Binder 驅動將註冊結果返回給 Service - servicemanager 內核空間

上述 ioctl 系統調用最終也會執行到 Binder 驅動的 binder_ioctl 函數。通過之前幾篇文章分析可知,binder_ioctl 中會調用 binder_thread_write 函數,後者又會調用 Binder 驅動的核心處理函數 binder_transaction,需要注意的是此時參數 reply 的值爲 true。接下來看看這個過程:

代碼路徑:linux/drivers/staging/android/binder.c

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

    ......
    if (reply) {
        in_reply_to = thread->transaction_stack;                                                  [1]
        ......
        thread->transaction_stack = in_reply_to->to_parent;                                       [2]
        target_thread = in_reply_to->from;                                                        [3]
        ......
        target_proc = target_thread->proc;                                                        [4]
    } else {
        ......
    }
    if (target_thread) {
        e->to_thread = target_thread->pid;
        target_list = &target_thread->todo;                                                       [5]
        target_wait = &target_thread->wait;                                                       [6]
    } else {
        target_list = &target_proc->todo;
        target_wait = &target_proc->wait;
    }
    e->to_proc = target_proc->pid;

    /* TODO: reuse incoming transaction for reply */
    t = kzalloc(sizeof(*t), GFP_KERNEL);
    if (t == NULL) {
        return_error = BR_FAILED_REPLY;
        goto err_alloc_t_failed;
    }
    binder_stats_created(BINDER_STAT_TRANSACTION);

    tcomplete = kzalloc(sizeof(*tcomplete), GFP_KERNEL);
    ......

    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);

    trace_binder_transaction(reply, t, target_node);

    t->buffer = binder_alloc_buf(target_proc, tr->data_size,
        tr->offsets_size, !reply && (t->flags & TF_ONE_WAY));
    if (t->buffer == NULL) {
        return_error = BR_FAILED_REPLY;
        goto err_binder_alloc_buf_failed;
    }
    t->buffer->allow_user_free = 0;
    t->buffer->debug_id = t->debug_id;
    t->buffer->transaction = t;
    t->buffer->target_node = target_node;
    trace_binder_transaction_alloc_buf(t->buffer);
    if (target_node)
        binder_inc_node(target_node, 1, 0, NULL);

    offp = (size_t *)(t->buffer->data + ALIGN(tr->data_size, sizeof(void *)));

    if (copy_from_user(t->buffer->data, tr->data.ptr.buffer, tr->data_size)) {
        ......
    }
    if (copy_from_user(offp, tr->data.ptr.offsets, tr->offsets_size)) {
        ......
    }

    off_end = (void *)offp + tr->offsets_size;
    for (; offp < off_end; offp++) {                                                              [7]
        ......
    }

    if (reply) {
        BUG_ON(t->buffer->async_transaction != 0);
        binder_pop_transaction(target_thread, in_reply_to);                                       [8]
    } 

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

[1] 初始化 in_reply_to 爲指向當前線程 thread(servicemanager 線程) 的事務堆棧 binder_transaction 的指針。前面 binder_thread_read 函數將處理事務加入到了 thread 的事務堆棧,因此 in_reply_to 指針實際指向了之前加入的事務。
[2] 將 in_reply_to 指向的事務從當前線程的事務堆棧中退棧。
[3] 根據 in_reply_to 初始化目標線程 target_thread 爲 MediaPlayerService 線程。
[4] 根據 target_thread 初始化目標進程 target_proc 爲 MediaPlayerService 進程。
[5] 初始化 target_list 爲目標線程 target_thread 的待處理工作隊列。
[6] 初始化 target_wait 爲目標線程 target_thread 的等待隊列。
[7] 由於返回結果數據中不存在 Binder 對象,因此不會進入 for 循環。
[8] 調用 binder_pop_transaction 函數將 in_reply_to 指向的事務從目標線程的事務堆棧中退棧。

這裏省略了與文章 Binder之請求註冊Service組件中分析binder_transaction 函數重複的內容。

和之前的分析一樣,最後會將待處理事務 t 和待完成工作項 tcomplete 分別添加到 target_list 和 thread->todo 中,因此待處理事務 t 將由 MediaPlayerService 來處理,而待完成工作項 tcomplete 將由 servicemanager 來處理,處理過程之前也都分析過。

至此,Service 註冊流程終於分析完成,下圖爲註冊流程所涉及的 MediaPlayerService,Binder 驅動和 servicemanager 三者之間的交互過程:
這裏寫圖片描述

參考文獻:
1. Android系統進程間通信(IPC)機制Binder中的Server啓動過程源代碼分析
2. Android Binder機制(六) addService詳解之請求的處理

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