1 前言
用AIDL的人應該都知道下面代碼中start和stop方法定義成oneway代表這個Binder接口是異步調用谆棱。
interface IPlayer {
oneway void start();//異步,假設執(zhí)行2秒
oneway void stop();//異步糙俗,假設執(zhí)行2秒
int getVolume();// 同步,假設執(zhí)行1秒
}
1.1 什么是異步調用预鬓?
舉個例子:假如Client端調用IPlayer.start()巧骚,而且Server端的start需要執(zhí)行2秒,由于定義的接口是異步的格二,Client端可以快速的執(zhí)行IPlayer.start()劈彪,不會被Server端block住2秒。
1.2 什么是同步調用顶猜?
舉個例子:假如Client端調用IPlayer. getVolume()沧奴,而且Server端的getVolume需要執(zhí)行1秒,由于定義的接口是同步的长窄,Client端在執(zhí)行IPlayer. getVolume()的時候滔吠,會被Server端block住1秒。
1.3 為什么會有同步調用和異步調用挠日?
細心的讀者已經發(fā)現(xiàn)了疮绷,其實一般使用異步調用的時候,Client并不需要得到Server端的執(zhí)行Binder服務的狀態(tài)或者返回值嚣潜,這時候使用異步調用冬骚,可以有效的提高Client執(zhí)行的效率。
2 提問
好像很多人都明白前面講的意思郑原,我就出幾個問題考考大家
假設進程A中有如下兩個Binder服務IPlayer1和IPlayer2,這兩個服務都有兩個異步的接口start和stop夜涕。
interface IPlayer1 {
oneway void start();//異步犯犁,執(zhí)行2秒
oneway void stop();//異步,執(zhí)行2秒
}
interface IPlayer2 {
oneway void start();//異步女器,執(zhí)行2秒
oneway void stop();//異步酸役,執(zhí)行2秒
}
2.1 問題1
如果進程B和進程C同一時刻分別調用IPlayer1.start()和IPlayer2.start(),請問進程A能否同時響應這兩次Binder調用并執(zhí)行驾胆?
正確答案:可以同時執(zhí)行涣澡。
2.2 問題2
如果進程B和進程C同一時刻分別調用IPlayer1.start()和IPlayer1.start(),請問進程A能否同時響應這兩次Binder調用并執(zhí)行丧诺?
正確答案:不能同時執(zhí)行入桂,需要一個一個排隊執(zhí)行。
2.3 問題3
如果進程B和進程C同一時刻分別調用IPlayer1.start()和IPlayer1.end()驳阎,請問進程A能否同時響應這兩次Binder調用并執(zhí)行抗愁?
正確答案:不能同時執(zhí)行馁蒂,需要一個一個排隊執(zhí)行。
如果回答正確并且知道原因的朋友蜘腌,這個文章就可以不看了沫屡。如果回答錯誤或者蒙對了不清楚原因的朋友,請繼續(xù)閱讀文章幫你理解這些問題撮珠。
3 代碼分析
話不多說沮脖,先看源碼,我們首先來看看oneway的Binder調用在Binder Driver中的邏輯
/**
* binder_proc_transaction() - sends a transaction to a process and wakes it up
* @t: transaction to send
* @proc: process to send the transaction to
* @thread: thread in @proc to send the transaction to (may be NULL)
*/
static bool binder_proc_transaction(struct binder_transaction *t,
struct binder_proc *proc,
struct binder_thread *thread)
{
//找到Server端的對應Binder服務在Binder驅動中對應的對象binder_node
struct binder_node *node = t->buffer->target_node;
//判斷這次Binder調用是不是oneway
bool oneway = !!(t->flags & TF_ONE_WAY);
//初始化為false芯急,用于標記當前Server端的對應Binder服務是否正在執(zhí)行oneway的方法
bool pending_async = false;
binder_node_lock(node);
//oneway == true
if (oneway) {
if (node->has_async_transaction) {
//第2次oneway調用執(zhí)行這里
//發(fā)現(xiàn)對應Binder服務正在執(zhí)行oneway的方法勺届,設置pending_async為true
pending_async = true;
} else {
//第1次oneway調用執(zhí)行這里
//發(fā)現(xiàn)對應Binder服務沒有執(zhí)行oneway的方法,設置has_async_transaction為1
node->has_async_transaction = 1;
}
}
binder_inner_proc_lock(proc);
//如果發(fā)現(xiàn)Server端已經死亡志于,就直接返回了涮因,正常不會執(zhí)行
if (proc->is_dead || (thread && thread->is_dead)) {
binder_inner_proc_unlock(proc);
binder_node_unlock(node);
return false;
}
//oneway的調用thread為空,第1次oneway調用伺绽,pending_async為false
if (!thread && !pending_async)
//第1次oneway調用會找到一個空閑的Server端線程养泡,用于響應這次oneway調用
thread = binder_select_thread_ilocked(proc);
if (thread) {
//第1次oneway調用,thread不為空,直接把這次Binder work放到thread的工作隊列去執(zhí)行
binder_enqueue_thread_work_ilocked(thread, &t->work);
} else if (!pending_async) {
binder_enqueue_work_ilocked(&t->work, &proc->todo);
} else {
//第2次oneway調用,thread為空奈应,pending_async為true澜掩,
//這次Binder work放到Binder Node的async_todo隊列中,不會立刻執(zhí)行
binder_enqueue_work_ilocked(&t->work, &node->async_todo);
}
if (!pending_async)
//第1次oneway調用,thread不為空杖挣,所以需要喚醒thread執(zhí)行工作隊列中的Binder work
binder_wakeup_thread_ilocked(proc, thread, !oneway /* sync */);
binder_inner_proc_unlock(proc);
binder_node_unlock(node);
return true;
}
對應到我們的三個問題肩榕,我們首先有這樣子的前提,進程A中有兩個Binder Server端IPlayer1和IPlayer2惩妇,也就是在Binder驅動中有兩個binder node的結構體株汉,并且進程A的Binder線程池處于空閑的狀態(tài)。還有一點要明確的是歌殃,就算進程B和進程C同時發(fā)起B(yǎng)inder調用乔妈,但是在Binder驅動中還是有先后順序,因為有一把鎖binder_inner_proc_lock(proc)氓皱。
問題1解析:
因為進程B和進程C分別調用兩個Binder服務路召,也就是兩個binder node,所以進程B和進程C都會走如下的代碼波材,也就是說進程A會有兩個線程分別處理進程B的IPlayer1.start()和進程C的IPlayer2.start()股淡,所以答案是同時執(zhí)行
static bool binder_proc_transaction(struct binder_transaction *t,
struct binder_proc *proc,
struct binder_thread *thread)
{
struct binder_node *node = t->buffer->target_node;
bool oneway = !!(t->flags & TF_ONE_WAY);
bool pending_async = false;
binder_node_lock(node);
if (oneway) {
//不管是是進程B還是進程C,因為不是同一個binder_node廷区,所以都是走false的邏輯
if (node->has_async_transaction) {
//不執(zhí)行
} else {
node->has_async_transaction = 1;
}
}
binder_inner_proc_lock(proc);
if (!thread && !pending_async)
//oneway調用會找到一個空閑的Server端線程唯灵,用于響應這次oneway調用
thread = binder_select_thread_ilocked(proc);
if (thread) {
//oneway調用,thread不為空,直接把這次Binder work放到thread的工作隊列去執(zhí)行
binder_enqueue_thread_work_ilocked(thread, &t->work);
} else if (!pending_async) {
//不執(zhí)行
} else {
//不執(zhí)行
}
if (!pending_async)
//oneway調用隙轻,thread不為空早敬,所以需要喚醒thread執(zhí)行工作隊列中的Binder work
binder_wakeup_thread_ilocked(proc, thread, !oneway /* sync */);
binder_inner_proc_unlock(proc);
binder_node_unlock(node);
return true;
}
問題2解析:
我們假設先處理進程B的IPlayer1.start()的調用忌傻,進程B會執(zhí)行和問題1中描述的代碼一樣的操作,喚醒進程A中的一個線程搞监,處理這次進程B的IPlayer1.start()調用水孩。
但是進程C的IPlayer1.start()調用邏輯就不一樣了,應該是下面這個邏輯琐驴,也就是說進程A不會立刻處理進程C的IPlayer1.start()的調用俘种。所以答案就是不能同時執(zhí)行,需要一個一個排隊執(zhí)行绝淡。
static bool binder_proc_transaction(struct binder_transaction *t,
struct binder_proc *proc,
struct binder_thread *thread)
{
struct binder_node *node = t->buffer->target_node;
bool oneway = !!(t->flags & TF_ONE_WAY);
bool pending_async = false;
binder_node_lock(node);
//oneway == true
if (oneway) {
if (node->has_async_transaction) {
//因為是進程C和進程B是同一個binder_node宙刘,進程B已經將has_async_transaction設置true
pending_async = true;
} else {
//不執(zhí)行
}
}
binder_inner_proc_lock(proc);
if (thread) {
//不執(zhí)行
} else if (!pending_async) {
//不執(zhí)行
} else {
//這次Binder work放到binder_node的async_todo隊列中,不會立刻執(zhí)行
binder_enqueue_work_ilocked(&t->work, &node->async_todo);
}
binder_inner_proc_unlock(proc);
binder_node_unlock(node);
return true;
}
那什么時候處理進程C的IPlayer1.start(),看下面代碼牢酵,簡單說就是會在處理完進程B的IPlayer1.start()之后悬包,在釋放進程B調用IPlayer1.start()申請的buffer的時候,處理進程C的IPlayer1.start()馍乙。
case BC_FREE_BUFFER: {
//準確釋放進程B申請的buffer
if (buffer->async_transaction && buffer->target_node) {
struct binder_node *buf_node;
struct binder_work *w;
//先拿到這塊buffer處理的binder node布近,也就是IPlayer1對應的binder node
buf_node = buffer->target_node;
binder_node_inner_lock(buf_node);
//檢查一下buf_node是否有未處理的oneway的binder work
w = binder_dequeue_work_head_ilocked(
&buf_node->async_todo);
if (!w) {
//不執(zhí)行
buf_node->has_async_transaction = 0;
} else {
//如果有未處理完的oneway的binder work,就將binder node保存的async_todo全部添加到進程A的todo丝格。
binder_enqueue_work_ilocked(
w, &proc->todo);
//喚醒一個線程去處理todo中的binder work撑瞧,也就是進程C的IPlayer1.start()
binder_wakeup_proc_ilocked(proc);
}
binder_node_inner_unlock(buf_node);
}
//釋放進程B申請的buffer
trace_binder_transaction_buffer_release(buffer);
binder_transaction_buffer_release(proc, buffer, NULL);
binder_alloc_free_buf(&proc->alloc, buffer);
break;
}
問題3解析:
雖然進程B和進程C同一時刻分別調用IPlayer1.start()和IPlayer1.end()兩個不同的方法,但是兩個進程調用的Server端都是IPlayer1显蝌,也就是binder node是同一個预伺,所以答案和問題2一樣。
4 思考一個問題
假如一個進程B曼尊,在短時間內酬诀,例如一秒內,調用1000次進程A的IPlayer1.start()會發(fā)生什么骆撇。
第1次IPlayer1.start():喚醒進程A的一個線程處理IPlayer1.start()瞒御,兩秒之后完成
第2-1000次IPlayer1.start():發(fā)現(xiàn)IPlayer1對應的binder node正在處理一個oneway的方法,會把所有2~1000次的調用放到binder node的async_todo隊列中艾船,等第一次IPlayer1.start()執(zhí)行完成之后葵腹,釋放buffer的時候高每,才能去統(tǒng)一處理這些async_todo中保存的第2-1000次屿岂。
那么問題就來了,雖然第2-1000次的調用不會立刻執(zhí)行鲸匿,但是已經在進程A中申請了所有的2~1000次IPlayer1.start()所需要的buffer爷怀,一個zygote進程A,最大oneway請求的buffer上限為(1MB -8KB)/2 = 508KB带欢,不懂的可以看[007]一次Binder通信最大可以傳輸多大的數據运授?這個博客烤惊,假設一次IPlayer1.start(),需要申請1KB的buffer吁朦,也就意味這在第509次IPlayer1.start()的時候柒室,無法申請到buffer從而導致IPlayer1.start()的Binder調用失敗。
在[011]一個看似是系統(tǒng)問題的應用問題的解決過程中解決的就是這個問題逗宜。
5 小結
Binder機制是一個非常牛逼的機制雄右,里面有很多小的細節(jié)值得我們去深挖,只有完全理解Binder驅動纺讲,才能從微觀的角度去解決宏觀的問題擂仍。