Android后臺殺死系列之四:Binder訃告原理

Binder是一個類似于C/S架構的通信框架,有時候客戶端可能想知道服務端的狀態,比如服務端如果掛了,客戶端希望能及時的被通知到,而不是等到再起請求服務端的時候才知道,這種場景其實在互為C/S的時候最常用,比如AMS與APP,當APP端進程異常退出的時候,AMS希望能及時知道,不僅僅是清理APP端在AMS中的一些信息,比如ActivityRecord,ServiceRecord等,有時候可能還需要及時恢復一些自啟動的Service。Binder實現了一套”死亡訃告”的功能,即:服務端掛了,或者正常退出,Binder驅動會向客戶端發送一份訃告,告訴客戶端Binder服務掛了。

這個“訃告”究竟是如何實現的呢?其作用又是什么呢?對于Android而言,Binder“訃告”有點采用了類似觀察者模式,因此,首先需要將Observer注冊到目標對象中,其實就是將Client注冊到Binder驅動,將來Binder服務掛掉時候,就能通過驅動去發送。Binder“訃告”發送的入口只有一個:在釋放binder設備的時候,在在操作系統中,無論進程是正常退出還是異常退出,進程所申請的所有資源都會被回收,包括打開的一些設備文件,如Binder字符設備等。在釋放的時候,就會調用相應的release函數,“訃告”也就是在這個時候去發送的。因此Binder訃告其實就僅僅包括兩部分:注冊與通知。

Binder"訃告"的注冊入口

這里拿bindService為例子進行分析,其他場景類似,bindService會首先請求AMS去啟動Service,Server端進程在啟動時,會調用函數open來打開設備文件/dev/binder,同時將Binder服務實體回傳給AMS,AMS再將Binder實體的引用句柄通過Binder通信傳遞給Client,也就是在AMS回傳給Client的時候,會向Binder驅動注冊。其實這也比較好理解,獲得了服務端的代理,就應該關心服務端的死活 。當AMS利用IServiceConnection這條binder通信線路為Client回傳Binder服務實體的時候,InnerConnection就會間接的將死亡回調注冊到內核:

    private static class InnerConnection extends IServiceConnection.Stub {
        final WeakReference<LoadedApk.ServiceDispatcher> mDispatcher;

        public void connected(ComponentName name, IBinder service) throws RemoteException {
            LoadedApk.ServiceDispatcher sd = mDispatcher.get();
            if (sd != null) {
                sd.connected(name, service);
            }
        }
    }

ServiceDispatcher函數進一步調用 doConnected

public void doConnected(ComponentName name, IBinder service) {
    ServiceDispatcher.ConnectionInfo old;
    ServiceDispatcher.ConnectionInfo info;
    synchronized (this) {     
        if (service != null) {
            mDied = false;
            info = new ConnectionInfo();
            info.binder = service;
            info.deathMonitor = new DeathMonitor(name, service);
            try {
            <!-- 關鍵點點1-->
                service.linkToDeath(info.deathMonitor, 0);
            } 
}

看關鍵點點1 ,這里的IBinder service其實是AMS回傳的服務代理BinderProxy,linkToDeath是一個Native函數,會進一步調用BpBinde的linkToDeath:

status_t BpBinder::linkToDeath(
    const sp<DeathRecipient>& recipient, void* cookie, uint32_t flags){
    <!--關鍵點1-->              
                IPCThreadState* self = IPCThreadState::self();
                self->requestDeathNotification(mHandle, this);
                self->flushCommands();

}

最終調用IPCThreadState的requestDeathNotification(mHandle, this)向內核發送BC_REQUEST_DEATH_NOTIFICATION請求:

status_t IPCThreadState::requestDeathNotification(int32_t handle, BpBinder* proxy)
{
    mOut.writeInt32(BC_REQUEST_DEATH_NOTIFICATION);
    mOut.writeInt32((int32_t)handle);
    mOut.writeInt32((int32_t)proxy);
    return NO_ERROR;
}

最后來看一下在內核中,是怎么登記注冊的:

int
binder_thread_write(struct binder_proc *proc, struct binder_thread *thread,
            void __user *buffer, int size, signed long *consumed)
{
...
case BC_REQUEST_DEATH_NOTIFICATION:
        case BC_CLEAR_DEATH_NOTIFICATION: {
            ...
            ref = binder_get_ref(proc, target);
            if (cmd == BC_REQUEST_DEATH_NOTIFICATION) {
                ...關鍵點1
                death = kzalloc(sizeof(*death), GFP_KERNEL);
                binder_stats.obj_created[BINDER_STAT_DEATH]++;
                INIT_LIST_HEAD(&death->work.entry);
                death->cookie = cookie;
                ref->death = death;
                if (ref->node->proc == NULL) {
                    ref->death->work.type = BINDER_WORK_DEAD_BINDER;
                    if (thread->looper & (BINDER_LOOPER_STATE_REGISTERED | BINDER_LOOPER_STATE_ENTERED)) {
                        list_add_tail(&ref->death->work.entry, &thread->todo);
                    } else {
                        list_add_tail(&ref->death->work.entry, &proc->todo);
                        wake_up_interruptible(&proc->wait);
                    }
                }
            } 
 }

看關鍵點1 ,其實就是為Client新建binder_ref_death對象,并賦值給binder_ref。在binder驅動中,binder_node節點會記錄所有binder_ref,當binder_node所在的進程掛掉后,驅動就能根據這個全局binder_ref列表找到所有Client的binder_ref,并對于設置了死亡回調的Client發送“訃告”,這是因為在binder_get_ref_for_node向Client插入binder_ref的時候,也會插入binder_node的binder_ref列表。

static struct binder_ref *
binder_get_ref_for_node(struct binder_proc *proc, struct binder_node *node)
{
    struct rb_node *n;
    struct rb_node **p = &proc->refs_by_node.rb_node;
    struct rb_node *parent = NULL;
    struct binder_ref *ref, *new_ref;

    if (node) {
        hlist_add_head(&new_ref->node_entry, &node->refs);
        }
    return new_ref;
}
binder訃告原理.jpg

如此,死亡回調入口就被注冊到binder內核驅動,之后,等到進程結束要釋放binder的時候,就會觸發死亡回調。

死亡訃告的注冊.png

死亡通知的發送

在調用binder_realease函數來釋放相應資源的時候,最終會調用binder_deferred_release函數。該函數會遍歷該binder_proc內所有的binder_node節點,并向注冊了死亡回調的Client發送訃告,

static void binder_deferred_release(struct binder_proc *proc)
    {         ....
        if (ref->death) {
                death++;
                if (list_empty(&ref->death->work.entry)) {
                    ref->death->work.type = BINDER_WORK_DEAD_BINDER;
                    list_add_tail(&ref->death->work.entry, &ref->proc->todo);
                    // 插入到binder_ref請求進程的binder線程等待隊列????? 天然支持binder通信嗎?
                    // 什么時候,需要死亡回調,自己也是binder服務?
                    wake_up_interruptible(&ref->proc->wait);
                }               
        ...
 }

死亡訃告被直接發送到Client端的binder進程todo隊列上,這里似乎也只對于互為C/S通信的場景有用,當Client的binder線程被喚醒后,就會針對“訃告”做一些清理及善后工作:

static int
binder_thread_read(struct binder_proc *proc, struct binder_thread *thread,
    void  __user *buffer, int size, signed long *consumed, int non_block)
    {
        case BINDER_WORK_DEAD_BINDER:
                case BINDER_WORK_DEAD_BINDER_AND_CLEAR:
                case BINDER_WORK_CLEAR_DEATH_NOTIFICATION: {
                    struct binder_ref_death *death = container_of(w, struct binder_ref_death, work);
                    uint32_t cmd;
                    if (w->type == BINDER_WORK_CLEAR_DEATH_NOTIFICATION)
                        cmd = BR_CLEAR_DEATH_NOTIFICATION_DONE;
                    else
                        cmd = BR_DEAD_BINDER;
                    ...
 }

這里會向用戶空間寫入一個BR_DEAD_BINDER命令,并返回talkWithDriver函數,返回后,IPCThreadState會繼續執行executeCommand,

status_t IPCThreadState::executeCommand(int32_t cmd)
{
    // 死亡訃告
    case BR_DEAD_BINDER:
        {
            BpBinder *proxy = (BpBinder*)mIn.readInt32();
            <!--關鍵點1 -->
            proxy->sendObituary();
            mOut.writeInt32(BC_DEAD_BINDER_DONE);
            mOut.writeInt32((int32_t)proxy);
        } break;

}

看關鍵點1,Obituary直譯過來就是訃告,其實就是利用BpBinder發送訃告,待訃告處理結束后,再向Binder驅動發送確認通知。

void BpBinder::sendObituary()
{
    ALOGV("Sending obituary for proxy %p handle %d, mObitsSent=%s\n",
        this, mHandle, mObitsSent ? "true" : "false");
    mAlive = 0;
    if (mObitsSent) return;
    mLock.lock();
    Vector<Obituary>* obits = mObituaries;
    if(obits != NULL) {
    <!--關鍵點1-->
        IPCThreadState* self = IPCThreadState::self();
        self->clearDeathNotification(mHandle, this);
        self->flushCommands();
        mObituaries = NULL;
    }
    mObitsSent = 1;
    mLock.unlock();
    if (obits != NULL) {
        const size_t N = obits->size();
        for (size_t i=0; i<N; i++) {
            reportOneDeath(obits->itemAt(i));
        }
        delete obits;
    }
}

看關鍵點1,這里跟注冊相對應,將自己從觀察者列表中清除,之后再上報

void BpBinder::reportOneDeath(const Obituary& obit)
{
    sp<DeathRecipient> recipient = obit.recipient.promote();
    ALOGV("Reporting death to recipient: %p\n", recipient.get());
    if (recipient == NULL) return;

    recipient->binderDied(this);
}

進而調用上層DeathRecipient的回調,做一些清理之類的邏輯。以AMS為例,其binderDied函數就挺復雜,包括了一些數據的清理,甚至還有進程的重建等,不做討論。

死亡訃告的發送.png

Android后臺殺死系列之一:FragmentActivity及PhoneWindow后臺殺死處理機制
Android后臺殺死系列之二:ActivityManagerService與App現場恢復機制
Android后臺殺死系列之三:LowMemoryKiller原理(4.3-6.0)
Android后臺殺死系列之四:Binder訃告原理
Android后臺殺死系列之五:Android進程?;?自“裁”或者耍流氓

作者:看書的小蝸牛
原文鏈接: Android后臺殺死系列之四:Binder訃告原理

參考文檔

Android Binder 分析——死亡通知(DeathRecipient)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,990評論 2 374

推薦閱讀更多精彩內容