細(xì)讀《深入理解 Android 內(nèi)核設(shè)計(jì)思想》(五)Binder 機(jī)制 [下]

對(duì)冗余挑揀重點(diǎn),對(duì)重點(diǎn)深入補(bǔ)充酬滤,輸出結(jié)構(gòu)清晰的精簡(jiǎn)版

  1. 深入 binder 驅(qū)動(dòng)內(nèi)部
    binder_ioctl
    binder_get_thread
    binder_ioctl_write_read
    binder_thread_write
    binder_transaction
    binder_thread_read
    小結(jié)
  2. binder Q&A
    如何找到目標(biāo)進(jìn)程 Binder 實(shí)體
    如何實(shí)現(xiàn) Binder 線程的睡眠與喚醒
  3. 最后

深入 binder 驅(qū)動(dòng)內(nèi)部

前兩篇文章都有提到 binder_ioctl 方法褥蚯,在 Binder 機(jī)制 [上] 中介紹了 binder_ioctl 支持的命令瘩扼;Binder 機(jī)制 [中] 中提到 IPCThreadState 會(huì)調(diào)用到 binder_ioctl 方法拣挪。

書中對(duì) binder 驅(qū)動(dòng)內(nèi)部調(diào)用的講解沒(méi)有分為較清晰的步驟昆烁,一口氣就是 20 頁(yè)篇幅的源碼詳解婶博,理解起來(lái)有些難度寻定,容易迷失塔插。在細(xì)讀了三四遍后歧强,終于感覺(jué)對(duì)整體有些掌握了栽燕,結(jié)合前面的學(xué)習(xí)與自己的理解赶诊,將一次 IPC 調(diào)用中 binder 驅(qū)動(dòng)的工作分為以下 5 步:

1.準(zhǔn)備數(shù)據(jù)璧南,根據(jù)命令分發(fā)給具體的方法去處理
2.找到目標(biāo)進(jìn)程的相關(guān)信息
3.將數(shù)據(jù)一次拷貝到目標(biāo)進(jìn)程所映射的物理內(nèi)存塊
4.記錄待處理的任務(wù)赤屋,喚醒目標(biāo)線程
5.調(diào)用線程進(jìn)入休眠
6.目標(biāo)進(jìn)程直接拿到數(shù)據(jù)進(jìn)行處理立镶,處理完后喚醒調(diào)用線程
7.調(diào)用線程返回處理結(jié)果

與上篇文章一樣仍以 getService() 為例,按照上面的工作步驟為脈絡(luò)类早,深入分析驅(qū)動(dòng)層中的執(zhí)行邏輯媚媒,徹底搞定 binder 驅(qū)動(dòng)!

binder_ioctl

在 IPCThreadState 中這樣調(diào)用了 binder_ioctl() 方法:

    ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr)

binder_ioctl() 方法中會(huì)根據(jù) BINDER_WRITE_READ涩僻、BINDER_SET_MAX_THREADS 等不同 cmd 轉(zhuǎn)調(diào)到不同的方法去執(zhí)行缭召,這里我們只關(guān)注 BINDER_WRITE_READ,簡(jiǎn)化后代碼如下:

static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg){
    int ret;
    //拿到調(diào)用進(jìn)程在 binder_open() 中記錄的 binder_proc
    struct binder_proc *proc = filp->private_data;
    struct binder_thread *thread;
    binder_lock(__func__);
    //獲取調(diào)用線程 binder_thread
    thread = binder_get_thread(proc);
    switch (cmd) {
    case BINDER_WRITE_READ:
        //處理 binder 數(shù)據(jù)讀寫,binder IPC 通信的核心邏輯
        ret = binder_ioctl_write_read(filp, cmd, arg, thread);
        if (ret)
            goto err;
        break;
    case BINDER_SET_MAX_THREADS:{...} //設(shè)置 binder 最大線程數(shù)
    case BINDER_SET_CONTEXT_MGR:{...} //設(shè)置 service 大管家逆日,即 ServiceManager
    case BINDER_THREAD_EXIT:{...} //binder 線程退出命令嵌巷,釋放相關(guān)資源
    case BINDER_VERSION: {...} //獲取 binder 驅(qū)動(dòng)版本號(hào)
    ...
}

Binder 機(jī)制 [上] 中詳細(xì)介紹過(guò) binder_open() 方法,它主要做了兩個(gè)工作:1.創(chuàng)建及初始化每個(gè)進(jìn)程獨(dú)有一份的室抽、用來(lái)存放 binder 相關(guān)數(shù)據(jù)的 binder_proc 結(jié)構(gòu)體搪哪,2.將 binder_proc 記錄起來(lái),方便后續(xù)使用坪圾。正是通過(guò) file 來(lái)記錄的:

static int binder_open(struct inode *nodp, struct file *filp){
    ...
    filp->private_data = proc;
    ...
}

拿到調(diào)用進(jìn)程后晓折,進(jìn)一步通過(guò) binder_get_thread() 方法拿到調(diào)用線程惑朦,然后就交給 binder_ioctl_write_read() 方法去執(zhí)行具體的 binder 數(shù)據(jù)讀寫了,可見(jiàn) binder_ioctl() 方法本身的邏輯非常簡(jiǎn)單漓概,將數(shù)據(jù) arg 透?jìng)髁顺鋈バ朽汀O旅娣謩e來(lái)看 binder_get_thread()、binder_ioctl_write_read() 這兩個(gè)方法垛耳。

binder_get_thread

static struct binder_thread *binder_get_thread(struct binder_proc *proc){
    struct binder_thread *thread = NULL;
    struct rb_node *parent = NULL;
    struct rb_node **p = &proc->threads.rb_node; //從 proc 中獲取紅黑樹(shù)根節(jié)點(diǎn)
    //查找 pid 等于當(dāng)前線程 id 的thread栅屏,該紅黑樹(shù)以 pid 大小為序存放
    while (*p) {
        parent = *p;
        thread = rb_entry(parent, struct binder_thread, rb_node);
        if (current->pid < thread->pid) //current->pid 是當(dāng)前調(diào)用線程的 id
            p = &(*p)->rb_left;
        else if (current->pid > thread->pid)
            p = &(*p)->rb_right;
        else
            break;
    }
    if (*p == NULL) {//如果沒(méi)有找到,則新創(chuàng)建一個(gè)
        thread = kzalloc(sizeof(*thread), GFP_KERNEL);
        if (thread == NULL)
            return NULL;
        binder_stats_created(BINDER_STAT_THREAD);
        thread->proc = proc;
        thread->pid = current->pid;
        init_waitqueue_head(&thread->wait);    //初始化等待隊(duì)列
        INIT_LIST_HEAD(&thread->todo);       //初始化待處理隊(duì)列
        rb_link_node(&thread->rb_node, parent, p);  //加入到 proc 的 threads 紅黑樹(shù)中
        rb_insert_color(&thread->rb_node, &proc->threads);
        thread->looper |= BINDER_LOOPER_STATE_NEED_RETURN;
        thread->return_error = BR_OK;
        thread->return_error2 = BR_OK;
    }
    return thread;
}

binder_thread 是用來(lái)描述線程的結(jié)構(gòu)體堂鲜,binder_get_thread() 方法中邏輯也很簡(jiǎn)單栈雳,首先從調(diào)用進(jìn)程 proc 中查找當(dāng)前線程是否已被記錄,如果找到就直接返回缔莲,否則新建一個(gè)返回哥纫,并記錄到 proc 中。也就是說(shuō)所有調(diào)用 binder_ioctl() 的線程痴奏,都會(huì)被記錄起來(lái)蛀骇。

binder_ioctl_write_read

此方法分為兩部分來(lái)看,首先是整體:

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; //用戶傳下來(lái)的數(shù)據(jù)賦值給 ubuf
    struct binder_write_read bwr;
    //把用戶空間數(shù)據(jù) ubuf 拷貝到 bwr
    if (copy_from_user(&bwr, ubuf, sizeof(bwr))) {
        ret = -EFAULT;
        goto out;
    }
    處理數(shù)據(jù)...
    //將讀寫后的數(shù)據(jù)寫回給用戶空間
    if (copy_to_user(ubuf, &bwr, sizeof(bwr))) {
        ret = -EFAULT;
        goto out;
    }
out:
    return ret;
}

起初看到 copy_from_user() 方法時(shí)難以理解读拆,因?yàn)樗雌饋?lái)是將我們要傳輸?shù)臄?shù)據(jù)拷貝到內(nèi)核空間了擅憔,但目前還沒(méi)有看到 server 端的任何線索,bwr 跟 server 端沒(méi)有映射關(guān)系檐晕,那后續(xù)再將 bwr 傳輸給 server 端的時(shí)候又要拷貝暑诸,這樣豈不是多次拷貝了?

其實(shí)這里的 copy_from_user() 方法并沒(méi)有拷貝要傳輸?shù)臄?shù)據(jù)辟灰,而僅是拷貝了持有傳輸數(shù)據(jù)內(nèi)存地址的 bwr个榕。后續(xù)處理數(shù)據(jù)時(shí)會(huì)根據(jù) bwr 信息真正的去拷貝要傳輸?shù)臄?shù)據(jù)。

處理完數(shù)據(jù)后芥喇,會(huì)將處理結(jié)果體現(xiàn)在 bwr 中西采,然后返回給用戶空間處理。那是如何處理數(shù)據(jù)的呢继控?所謂的處理數(shù)據(jù)械馆,就是對(duì)數(shù)據(jù)的讀寫而已:

    if (bwr.write_size > 0) {//寫數(shù)據(jù)
        ret = binder_thread_write(proc, 
             thread,
             bwr.write_buffer, bwr.write_size,
             &bwr.write_consumed);
        trace_binder_write_done(ret);
        if (ret < 0) { //寫失敗
            bwr.read_consumed = 0;
            if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
                ret = -EFAULT;
            goto out;
        }
    }
    if (bwr.read_size > 0) {//讀數(shù)據(jù)
        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);
        if (!list_empty(&proc->todo))
            wake_up_interruptible(&proc->wait);//喚醒等待狀態(tài)的線程
        if (ret < 0) { //讀失敗
            if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
                ret = -EFAULT;
            goto out;
        }
    }

可見(jiàn) binder 驅(qū)動(dòng)內(nèi)部依賴用戶空間的 binder_write_read 決定是要讀取還是寫入數(shù)據(jù):其內(nèi)部變量 read_size>0 則代表要讀取數(shù)據(jù),write_size>0 代表要寫入數(shù)據(jù)湿诊,若都大于 0 則先寫入狱杰,后讀取。

至此焦點(diǎn)應(yīng)該集中在 binder_thread_write() 和 binder_thread_read()厅须,下面分析這兩個(gè)方法。

binder_thread_write

在上面的 binder_ioctl_write_read() 方法中調(diào)用 binder_thread_write() 時(shí)傳入了 bwr.write_buffer食棕、bwr.write_size 等朗和,先搞清楚這些參數(shù)是什么错沽。

最開(kāi)始是在用戶空間 IPCThreadState 的 transact() 中通過(guò) writeTransactionData() 方法創(chuàng)建數(shù)據(jù)并寫入 mOut 的,writeTransactionData 方法代碼如下:

status_t IPCThreadState::writeTransactionData(int32_t cmd, uint32_t binderFlags,
    int32_t handle, uint32_t code, const Parcel& data, status_t* statusBuffer){
    binder_transaction_data tr; //到驅(qū)動(dòng)內(nèi)部后會(huì)取出此結(jié)構(gòu)體進(jìn)行處理
    tr.target.ptr = 0;
    tr.target.handle = handle; //目標(biāo) server 的 binder 的句柄
    tr.code = code; //請(qǐng)求碼眶拉,getService() 服務(wù)對(duì)應(yīng)的是 GET_SERVICE_TRANSACTION
    tr.flags = binderFlags;
    tr.cookie = 0;
    tr.sender_pid = 0;
    tr.sender_euid = 0;
    const status_t err = data.errorCheck(); //驗(yàn)證數(shù)據(jù)合理性
    if (err == NO_ERROR) {
        tr.data_size = data.ipcDataSize(); //傳輸數(shù)據(jù)大小
        tr.data.ptr.buffer = data.ipcData(); //傳輸數(shù)據(jù)
        tr.offsets_size = data.ipcObjectsCount()*sizeof(binder_size_t);
        tr.data.ptr.offsets = data.ipcObjects();
    } else {...}
    mOut.writeInt32(cmd); // transact 傳入的 cmd 是 BC_TRANSACTION
    mOut.write(&tr, sizeof(tr)); //打包成 binder_transaction_data
    return NO_ERROR;
}

然后在 IPCThreadState 的 talkWithDriver() 方法中對(duì) write_buffer 賦值:

    bwr.write_buffer = (uintptr_t)mOut.data();

搞清楚了數(shù)據(jù)的來(lái)源千埃,再來(lái)看 binder_thread_write() 方法,binder_thread_write() 方法中處理了大量的 BC_XXX 命令忆植,代碼很長(zhǎng)放可,這里我們只關(guān)注當(dāng)前正在處理的 BC_TRANSACTION 命令,簡(jiǎn)化后代碼如下:

static int binder_thread_write(struct binder_proc *proc,
        struct binder_thread *thread,
        binder_uintptr_t binder_buffer, size_t size,
        binder_size_t *consumed){
    uint32_t cmd;
    void __user *buffer = (void __user *)(uintptr_t)binder_buffer; //就是 bwr.write_buffer
    void __user *ptr = buffer + *consumed; //數(shù)據(jù)起始地址
    void __user *end = buffer + size; //數(shù)據(jù)結(jié)束地址
    while (ptr < end && thread->return_error == BR_OK) { //可能有多個(gè)命令及對(duì)應(yīng)數(shù)據(jù)要處理朝刊,所以要循環(huán)
        if (get_user(cmd, (uint32_t __user *)ptr)) // 讀取一個(gè) cmd
            return -EFAULT;
        ptr += sizeof(uint32_t); //跳過(guò) cmd 所占的空間耀里,指向要處理的數(shù)據(jù)
        switch (cmd) {
            case BC_TRANSACTION:
            case BC_REPLY: {
                 struct binder_transaction_data tr; //與 writeTransactionData 中準(zhǔn)備的數(shù)據(jù)結(jié)構(gòu)體對(duì)應(yīng)
                 if (copy_from_user(&tr, ptr, sizeof(tr))) //拷貝到內(nèi)核空間 tr 中
                    return -EFAULT;
                 ptr += sizeof(tr); //跳過(guò)數(shù)據(jù)所占空間,指向下一個(gè) cmd
                 binder_transaction(proc, thread, &tr, cmd == BC_REPLY); //處理數(shù)據(jù)
                 break;
            }
            處理其他 BC_XX 命令...
        }
    *consumed = ptr - buffer; //被寫入處理消耗的數(shù)據(jù)量拾氓,對(duì)應(yīng)于用戶空間的 bwr.write_consumed

binder_thread_write() 中從 bwr.write_buffer 中取出了 cmd 和 cmd 對(duì)應(yīng)的數(shù)據(jù)冯挎,進(jìn)一步交給 binder_transaction() 處理,需要注意的是咙鞍,BC_TRANSACTION房官、BC_REPLY 這兩個(gè)命令都是由 binder_transaction() 處理的。

簡(jiǎn)單梳理一下续滋,由 binder_ioctl -> binder_ioctl_write_read -> binder_thread_write 翰守,到目前為止還只是在準(zhǔn)備數(shù)據(jù),沒(méi)有看到跟目標(biāo)進(jìn)程相關(guān)的任何處理疲酌,都屬于 "準(zhǔn)備數(shù)據(jù)潦俺,根據(jù)命令分發(fā)給具體的方法去處理" 第 1 個(gè)工作。而到此為止徐勃,第 1 個(gè)工作便結(jié)束事示,下一步的 binder_transaction() 方法終于要開(kāi)始后面的工作了。

binder_transaction

binder_transaction() 方法中代碼較長(zhǎng)僻肖,先總結(jié)它干了哪些事:對(duì)應(yīng)開(kāi)頭列出的工作肖爵,此方法中做了非常關(guān)鍵的 2-4 步:

  • 找到目標(biāo)進(jìn)程的相關(guān)信息
  • 將數(shù)據(jù)一次拷貝到目標(biāo)進(jìn)程所映射的物理內(nèi)存塊
  • 記錄待處理的任務(wù),喚醒目標(biāo)線程

以這些工作為線索臀脏,將代碼分為對(duì)應(yīng)的部分來(lái)看劝堪,首先是找到目標(biāo)進(jìn)程的相關(guān)信息,簡(jiǎn)化后代碼如下:

static void binder_transaction(struct binder_proc *proc,
                   struct binder_thread *thread,
                   struct binder_transaction_data *tr, int reply){
    struct binder_transaction *t; //用于描述本次 server 端要進(jìn)行的 transaction
    struct binder_work *tcomplete; //用于描述當(dāng)前調(diào)用線程未完成的 transaction
    binder_size_t *offp, *off_end;
    struct binder_proc *target_proc; //目標(biāo)進(jìn)程
    struct binder_thread *target_thread = NULL; //目標(biāo)線程
    struct binder_node *target_node = NULL; //目標(biāo) binder 節(jié)點(diǎn)
    struct list_head *target_list; //目標(biāo) TODO 隊(duì)列
    wait_queue_head_t *target_wait; //目標(biāo)等待隊(duì)列
    if(reply){ 
        in_reply_to = thread->transaction_stack;
        ...處理 BC_REPLY揉稚,暫不關(guān)注
    }else{ 
        //處理 BC_TRANSACTION
        if (tr->target.handle) { //handle 不為 0
            struct binder_ref *ref;
            //根據(jù) handle 找到目標(biāo) binder 實(shí)體節(jié)點(diǎn)的引用
            ref = binder_get_ref(proc, tr->target.handle);
            target_node = ref->node; //拿到目標(biāo) binder 節(jié)點(diǎn)
        } else { 
            // handle 為 0 則代表目標(biāo) binder 是 service manager
            // 對(duì)于本次調(diào)用來(lái)說(shuō)目標(biāo)就是 service manager
            target_node = binder_context_mgr_node;
        }
    }
    target_proc = target_node->proc; //拿到目標(biāo)進(jìn)程
    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; //拿到目標(biāo)線程
            tmp = tmp->from_parent;
        }
    }
    target_list = &target_thread->todo; //拿到目標(biāo) TODO 隊(duì)列
    target_wait = &target_thread->wait; //拿到目標(biāo)等待隊(duì)列

binder_transaction秒啦、binder_work 等結(jié)構(gòu)體在上一篇中有介紹,上面代碼中也詳細(xì)注釋了它們的含義搀玖。比較關(guān)鍵的是 binder_get_ref() 方法余境,它是如何找到目標(biāo) binder 的呢?這里暫不延伸,下文再做分析芳来。

繼續(xù)看 binder_transaction() 方法的第 2 個(gè)工作含末,將數(shù)據(jù)一次拷貝到目標(biāo)進(jìn)程所映射的物理內(nèi)存塊

    t = kzalloc(sizeof(*t), GFP_KERNEL); //創(chuàng)建用于描述本次 server 端要進(jìn)行的 transaction
    tcomplete = kzalloc(sizeof(*tcomplete), GFP_KERNEL); //創(chuàng)建用于描述當(dāng)前調(diào)用線程未完成的 transaction
    if (!reply && !(tr->flags & TF_ONE_WAY)) //將信息記錄到 t 中:
        t->from = thread; //記錄調(diào)用線程
    else
        t->from = NULL;
    t->sender_euid = task_euid(proc->tsk);
    t->to_proc = target_proc; //記錄目標(biāo)進(jìn)程
    t->to_thread = target_thread; //記錄目標(biāo)線程
    t->code = tr->code; //記錄請(qǐng)求碼,getService() 對(duì)應(yīng)的是 GET_SERVICE_TRANSACTION
    t->flags = tr->flags;
    //實(shí)際申請(qǐng)目標(biāo)進(jìn)程所映射的物理內(nèi)存即舌,準(zhǔn)備接收要傳輸?shù)臄?shù)據(jù)
    t->buffer = binder_alloc_buf(target_proc, tr->data_size,
        tr->offsets_size, !reply && (t->flags & TF_ONE_WAY));
    //申請(qǐng)到 t->buffer 后佣盒,從用戶空間將數(shù)據(jù)拷貝進(jìn)來(lái),這里就是一次拷貝數(shù)據(jù)的地方M缒簟肥惭!
    if (copy_from_user(t->buffer->data, (const void __user *)(uintptr_t)
        tr->data.ptr.buffer, tr->data_size)) {
        return_error = BR_FAILED_REPLY;
        goto err_copy_data_failed;
    }

為什么在拷貝之前要先申請(qǐng)物理內(nèi)存呢?在 Binder 機(jī)制 [上] 中介紹 binder_mmap() 時(shí)詳細(xì)分析過(guò)紊搪,雖然 binder_mmap() 直接映射了 (1M-8K) 的虛擬內(nèi)存蜜葱,但卻只申請(qǐng)了 1 頁(yè)的物理頁(yè)面,等到實(shí)際使用時(shí)再動(dòng)態(tài)申請(qǐng)嗦明。也就是說(shuō)笼沥,在 binder_ioctl() 實(shí)際傳輸數(shù)據(jù)的時(shí)候,再通過(guò) binder_alloc_buf() 方法去申請(qǐng)物理內(nèi)存娶牌。

至此已經(jīng)將要傳輸?shù)臄?shù)據(jù)拷貝到目標(biāo)進(jìn)程奔浅,目標(biāo)進(jìn)程可以直接讀取到了,接下來(lái)要做的就是將目標(biāo)進(jìn)程要處理的任務(wù)記錄起來(lái)诗良,然后喚醒目標(biāo)進(jìn)程汹桦,這樣在目標(biāo)進(jìn)程被喚醒后,才能知道要處理什么任務(wù)鉴裹。

最后來(lái)看 binder_transaction() 方法的第 3 個(gè)工作舞骆,記錄待處理的任務(wù),喚醒目標(biāo)線程

    if (reply) { //如果是處理 BC_REPLY径荔,pop 出來(lái)?xiàng)m斢涗浀?transaction(實(shí)際上是刪除鏈表頭元素)
        binder_pop_transaction(target_thread, in_reply_to);
    } else if (!(t->flags & TF_ONE_WAY)) {
        //如果不是 oneway督禽,將 server 端要處理的 transaction 記錄到當(dāng)前調(diào)用線程
        t->need_reply = 1;
        t->from_parent = thread->transaction_stack;
        thread->transaction_stack = t;
    } else {
        ...暫不關(guān)注 oneway 的情況
    }
    t->work.type = BINDER_WORK_TRANSACTION;
    list_add_tail(&t->work.entry, target_list); //加入目標(biāo)的處理隊(duì)列中
    tcomplete->type = BINDER_WORK_TRANSACTION_COMPLETE; //設(shè)置調(diào)用線程待處理的任務(wù)類型
    list_add_tail(&tcomplete->entry, &thread->todo); //記錄調(diào)用線程待處理的任務(wù)
    if (target_wait)
        wake_up_interruptible(target_wait); //喚醒目標(biāo)線程

再次梳理一下,至此已經(jīng)完成了前四個(gè)工作:

1.準(zhǔn)備數(shù)據(jù)总处,根據(jù)命令分發(fā)給具體的方法去處理
2.找到目標(biāo)進(jìn)程的相關(guān)信息
3.將數(shù)據(jù)一次拷貝到目標(biāo)進(jìn)程所映射的物理內(nèi)存塊
4.記錄待處理的任務(wù)狈惫,喚醒目標(biāo)線程

其中第 1 個(gè)工作涉及到的方法為 binder_ioctl() -> binder_get_thread() -> binder_ioctl_write_read() -> binder_thread_write() ,主要是一些數(shù)據(jù)的準(zhǔn)備和方法轉(zhuǎn)跳鹦马,沒(méi)做什么實(shí)質(zhì)的事情胧谈。而 binder_transaction() 方法中做了非常重要的 2-4 工作。

剩下的工作還有:

5.調(diào)用線程進(jìn)入休眠
6.目標(biāo)進(jìn)程直接拿到數(shù)據(jù)進(jìn)行處理荸频,處理完后喚醒調(diào)用線程
7.調(diào)用線程返回處理結(jié)果

可以想到菱肖,5 和 6 其實(shí)沒(méi)有時(shí)序上的限制,而是并行處理的旭从。下面先來(lái)看第 5 個(gè)工作:調(diào)用線程是如何進(jìn)入休眠等待服務(wù)端執(zhí)行結(jié)果的稳强。

binder_thread_read

在喚醒目標(biāo)線程后场仲,調(diào)用線程就執(zhí)行完 binder_thread_write() 寫完了數(shù)據(jù),返回到 binder_ioctl_write_read() 方法中键袱,接著執(zhí)行 binder_thread_read() 方法燎窘。

而調(diào)用線程的休眠就是在此方法中觸發(fā)的摹闽,下面將 binder_thread_read() 分為兩部分來(lái)看蹄咖,首先是是否阻塞當(dāng)前線程的判斷邏輯:

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){
    void __user *buffer = (void __user *)(uintptr_t)binder_buffer; //bwr.read_buffer
    void __user *ptr = buffer + *consumed; //數(shù)據(jù)起始地址
    void __user *end = buffer + size; //數(shù)據(jù)結(jié)束地址
    if (*consumed == 0) {
        if (put_user(BR_NOOP, (uint32_t __user *)ptr))
            return -EFAULT;
        ptr += sizeof(uint32_t);
    }
    //是否要準(zhǔn)備睡眠當(dāng)前線程
    wait_for_proc_work = thread->transaction_stack == NULL &&
            list_empty(&thread->todo);
    if (wait_for_proc_work) {
        if (non_block) { //non_block 為 false
            if (!binder_has_proc_work(proc, thread))
                ret = -EAGAIN;
        } else
            ret = wait_event_freezable_exclusive(proc->wait, 
                        binder_has_proc_work(proc, thread));
    } else {
        if (non_block) { //non_block 為 false
            if (!binder_has_thread_work(thread))
                ret = -EAGAIN;
        } else
            ret = wait_event_freezable(thread->wait, 
                        binder_has_thread_work(thread));
    }

consumed 即用戶空間的 bwr.read_consumed,這里是 0 付鹿,所以將一個(gè) BR_NOOP 加到了 ptr 中澜汤。

怎么理解 wait_for_proc_work 條件呢?在 binder_transaction() 方法中將 server 端要處理的 transaction 記錄到了當(dāng)前調(diào)用線程 thread->transaction_stack 中舵匾;將當(dāng)前調(diào)用線程待處理的任務(wù)記錄到了 thread->todo 中俊抵。所以這里的 thread->transaction_stack 和 thread->todo 都不為空,wait_for_proc_work 為 false坐梯,代表不準(zhǔn)備阻塞當(dāng)前線程徽诲。

但 wait_for_proc_work 并不是決定是否睡眠的最終條件,接著往下看吵血,其中 non_block 恒為 false谎替,那是否要睡眠當(dāng)前線程就取決于 binder_has_thread_work() 的返回值,binder_has_thread_work() 方法如下:

static int binder_has_thread_work(struct binder_thread *thread){
    return !list_empty(&thread->todo) || thread->return_error != BR_OK ||
        (thread->looper & BINDER_LOOPER_STATE_NEED_RETURN);
}

thread->todo 不為空蹋辅,所以 binder_has_thread_work() 返回 true钱贯,當(dāng)前調(diào)用線程不進(jìn)入休眠,繼續(xù)往下執(zhí)行侦另。你可能會(huì)有疑問(wèn)秩命,不是說(shuō)調(diào)用線程的休眠就是在 binder_thread_read() 方法中觸發(fā)的嗎?確實(shí)是褒傅,只不過(guò)不是本次弃锐,先接著分析 binder_thread_read() 繼續(xù)往下要執(zhí)行的邏輯:

struct binder_work *w;
w = list_first_entry(&thread->todo, struct binder_work,entry);
switch (w->type) {
    case BINDER_WORK_TRANSACTION_COMPLETE: {
        cmd = BR_TRANSACTION_COMPLETE;
        if (put_user(cmd, (uint32_t __user *)ptr))
            return -EFAULT;
        ptr += sizeof(uint32_t);
        binder_stat_br(proc, thread, cmd);
        list_del(&w->entry); //刪除 binder_work 在 thread->todo 中的引用
        kfree(w);
    }
    case BINDER_WORK_NODE{...}
    case BINDER_WORK_DEAD_BINDER{...}
    ...

在上面 binder_transaction() 方法最后,將 BINDER_WORK_TRANSACTION_COMPLETE 類型的 binder_work 加入到 thread->todo 中殿托。而這里就是對(duì)這個(gè) binder_work 進(jìn)行處理霹菊,將一個(gè) BR_TRANSACTION_COMPLETE 命令加到了 ptr 中。

梳理一下目前的邏輯碌尔,至此已經(jīng)順序執(zhí)行完 binder_thread_write()浇辜、binder_thread_read() 方法,并且在 binder_thread_read() 中往用戶空間傳輸了兩個(gè)命令:BR_NOOP 和 BR_TRANSACTION_COMPLETE唾戚。

本次 binder_ioctl() 調(diào)用就執(zhí)行完了柳洋,然后會(huì)回到 IPCThreadState 中,上一篇文章中詳細(xì)分析了 IPCThreadState 中的代碼叹坦,這里就不再展開(kāi)熊镣,簡(jiǎn)單概括一下后續(xù)執(zhí)行的邏輯:

mIn 中有 BR_NOOP 和 BR_TRANSACTION_COMPLETE 兩個(gè)命令,首先處理 BR_NOOP 命令,此命令什么也沒(méi)做绪囱,由于 talkWithDriver() 處于 while 循環(huán)中测蹲,會(huì)再一次進(jìn)入 talkWithDriver(),但因?yàn)榇藭r(shí) mIn 中還有數(shù)據(jù)沒(méi)讀完鬼吵,不會(huì)調(diào)用 binder_ioctl()扣甲。

然后處理 BR_TRANSACTION_COMPLETE 命令,如果是 oneway 就直接結(jié)束本次 IPC 調(diào)用齿椅,否則再一次進(jìn)入 talkWithDriver()琉挖,第二次進(jìn)入 talkWithDriver 時(shí),bwr.write_size = 0涣脚,bwr.read_size > 0示辈,所以會(huì)第二次調(diào)用 binder_ioctl() 方法。第二次執(zhí)行 binder_ioctl() 時(shí)遣蚀,bwr.write_size = 0矾麻,bwr.read_size > 0,所以不會(huì)再執(zhí)行 binder_thread_write() 方法芭梯,而只執(zhí)行 binder_thread_read() 方法险耀。

第二次執(zhí)行 binder_thread_read() 時(shí),thread->todo 已經(jīng)被處理為空粥帚,但是 thread->transaction_stack 還不為空胰耗,wait_for_proc_work 仍然為 false,但最終決定是否要休眠的條件成立了: binder_has_thread_work(thread) 返回 false芒涡,由此當(dāng)前調(diào)用線程通過(guò) wait_event_freezable() 進(jìn)入休眠柴灯。

小結(jié)

至此還剩下兩個(gè)工作:

6.目標(biāo)進(jìn)程直接拿到數(shù)據(jù)進(jìn)行處理,處理完后喚醒調(diào)用線程
7.調(diào)用線程返回處理結(jié)果

但是已經(jīng)不用再看代碼了费尽,因?yàn)樯鲜龇椒ㄒ呀?jīng)覆蓋了剩下的工作赠群。對(duì)于 getService() 來(lái)說(shuō),目標(biāo)進(jìn)程就是 Service Manager旱幼,相關(guān)的代碼在 Binder 機(jī)制 [上] 也已經(jīng)做過(guò)詳細(xì)的分析查描。用圖來(lái)概括整體的工作。調(diào)用進(jìn)程邏輯:

Service Manager 端邏輯:

本節(jié)完整的分析了一次 IPC 調(diào)用中 binder 驅(qū)動(dòng)內(nèi)部具體的執(zhí)行邏輯柏卤,此部分也是 binder 機(jī)制中最難的冬三,而將最難的部分掌握后,可以極大的提高信心缘缚。

想要完全掌握本節(jié)內(nèi)容勾笆,前提是必須對(duì)前兩篇 binder 文章已經(jīng)熟悉,因?yàn)?binder 驅(qū)動(dòng)和用戶空間的 IPCThreadState 以及 servicemanager 關(guān)聯(lián)是十分緊密的桥滨,如果沒(méi)有掌握窝爪,是無(wú)法真正理清本節(jié)內(nèi)容的弛车。

binder Q&A

在上面分析 binder 驅(qū)動(dòng)代碼過(guò)程中,主要是根據(jù)文章開(kāi)頭的七個(gè)工作為線索蒲每,為了不偏離主線纷跛,在遇到比較重要的知識(shí)點(diǎn)時(shí)都沒(méi)做延伸,這里以 Q&A 的方式補(bǔ)充分析邀杏。

如何找到目標(biāo)進(jìn)程 Binder 實(shí)體

servicemanager 的 binder 實(shí)體固定為 binder_context_mgr_node贫奠,直接返回即可。如何獲取其他 binder 實(shí)體呢淮阐?在上面的 binder_transaction() 方法中是這樣獲取目標(biāo) binder 的:

    //根據(jù) handle 找到目標(biāo) binder 實(shí)體節(jié)點(diǎn)的引用
    ref = binder_get_ref(proc, tr->target.handle);

binder_get_ref() 方法如下:

static struct binder_ref *binder_get_ref(struct binder_proc *proc,
                uint32_t desc){
    struct rb_node *n = proc->refs_by_desc.rb_node; //取紅黑樹(shù)根結(jié)點(diǎn)
    struct binder_ref *ref;
    while (n) { //遍歷查詢指定 desc(handle) 值的 binder 實(shí)體
        ref = rb_entry(n, struct binder_ref, rb_node_desc);
        if (desc < ref->desc)
            n = n->rb_left;
        else if (desc > ref->desc)
            n = n->rb_right;
        else
            return ref;
    }
    return NULL;
}

可見(jiàn)是在調(diào)用進(jìn)程的 binder_proc 結(jié)構(gòu)體中獲取到的叮阅,那目標(biāo) binder 實(shí)體又是什么時(shí)候存儲(chǔ)到調(diào)用進(jìn)程中的呢刁品?

對(duì)于實(shí)名的 Server泣特,當(dāng)它利用 addService() 把自身加到 ServiceManager 中時(shí),會(huì)"路過(guò)" binder 驅(qū)動(dòng)挑随,binder 驅(qū)動(dòng)就會(huì)把這一 binder 實(shí)體記錄到 ServiceManager 的 binder_proc->refs_by_desc 和 binder_proc->refs_by_node 中状您。

當(dāng)一個(gè) Binder Client 通過(guò) getService() 向 ServiceManager 發(fā)起查詢時(shí),ServiceManager 就可以準(zhǔn)確的告訴 Client 目標(biāo) Binder 實(shí)體兜挨,將其也記錄到 Client 的 binder_proc->refs_by_desc 和 binder_proc->refs_by_node 中膏孟。

上面說(shuō)的是通過(guò) addService() 將自身注冊(cè)到 Service Manager 中的 Server,任何 Binder Client 都能通過(guò) Service Manager 獲取到拌汇,這種叫做 "實(shí)名" Server柒桑。 而 Android 中還存在另一種 Binder Server,并不在 Service Manager 中注冊(cè)噪舀,比如我們自定義的 Service魁淳,這種可以叫做 "匿名" Server。

如何找到一個(gè)匿名 Server 的 binder 實(shí)體呢与倡?匿名 Server 一般要通過(guò)其他實(shí)名 Server 為中介來(lái)傳遞界逛,比如 IWindowSession 是靠 WindowManagerService 來(lái)傳遞的,當(dāng) Binder Client 調(diào)用 openSession 真正生成一個(gè) Session 對(duì)象纺座,這個(gè)對(duì)象作為 reply 第一次 "路過(guò)" binder 驅(qū)動(dòng)時(shí)息拜,就會(huì)被記錄到 Client 的 binder_proc->refs_by_desc 和 binder_proc->refs_by_node 中。

如何實(shí)現(xiàn) Binder 線程的睡眠與喚醒

答案在上文中已經(jīng)有很詳細(xì)的分析了净响,這里再概括一下:

喚醒:在 binder_transaction() 方法中寫完數(shù)據(jù)后少欺,通過(guò) wake_up_interruptible(target_wait) 喚醒目標(biāo)線程

睡眠:在 binder_thread_read() 中,通過(guò) wait_event_freezable_exclusive() 或 wait_event_freezable() 調(diào)用進(jìn)入睡眠

最后

最后再對(duì) binder 的整體架構(gòu)做一個(gè)簡(jiǎn)要的概述馋贤。對(duì)于一個(gè)比較典型的赞别、兩個(gè)應(yīng)用之間的 IPC 通信流程而言:

image.png

Client 通過(guò) ServiceManager 或 AMS 獲取到的遠(yuǎn)程 binder 實(shí)體,一般會(huì)用 Proxy 做一層封裝掸掸,比如 ServiceManagerProxy氯庆、 AIDL 生成的 Proxy 類蹭秋。而被封裝的遠(yuǎn)程 binder 實(shí)體是一個(gè) BinderProxy

BpBinder 和 BinderProxy 其實(shí)是一個(gè)東西:遠(yuǎn)程 binder 實(shí)體堤撵,只不過(guò)一個(gè) Native 層仁讨、一個(gè) Java 層,BpBinder 內(nèi)部持有了一個(gè) binder 句柄值 handle实昨。

ProcessState 是進(jìn)程單例洞豁,負(fù)責(zé)打開(kāi) Binder 驅(qū)動(dòng)設(shè)備及 mmap;IPCThreadState 為線程單例荒给,負(fù)責(zé)與 binder 驅(qū)動(dòng)進(jìn)行具體的命令通信丈挟。

由 Proxy 發(fā)起 transact() 調(diào)用,會(huì)將數(shù)據(jù)打包到 Parcel 中志电,層層向下調(diào)用到 BpBinder 曙咽,在 BpBinder 中調(diào)用 IPCThreadState 的 transact() 方法并傳入 handle 句柄值,IPCThreadState 再去執(zhí)行具體的 binder 命令挑辆。

由 binder 驅(qū)動(dòng)到 Server 的大概流程就是:Server 通過(guò) IPCThreadState 接收到 Client 的請(qǐng)求后例朱,層層向上,最后回調(diào)到 Stub 的 onTransact() 方法鱼蝉。

當(dāng)然這不代表所有的 IPC 流程洒嗤,比如 Service Manager 作為一個(gè) Server 時(shí),便沒(méi)有上層的封裝魁亦,也沒(méi)有借助 IPCThreadState渔隶,而是初始化后通過(guò) binder_loop() 方法直接與 binder 驅(qū)動(dòng)通信的。

鏈接:細(xì)讀《深入理解 Android 內(nèi)核設(shè)計(jì)思想》系列

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末洁奈,一起剝皮案震驚了整個(gè)濱河市间唉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌睬魂,老刑警劉巖终吼,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異氯哮,居然都是意外死亡际跪,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門喉钢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)姆打,“玉大人,你說(shuō)我怎么就攤上這事肠虽♂O罚” “怎么了税课?”我有些...
    開(kāi)封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵痊剖,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我垒玲,道長(zhǎng),這世上最難降的妖魔是什么合愈? 我笑而不...
    開(kāi)封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮佛析,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘寸莫。我一直安慰自己,他們只是感情好储狭,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布互婿。 她就那樣靜靜地躺著,像睡著了一般辽狈。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上呛牲,一...
    開(kāi)封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天刮萌,我揣著相機(jī)與錄音,去河邊找鬼娘扩。 笑死着茸,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的琐旁。 我是一名探鬼主播涮阔,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼灰殴!你這毒婦竟也來(lái)了敬特?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤牺陶,失蹤者是張志新(化名)和其女友劉穎伟阔,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體掰伸,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡皱炉,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了狮鸭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片合搅。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡多搀,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出灾部,到底是詐尸還是另有隱情酗昼,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布梳猪,位于F島的核電站麻削,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏呛哟。R本人自食惡果不足惜扫责,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一逃呼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧苏揣,春花似錦平匈、人聲如沸藏古。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)厂捞。三九已至,卻和暖如春饲嗽,著一層夾襖步出監(jiān)牢的瞬間貌虾,已是汗流浹背裙犹。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留袄膏,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓码党,卻偏偏與公主長(zhǎng)得像揖盘,于是被迫代替她去往敵國(guó)和親兽狭。 傳聞我的和親對(duì)象是個(gè)殘疾皇子鹿蜀,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345