對冗余挑揀重點,對重點深入補充,輸出結構清晰的精簡版
- 深入 binder 驅動內部
binder_ioctl
binder_get_thread
binder_ioctl_write_read
binder_thread_write
binder_transaction
binder_thread_read
小結- binder Q&A
如何找到目標進程 Binder 實體
如何實現(xiàn) Binder 線程的睡眠與喚醒- 最后
深入 binder 驅動內部
前兩篇文章都有提到 binder_ioctl 方法,在 Binder 機制 [上] 中介紹了 binder_ioctl 支持的命令;Binder 機制 [中] 中提到 IPCThreadState 會調用到 binder_ioctl 方法。
書中對 binder 驅動內部調用的講解沒有分為較清晰的步驟,一口氣就是 20 頁篇幅的源碼詳解,理解起來有些難度,容易迷失。在細讀了三四遍后,終于感覺對整體有些掌握了,結合前面的學習與自己的理解,將一次 IPC 調用中 binder 驅動的工作分為以下 5 步:
1.準備數(shù)據(jù),根據(jù)命令分發(fā)給具體的方法去處理
2.找到目標進程的相關信息
3.將數(shù)據(jù)一次拷貝到目標進程所映射的物理內存塊
4.記錄待處理的任務,喚醒目標線程
5.調用線程進入休眠
6.目標進程直接拿到數(shù)據(jù)進行處理,處理完后喚醒調用線程
7.調用線程返回處理結果
與上篇文章一樣仍以 getService() 為例,按照上面的工作步驟為脈絡,深入分析驅動層中的執(zhí)行邏輯,徹底搞定 binder 驅動!
binder_ioctl
在 IPCThreadState 中這樣調用了 binder_ioctl() 方法:
ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr)
binder_ioctl() 方法中會根據(jù) BINDER_WRITE_READ、BINDER_SET_MAX_THREADS 等不同 cmd 轉調到不同的方法去執(zhí)行,這里我們只關注 BINDER_WRITE_READ,簡化后代碼如下:
static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg){
int ret;
//拿到調用進程在 binder_open() 中記錄的 binder_proc
struct binder_proc *proc = filp->private_data;
struct binder_thread *thread;
binder_lock(__func__);
//獲取調用線程 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:{...} //設置 binder 最大線程數(shù)
case BINDER_SET_CONTEXT_MGR:{...} //設置 service 大管家,即 ServiceManager
case BINDER_THREAD_EXIT:{...} //binder 線程退出命令,釋放相關資源
case BINDER_VERSION: {...} //獲取 binder 驅動版本號
...
}
在 Binder 機制 [上] 中詳細介紹過 binder_open() 方法,它主要做了兩個工作:1.創(chuàng)建及初始化每個進程獨有一份的、用來存放 binder 相關數(shù)據(jù)的 binder_proc 結構體,2.將 binder_proc 記錄起來,方便后續(xù)使用。正是通過 file 來記錄的:
static int binder_open(struct inode *nodp, struct file *filp){
...
filp->private_data = proc;
...
}
拿到調用進程后,進一步通過 binder_get_thread() 方法拿到調用線程,然后就交給 binder_ioctl_write_read() 方法去執(zhí)行具體的 binder 數(shù)據(jù)讀寫了,可見 binder_ioctl() 方法本身的邏輯非常簡單,將數(shù)據(jù) arg 透傳了出去。下面分別來看 binder_get_thread()、binder_ioctl_write_read() 這兩個方法。
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 中獲取紅黑樹根節(jié)點
//查找 pid 等于當前線程 id 的thread,該紅黑樹以 pid 大小為序存放
while (*p) {
parent = *p;
thread = rb_entry(parent, struct binder_thread, rb_node);
if (current->pid < thread->pid) //current->pid 是當前調用線程的 id
p = &(*p)->rb_left;
else if (current->pid > thread->pid)
p = &(*p)->rb_right;
else
break;
}
if (*p == NULL) {//如果沒有找到,則新創(chuàng)建一個
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); //初始化等待隊列
INIT_LIST_HEAD(&thread->todo); //初始化待處理隊列
rb_link_node(&thread->rb_node, parent, p); //加入到 proc 的 threads 紅黑樹中
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 是用來描述線程的結構體,binder_get_thread() 方法中邏輯也很簡單,首先從調用進程 proc 中查找當前線程是否已被記錄,如果找到就直接返回,否則新建一個返回,并記錄到 proc 中。也就是說所有調用 binder_ioctl() 的線程,都會被記錄起來。
binder_ioctl_write_read
此方法分為兩部分來看,首先是整體:
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; //用戶傳下來的數(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ù)臄?shù)據(jù)拷貝到內核空間了,但目前還沒有看到 server 端的任何線索,bwr 跟 server 端沒有映射關系,那后續(xù)再將 bwr 傳輸給 server 端的時候又要拷貝,這樣豈不是多次拷貝了?
其實這里的 copy_from_user() 方法并沒有拷貝要傳輸?shù)臄?shù)據(jù),而僅是拷貝了持有傳輸數(shù)據(jù)內存地址的 bwr。后續(xù)處理數(shù)據(jù)時會根據(jù) bwr 信息真正的去拷貝要傳輸?shù)臄?shù)據(jù)。
處理完數(shù)據(jù)后,會將處理結果體現(xiàn)在 bwr 中,然后返回給用戶空間處理。那是如何處理數(shù)據(jù)的呢?所謂的處理數(shù)據(jù),就是對數(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;
}
}
可見 binder 驅動內部依賴用戶空間的 binder_write_read 決定是要讀取還是寫入數(shù)據(jù):其內部變量 read_size>0 則代表要讀取數(shù)據(jù),write_size>0 代表要寫入數(shù)據(jù),若都大于 0 則先寫入,后讀取。
至此焦點應該集中在 binder_thread_write() 和 binder_thread_read(),下面分析這兩個方法。
binder_thread_write
在上面的 binder_ioctl_write_read() 方法中調用 binder_thread_write() 時傳入了 bwr.write_buffer、bwr.write_size 等,先搞清楚這些參數(shù)是什么。
最開始是在用戶空間 IPCThreadState 的 transact() 中通過 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; //到驅動內部后會取出此結構體進行處理
tr.target.ptr = 0;
tr.target.handle = handle; //目標 server 的 binder 的句柄
tr.code = code; //請求碼,getService() 服務對應的是 GET_SERVICE_TRANSACTION
tr.flags = binderFlags;
tr.cookie = 0;
tr.sender_pid = 0;
tr.sender_euid = 0;
const status_t err = data.errorCheck(); //驗證數(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() 方法中對 write_buffer 賦值:
bwr.write_buffer = (uintptr_t)mOut.data();
搞清楚了數(shù)據(jù)的來源,再來看 binder_thread_write() 方法,binder_thread_write() 方法中處理了大量的 BC_XXX 命令,代碼很長,這里我們只關注當前正在處理的 BC_TRANSACTION 命令,簡化后代碼如下:
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ù)結束地址
while (ptr < end && thread->return_error == BR_OK) { //可能有多個命令及對應數(shù)據(jù)要處理,所以要循環(huán)
if (get_user(cmd, (uint32_t __user *)ptr)) // 讀取一個 cmd
return -EFAULT;
ptr += sizeof(uint32_t); //跳過 cmd 所占的空間,指向要處理的數(shù)據(jù)
switch (cmd) {
case BC_TRANSACTION:
case BC_REPLY: {
struct binder_transaction_data tr; //與 writeTransactionData 中準備的數(shù)據(jù)結構體對應
if (copy_from_user(&tr, ptr, sizeof(tr))) //拷貝到內核空間 tr 中
return -EFAULT;
ptr += sizeof(tr); //跳過數(shù)據(jù)所占空間,指向下一個 cmd
binder_transaction(proc, thread, &tr, cmd == BC_REPLY); //處理數(shù)據(jù)
break;
}
處理其他 BC_XX 命令...
}
*consumed = ptr - buffer; //被寫入處理消耗的數(shù)據(jù)量,對應于用戶空間的 bwr.write_consumed
binder_thread_write() 中從 bwr.write_buffer 中取出了 cmd 和 cmd 對應的數(shù)據(jù),進一步交給 binder_transaction() 處理,需要注意的是,BC_TRANSACTION、BC_REPLY 這兩個命令都是由 binder_transaction() 處理的。
簡單梳理一下,由 binder_ioctl -> binder_ioctl_write_read -> binder_thread_write ,到目前為止還只是在準備數(shù)據(jù),沒有看到跟目標進程相關的任何處理,都屬于 "準備數(shù)據(jù),根據(jù)命令分發(fā)給具體的方法去處理" 第 1 個工作。而到此為止,第 1 個工作便結束,下一步的 binder_transaction() 方法終于要開始后面的工作了。
binder_transaction
binder_transaction() 方法中代碼較長,先總結它干了哪些事:對應開頭列出的工作,此方法中做了非常關鍵的 2-4 步:
- 找到目標進程的相關信息
- 將數(shù)據(jù)一次拷貝到目標進程所映射的物理內存塊
- 記錄待處理的任務,喚醒目標線程
以這些工作為線索,將代碼分為對應的部分來看,首先是找到目標進程的相關信息,簡化后代碼如下:
static void binder_transaction(struct binder_proc *proc,
struct binder_thread *thread,
struct binder_transaction_data *tr, int reply){
struct binder_transaction *t; //用于描述本次 server 端要進行的 transaction
struct binder_work *tcomplete; //用于描述當前調用線程未完成的 transaction
binder_size_t *offp, *off_end;
struct binder_proc *target_proc; //目標進程
struct binder_thread *target_thread = NULL; //目標線程
struct binder_node *target_node = NULL; //目標 binder 節(jié)點
struct list_head *target_list; //目標 TODO 隊列
wait_queue_head_t *target_wait; //目標等待隊列
if(reply){
in_reply_to = thread->transaction_stack;
...處理 BC_REPLY,暫不關注
}else{
//處理 BC_TRANSACTION
if (tr->target.handle) { //handle 不為 0
struct binder_ref *ref;
//根據(jù) handle 找到目標 binder 實體節(jié)點的引用
ref = binder_get_ref(proc, tr->target.handle);
target_node = ref->node; //拿到目標 binder 節(jié)點
} else {
// handle 為 0 則代表目標 binder 是 service manager
// 對于本次調用來說目標就是 service manager
target_node = binder_context_mgr_node;
}
}
target_proc = target_node->proc; //拿到目標進程
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; //拿到目標線程
tmp = tmp->from_parent;
}
}
target_list = &target_thread->todo; //拿到目標 TODO 隊列
target_wait = &target_thread->wait; //拿到目標等待隊列
binder_transaction、binder_work 等結構體在上一篇中有介紹,上面代碼中也詳細注釋了它們的含義。比較關鍵的是 binder_get_ref() 方法,它是如何找到目標 binder 的呢?這里暫不延伸,下文再做分析。
繼續(xù)看 binder_transaction() 方法的第 2 個工作,將數(shù)據(jù)一次拷貝到目標進程所映射的物理內存塊:
t = kzalloc(sizeof(*t), GFP_KERNEL); //創(chuàng)建用于描述本次 server 端要進行的 transaction
tcomplete = kzalloc(sizeof(*tcomplete), GFP_KERNEL); //創(chuàng)建用于描述當前調用線程未完成的 transaction
if (!reply && !(tr->flags & TF_ONE_WAY)) //將信息記錄到 t 中:
t->from = thread; //記錄調用線程
else
t->from = NULL;
t->sender_euid = task_euid(proc->tsk);
t->to_proc = target_proc; //記錄目標進程
t->to_thread = target_thread; //記錄目標線程
t->code = tr->code; //記錄請求碼,getService() 對應的是 GET_SERVICE_TRANSACTION
t->flags = tr->flags;
//實際申請目標進程所映射的物理內存,準備接收要傳輸?shù)臄?shù)據(jù)
t->buffer = binder_alloc_buf(target_proc, tr->data_size,
tr->offsets_size, !reply && (t->flags & TF_ONE_WAY));
//申請到 t->buffer 后,從用戶空間將數(shù)據(jù)拷貝進來,這里就是一次拷貝數(shù)據(jù)的地方!!
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;
}
為什么在拷貝之前要先申請物理內存呢?在 Binder 機制 [上] 中介紹 binder_mmap() 時詳細分析過,雖然 binder_mmap() 直接映射了 (1M-8K) 的虛擬內存,但卻只申請了 1 頁的物理頁面,等到實際使用時再動態(tài)申請。也就是說,在 binder_ioctl() 實際傳輸數(shù)據(jù)的時候,再通過 binder_alloc_buf() 方法去申請物理內存。
至此已經(jīng)將要傳輸?shù)臄?shù)據(jù)拷貝到目標進程,目標進程可以直接讀取到了,接下來要做的就是將目標進程要處理的任務記錄起來,然后喚醒目標進程,這樣在目標進程被喚醒后,才能知道要處理什么任務。
最后來看 binder_transaction() 方法的第 3 個工作,記錄待處理的任務,喚醒目標線程:
if (reply) { //如果是處理 BC_REPLY,pop 出來棧頂記錄的 transaction(實際上是刪除鏈表頭元素)
binder_pop_transaction(target_thread, in_reply_to);
} else if (!(t->flags & TF_ONE_WAY)) {
//如果不是 oneway,將 server 端要處理的 transaction 記錄到當前調用線程
t->need_reply = 1;
t->from_parent = thread->transaction_stack;
thread->transaction_stack = t;
} else {
...暫不關注 oneway 的情況
}
t->work.type = BINDER_WORK_TRANSACTION;
list_add_tail(&t->work.entry, target_list); //加入目標的處理隊列中
tcomplete->type = BINDER_WORK_TRANSACTION_COMPLETE; //設置調用線程待處理的任務類型
list_add_tail(&tcomplete->entry, &thread->todo); //記錄調用線程待處理的任務
if (target_wait)
wake_up_interruptible(target_wait); //喚醒目標線程
再次梳理一下,至此已經(jīng)完成了前四個工作:
1.準備數(shù)據(jù),根據(jù)命令分發(fā)給具體的方法去處理
2.找到目標進程的相關信息
3.將數(shù)據(jù)一次拷貝到目標進程所映射的物理內存塊
4.記錄待處理的任務,喚醒目標線程
其中第 1 個工作涉及到的方法為 binder_ioctl() -> binder_get_thread() -> binder_ioctl_write_read() -> binder_thread_write() ,主要是一些數(shù)據(jù)的準備和方法轉跳,沒做什么實質的事情。而 binder_transaction() 方法中做了非常重要的 2-4 工作。
剩下的工作還有:
5.調用線程進入休眠
6.目標進程直接拿到數(shù)據(jù)進行處理,處理完后喚醒調用線程
7.調用線程返回處理結果
可以想到,5 和 6 其實沒有時序上的限制,而是并行處理的。下面先來看第 5 個工作:調用線程是如何進入休眠等待服務端執(zhí)行結果的。
binder_thread_read
在喚醒目標線程后,調用線程就執(zhí)行完 binder_thread_write() 寫完了數(shù)據(jù),返回到 binder_ioctl_write_read() 方法中,接著執(zhí)行 binder_thread_read() 方法。
而調用線程的休眠就是在此方法中觸發(fā)的,下面將 binder_thread_read() 分為兩部分來看,首先是是否阻塞當前線程的判斷邏輯:
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ù)結束地址
if (*consumed == 0) {
if (put_user(BR_NOOP, (uint32_t __user *)ptr))
return -EFAULT;
ptr += sizeof(uint32_t);
}
//是否要準備睡眠當前線程
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 ,所以將一個 BR_NOOP 加到了 ptr 中。
怎么理解 wait_for_proc_work 條件呢?在 binder_transaction() 方法中將 server 端要處理的 transaction 記錄到了當前調用線程 thread->transaction_stack 中;將當前調用線程待處理的任務記錄到了 thread->todo 中。所以這里的 thread->transaction_stack 和 thread->todo 都不為空,wait_for_proc_work 為 false,代表不準備阻塞當前線程。
但 wait_for_proc_work 并不是決定是否睡眠的最終條件,接著往下看,其中 non_block 恒為 false,那是否要睡眠當前線程就取決于 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,當前調用線程不進入休眠,繼續(xù)往下執(zhí)行。你可能會有疑問,不是說調用線程的休眠就是在 binder_thread_read() 方法中觸發(fā)的嗎?確實是,只不過不是本次,先接著分析 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 中。而這里就是對這個 binder_work 進行處理,將一個 BR_TRANSACTION_COMPLETE 命令加到了 ptr 中。
梳理一下目前的邏輯,至此已經(jīng)順序執(zhí)行完 binder_thread_write()、binder_thread_read() 方法,并且在 binder_thread_read() 中往用戶空間傳輸了兩個命令:BR_NOOP 和 BR_TRANSACTION_COMPLETE。
本次 binder_ioctl() 調用就執(zhí)行完了,然后會回到 IPCThreadState 中,上一篇文章中詳細分析了 IPCThreadState 中的代碼,這里就不再展開,簡單概括一下后續(xù)執(zhí)行的邏輯:
mIn 中有 BR_NOOP 和 BR_TRANSACTION_COMPLETE 兩個命令,首先處理 BR_NOOP 命令,此命令什么也沒做,由于 talkWithDriver() 處于 while 循環(huán)中,會再一次進入 talkWithDriver(),但因為此時 mIn 中還有數(shù)據(jù)沒讀完,不會調用 binder_ioctl()。
然后處理 BR_TRANSACTION_COMPLETE 命令,如果是 oneway 就直接結束本次 IPC 調用,否則再一次進入 talkWithDriver(),第二次進入 talkWithDriver 時,bwr.write_size = 0,bwr.read_size > 0,所以會第二次調用 binder_ioctl() 方法。第二次執(zhí)行 binder_ioctl() 時,bwr.write_size = 0,bwr.read_size > 0,所以不會再執(zhí)行 binder_thread_write() 方法,而只執(zhí)行 binder_thread_read() 方法。
第二次執(zhí)行 binder_thread_read() 時,thread->todo 已經(jīng)被處理為空,但是 thread->transaction_stack 還不為空,wait_for_proc_work 仍然為 false,但最終決定是否要休眠的條件成立了: binder_has_thread_work(thread) 返回 false,由此當前調用線程通過 wait_event_freezable() 進入休眠。
小結
至此還剩下兩個工作:
6.目標進程直接拿到數(shù)據(jù)進行處理,處理完后喚醒調用線程
7.調用線程返回處理結果
但是已經(jīng)不用再看代碼了,因為上述方法已經(jīng)覆蓋了剩下的工作。對于 getService() 來說,目標進程就是 Service Manager,相關的代碼在 Binder 機制 [上] 也已經(jīng)做過詳細的分析。用圖來概括整體的工作。調用進程邏輯:

Service Manager 端邏輯:

本節(jié)完整的分析了一次 IPC 調用中 binder 驅動內部具體的執(zhí)行邏輯,此部分也是 binder 機制中最難的,而將最難的部分掌握后,可以極大的提高信心。
想要完全掌握本節(jié)內容,前提是必須對前兩篇 binder 文章已經(jīng)熟悉,因為 binder 驅動和用戶空間的 IPCThreadState 以及 servicemanager 關聯(lián)是十分緊密的,如果沒有掌握,是無法真正理清本節(jié)內容的。
binder Q&A
在上面分析 binder 驅動代碼過程中,主要是根據(jù)文章開頭的七個工作為線索,為了不偏離主線,在遇到比較重要的知識點時都沒做延伸,這里以 Q&A 的方式補充分析。
如何找到目標進程 Binder 實體
servicemanager 的 binder 實體固定為 binder_context_mgr_node,直接返回即可。如何獲取其他 binder 實體呢?在上面的 binder_transaction() 方法中是這樣獲取目標 binder 的:
//根據(jù) handle 找到目標 binder 實體節(jié)點的引用
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; //取紅黑樹根結點
struct binder_ref *ref;
while (n) { //遍歷查詢指定 desc(handle) 值的 binder 實體
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;
}
可見是在調用進程的 binder_proc 結構體中獲取到的,那目標 binder 實體又是什么時候存儲到調用進程中的呢?
對于實名的 Server,當它利用 addService() 把自身加到 ServiceManager 中時,會"路過" binder 驅動,binder 驅動就會把這一 binder 實體記錄到 ServiceManager 的 binder_proc->refs_by_desc 和 binder_proc->refs_by_node 中。
當一個 Binder Client 通過 getService() 向 ServiceManager 發(fā)起查詢時,ServiceManager 就可以準確的告訴 Client 目標 Binder 實體,將其也記錄到 Client 的 binder_proc->refs_by_desc 和 binder_proc->refs_by_node 中。
上面說的是通過 addService() 將自身注冊到 Service Manager 中的 Server,任何 Binder Client 都能通過 Service Manager 獲取到,這種叫做 "實名" Server。 而 Android 中還存在另一種 Binder Server,并不在 Service Manager 中注冊,比如我們自定義的 Service,這種可以叫做 "匿名" Server。
如何找到一個匿名 Server 的 binder 實體呢?匿名 Server 一般要通過其他實名 Server 為中介來傳遞,比如 IWindowSession 是靠 WindowManagerService 來傳遞的,當 Binder Client 調用 openSession 真正生成一個 Session 對象,這個對象作為 reply 第一次 "路過" binder 驅動時,就會被記錄到 Client 的 binder_proc->refs_by_desc 和 binder_proc->refs_by_node 中。
如何實現(xiàn) Binder 線程的睡眠與喚醒
答案在上文中已經(jīng)有很詳細的分析了,這里再概括一下:
喚醒:在 binder_transaction() 方法中寫完數(shù)據(jù)后,通過 wake_up_interruptible(target_wait) 喚醒目標線程
睡眠:在 binder_thread_read() 中,通過 wait_event_freezable_exclusive() 或 wait_event_freezable() 調用進入睡眠
最后
最后再對 binder 的整體架構做一個簡要的概述。對于一個比較典型的、兩個應用之間的 IPC 通信流程而言:

Client 通過 ServiceManager 或 AMS 獲取到的遠程 binder 實體,一般會用 Proxy 做一層封裝,比如 ServiceManagerProxy、 AIDL 生成的 Proxy 類。而被封裝的遠程 binder 實體是一個 BinderProxy。
BpBinder 和 BinderProxy 其實是一個東西:遠程 binder 實體,只不過一個 Native 層、一個 Java 層,BpBinder 內部持有了一個 binder 句柄值 handle。
ProcessState 是進程單例,負責打開 Binder 驅動設備及 mmap;IPCThreadState 為線程單例,負責與 binder 驅動進行具體的命令通信。
由 Proxy 發(fā)起 transact() 調用,會將數(shù)據(jù)打包到 Parcel 中,層層向下調用到 BpBinder ,在 BpBinder 中調用 IPCThreadState 的 transact() 方法并傳入 handle 句柄值,IPCThreadState 再去執(zhí)行具體的 binder 命令。
由 binder 驅動到 Server 的大概流程就是:Server 通過 IPCThreadState 接收到 Client 的請求后,層層向上,最后回調到 Stub 的 onTransact() 方法。
當然這不代表所有的 IPC 流程,比如 Service Manager 作為一個 Server 時,便沒有上層的封裝,也沒有借助 IPCThreadState,而是初始化后通過 binder_loop() 方法直接與 binder 驅動通信的。