對(duì)冗余挑揀重點(diǎn),對(duì)重點(diǎn)深入補(bǔ)充酬滤,輸出結(jié)構(gòu)清晰的精簡(jiǎn)版
- 深入 binder 驅(qū)動(dòng)內(nèi)部
binder_ioctl
binder_get_thread
binder_ioctl_write_read
binder_thread_write
binder_transaction
binder_thread_read
小結(jié)- binder Q&A
如何找到目標(biāo)進(jìn)程 Binder 實(shí)體
如何實(shí)現(xiàn) Binder 線程的睡眠與喚醒- 最后
深入 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 通信流程而言:
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)通信的。