
Binder系列第一篇:《從getSystemService()開始,開擼Binder通訊機制》http://www.itdecent.cn/p/1050ce12bc1e
Binder系列第二篇:《能用【白話文】來分析Binder通訊機制?》http://www.itdecent.cn/p/fe816777f2cf
Binder系列第三篇:《Binder機制之一次響應(yīng)的故事》http://www.itdecent.cn/p/4fba927dce05
CoorChice在上次的文章 《從getSystemService()開始,開擼Binder通訊機制:http://www.itdecent.cn/p/1050ce12bc1e》 中留了一些關(guān)于Binder的坑,也許大家看的時候有些云里霧里的,這篇文章,CoorChice就開始填這些坑了。并開始逐步的深入Binder核心機制,讓你對Android中最重要的部分有所了解。
好了,咱們發(fā)車了!
由open_driver()開始

在
《從getSystemService()開始,開擼Binder通訊機制:http://www.itdecent.cn/p/1050ce12bc1e》這篇文章中,相信大家應(yīng)該看到在/frameworks/native/libs/binder/ProcessState.cpp文件中,有這樣一段代碼。
static int open_driver()
{
//打開"/dev/binder"Binder驅(qū)動文件,并獲得其描述符
int fd = open("/dev/binder", O_RDWR);
...
//獲取Binder驅(qū)動程序的版本號
status_t result = ioctl(fd, BINDER_VERSION, &vers);
...
size_t maxThreads = 15;
//告知驅(qū)動程序最多可以啟動15條線程處理事物
result = ioctl(fd, BINDER_SET_MAX_THREADS, &maxThreads);
...
return fd;
}
它在ProcessState創(chuàng)建的時候會被調(diào)用。它肩負(fù)了一項重要的使命,就是在該進程中打開/dev/binder設(shè)備文件,然后獲得該設(shè)備文件的描述符??梢钥吹?,打開設(shè)備文件是通過open()函數(shù)實現(xiàn)的,它是怎么實現(xiàn)的呢?
用戶空間函數(shù)與Binder驅(qū)動函數(shù)
首先打開/drivers/staging/android/binder.c文件,然后找到下面這個結(jié)構(gòu)體:
//這個結(jié)構(gòu)體中定義了文件操作符與Binder驅(qū)動的對應(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ū)動的binder_mmap()函數(shù)的調(diào)用
.mmap = binder_mmap,
//用戶空間的open()操作,會引起B(yǎng)inder驅(qū)動的binder_open()函數(shù)的調(diào)用
.open = binder_open,
.flush = binder_flush,
.release = binder_release,
};
這個結(jié)構(gòu)體會作為下面這個結(jié)構(gòu)體的一個成員,在Binder驅(qū)動注冊的時候被和Linux定義的文件操作符關(guān)聯(lián)。
static struct miscdevice binder_miscdev = {
.minor = MISC_DYNAMIC_MINOR,
//定義設(shè)備節(jié)點文件名。這里Binder驅(qū)動設(shè)備的文件路徑即為/dev/binder
.name = "binder",
//關(guān)聯(lián)Linux文件操作符
.fops = &binder_fops
};
這樣,在Binder驅(qū)動設(shè)備注冊完成后,在用戶空間調(diào)用poll()、open()等函數(shù)的時候,Binder驅(qū)動的對應(yīng)函數(shù)就會被調(diào)用。
open()函數(shù)的真面目
現(xiàn)在,我們知道了,當(dāng)我們在用戶空間調(diào)用open()函數(shù)時,Binder驅(qū)動層的binder_open()函數(shù)會被隨之調(diào)用。我們看看binder_open()函數(shù)做了些什么?
//用戶空間調(diào)用open()實際調(diào)用的是這里
//參數(shù)為打開設(shè)備文件后傳遞過來的
static int binder_open(struct inode *nodp, struct file *filp)
{
//binder_proc儲存進程信息的結(jié)構(gòu)體
//注意,這個進程結(jié)構(gòu)體是存在于Binder內(nèi)核空間中的
struct binder_proc *proc;
//將當(dāng)前進程的信息儲存到binder_proc中
...
//鎖定同步
binder_lock(__func__);
...
//將該進程上下文信息proc保存到Binder驅(qū)動的進程樹中
//以便查找使用
hlist_add_head(&proc->proc_node, &binder_procs);
// 設(shè)置進程id
proc->pid = current->group_leader->pid;
...
// 將進程信息結(jié)構(gòu)體賦值給文件私有數(shù)據(jù)
filp->private_data = proc;
//釋放鎖
binder_unlock(__func__);
...
//在/proc/binder/proc下創(chuàng)建名為進程id的文件,便于查看進程的通訊
snprintf(strbuf, sizeof(strbuf), "%u", proc->pid);
return 0;
}
這個函數(shù)主要的作用是為打開了/dev/binder設(shè)備文件的進程生成一個專屬的進程信息體,然后保存到驅(qū)動中。這樣,該進程就能和Binder驅(qū)動互動了。
可能有的細(xì)心的同學(xué)會發(fā)現(xiàn),open()函數(shù)會返回設(shè)備表述符,而binder_open()函數(shù)看起來只會返回0???CoorChice在前面說過,binder_open()只是和open()產(chǎn)生了關(guān)聯(lián),但實際打開設(shè)備文件的操作還是Linux再進行。想必你也可以看到,binder_open()函數(shù)的參數(shù)是在設(shè)備文件打開后才可能獲取的。所以,這個設(shè)備描述符應(yīng)該是由Linux來分配給進程的。
下面,接著看看在open_driver()中出現(xiàn)的另一個函數(shù)ioctl()。
ioctl()函數(shù)的真面目
如果你理解了上面的open()函數(shù),那么自然就知道,用戶空間的ioctl()函數(shù)會引起B(yǎng)inder驅(qū)動層的binder_ioctl()函數(shù)的調(diào)用。我們就看看驅(qū)動層的這個函數(shù)做了什么?這是一個十分重要的函數(shù)啊!
static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
...
// 從file結(jié)構(gòu)體中取出進程信息
struct binder_proc *proc = filp->private_data;
struct binder_thread *thread;
unsigned int size = _IOC_SIZE(cmd);
//表明arg是一個用戶空間地址
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ū)動的binder_ioctl()函數(shù)。
這個函數(shù)比較重要,CoorChice再一步一步的解析一下。

獲得進程信息體
首先Binder驅(qū)動根據(jù)傳入的文件體獲得其中的進程信息。
struct binder_proc *proc = filp->private_data;
還記得在binder_open()中生成的那個進程信息結(jié)構(gòu)體嗎?
來自用戶空間的參數(shù)
void __user *ubuf = (void __user *)arg;
首先我們需要知道,這個arg是從用戶空間傳遞過來的地址。比如ioctl(fd, BINDER_VERSION, &vers)傳遞過來了一個地址,指向用來儲存Binder版本號的空間。
在Binder驅(qū)動層,需要對這個地址進行轉(zhuǎn)換一下,用__user給它做上標(biāo)記,表明它指向的是用戶空間的地址。那么,這個arg指向的空間與Binder驅(qū)動的內(nèi)存空間的數(shù)據(jù)傳遞就需要通過copy_from_user()或者copy_to_user()來進行了。
不同的cmd對應(yīng)不同的操作
switch (cmd) {
...
}
switch中定義了幾個命令,分別對應(yīng)不同的操作,CoorChice不在這全部說了,后面遇到再說。
我們先看看在open_driver()中出現(xiàn)的兩個cmd就行了。
- BINDER_VERSION
這個命令用于獲取Binder驅(qū)動版本號。
//獲取Binder驅(qū)動的版本號
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ù)。因為這個值是需要寫到用戶空間去的。
- BINDER_SET_MAX_THREADS
設(shè)置進程可用于Binder通訊的最大線程數(shù)量。
//設(shè)置用戶進程最大線程數(shù)
case BINDER_SET_MAX_THREADS:
//使用copy_from_user()函數(shù),將用戶空間的數(shù)據(jù)拷貝到內(nèi)核空間
//這里就是把線程數(shù)拷貝給進程結(jié)構(gòu)體的max_threads
if (copy_from_user(&proc->max_threads, ubuf, sizeof(proc->max_threads))) {
...
}
break;
注意,上面使用了copy_from_user()函數(shù),把用戶空間的值,寫到了驅(qū)動層的進程信息體的成員max_threads。
好了,上次open_driver()這個坑算是補上了。

接下來看看ProcessState::getStrongProxyForHandle()函數(shù)留下的坑吧。
接著getStrongProxyForHandle()說
注意啦,從這里開始是山路十八彎,抓好扶好了啊!
先來看一張流程圖。

不夠高清?點這個鏈接下載吧!http://ogemdlrap.bkt.clouddn.com/Binder%E8%BF%9B%E9%98%B6%E5%AE%8C%E6%95%B4.png。So Sweet!
圖中相同顏色的流程線表示同一個流程,上面標(biāo)有數(shù)字,你需要按照數(shù)字順序來看,因為這真的是一個復(fù)雜無比的流程!
另外,同一種顏色的雙向箭頭線指向的是同一個變量或者值相同的變量。同理,相同顏色的帶字空心箭頭指向的也是同一個變量或者相同的值。
每個函數(shù)框上部的框表示在我們這個流程中,傳入函數(shù)的參數(shù)。
溫習(xí)一下getStrongProxyForHandle()中的坑
sp<IBinder> ProcessState::getStrongProxyForHandle(int32_t handle)
{
sp<IBinder> result;
...
//嘗試獲取handle對應(yīng)的handle_entry對象,沒有的話會創(chuàng)建一個
handle_entry* e = lookupHandleLocked(handle);
if (e != NULL) {
IBinder* b = e->binder;
if (b == NULL || !e->refs->attemptIncWeak(this)) {
// 上面的判斷確保了同一個handle不會重復(fù)創(chuàng)建新的BpBinder
if (handle == 0) {
Parcel data;
//在handle對應(yīng)的BpBinder第一次創(chuàng)建時
//會執(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)建一個BpBinder
//handle為0時創(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)建時
//會執(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)用進程在首次獲?。ɑ蛘哒f創(chuàng)建)ServiceManager的Binder前,會先和ServiceManager進行一次無意義的通訊(可以看到這次通訊的code為PING_TRANSACTION),以確保系統(tǒng)的ServiceManager已經(jīng)注冊。既然是在這第一次見到Binder通訊,那么我們就索性從這開始來探索Binder通訊機制的核心流程吧。
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;
//先檢查有沒有,以確保一個線程只有一個IPCThreadState
IPCThreadState* st = (IPCThreadState*)pthread_getspecific(k);
if (st) return st;
return new IPCThreadState; //沒有就new一個IPCThreadState
}
...
}
很明顯,這段代碼確保了進程中每一線程都只會有一個對應(yīng)IPCThreadState。
接下來看看IPCThreadState的構(gòu)造函數(shù)。
IPCThreadState::IPCThreadState()
//保存所在進程
: mProcess(ProcessState::self()),
mMyThreadId(androidGetTid()),
mStrictModePolicy(0),
mLastTransactionBinderFlags(0)
{
pthread_setspecific(gTLS, this);
clearCaller();
//用于接收Binder驅(qū)動的數(shù)據(jù),設(shè)置其大小為256
mIn.setDataCapacity(256);
//用于向Binder驅(qū)動發(fā)送數(shù)據(jù),同樣設(shè)置其大小為256
mOut.setDataCapacity(256);
}
CoorChice注釋的地方比較重要哦,想要看懂后面的流程,上面3個注釋的記住哦!
好了,我們的IPCThreadState算是創(chuàng)建出來了。事實上IPCThreadState主要就是封裝了和Binder通訊的邏輯,當(dāng)我們需要進行通訊時,就需要通過它來完成。

下面就來看看通訊是怎么開始的。
第一步 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ù)中添加一個TF_ACCEPT_FDS標(biāo)志,表示返回數(shù)據(jù)中可以包含文件描述符。以下是幾個標(biāo)志位的意義:
enum transaction_flags {
TF_ONE_WAY = 0x01, /*異步的單向調(diào)用,沒有返回值*/
TF_ROOT_OBJECT = 0x04, /*里面的數(shù)據(jù)是一個組件的根對象*/
TF_STATUS_CODE = 0x08, /*數(shù)據(jù)包含的是一個32bit的狀態(tài)碼*/
TF_ACCEPT_FDS = 0x10, /*允許返回對象中,包含文件描述符*/
}
接著,會調(diào)用writeTransactionData()函數(shù),把需要發(fā)送的數(shù)據(jù)準(zhǔn)備好。注意這里的命令是BC_TRANSACTION哦。如果你隨時對照著圖查看參數(shù)的話,這個流程將會變的容易理解一些。
第二步 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)進程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;
}
如你所見,這個函數(shù)主要創(chuàng)建了一個用于儲存通訊事務(wù)數(shù)據(jù)的binder_transaction_data結(jié)構(gòu)t,并把需要發(fā)送的事務(wù)數(shù)據(jù)放到其中,然后再把這個tr寫入IPCThreadState的mOut中。這樣一來,后面就可以從mOut中取出這個通訊事務(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ù)時,如果沒有reply,會創(chuàng)建一個fakeReplay。我們回顧一下:
transact(0, IBinder::PING_TRANSACTION, data, NULL, 0)
看,我們上面?zhèn)魅氲膔eplay是一個NULL,所以這里是會創(chuàng)建一個fakeReplay的。
緊接著,我們就進入到IPCThreadState::waitForResponse()中了??梢钥聪聢D中的流程線哦,對應(yīng)紅色編號3的線。
status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult)
{
...
while (1) {
//真正和Binder驅(qū)動交互的是talkWithDriver()函數(shù)
if ((err=talkWithDriver()) < NO_ERROR) break;
...
}
...
}
這個方法中,一開始就有些隱蔽的調(diào)用了一個十分重要的方法IPCThreadState::talkWithDriver(),從名字也能看出來,真正和Binder驅(qū)動talk的邏輯是在這個函數(shù)中的。這個地方給差評!

順著代碼,我們進入talkWithDriver()看看用戶空間是如何和Binder驅(qū)動talk的。
第四步 talkWithDriver()和Binder talk!
注意,需要說明一下,IPCThreadState::talkWithDriver()這個函數(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)核進行讀寫
if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)
err = NO_ERROR;
...
} while (err == -EINTR);
...
}
這個函數(shù)中,有一個重要結(jié)構(gòu)被定義,就是binder_write_read。它能夠儲存一些必要的發(fā)送和接收的通訊信息,它就像用戶空間和內(nèi)核空間之間的一個信使一樣,在兩端傳遞信息。
在這個函數(shù)中,首先會把用戶空間要傳遞/讀取信息放到bwr中,然后通過一句關(guān)鍵的代碼ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr)與Binder驅(qū)動talk。ioctl()函數(shù)CoorChice已經(jīng)在上一篇中說了,它最終會調(diào)用到Binder內(nèi)核的binder_ioctl()函數(shù),至于為什么?你可以再看看上一篇文章回顧下。

注意這里我們給ioctl()函數(shù)傳遞的參數(shù)。
- 第一個參數(shù),是從本進程中取出上面篇中打開并保存Binder設(shè)備文件描述符,通過它可以獲取到之前生成的file文件結(jié)構(gòu),然后傳給
binder_ioctl()函數(shù)。沒印象的同學(xué)先看看上篇回顧下這里。 - 第二個參數(shù),是命令,它決定了待會到內(nèi)核空間中要執(zhí)行那段邏輯。
- 第三個參數(shù),我們把剛剛定義的信使bwr的內(nèi)存地址傳到內(nèi)核空間去。
這些參數(shù)是理解后面步驟的關(guān)鍵,不要忘了哦!現(xià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)體中取出進程信息
struct binder_proc *proc = filp->private_data;
struct binder_thread *thread;
unsigned int size = _IOC_SIZE(cmd);
//表明arg是一個用戶空間地址
//__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__);
...
}
這個函數(shù)看過《從getSystemService()開始,開擼Binder通訊機制:http://www.itdecent.cn/p/1050ce12bc1e》的同學(xué)應(yīng)該不會陌生。首先會根據(jù)文件描述符獲得的file結(jié)構(gòu),獲取到調(diào)用ioctl()函數(shù)的進程的進程信息,從而再獲得進程的線程。然后將arg參數(shù)地址轉(zhuǎn)換成有用戶空間標(biāo)記的指針。接著,在switch中根據(jù)cmd參數(shù)判斷需要執(zhí)行什么操作。這些步驟和上篇文章中是一樣的。不同的是,我們這次的cmd命令是BINDER_WRITE_READ,表示要進行讀寫操作。可以看到,接下來的讀寫邏輯是在binder_ioctl_write_read()函數(shù)中的。
嗯,接下來,我們即將進入第6步,看看Binder驅(qū)動中的這段讀寫通訊邏輯是怎樣的?
第六步 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ā)送進程信息
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);
...
}
咱們先看上面這個片段。
首先自然是取出用戶空間的進程信息,然后轉(zhuǎn)換獲得用戶空間的參數(shù)地址(對應(yīng)本次通訊中為bwr的地址),這些都跟在binder_ioctl()中做的差不多。
接下來,你可以看到一個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ù)來處理,我看看是如何進行處理的。
第七步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;
}
一開始就是對一些變量進行賦值。
首先,binder_buffer是啥?哪來的?快到到傳參的地方方看看bwr.write_buffer,它是寫的buffer。那么它里面裝了啥?這就得回到第4步中找了,因為bwr是在那個地方定義和初始化的。bwr.write_buffer = (uintptr_t)mOut.data(),嗯,它指向了mOut中的數(shù)據(jù)。那么問題又來了?mOut中的數(shù)據(jù)是啥?...

看,這就是為什么CoorChice一直在強調(diào),前面的一些參數(shù)和變量一定要記??!不然到后面就會云里霧里的!不過還好,有了CoorChcie上面那張圖,你隨時可以快速的找到答案。我們回到第二步writeTransactionData(),就是通訊事務(wù)結(jié)構(gòu)定義的那個地方??吹?jīng)],mOut中儲存的就是一個通訊事務(wù)結(jié)構(gòu)。
現(xiàn)在答案就明了了,buffer指向了用戶空間的通訊事務(wù)數(shù)據(jù)。
另外兩個參數(shù),ptr此刻和buffer的值是一樣的,因為consumed為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ù)就是干這個的,拷貝一些簡單的變量?;氐降?步writeTransactionData()中,看看參數(shù)。沒錯,cmd為BC_TRANSACTION。所以,進到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);
先定義了一個內(nèi)核空間的通訊事務(wù)數(shù)據(jù)tr,然后把用戶空間的通訊事務(wù)數(shù)據(jù)拷貝到內(nèi)核中tr。此時,ptr指針移動sizeof(tr)個單位,現(xiàn)在ptr應(yīng)該和end的值是一樣的了。然后,調(diào)用binder_transaction()來處理事務(wù)。
第八步 binder_transaction()來處理事務(wù)
順著流程線8看過去,WTF!這是一個復(fù)雜無比的函數(shù)!很多!很長!
函數(shù)一開始定義了一堆變量,我們先不管,用到時再說。先看第一個使用到的參數(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ù)事物信息中的進程的句柄不為0,即不是系統(tǒng)ServiceManager進程
//定義binder引用
struct binder_ref *ref;
//根據(jù)參數(shù)binder進程和句柄handle來查找對應(yīng)的binder
ref = binder_get_ref(proc, tr->target.handle);
...
//設(shè)置目標(biāo)binder實體為上面找到的參數(shù)進程的binder引用的binder實體
target_node = ref->node;
} else {
//如果參數(shù)事物信息中的進程的句柄為0,即是系統(tǒng)ServiceManager進程
//設(shè)置通訊目標(biāo)進程的Binder實體為ServiceManager對應(yīng)的Binder
target_node = binder_context_mgr_node;
}
//設(shè)置通訊目標(biāo)進程為target_node對應(yīng)的進程
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),這個值來自IPCThreadState::transact()函數(shù)的參數(shù)handle。那么回到我們一開始調(diào)用這個函數(shù)的地方IPCThreadState::self()->transact(0, IBinder::PING_TRANSACTION, data, NULL, 0)。哦,handle為0。所以這里的target就是ServiceManager。那么直接把binder_context_mgr_node(它表示ServiceManager的Binder,在ServiceManager注冊的時候被緩存到了Binder內(nèi)核中)賦值給target_node,記住了哦!后面這些都會用到??傊?,我們就是需要先獲取到一個目標(biāo)進程。
接下來,通過target_node,也就是ServiceManager進程的Binder(其它情況就是對應(yīng)進程的Binder),我們可以獲取到目標(biāo)進程信息,然后賦值給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隊列
target_list = &target_thread->todo;
target_wait = &target_thread->wait;
...
} else {
//獲得通訊目標(biāo)進程的任務(wù)隊列
target_list = &target_proc->todo;
//獲取通訊目標(biāo)進程的等待對象
target_wait = &target_proc->wait;
}
...
}
首先看target_thread是否為空,由于我們沒有走if(replay)的TRUE邏輯,所以這里target_thread是為空的。那么,從目標(biāo)進程信息target_proc分別去除todo任務(wù)隊列和wait對象,賦值給target_list和target_wait。同樣需要記?。?/p>
繼續(xù)下一段代碼。
static void binder_transaction(struct binder_proc *proc,
struct binder_thread *thread,
struct binder_transaction_data *tr, int reply){
struct binder_transaction *t; //表示一個binder通訊事務(wù)
struct binder_work *tcomplete; //表示一項work
...
struct list_head *target_list; //通訊目標(biāo)進程的事務(wù)隊列
wait_queue_head_t *target_wait; //通訊目標(biāo)進程的等待對象
...
//為本次通訊事務(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ù)的接收進程為目標(biāo)進程
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)進程中分配進行本次通訊的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實體為target_node
//如前面一樣,通過這個buffer可以找到對應(yīng)的進程
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);
...
//計算結(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)示這是一個同步事務(wù),需要等待對方回復(fù)。0表示這是一個異步事務(wù),不用等對方回復(fù)
//設(shè)置本次通訊事務(wù)的from_parent為發(fā)送方進程的事務(wù)
t->from_parent = thread->transaction_stack;
//設(shè)置發(fā)送方進程的事務(wù)棧為本次通訊事務(wù)
thread->transaction_stack = t;
}
...
//將本次通訊事務(wù)的work類型設(shè)置為BINDER_WORK_TRANSACTION
t->work.type = BINDER_WORK_TRANSACTION;
//將本次通訊事務(wù)的work添加到目標(biāo)進程的事務(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)進程,開始執(zhí)行目標(biāo)進程的事務(wù)棧
wake_up_interruptible(target_wait);
return;
}
在開始分析之前,大家先吧這段代碼開始的幾個變量定義記住,不然后面會很迷茫的!
這段代碼很多!很長!CoorChice已經(jīng)盡量的刪去一些沒那么重要的和我不知道是干啥的了?。?/p>

其實這么多代碼,主要使用在給binder事務(wù)t的成員賦值了。我們簡單看幾個我認(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ù)的進程(即發(fā)送方進程)的進程信息中獲取到了thread。
接著設(shè)置事務(wù)t的目標(biāo)進程t->to_proc和目標(biāo)進程的線程t->to_thread為前面處理好的target_proc和target_thread(本次通訊,target_thread為空哦)。
然后把通訊事務(wù)數(shù)據(jù)tr中的code和flags賦值給事務(wù)t的code和flags。code和flags是什么呢?我們回到用戶空間,定義通訊事務(wù)數(shù)據(jù),即第2步IPCThreadState::writeTransaction()中可以看到,code和flags均是傳進來的參數(shù)。而的發(fā)源地是通訊的起始點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)進程target_proc中為t->buffer申請了內(nèi)存,即t->buffer指向了目標(biāo)進程空間中的一段內(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)進程的事務(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)進程,開始執(zhí)行目標(biāo)進程的事務(wù)棧
wake_up_interruptible(target_wait);
return;
先把事務(wù)t的work.type類型設(shè)置為BINDER_WORK_TRANSACTION類型,這決定了該事務(wù)后面走的流程,然后把事務(wù)t的任務(wù)添加到目標(biāo)進程的任務(wù)棧target_list中。接著把work tcomplete的類型設(shè)置為BINDER_WORK_TRANSACTION_COMPLETE,用于告訴發(fā)送方,和Binder驅(qū)動的一次talk完成了,同樣,需要把這個項任務(wù)添加到發(fā)送方的任務(wù)列表里。
最后,通過wake_up_interruptible(target_wait)函數(shù)喚醒休眠中的目標(biāo)進程,讓它開始處理任務(wù)棧中的任務(wù),也就是剛剛我們添加到target_list中的任務(wù)。接著return結(jié)束該函數(shù)。
結(jié)束這個函數(shù)你以為就忘啦?Native!接著往下看。
第9步 binder_thread_read()讀取數(shù)據(jù)
上一個函數(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ū)動會調(diào)用binder_thread_read()函數(shù),為發(fā)送進程讀取數(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隊列
w = list_first_entry(&thread->todo, struct binder_work, entry);
} else if (!list_empty(&proc->todo) && wait_for_proc_work) {
//獲取從進程獲取work隊列
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;
}
...
}
我們先看這個片段,前面一堆代碼掠過了。首先,需要看看能不能從發(fā)送進程的線程thread的任務(wù)棧中取出任務(wù)來,回顧第8步binder_transaction()中,我們在最后往發(fā)送進程的線程thread的任務(wù)棧中添加了一個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寫入用戶進程空間的mIn中
put_user(cmd, (uint32_t __user *)ptr);
//從事務(wù)隊列中刪除本次work
list_del(&w->entry);
//釋放
kfree(w);
break;
...
}
}
...
}
由于這個流程中,我們知道work的type為BINDER_WORK_TRANSACTION_COMPLETE類型,所以就先只看這種情況了。在這段代碼中,cmd = BR_TRANSACTION_COMPLETE很重要,要記住!接著把cmd拷貝到用戶空間的發(fā)送進程,然后刪除任務(wù),釋放內(nèi)存。
一次和Binder驅(qū)動的通訊完成!
上面代碼執(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ū)動交互的是talkWithDriver()函數(shù)
if ((err=talkWithDriver()) < NO_ERROR) break;
err = mIn.errorCheck();
...
if (mIn.dataAvail() == 0) continue;
//取出在內(nèi)核中寫進去的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。在這段邏輯中,由于之前我們傳入了一個fakeReplay進來,所以程序走bredk,然后繼續(xù)循環(huán),執(zhí)行下一次talkWithDriver()函數(shù)。到此,我們和Binder內(nèi)核的一次通訊算是完成了。
但是我們發(fā)起的這次通訊還沒有得到回應(yīng)哦!猜猜看回應(yīng)的流程是怎樣的呀?

文章太長了,回應(yīng)流程放到下一篇了。
總結(jié)
- 抽出空余時間寫文章分享需要動力,還請各位看官動動小手點個贊,給我點鼓勵??
- 我一直在不定期的創(chuàng)作新的干貨,想要上車只需進到我的【個人主頁】點個關(guān)注就好了哦。發(fā)車嘍~
本篇CoorChice填了上篇文章中的一些坑,并借此跑通了一遍客戶端和Binder驅(qū)動通訊的流程。這是個很復(fù)雜的過程,大家看著圖走一遍,再思考思考?;剡^頭來一想,其實也沒那么難了。
俗話說會者不難, 難者不會,大概就是這樣吧。
功力有限,有錯還請指出一起交流交流。
看到這里的童鞋快獎勵自己一口辣條吧!
想要看CoorChice的更多文章,請點個關(guān)注哦!