Android Binder 全解析(1) -- 概述

摘要 如果各位玩過《爐石傳說》,那麼可能對法師的職業卡「不穩定的傳送門」很有印象,特別是沒有歐洲玩家,經常能夠拿到其他職業的強力單卡。Android 也提供了傳送門,讓我們可以像使用本地方法一樣,調用其他進程的方法,他有一個響亮的名字,Binder! Binder 在 Android 是如此的重要,承當起整個Android的通信任務,作爲優秀的Android工程師有什麼理由不瞭解了?在接下來的文章中,會陸陸續續講解Android Binder,希望大家持續關注。 本文是 Android 系統學習系列文章中的第二章節,在前面一些細節概念的鋪墊下,大體上知道 Binder Framework 是怎麼運作的,在這篇文章中,將詳細說明下 Binder Framework 的具體實現,這一套機制如何盤活整個 Android 系統。對此係列感興趣的同學,可以收藏這個鏈接 Android 系統學習,也可以點擊 RSS訂閱 進行訂閱。

什麼是Binder? 爲什麼我們需要它?

在提及Binder之前,我們先來看看Android的設計。在Linux系統裏面,進程之間是相互隔離的,也就是說進程之間的各個數據是互相獨立,互不影響,而如果一個進程崩潰了,也不會影響到另一個進程。這樣的前提下將互相不影響的系統功能分拆到不同的進程裏面去,有助於提升系統的穩定性,畢竟我們都不想自己的應用進程崩潰會導致整個手機系統的崩潰。而Android是基於Linux系統進行開發的,也充分利用的進程隔離這一特性。

這些Android的系統進程,從System Server 到 SurfaceFlinger,qcks,各個進程各司其職,支撐起整個Android系統。而我們進行的Android開發也是和這些系統進程打交道,通過他們提供的服務,架構起我們的App程序。那麼有了這些進程之後,問題緊接着而來,我們怎麼和這些進程合作了?答案就是IPC(進程間通信)。

Linux System 在IPC中,做了很多工作,提供了不少進程間通信的方式,下面羅列了幾種比較常見的方式。

  • Signals 信號量
  • Pipes 管道
  • Socket 套接字
  • Message Queue 消息隊列
  • Shared Memory 共享內存

按照複用的角度上看,既然有這麼多”輪子”後,就應該合理利用這些”輪子”,從而方便地調用系統服務。然而事實並沒有這麼簡單,Android系統作爲嵌入式的移動操作系統,通信條件相對更加苛責一些,苛責的地方提現在:

  • 拮据的內存,移動設備上的內存情況不同於PC平臺,內存受限,因而需要有合適的機制來保證對空閒進程的回收
  • Android 不支持System V IPCs
  • 安全性問題顯得更爲突出,移動平臺特有的權限問題
  • 需要Death Notification(進程終止的通知)的支持

由於前面提及的特殊性,先前的輪子已經不能滿足所有的需求了,因而就有了今天的主角 Binder。Binder 是一個基於OpenBinder開發,Google在其中進行了相應的改造和優化,在面向對象系統裏面的IPC/組件,適配了相關特性,並致力於建立具有擴展性、穩定、靈活的系統。

在這一小節結束的時候,來看一看Binder在Android系統中的使用場景,也就是圖中的IPC模塊。

Binder 存在的地方


Binder Framework 願景

既然需要重新造輪子,那麼接下來讓我們沿着設計者的思路,來看看如何一步一步滿足前面提及的特殊需求。Binder在本質上需要解決的問題是讓兩個不同的進程之間能夠互相調用的問題,所以從開發者的視角來看,應該就簡單地如下圖:

binder-user.png

同時從效能的角度上出發,希望提供服務調用的程序能夠支持併發,這樣使得能夠儘可能地響應多個程序的IPC請求,由此得出的實際運行圖是下面這個樣子的。

biner-multi-thread.png


Binder Framework 實現細節

有了前面這些願景後,再來看看Binder Framework的一些實現細節,如何走到這一步的,當然這是非常泛的細節,作爲常識瞭解一下。

如何跨進程調用

那麼如何使得調用者能像上述一樣簡單地調用遠程方法?畢竟兩者存在於不同的進程空間裏面。那麼可以引入一個黑盒模塊,用這個黑盒模塊來幫我們完成其中的細節,這個模塊也被稱爲Binder Driver。方法的跨進程調用受到了 Linux 進程隔離的限制,而解決方案就是將其置於所有進程都能共享的區域 – Kernel,而 Binder Driver 提供的功能也就是讓各進程使用內核空間,將進程中的地址和Kernel中的地址映射起來,其中Linux ioctl 函數實現了從用戶空間轉移到內核空間的功能。在 Binder Driver 的支持下,就能實現跨進程調用。

Client / Server 架構

在設計的時候,Binder Framework 交互模型採用的是客戶端/服務器模型。客戶端需要調用遠程服務的內容時, 會初始化一個連接,並等待服務器的返回,同時會block住自己。Binder Framework在客戶端這邊實現了一個代理,而在服務端,通過線程池的方式來響應請求。在如下圖所示,A進程就是客戶端,並且通過Proxy來完成對Binder Driver內核的交互。Process B是系統服務進程,在這個進程裏面維護着多個Binder Thread,直到達到設置的線程上限。Proxy對象通過和Binder Driver進行交互,從而使得Binder Driver將信息傳遞到目標對象。從Android開發者的角度出發,Binder Framework提供的最方便的改進就是能像調用本地方法一樣調用遠程方法或對象。客戶端的進程調用會在Server進程返回之前一直處於block的狀況。在這種機制下,客戶端就不必提供一個單獨的線程模型和回調機制。(同步轉異步簡單,而異步變同步則很困難)

binder-proxy.png

傳遞的數據格式

在實現跨進程調用的時候,涉及到參數和命令的傳遞,得有一個合適的數據結構來表達需要遠程執行的東西。binder-data.png

Target是指目標binder,Cookie這涵蓋着一些內部信息,sender Id則包含了安全相關的信息,data則包含着一些數據的數組。每個數組的Entry是由相關的命令和參數組成的,這部分參數將傳遞給目標binder。

而這裏面的Sender Id 則非常的重要,不僅可以起到唯一標示Binder的作用,還可以在跨進程的地方作爲標記的作用,在接下來的文章裏再詳細說明。

Service Manager

我們接觸的服務很多,從Display到Location,從Audio到Wifi,如果我們和每一個服務都通過前面描述的方式進行交互,即便通過 Proxy 的方式也是非常的繁瑣。而且在調用每個系統服務的時候,必須知道對應的系統服務的地址,而系統服務的地址出於安全性的考慮也不應該暴露出來。那麼如何方便我們進行系統服務調用了?

Service Manager 就是來幫助我們解決這個問題。這是Binder Framework的一個特殊節點,也是第一個起點。其作爲一個命令服務,起到了DNS的作用,使得可以通過名字的方式來查找相應的Binder接口。這很重要,因爲客戶端不應該知道遠程服務的調用地址,如果知道了這勢必會很不安全。每一個Binder需要將自己的名字和Binder Token交給Service Manager,客戶端只需要知道服務的名字就可以。

binder-mananger.png

參考文獻

  1. http://mindtherobot.com/blog/159/android-guts-intro-to-loopers-and-handlers/
  2. http://www.androiddesignpatterns.com/2013/08/binders-death-recipients.html
  3. http://events.linuxfoundation.org/images/stories/slides/abs2013_gargentas.pdf

  4. http://www.androiddesignpatterns.com/2013/08/binders-death-recipients.html
  5. http://www.androiddesignpatterns.com/2013/07/binders-window-tokens.html

Published:May232016

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章