[023]你真的懂AIDL的oneway嘛灵疮?

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驅動纺讲,才能從微觀的角度去解決宏觀的問題擂仍。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市熬甚,隨后出現(xiàn)的幾起案子逢渔,更是在濱河造成了極大的恐慌,老刑警劉巖乡括,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件肃廓,死亡現(xiàn)場離奇詭異,居然都是意外死亡粟判,警方通過查閱死者的電腦和手機亿昏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來档礁,“玉大人角钩,你說我怎么就攤上這事∩肜剑” “怎么了递礼?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長羹幸。 經常有香客問我脊髓,道長,這世上最難降的妖魔是什么栅受? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任将硝,我火速辦了婚禮,結果婚禮上屏镊,老公的妹妹穿的比我還像新娘依疼。我一直安慰自己,他們只是感情好而芥,可當我...
    茶點故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布律罢。 她就那樣靜靜地躺著,像睡著了一般棍丐。 火紅的嫁衣襯著肌膚如雪误辑。 梳的紋絲不亂的頭發(fā)上沧踏,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天,我揣著相機與錄音巾钉,去河邊找鬼翘狱。 笑死,一個胖子當著我的面吹牛砰苍,可吹牛的內容都是我干的盒蟆。 我是一名探鬼主播,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼师骗,長吁一口氣:“原來是場噩夢啊……” “哼历等!你這毒婦竟也來了?” 一聲冷哼從身側響起辟癌,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤寒屯,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后黍少,有當地人在樹林里發(fā)現(xiàn)了一具尸體寡夹,經...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年厂置,在試婚紗的時候發(fā)現(xiàn)自己被綠了菩掏。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡昵济,死狀恐怖智绸,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情访忿,我是刑警寧澤瞧栗,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站海铆,受9級特大地震影響迹恐,放射性物質發(fā)生泄漏。R本人自食惡果不足惜卧斟,卻給世界環(huán)境...
    茶點故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一殴边、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧珍语,春花似錦锤岸、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽赏枚。三九已至亡驰,卻和暖如春晓猛,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背凡辱。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工戒职, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人透乾。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓洪燥,卻偏偏與公主長得像,于是被迫代替她去往敵國和親乳乌。 傳聞我的和親對象是個殘疾皇子捧韵,可洞房花燭夜當晚...
    茶點故事閱讀 45,685評論 2 360