能用【白話文】來分析Binder通訊機(jī)制煞赢?

image

Binder系列第一篇:《從getSystemService()開始,開擼Binder通訊機(jī)制》http://www.reibang.com/p/1050ce12bc1e

Binder系列第二篇:《能用【白話文】來分析Binder通訊機(jī)制哄孤?》http://www.reibang.com/p/fe816777f2cf

Binder系列第三篇:《Binder機(jī)制之一次響應(yīng)的故事》http://www.reibang.com/p/4fba927dce05

CoorChice在上次的文章 《從getSystemService()開始耕驰,開擼Binder通訊機(jī)制:http://www.reibang.com/p/1050ce12bc1e》 中留了一些關(guān)于Binder的坑,也許大家看的時(shí)候有些云里霧里的录豺,這篇文章朦肘,CoorChice就開始填這些坑了。并開始逐步的深入Binder核心機(jī)制双饥,讓你對Android中最重要的部分有所了解媒抠。

好了,咱們發(fā)車了咏花!

由open_driver()開始

image


《從getSystemService()開始趴生,開擼Binder通訊機(jī)制:http://www.reibang.com/p/1050ce12bc1e》這篇文章中,相信大家應(yīng)該看到在/frameworks/native/libs/binder/ProcessState.cpp文件中昏翰,有這樣一段代碼苍匆。

static int open_driver()
{
    //打開"/dev/binder"Binder驅(qū)動(dòng)文件,并獲得其描述符
    int fd = open("/dev/binder", O_RDWR);
    ...
    //獲取Binder驅(qū)動(dòng)程序的版本號
    status_t result = ioctl(fd, BINDER_VERSION, &vers);
    ...
    size_t maxThreads = 15;
    //告知驅(qū)動(dòng)程序最多可以啟動(dòng)15條線程處理事物
    result = ioctl(fd, BINDER_SET_MAX_THREADS, &maxThreads);
    ...
    return fd;
}

它在ProcessState創(chuàng)建的時(shí)候會被調(diào)用棚菊。它肩負(fù)了一項(xiàng)重要的使命浸踩,就是在該進(jìn)程中打開/dev/binder設(shè)備文件,然后獲得該設(shè)備文件的描述符统求〖焱耄可以看到,打開設(shè)備文件是通過open()函數(shù)實(shí)現(xiàn)的码邻,它是怎么實(shí)現(xiàn)的呢折剃?

用戶空間函數(shù)與Binder驅(qū)動(dòng)函數(shù)

首先打開/drivers/staging/android/binder.c文件,然后找到下面這個(gè)結(jié)構(gòu)體:

//這個(gè)結(jié)構(gòu)體中定義了文件操作符與Binder驅(qū)動(dòng)的對應(yīng)函數(shù)關(guān)聯(lián)
static const struct file_operations binder_fops = {
    .owner = THIS_MODULE,
    .poll = binder_poll,
    .unlocked_ioctl = binder_ioctl,
    .compat_ioctl = binder_ioctl,
    //用戶空間的mmap()操作像屋,會引起B(yǎng)inder驅(qū)動(dòng)的binder_mmap()函數(shù)的調(diào)用
    .mmap = binder_mmap,
    //用戶空間的open()操作怕犁,會引起B(yǎng)inder驅(qū)動(dòng)的binder_open()函數(shù)的調(diào)用
    .open = binder_open, 
    .flush = binder_flush,
    .release = binder_release,
};

這個(gè)結(jié)構(gòu)體會作為下面這個(gè)結(jié)構(gòu)體的一個(gè)成員,在Binder驅(qū)動(dòng)注冊的時(shí)候被和Linux定義的文件操作符關(guān)聯(lián)。

static struct miscdevice binder_miscdev = {
    .minor = MISC_DYNAMIC_MINOR,
    //定義設(shè)備節(jié)點(diǎn)文件名奏甫。這里Binder驅(qū)動(dòng)設(shè)備的文件路徑即為/dev/binder
    .name = "binder",  
    //關(guān)聯(lián)Linux文件操作符
    .fops = &binder_fops
};

這樣戈轿,在Binder驅(qū)動(dòng)設(shè)備注冊完成后,在用戶空間調(diào)用poll()扶檐、open()等函數(shù)的時(shí)候凶杖,Binder驅(qū)動(dòng)的對應(yīng)函數(shù)就會被調(diào)用胁艰。

open()函數(shù)的真面目

現(xiàn)在款筑,我們知道了,當(dāng)我們在用戶空間調(diào)用open()函數(shù)時(shí)腾么,Binder驅(qū)動(dòng)層的binder_open()函數(shù)會被隨之調(diào)用奈梳。我們看看binder_open()函數(shù)做了些什么?

//用戶空間調(diào)用open()實(shí)際調(diào)用的是這里
//參數(shù)為打開設(shè)備文件后傳遞過來的
static int binder_open(struct inode *nodp, struct file *filp)
{
    //binder_proc儲存進(jìn)程信息的結(jié)構(gòu)體
    //注意解虱,這個(gè)進(jìn)程結(jié)構(gòu)體是存在于Binder內(nèi)核空間中的
    struct binder_proc *proc;
    //將當(dāng)前進(jìn)程的信息儲存到binder_proc中
    ...
    //鎖定同步
    binder_lock(__func__);
    ...
    //將該進(jìn)程上下文信息proc保存到Binder驅(qū)動(dòng)的進(jìn)程樹中
    //以便查找使用
    hlist_add_head(&proc->proc_node, &binder_procs);
    // 設(shè)置進(jìn)程id
    proc->pid = current->group_leader->pid;
    ...
    // 將進(jìn)程信息結(jié)構(gòu)體賦值給文件私有數(shù)據(jù)
    filp->private_data = proc;
    //釋放鎖
    binder_unlock(__func__);
    ...
    //在/proc/binder/proc下創(chuàng)建名為進(jìn)程id的文件攘须,便于查看進(jìn)程的通訊
    snprintf(strbuf, sizeof(strbuf), "%u", proc->pid);
    return 0;
}

這個(gè)函數(shù)主要的作用是為打開了/dev/binder設(shè)備文件的進(jìn)程生成一個(gè)專屬的進(jìn)程信息體,然后保存到驅(qū)動(dòng)中殴泰。這樣于宙,該進(jìn)程就能和Binder驅(qū)動(dòng)互動(dòng)了。

可能有的細(xì)心的同學(xué)會發(fā)現(xiàn)悍汛,open()函數(shù)會返回設(shè)備表述符捞魁,而binder_open()函數(shù)看起來只會返回0啊离咐?CoorChice在前面說過谱俭,binder_open()只是和open()產(chǎn)生了關(guān)聯(lián),但實(shí)際打開設(shè)備文件的操作還是Linux再進(jìn)行宵蛀。想必你也可以看到昆著,binder_open()函數(shù)的參數(shù)是在設(shè)備文件打開后才可能獲取的。所以术陶,這個(gè)設(shè)備描述符應(yīng)該是由Linux來分配給進(jìn)程的凑懂。

下面,接著看看在open_driver()中出現(xiàn)的另一個(gè)函數(shù)ioctl()梧宫。

ioctl()函數(shù)的真面目

如果你理解了上面的open()函數(shù)征候,那么自然就知道,用戶空間的ioctl()函數(shù)會引起B(yǎng)inder驅(qū)動(dòng)層的binder_ioctl()函數(shù)的調(diào)用祟敛。我們就看看驅(qū)動(dòng)層的這個(gè)函數(shù)做了什么疤坝?這是一個(gè)十分重要的函數(shù)啊馆铁!

static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    ...
    // 從file結(jié)構(gòu)體中取出進(jìn)程信息
    struct binder_proc *proc = filp->private_data;
    struct binder_thread *thread;
    unsigned int size = _IOC_SIZE(cmd);
    //表明arg是一個(gè)用戶空間地址
    void __user *ubuf = (void __user *)arg;
    ...
    //取出線程信息
    thread = binder_get_thread(proc);
    ...
    switch (cmd) {
        ...
    }
    ...
}

同樣跑揉,用戶層調(diào)用ioctl(fd, cmd, arg)函數(shù),會先由Linux內(nèi)核根據(jù)設(shè)備描述符fd獲得對應(yīng)的設(shè)備文件體file,然后調(diào)用Binder驅(qū)動(dòng)的binder_ioctl()函數(shù)历谍。

這個(gè)函數(shù)比較重要现拒,CoorChice再一步一步的解析一下。

image

獲得進(jìn)程信息體

首先Binder驅(qū)動(dòng)根據(jù)傳入的文件體獲得其中的進(jìn)程信息望侈。

struct binder_proc *proc = filp->private_data;

還記得在binder_open()中生成的那個(gè)進(jìn)程信息結(jié)構(gòu)體嗎印蔬?

來自用戶空間的參數(shù)

void __user *ubuf = (void __user *)arg;

首先我們需要知道,這個(gè)arg是從用戶空間傳遞過來的地址脱衙。比如ioctl(fd, BINDER_VERSION, &vers)傳遞過來了一個(gè)地址侥猬,指向用來儲存Binder版本號的空間。

在Binder驅(qū)動(dòng)層捐韩,需要對這個(gè)地址進(jìn)行轉(zhuǎn)換一下退唠,用__user給它做上標(biāo)記,表明它指向的是用戶空間的地址荤胁。那么瞧预,這個(gè)arg指向的空間與Binder驅(qū)動(dòng)的內(nèi)存空間的數(shù)據(jù)傳遞就需要通過copy_from_user()或者copy_to_user()來進(jìn)行了。

不同的cmd對應(yīng)不同的操作

switch (cmd) {
        ...
    }

switch中定義了幾個(gè)命令仅政,分別對應(yīng)不同的操作垢油,CoorChice不在這全部說了,后面遇到再說圆丹。

我們先看看在open_driver()中出現(xiàn)的兩個(gè)cmd就行了滩愁。

  • BINDER_VERSION
    這個(gè)命令用于獲取Binder驅(qū)動(dòng)版本號。
//獲取Binder驅(qū)動(dòng)的版本號
case BINDER_VERSION: {
    //表示用戶空間的binder版本信息
    struct binder_version __user *ver = ubuf;
    ...
    //把版本號賦值給binder_version的protocol_version成員
    if (put_user(BINDER_CURRENT_PROTOCOL_VERSION,
                 &ver->protocol_version)) {
        ...
    }
    break;
}

注意运褪,上面不是直接賦值惊楼,而是使用了put_user()函數(shù)。因?yàn)檫@個(gè)值是需要寫到用戶空間去的秸讹。

  • BINDER_SET_MAX_THREADS
    設(shè)置進(jìn)程可用于Binder通訊的最大線程數(shù)量檀咙。
//設(shè)置用戶進(jìn)程最大線程數(shù)
case BINDER_SET_MAX_THREADS:
    //使用copy_from_user()函數(shù),將用戶空間的數(shù)據(jù)拷貝到內(nèi)核空間
    //這里就是把線程數(shù)拷貝給進(jìn)程結(jié)構(gòu)體的max_threads
    if (copy_from_user(&proc->max_threads, ubuf, sizeof(proc->max_threads))) {
        ...
    }
    break;

注意璃诀,上面使用了copy_from_user()函數(shù)弧可,把用戶空間的值,寫到了驅(qū)動(dòng)層的進(jìn)程信息體的成員max_threads劣欢。

好了棕诵,上次open_driver()這個(gè)坑算是補(bǔ)上了。

image

接下來看看ProcessState::getStrongProxyForHandle()函數(shù)留下的坑吧凿将。

接著getStrongProxyForHandle()說

注意啦校套,從這里開始是山路十八彎,抓好扶好了澳恋帧笛匙!

先來看一張流程圖侨把。

image

不夠高清?點(diǎn)這個(gè)鏈接下載吧妹孙!http://ogemdlrap.bkt.clouddn.com/Binder%E8%BF%9B%E9%98%B6%E5%AE%8C%E6%95%B4.png秋柄。So Sweet!

圖中相同顏色的流程線表示同一個(gè)流程蠢正,上面標(biāo)有數(shù)字骇笔,你需要按照數(shù)字順序來看,因?yàn)檫@真的是一個(gè)復(fù)雜無比的流程!

另外,同一種顏色的雙向箭頭線指向的是同一個(gè)變量或者值相同的變量夺脾。同理,相同顏色的帶字空心箭頭指向的也是同一個(gè)變量或者相同的值旭旭。

每個(gè)函數(shù)框上部的框表示在我們這個(gè)流程中谎脯,傳入函數(shù)的參數(shù)葱跋。

溫習(xí)一下getStrongProxyForHandle()中的坑

sp<IBinder> ProcessState::getStrongProxyForHandle(int32_t handle)
{
    sp<IBinder> result;
    
    ...
    //嘗試獲取handle對應(yīng)的handle_entry對象,沒有的話會創(chuàng)建一個(gè)
    handle_entry* e = lookupHandleLocked(handle);
    
    if (e != NULL) {
        IBinder* b = e->binder;
        if (b == NULL || !e->refs->attemptIncWeak(this)) {
            // 上面的判斷確保了同一個(gè)handle不會重復(fù)創(chuàng)建新的BpBinder
            if (handle == 0) {
                Parcel data;
                //在handle對應(yīng)的BpBinder第一次創(chuàng)建時(shí)
                //會執(zhí)行一次虛擬的事務(wù)請求源梭,以確保ServiceManager已經(jīng)注冊
                status_t status = IPCThreadState::self()->transact(0, IBinder::PING_TRANSACTION, data, NULL, 0);
                if (status == DEAD_OBJECT)
                    //如果ServiceManager沒有注冊娱俺,直接返回
                    return NULL;
            }
            //創(chuàng)建一個(gè)BpBinder
            //handle為0時(shí)創(chuàng)建的是ServiceManager對應(yīng)的BpBinder
            b = new BpBinder(handle);
            e->binder = b;
            if (b) e->refs = b->getWeakRefs();
            result = b;  //待會兒返回b
        }
        ...
    }
    
    return result;
}

上次CoorChice在getStrongProxyForHandle()函數(shù)中是把下面這段代碼省略了的,為了方便大家關(guān)注流程废麻。

if (handle == 0) {
    Parcel data;
    //在handle對應(yīng)的BpBinder第一次創(chuàng)建時(shí)
    //會執(zhí)行一次虛擬的事務(wù)請求荠卷,以確保ServiceManager已經(jīng)注冊
    status_t status = IPCThreadState::self()->transact(0, IBinder::PING_TRANSACTION, data, NULL, 0);
    if (status == DEAD_OBJECT)
    //如果ServiceManager沒有注冊,直接返回
    return NULL;
}

由于我們發(fā)起了獲取ServiceManager的Binder的請求烛愧,所以handle是0的油宜。還記得嗎?應(yīng)用進(jìn)程在首次獲攘恕(或者說創(chuàng)建)ServiceManager的Binder前慎冤,會先和ServiceManager進(jìn)行一次無意義的通訊(可以看到這次通訊的code為PING_TRANSACTION),以確保系統(tǒng)的ServiceManager已經(jīng)注冊沧卢。既然是在這第一次見到Binder通訊蚁堤,那么我們就索性從這開始來探索Binder通訊機(jī)制的核心流程吧。

IPCThreadState的創(chuàng)建

IPCThreadState::self()->transact(0, IBinder::PING_TRANSACTION, data, NULL, 0)

這句代碼首先會獲取IPCThreadState單例但狭。這是我在圖中省略了的披诗。

IPCThreadState* IPCThreadState::self()
{
    if (gHaveTLS) {
    restart:
        const pthread_key_t k = gTLS;
        //先檢查有沒有,以確保一個(gè)線程只有一個(gè)IPCThreadState
        IPCThreadState* st = (IPCThreadState*)pthread_getspecific(k);
        if (st) return st;
        return new IPCThreadState; //沒有就new一個(gè)IPCThreadState
    }
    ...
}

很明顯立磁,這段代碼確保了進(jìn)程中每一線程都只會有一個(gè)對應(yīng)IPCThreadState呈队。

接下來看看IPCThreadState的構(gòu)造函數(shù)。

IPCThreadState::IPCThreadState()
  //保存所在進(jìn)程
: mProcess(ProcessState::self()),
mMyThreadId(androidGetTid()),
mStrictModePolicy(0),
mLastTransactionBinderFlags(0)
{
    pthread_setspecific(gTLS, this);
    clearCaller();
    //用于接收Binder驅(qū)動(dòng)的數(shù)據(jù)唱歧,設(shè)置其大小為256
    mIn.setDataCapacity(256);
    //用于向Binder驅(qū)動(dòng)發(fā)送數(shù)據(jù)宪摧,同樣設(shè)置其大小為256
    mOut.setDataCapacity(256);
}

CoorChice注釋的地方比較重要哦,想要看懂后面的流程,上面3個(gè)注釋的記住哦绍刮!

好了温圆,我們的IPCThreadState算是創(chuàng)建出來了。事實(shí)上IPCThreadState主要就是封裝了和Binder通訊的邏輯孩革,當(dāng)我們需要進(jìn)行通訊時(shí)岁歉,就需要通過它來完成。

image

下面就來看看通訊是怎么開始的膝蜈。

第一步 IPCThreadState::transact()發(fā)起通訊

你可以先在圖中找到對應(yīng)的流程線锅移。transact()完整代碼的話你可以看圖中的,或者在/frameworks/native/libs/binder/IPCThreadState.cpp看源碼饱搏。由于流程復(fù)雜非剃,CoorChice就以小片段來說明。

status_t IPCThreadState::transact(int32_t handle,
                                  uint32_t code, const Parcel& data,
                                  Parcel* reply, uint32_t flags)
{
    flags |= TF_ACCEPT_FDS;   //添加TF_ACCEPT_FDS
        ...
     if (err == NO_ERROR) {
         ...
         //將需要發(fā)送的數(shù)據(jù)寫入mOut中
         err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL);
     }
    ...
}

首先推沸,在傳入的flags參數(shù)中添加一個(gè)TF_ACCEPT_FDS標(biāo)志备绽,表示返回?cái)?shù)據(jù)中可以包含文件描述符。以下是幾個(gè)標(biāo)志位的意義:

enum transaction_flags {
    TF_ONE_WAY     = 0x01, /*異步的單向調(diào)用鬓催,沒有返回值*/
    TF_ROOT_OBJECT = 0x04, /*里面的數(shù)據(jù)是一個(gè)組件的根對象*/
    TF_STATUS_CODE = 0x08, /*數(shù)據(jù)包含的是一個(gè)32bit的狀態(tài)碼*/
    TF_ACCEPT_FDS  = 0x10, /*允許返回對象中肺素,包含文件描述符*/
}

接著,會調(diào)用writeTransactionData()函數(shù)宇驾,把需要發(fā)送的數(shù)據(jù)準(zhǔn)備好倍靡。注意這里的命令是BC_TRANSACTION哦。如果你隨時(shí)對照著圖查看參數(shù)的話课舍,這個(gè)流程將會變的容易理解一些塌西。

第二步 writeTransactionData()準(zhǔn)備發(fā)送數(shù)據(jù)

status_t IPCThreadState::writeTransactionData(int32_t cmd, uint32_t binderFlags,
    int32_t handle, uint32_t code, const Parcel& data, status_t* statusBuffer)
{
    //儲存通訊事務(wù)數(shù)據(jù)的結(jié)構(gòu)
    binder_transaction_data tr;
    tr.target.ptr = 0;  //binder_node的地址
    tr.target.handle = handle;  //用于查找目標(biāo)進(jìn)程Binder的handle,對應(yīng)binder_ref
    tr.code = code; //表示事務(wù)類型
    tr.flags = binderFlags;
    tr.cookie= 0;
    ...
    //Parcel mOut筝尾,與之相反的有Parcel mIn
    //寫入本次通訊的cmd指令
    mOut.writeInt32(cmd);
    //把本次通訊事務(wù)數(shù)據(jù)寫入mOut中
    mOut.write(&tr, sizeof(tr));
    return NO_ERROR;
}

如你所見捡需,這個(gè)函數(shù)主要?jiǎng)?chuàng)建了一個(gè)用于儲存通訊事務(wù)數(shù)據(jù)的binder_transaction_data結(jié)構(gòu)t,并把需要發(fā)送的事務(wù)數(shù)據(jù)放到其中忿等,然后再把這個(gè)tr寫入IPCThreadState的mOut中栖忠。這樣一來,后面就可以從mOut中取出這個(gè)通訊事務(wù)數(shù)據(jù)結(jié)構(gòu)了贸街。它非常重要庵寞,你一定要記住它是什么?以及從那來的薛匪?

此外捐川,還需要把本次通訊的命令也寫入mOut中,這樣后面才能獲取到發(fā)送方的命令逸尖,然后執(zhí)行相應(yīng)的操作古沥。

第三步 waitForResponse()等待響應(yīng)

第二步完成后瘸右,我們再次回到IPCThreadState::transact()函數(shù)中。

status_t IPCThreadState::transact(int32_t handle,
                                  uint32_t code, const Parcel& data,
                                  Parcel* reply, uint32_t flags)
{
    ...
    flags |= TF_ACCEPT_FDS;   //添加TF_ACCEPT_FDS
    ...
    //等待響應(yīng)
    if ((flags & TF_ONE_WAY) == 0) { //檢查本次通訊是否有TF_ONE_WAY標(biāo)志岩齿,即沒有響應(yīng)
        //reply是否為空
        if (reply) {
            err = waitForResponse(reply);
        } else {
            Parcel fakeReply;
            err = waitForResponse(&fakeReply);
        }
        ...
    }
    ...
    return err;
}

一般通訊都需要響應(yīng)太颤,所以我們就只看有響應(yīng)的情況了,即flags中不包含TF_ONE_WAY標(biāo)記盹沈。調(diào)用waitForResponse()函數(shù)時(shí)龄章,如果沒有reply,會創(chuàng)建一個(gè)fakeReplay乞封。我們回顧一下:

transact(0, IBinder::PING_TRANSACTION, data, NULL, 0)

看做裙,我們上面?zhèn)魅氲膔eplay是一個(gè)NULL,所以這里是會創(chuàng)建一個(gè)fakeReplay的肃晚。

緊接著锚贱,我們就進(jìn)入到IPCThreadState::waitForResponse()中了」卮可以看下圖中的流程線哦拧廊,對應(yīng)紅色編號3的線。

status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult)
{
    ...
    while (1) {
        //真正和Binder驅(qū)動(dòng)交互的是talkWithDriver()函數(shù)
        if ((err=talkWithDriver()) < NO_ERROR) break;
        ...
    }
    ...
}

這個(gè)方法中悍缠,一開始就有些隱蔽的調(diào)用了一個(gè)十分重要的方法IPCThreadState::talkWithDriver()卦绣,從名字也能看出來耐量,真正和Binder驅(qū)動(dòng)talk的邏輯是在這個(gè)函數(shù)中的飞蚓。這個(gè)地方給差評!

image

順著代碼廊蜒,我們進(jìn)入talkWithDriver()看看用戶空間是如何和Binder驅(qū)動(dòng)talk的趴拧。

第四步 talkWithDriver()和Binder talk!

注意山叮,需要說明一下著榴,IPCThreadState::talkWithDriver()這個(gè)函數(shù)的參數(shù)默認(rèn)為true!一定要記住屁倔,不然后面就看不懂了脑又!

status_t IPCThreadState::talkWithDriver(bool doReceive)
{
    ...
    //讀寫結(jié)構(gòu)體,它是用戶空間和內(nèi)核空間的信使
    binder_write_read bwr;
    ...
    //配置發(fā)送信息
    bwr.write_size = outAvail;
    bwr.write_buffer = (uintptr_t)mOut.data();
    ...
    //獲取接收信息
    if(doReceive && needRead){
        bwr.read_size = mIn.dataCapacity();
        bwr.read_buffer = (uintptr_t)mIn.data();
    } else {
        bwr.read_size = 0;
        bwr.read_buffer = 0;
    }
    ...
    //設(shè)置消耗為0
    bwr.write_consumed = 0;
    bwr.read_consumed = 0;
    status_t err;
    do {
        ...
        //通過ioctl操作與內(nèi)核進(jìn)行讀寫
        if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)
            err = NO_ERROR;
        ...
    } while (err == -EINTR);
    ...
}

這個(gè)函數(shù)中,有一個(gè)重要結(jié)構(gòu)被定義锐借,就是binder_write_read问麸。它能夠儲存一些必要的發(fā)送和接收的通訊信息,它就像用戶空間和內(nèi)核空間之間的一個(gè)信使一樣钞翔,在兩端傳遞信息严卖。

在這個(gè)函數(shù)中,首先會把用戶空間要傳遞/讀取信息放到bwr中布轿,然后通過一句關(guān)鍵的代碼ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr)與Binder驅(qū)動(dòng)talk哮笆。ioctl()函數(shù)CoorChice已經(jīng)在上一篇中說了来颤,它最終會調(diào)用到Binder內(nèi)核的binder_ioctl()函數(shù),至于為什么稠肘?你可以再看看上一篇文章回顧下福铅。

image

注意這里我們給ioctl()函數(shù)傳遞的參數(shù)。

  • 第一個(gè)參數(shù)项阴,是從本進(jìn)程中取出上面篇中打開并保存Binder設(shè)備文件描述符本讥,通過它可以獲取到之前生成的file文件結(jié)構(gòu),然后傳給binder_ioctl()函數(shù)鲁冯。沒印象的同學(xué)先看看上篇回顧下這里拷沸。
  • 第二個(gè)參數(shù),是命令薯演,它決定了待會到內(nèi)核空間中要執(zhí)行那段邏輯撞芍。
  • 第三個(gè)參數(shù),我們把剛剛定義的信使bwr的內(nèi)存地址傳到內(nèi)核空間去跨扮。

這些參數(shù)是理解后面步驟的關(guān)鍵序无,不要忘了哦!現(xiàn)在衡创,進(jìn)入到老盆友binder_ioctl()函數(shù)中帝嗡,看看收到用戶空間的消息后,它干了什么璃氢?

第五步 在binder_ioctl()中處理消息

static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    int ret;
    // 從file結(jié)構(gòu)體中取出進(jìn)程信息
    struct binder_proc *proc = filp->private_data;
    struct binder_thread *thread;
    unsigned int size = _IOC_SIZE(cmd);
    //表明arg是一個(gè)用戶空間地址
    //__user標(biāo)記該指針為用戶空間指針哟玷,在當(dāng)前空間內(nèi)無意義
    void __user *ubuf = (void __user *)arg;
    ...
    //鎖定同步
    binder_lock(__func__);
    //取出線程信息
    thread = binder_get_thread(proc);
    ...
    switch (cmd) {
        //讀寫數(shù)據(jù)
        case BINDER_WRITE_READ:
            ret = binder_ioctl_write_read(filp, cmd, arg, thread); 
            ...
        }
        ...
    }
    ...
    //解鎖
    binder_unlock(__func__);
    ...
}

這個(gè)函數(shù)看過《從getSystemService()開始,開擼Binder通訊機(jī)制:http://www.reibang.com/p/1050ce12bc1e》的同學(xué)應(yīng)該不會陌生一也。首先會根據(jù)文件描述符獲得的file結(jié)構(gòu)巢寡,獲取到調(diào)用ioctl()函數(shù)的進(jìn)程的進(jìn)程信息,從而再獲得進(jìn)程的線程椰苟。然后將arg參數(shù)地址轉(zhuǎn)換成有用戶空間標(biāo)記的指針抑月。接著,在switch中根據(jù)cmd參數(shù)判斷需要執(zhí)行什么操作舆蝴。這些步驟和上篇文章中是一樣的谦絮。不同的是,我們這次的cmd命令是BINDER_WRITE_READ洁仗,表示要進(jìn)行讀寫操作层皱。可以看到京痢,接下來的讀寫邏輯是在binder_ioctl_write_read()函數(shù)中的奶甘。

嗯,接下來祭椰,我們即將進(jìn)入第6步臭家,看看Binder驅(qū)動(dòng)中的這段讀寫通訊邏輯是怎樣的疲陕?

第六步 binder_ioctl_write_read() talking

static int binder_ioctl_write_read(struct file *filp,
                                   unsigned int cmd, unsigned long arg,
                                   struct binder_thread *thread)
{
    int ret = 0;
    //獲取發(fā)送進(jìn)程信息
    struct binder_proc *proc = filp->private_data;
    unsigned int size = _IOC_SIZE(cmd);
    //來自用戶空間的參數(shù)地址
    void __user *ubuf = (void __user *)arg;
    //讀寫信息結(jié)構(gòu)體
    struct binder_write_read bwr;
    ...
    //拷貝用戶空間的通訊信息bwr到內(nèi)核的bwr
    if (copy_from_user(&bwr, ubuf, sizeof(bwr)))
    ...
    if (bwr.write_size > 0) {
        //寫數(shù)據(jù)
        ret = binder_thread_write(proc, thread,
                                  bwr.write_buffer,
                                  bwr.write_size,
                                  &bwr.write_consumed);
    ...
}

咱們先看上面這個(gè)片段。

首先自然是取出用戶空間的進(jìn)程信息钉赁,然后轉(zhuǎn)換獲得用戶空間的參數(shù)地址(對應(yīng)本次通訊中為bwr的地址)蹄殃,這些都跟在binder_ioctl()中做的差不多。

接下來你踩,你可以看到一個(gè)binder_write_read結(jié)構(gòu)的申明struct binder_write_read bwr诅岩,緊跟著通過copy_from_user(&bwr, ubuf, sizeof(bwr))把用戶空間的bwr拷貝到了當(dāng)前內(nèi)核空間的bwr。現(xiàn)在带膜,Binder內(nèi)核空間的bwr就獲取到了來自用戶空間的通訊信息了吩谦。

獲取到來自用戶空間的信息后,先調(diào)用binder_thread_write()函數(shù)來處理膝藕,我看看是如何進(jìn)行處理的式廷。

第七步binder_thread_write()處理寫入

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;
    void __user *ptr = buffer + *consumed;    //起始地址
    void __user *end = buffer + size;         //結(jié)束地址
    while (ptr < end && thread->return_error == BR_OK) {
        //從用戶空間獲取cmd命令
        if (get_user(cmd, (uint32_t __user *)ptr)) -EFAULT;
        ptr += sizeof(uint32_t);
        switch (cmd) {
            case BC_TRANSACTION:
            case BC_REPLY: {
                //用來儲存通訊信息的結(jié)構(gòu)體
                struct binder_transaction_data tr;
                //拷貝用戶空間的binder_transaction_data
                if (copy_from_user(&tr, ptr, sizeof(tr)))   return -EFAULT;
                ptr += sizeof(tr);
                //處理通訊
                binder_transaction(proc, thread, &tr, cmd == BC_REPLY);
                break;
            }
                ...
        }
        *consumed = ptr - buffer;
    }
    return 0;
}

一開始就是對一些變量進(jìn)行賦值。

首先芭挽,binder_buffer是啥滑废?哪來的?快到到傳參的地方方看看bwr.write_buffer袜爪,它是寫的buffer蠕趁。那么它里面裝了啥?這就得回到第4步中找了辛馆,因?yàn)閎wr是在那個(gè)地方定義和初始化的俺陋。bwr.write_buffer = (uintptr_t)mOut.data(),嗯怀各,它指向了mOut中的數(shù)據(jù)倔韭。那么問題又來了?mOut中的數(shù)據(jù)是啥瓢对?...

image

看,這就是為什么CoorChice一直在強(qiáng)調(diào)胰苏,前面的一些參數(shù)和變量一定要記姿队肌!不然到后面就會云里霧里的硕并!不過還好法焰,有了CoorChcie上面那張圖,你隨時(shí)可以快速的找到答案倔毙。我們回到第二步writeTransactionData()埃仪,就是通訊事務(wù)結(jié)構(gòu)定義的那個(gè)地方∩略撸看到?jīng)]卵蛉,mOut中儲存的就是一個(gè)通訊事務(wù)結(jié)構(gòu)颁股。

現(xiàn)在答案就明了了,buffer指向了用戶空間的通訊事務(wù)數(shù)據(jù)傻丝。

另外兩個(gè)參數(shù)甘有,ptr此刻和buffer的值是一樣的,因?yàn)閏onsumed為0葡缰,所以它現(xiàn)在也相當(dāng)于是用戶空間的通訊事務(wù)數(shù)據(jù)tr的指針亏掀;而end可以明顯的看出,它指向了tr的末尾泛释。

通過get_user(cmd, (uint32_t __user *)ptr)函數(shù)滤愕,我們可以將用戶空間的tr的cmd拷貝到內(nèi)核空間。get_user()put_user()這對函數(shù)就是干這個(gè)的怜校,拷貝一些簡單的變量该互。回到第2步writeTransactionData()中韭畸,看看參數(shù)宇智。沒錯(cuò),cmd為BC_TRANSACTION胰丁。所以随橘,進(jìn)到switch中,對應(yīng)執(zhí)行的就是case BC_TRANSACTION锦庸。

可以看到BC_TRANSACTION事務(wù)命令和BC_REPLAY響應(yīng)命令机蔗,執(zhí)行的是相同的邏輯。

//用來儲存通訊信息的結(jié)構(gòu)體
struct binder_transaction_data tr;
//拷貝用戶空間的binder_transaction_data
if (copy_from_user(&tr, ptr, sizeof(tr)))   return -EFAU
ptr += sizeof(tr);
//處理通訊
binder_transaction(proc, thread, &tr, cmd == BC_REPLY);

先定義了一個(gè)內(nèi)核空間的通訊事務(wù)數(shù)據(jù)tr甘萧,然后把用戶空間的通訊事務(wù)數(shù)據(jù)拷貝到內(nèi)核中tr萝嘁。此時(shí),ptr指針移動(dòng)sizeof(tr)個(gè)單位扬卷,現(xiàn)在ptr應(yīng)該和end的值是一樣的了牙言。然后,調(diào)用binder_transaction()來處理事務(wù)怪得。

第八步 binder_transaction()來處理事務(wù)

順著流程線8看過去咱枉,WTF!這是一個(gè)復(fù)雜無比的函數(shù)徒恋!很多蚕断!很長!

函數(shù)一開始定義了一堆變量入挣,我們先不管亿乳,用到時(shí)再說。先看第一個(gè)使用到的參數(shù)replay径筏。由于上一步中的cmd為BC_TRANSACTION葛假,所以很明顯障陶,走的是false,所以我們直接看false里的邏輯桐款。

static void binder_transaction(struct binder_proc *proc,
                               struct binder_thread *thread,
                               struct binder_transaction_data *tr, int reply){
    ...
    if (reply) {
        ...
    } else {
        if (tr->target.handle) {
            //如果參數(shù)事物信息中的進(jìn)程的句柄不為0咸这,即不是系統(tǒng)ServiceManager進(jìn)程
            
            //定義binder引用
            struct binder_ref *ref;
            //根據(jù)參數(shù)binder進(jìn)程和句柄handle來查找對應(yīng)的binder
            ref = binder_get_ref(proc, tr->target.handle);
            ...
            //設(shè)置目標(biāo)binder實(shí)體為上面找到的參數(shù)進(jìn)程的binder引用的binder實(shí)體
            target_node = ref->node;
        } else {
            //如果參數(shù)事物信息中的進(jìn)程的句柄為0,即是系統(tǒng)ServiceManager進(jìn)程
            
            //設(shè)置通訊目標(biāo)進(jìn)程的Binder實(shí)體為ServiceManager對應(yīng)的Binder
            target_node = binder_context_mgr_node;
        }
        //設(shè)置通訊目標(biāo)進(jìn)程為target_node對應(yīng)的進(jìn)程
        target_proc = target_node->proc;
        ...
}

一開始先判斷tr->target.handle為不為0魔眨。還記得上一篇說的嗎媳维?handle為0表示的是ServiceManager,如果不為0表示的其它Service遏暴。那么這里為不為0呢侄刽?看圖!

我們順著指向tr的綠線一直找朋凉,可以看到州丹。在用戶空間通訊事務(wù)數(shù)據(jù)被定義的地方,也就是第2步IPCThreadState::writeTransactionData()中杂彭,給tr->target_handle賦值了墓毒,往上看發(fā)現(xiàn),這個(gè)值來自IPCThreadState::transact()函數(shù)的參數(shù)handle亲怠。那么回到我們一開始調(diào)用這個(gè)函數(shù)的地方IPCThreadState::self()->transact(0, IBinder::PING_TRANSACTION, data, NULL, 0)所计。哦,handle為0团秽。所以這里的target就是ServiceManager主胧。那么直接把binder_context_mgr_node(它表示ServiceManager的Binder,在ServiceManager注冊的時(shí)候被緩存到了Binder內(nèi)核中)賦值給target_node习勤,記住了哦踪栋!后面這些都會用到⊥急希總之夷都,我們就是需要先獲取到一個(gè)目標(biāo)進(jìn)程。

接下來吴旋,通過target_node损肛,也就是ServiceManager進(jìn)程的Binder(其它情況就是對應(yīng)進(jìn)程的Binder),我們可以獲取到目標(biāo)進(jìn)程信息荣瑟,然后賦值給target_proc。記住了哦摩泪!

繼續(xù)下一段代碼笆焰。

static void binder_transaction(struct binder_proc *proc,
                               struct binder_thread *thread,
                               struct binder_transaction_data *tr, int reply){
    ...
    //判斷目標(biāo)線程是否為空
    if (target_thread) {
        ...
        //目標(biāo)線程的todo隊(duì)列
        target_list = &target_thread->todo;
        target_wait = &target_thread->wait;
        ...
    } else {
        //獲得通訊目標(biāo)進(jìn)程的任務(wù)隊(duì)列
        target_list = &target_proc->todo;
        //獲取通訊目標(biāo)進(jìn)程的等待對象
        target_wait = &target_proc->wait;
    }
    ...
}

首先看target_thread是否為空,由于我們沒有走if(replay)的TRUE邏輯见坑,所以這里target_thread是為空的嚷掠。那么捏检,從目標(biāo)進(jìn)程信息target_proc分別去除todo任務(wù)隊(duì)列和wait對象,賦值給target_listtarget_wait不皆。同樣需要記坠岢恰!

繼續(xù)下一段代碼霹娄。

static void binder_transaction(struct binder_proc *proc,
                               struct binder_thread *thread,
                               struct binder_transaction_data *tr, int reply){
                               
   struct binder_transaction *t; //表示一個(gè)binder通訊事務(wù)
   struct binder_work *tcomplete; //表示一項(xiàng)work
   ...
   struct list_head *target_list;  //通訊目標(biāo)進(jìn)程的事務(wù)隊(duì)列
   wait_queue_head_t *target_wait; //通訊目標(biāo)進(jìn)程的等待對象
    ...
    //為本次通訊事務(wù)t申請空間
    t = kzalloc(sizeof(*t), GFP_KERNEL);
    ...
    tcomplete = kzalloc(sizeof(*tcomplete), GFP_KERNEL);
    ...
    if (!reply && !(tr->flags & TF_ONE_WAY))
        //采用非one way通訊方式能犯,即需要等待服務(wù)端返回結(jié)果的通訊方式
        
        //設(shè)置本次通訊事務(wù)t的發(fā)送線程為用戶空間的線程
        t->from = thread;
    else
        t->from = NULL;
    ...
    //設(shè)置本次通訊事務(wù)的接收進(jìn)程為目標(biāo)進(jìn)程
    t->to_proc = target_proc;
    //設(shè)置本次通訊事務(wù)的接收線程為目標(biāo)線程
    t->to_thread = target_thread;
    //設(shè)置本次通訊事務(wù)的命令為用戶空間傳來的命令
    t->code = tr->code;
    //設(shè)置本次通訊事務(wù)的命令為用戶空間傳來的flags
    t->flags = tr->flags;
    ...
    //開始配置本次通訊的buffer
    //在目標(biāo)進(jìn)程中分配進(jìn)行本次通訊的buffer的空間
    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; //通訊buffer允許釋放
    t->buffer->transaction = t;  //把本次通訊存入buffer中
    //設(shè)置本次通訊的buffer的目標(biāo)Binder實(shí)體為target_node
    //如前面一樣,通過這個(gè)buffer可以找到對應(yīng)的進(jìn)程
    t->buffer->target_node = target_node;
    ...
    offp = (binder_size_t *)(t->buffer->data + ALIGN(tr->data_size, sizeof(void *)));
    //將用戶空間發(fā)送來的數(shù)據(jù)拷貝到本次通訊的buffer的data中
    copy_from_user(t->buffer->data, (const void __user *)(uintptr_t)tr->data.ptr.buffer, tr->data_size);
    ...
    //將用戶空間發(fā)送來的偏移量offsets拷貝給起始o(jì)ffp
    copy_from_user(offp, (const void __user *)(uintptr_t)tr->data.ptr.offsets, tr->offsets_size);
    ...
    //計(jì)算結(jié)尾off_end
    off_end = (void *)offp + tr->offsets_size;
    ...
    //判斷是否是BC_REPLY
    if (reply) {
        ...
        
        binder_pop_transaction(target_thread, in_reply_to);
    } else if (!(t->flags & TF_ONE_WAY)) {
        //如果沒有ONE_WAY標(biāo)記犬耻,即需要等待響應(yīng)
        t->need_reply = 1;  //1標(biāo)示這是一個(gè)同步事務(wù)踩晶,需要等待對方回復(fù)。0表示這是一個(gè)異步事務(wù)枕磁,不用等對方回復(fù)
        //設(shè)置本次通訊事務(wù)的from_parent為發(fā)送方進(jìn)程的事務(wù)
        t->from_parent = thread->transaction_stack;
        //設(shè)置發(fā)送方進(jìn)程的事務(wù)棧為本次通訊事務(wù)
        thread->transaction_stack = t;
    }
    ...
    
    //將本次通訊事務(wù)的work類型設(shè)置為BINDER_WORK_TRANSACTION
    t->work.type = BINDER_WORK_TRANSACTION;
    //將本次通訊事務(wù)的work添加到目標(biāo)進(jìn)程的事務(wù)列表中
    list_add_tail(&t->work.entry, target_list);
    
    //設(shè)置work類型為BINDER_WORK_TRANSACTION_COMPLETE
    tcomplete->type = BINDER_WORK_TRANSACTION_COMPLETE;
    //將BINDER_WORK_TRANSACTION_COMPLETE類型的work添加到發(fā)送方的事務(wù)列表中
    list_add_tail(&tcomplete->entry, &thread->todo);
    
    if (target_wait)
        //喚醒目標(biāo)進(jìn)程渡蜻,開始執(zhí)行目標(biāo)進(jìn)程的事務(wù)棧
        wake_up_interruptible(target_wait);
    return;
}

在開始分析之前,大家先吧這段代碼開始的幾個(gè)變量定義記住计济,不然后面會很迷茫的茸苇!

這段代碼很多!很長沦寂!CoorChice已經(jīng)盡量的刪去一些沒那么重要的和我不知道是干啥的了Q堋!

image

其實(shí)這么多代碼凑队,主要使用在給binder事務(wù)t的成員賦值了则果。我們簡單看幾個(gè)我認(rèn)為重要的賦值。

首先為事務(wù)t和work tcomplete申請了內(nèi)存漩氨。然后設(shè)置事務(wù)t的from線程(也就是發(fā)送方線程)的值西壮,如果不是BC_REPLAY事務(wù),并且通訊標(biāo)記沒有TF_ONE_WAY(即本次通訊需要有響應(yīng))叫惊,那么把參數(shù)thread賦值給t->from款青。前面說過,我們本次通訊是BC_TRANSACTION事務(wù)霍狰,所以事務(wù)t就需要儲存發(fā)送方的線程信息抡草,以便后面給發(fā)送方響應(yīng)使用。

參數(shù)thread是那來的呢蔗坯?順著往回找康震,在第5步binder_ioctl()中,我們從用戶空間調(diào)用ioctl()函數(shù)的進(jìn)程(即發(fā)送方進(jìn)程)的進(jìn)程信息中獲取到了thread宾濒。

接著設(shè)置事務(wù)t的目標(biāo)進(jìn)程t->to_proc和目標(biāo)進(jìn)程的線程t->to_thread為前面處理好的target_proctarget_thread(本次通訊腿短,target_thread為空哦)。

然后把通訊事務(wù)數(shù)據(jù)tr中的code和flags賦值給事務(wù)t的code和flags。code和flags是什么呢橘忱?我們回到用戶空間赴魁,定義通訊事務(wù)數(shù)據(jù),即第2步IPCThreadState::writeTransaction()中可以看到钝诚,code和flags均是傳進(jìn)來的參數(shù)颖御。而的發(fā)源地是通訊的起始點(diǎn)IPCThreadState::self()->transact(0, IBinder::PING_TRANSACTION, data, NULL, 0),即code = IBinder::PING_TRANSACTION凝颇, flags = 0潘拱。記住了哦!后面還會用祈噪。

然后開始設(shè)置事務(wù)t的buffer信息泽铛。首先通過binder_alloc_buf()函數(shù),在目標(biāo)進(jìn)程target_proc中為t->buffer申請了內(nèi)存辑鲤,即t->buffer指向了目標(biāo)進(jìn)程空間中的一段內(nèi)存盔腔。然后配置一下t->buffer的信息,這些信息后面也會用到月褥。記住了哦弛随!

接著通過copy_from_user()函數(shù),把用戶空間的需要發(fā)送的數(shù)據(jù)拷貝到t->buffer的data中宁赤。

再往下到了if(replay)舀透,本次通訊會走false邏輯。于是决左,事務(wù)t會把發(fā)送方的事務(wù)棧transaction_stack儲存在from_parent中愕够,而發(fā)送方把自己的事務(wù)棧設(shè)置以成t開始。這些都需要記住佛猛,不然再往后你就會越來越迷糊惑芭!

最重要的部分來了!

//將本次通訊事務(wù)的work類型設(shè)置為BINDER_WORK_TRANSACTION
t->work.type = BINDER_WORK_TRANSACTION;
//將本次通訊事務(wù)的work添加到目標(biāo)進(jìn)程的事務(wù)列表中
list_add_tail(&t->work.entry, target_list);
    
//設(shè)置work類型為BINDER_WORK_TRANSACTION_COMPLETE
tcomplete->type = BINDER_WORK_TRANSACTION_COMPLETE;
將BINDER_WORK_TRANSACTION_COMPLETE類型的work添加到發(fā)送方的事務(wù)列表中
list_add_tail(&tcomplete->entry, &thread->todo);

if (target_wait)
    //喚醒目標(biāo)進(jìn)程继找,開始執(zhí)行目標(biāo)進(jìn)程的事務(wù)棧
    wake_up_interruptible(target_wait);
return;

先把事務(wù)t的work.type類型設(shè)置為BINDER_WORK_TRANSACTION類型遂跟,這決定了該事務(wù)后面走的流程,然后把事務(wù)t的任務(wù)添加到目標(biāo)進(jìn)程的任務(wù)棧target_list中婴渡。接著把work tcomplete的類型設(shè)置為BINDER_WORK_TRANSACTION_COMPLETE幻锁,用于告訴發(fā)送方,和Binder驅(qū)動(dòng)的一次talk完成了边臼,同樣哄尔,需要把這個(gè)項(xiàng)任務(wù)添加到發(fā)送方的任務(wù)列表里。

最后柠并,通過wake_up_interruptible(target_wait)函數(shù)喚醒休眠中的目標(biāo)進(jìn)程究飞,讓它開始處理任務(wù)棧中的任務(wù)置谦,也就是剛剛我們添加到target_list中的任務(wù)堂鲤。接著return結(jié)束該函數(shù)亿傅。

結(jié)束這個(gè)函數(shù)你以為就忘啦?Native瘟栖!接著往下看葵擎。

第9步 binder_thread_read()讀取數(shù)據(jù)

上一個(gè)函數(shù)結(jié)束后回到第7步binder_thread_write()函數(shù)中,retrun 0;半哟,binder_thread_write()函數(shù)結(jié)束酬滤。然后回到第6步binder_ioctl_write_read()函數(shù)中繼續(xù)執(zhí)行。

static int binder_ioctl_write_read(struct file *filp,
                                   unsigned int cmd, unsigned long arg,
                                   struct binder_thread *thread)
{
    ...
    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);
        ...
}

Binder驅(qū)動(dòng)會調(diào)用binder_thread_read()函數(shù)寓涨,為發(fā)送進(jìn)程讀取數(shù)據(jù)盯串。我們看看是怎么讀取的。

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;
        if (!list_empty(&thread->todo)) {
            //獲取線程的work隊(duì)列
            w = list_first_entry(&thread->todo, struct binder_work, entry);
        } else if (!list_empty(&proc->todo) && wait_for_proc_work) {
            //獲取從進(jìn)程獲取work隊(duì)列
            w = list_first_entry(&proc->todo, struct binder_work, entry);
        } else {
            //沒有數(shù)據(jù),則返回retry
            if (ptr - buffer == 4 &&
                !(thread->looper & BINDER_LOOPER_STATE_NEED_RETURN))
                goto retry;
            break;
        }
    ...
}

我們先看這個(gè)片段戒良,前面一堆代碼掠過了体捏。首先,需要看看能不能從發(fā)送進(jìn)程的線程thread的任務(wù)棧中取出任務(wù)來糯崎,回顧第8步binder_transaction()中几缭,我們在最后往發(fā)送進(jìn)程的線程thread的任務(wù)棧中添加了一個(gè)BINDER_WORK_TRANSACTION_COMPLETE類型的work。所以這里是能取到任務(wù)的沃呢,就直接執(zhí)行下一步了年栓。

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;
    void __user *ptr = buffer + *consumed; //
    void __user *end = buffer + size; //用戶空間結(jié)束
    ...
    while (1) {
        uint32_t cmd;
        struct binder_transaction_data tr;
        struct binder_work *w;
        ...
        switch (w->type) {
            ...
            case BINDER_WORK_TRANSACTION_COMPLETE:
                //設(shè)置cmd為BR_TRANSACTION_COMPLETE
                cmd = BR_TRANSACTION_COMPLETE;
                //將BR_TRANSACTION_COMPLETE寫入用戶進(jìn)程空間的mIn中
                put_user(cmd, (uint32_t __user *)ptr);
                //從事務(wù)隊(duì)列中刪除本次work
                list_del(&w->entry);
                //釋放
                kfree(w);
                break;
            ...
        }
    }
    ...
}

由于這個(gè)流程中薄霜,我們知道work的type為BINDER_WORK_TRANSACTION_COMPLETE類型某抓,所以就先只看這種情況了。在這段代碼中惰瓜,cmd = BR_TRANSACTION_COMPLETE很重要否副,要記住鸵熟!接著把cmd拷貝到用戶空間的發(fā)送進(jìn)程副编,然后刪除任務(wù),釋放內(nèi)存流强。

一次和Binder驅(qū)動(dòng)的通訊完成痹届!

上面代碼執(zhí)行完后,binder_thread_read()函數(shù)差不多就結(jié)束了打月,接著又會回到binder_ioctl_write_read()函數(shù)队腐。

static int binder_ioctl_write_read(struct file *filp,
                                   unsigned int cmd, unsigned long arg,
                                   struct binder_thread *thread)
{
    ...
    //將內(nèi)核的信使bwr拷貝到用戶空間
    if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
    ...
}

上面函數(shù)最后會把內(nèi)核中的信使拷貝到用戶空間。

然后奏篙,我們直接的再次的回到第3步的函數(shù)IPCThreadState::waitForResponse()中柴淘。

status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult)
{
    ...
    while (1) {
        //真正和Binder驅(qū)動(dòng)交互的是talkWithDriver()函數(shù)
        if ((err=talkWithDriver()) < NO_ERROR) break;
        err = mIn.errorCheck();
        ...
        if (mIn.dataAvail() == 0) continue;
        //取出在內(nèi)核中寫進(jìn)去的cmd命令
        cmd = mIn.readInt32();
        ...
        
        switch (cmd) {
            //表示和內(nèi)核的一次通訊完成
            case BR_TRANSACTION_COMPLETE:
                if (!reply && !acquireResult) goto finish;
                break;
            ...
        }
        
    }
    ...
}

經(jīng)過剛剛的讀取迫淹,這次mIn中可是有數(shù)據(jù)了哦!我們從mIn中取出cmd命令为严。這是什么命令呢敛熬?就是剛剛寫到用戶空間的BR_TRANSACTION_COMPLETE。在這段邏輯中第股,由于之前我們傳入了一個(gè)fakeReplay進(jìn)來应民,所以程序走bredk,然后繼續(xù)循環(huán)夕吻,執(zhí)行下一次talkWithDriver()函數(shù)诲锹。到此,我們和Binder內(nèi)核的一次通訊算是完成了涉馅。

但是我們發(fā)起的這次通訊還沒有得到回應(yīng)哦归园!猜猜看回應(yīng)的流程是怎樣的呀?

image

文章太長了稚矿,回應(yīng)流程放到下一篇了庸诱。

總結(jié)

  • 抽出空余時(shí)間寫文章分享需要?jiǎng)恿Γ€請各位看官動(dòng)動(dòng)小手點(diǎn)個(gè)贊盐捷,給我點(diǎn)鼓勵(lì)??
  • 我一直在不定期的創(chuàng)作新的干貨偶翅,想要上車只需進(jìn)到我的【個(gè)人主頁】點(diǎn)個(gè)關(guān)注就好了哦。發(fā)車嘍~

本篇CoorChice填了上篇文章中的一些坑碉渡,并借此跑通了一遍客戶端和Binder驅(qū)動(dòng)通訊的流程聚谁。這是個(gè)很復(fù)雜的過程,大家看著圖走一遍滞诺,再思考思考形导。回過頭來一想习霹,其實(shí)也沒那么難了朵耕。

俗話說會者不難, 難者不會,大概就是這樣吧淋叶。

功力有限阎曹,有錯(cuò)還請指出一起交流交流。

看到這里的童鞋快獎(jiǎng)勵(lì)自己一口辣條吧煞檩!

想要看CoorChice的更多文章处嫌,請點(diǎn)個(gè)關(guān)注哦!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末斟湃,一起剝皮案震驚了整個(gè)濱河市熏迹,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌凝赛,老刑警劉巖注暗,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件坛缕,死亡現(xiàn)場離奇詭異,居然都是意外死亡捆昏,警方通過查閱死者的電腦和手機(jī)赚楚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來屡立,“玉大人直晨,你說我怎么就攤上這事单默「” “怎么了榴芳?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長焚刺。 經(jīng)常有香客問我,道長门烂,這世上最難降的妖魔是什么乳愉? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮屯远,結(jié)果婚禮上蔓姚,老公的妹妹穿的比我還像新娘。我一直安慰自己慨丐,他們只是感情好坡脐,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著房揭,像睡著了一般备闲。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上捅暴,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天恬砂,我揣著相機(jī)與錄音,去河邊找鬼蓬痒。 笑死泻骤,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的梧奢。 我是一名探鬼主播狱掂,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼粹断!你這毒婦竟也來了符欠?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤瓶埋,失蹤者是張志新(化名)和其女友劉穎希柿,沒想到半個(gè)月后诊沪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡曾撤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年端姚,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片挤悉。...
    茶點(diǎn)故事閱讀 39,785評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡渐裸,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出装悲,到底是詐尸還是另有隱情昏鹃,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布诀诊,位于F島的核電站洞渤,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏属瓣。R本人自食惡果不足惜载迄,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望抡蛙。 院中可真熱鬧护昧,春花似錦、人聲如沸粗截。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽慈格。三九已至怠晴,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間浴捆,已是汗流浹背蒜田。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留选泻,地道東北人冲粤。 一個(gè)月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像页眯,于是被迫代替她去往敵國和親梯捕。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容