藉助 AIDL 理解 Android Binder 機制——Binder 前因後果

AIDL 是 Android Interface Definition Language(Android 接口定義語言)的縮寫,它是 Android 進程間通訊的接口語言。因爲 Android 系統的內核是 Linux,它採用了進程隔離機制,使得不一樣的應用程序運行在不一樣的進程當中,有時候兩個應用之間須要傳遞或者共享某些數據,就須要進行進程間的通訊訊。緩存

Android 進程間通訊的方式有不少種,好比 Messenger、文件(SharePreference)、AIDL、Socket 和 Content Provider 等,它們當中 Messenge、AIDL 和 Content Provider 的底層都是依賴於 Binder 機制去實現的。除此以外,Android 四大組件的啓動和通訊的核心過程也是經過 Binder 機制去實現的,這裏,咱們藉助 AIDL 來了解 Binder 的實現機制。安全

在瞭解 AIDL 機制和用法以前,首先要了解幾個概念,這對後續的深刻理解有較大的幫助。服務器

進程隔離

如下內容來自維基百科架構

進程隔離是爲保護操做系統中進程互不干擾而設計的一組不一樣硬件和軟件的技術。這個技術是爲了不進程 A 寫入進程 B 的狀況發生。 進程的隔離實現,使用了虛擬地址空間。進程 A 的虛擬地址和進程B的虛擬地址不一樣,這樣就防止進程 A 將數據信息寫入進程 B。ide

Linux IPC 原理

因爲 Linux 採用了虛擬地址空間技術,操做系統在邏輯上將虛擬內存分爲用戶空間(User Space)內核空間(Kernel Space),普通應用程序運行在用戶空間,系統內核運行在內核空間,爲了控制應用程序的訪問範圍、保證系統安全,用戶空間只能經過系統調用的方式去訪問內核空間。當進程執行系統調用而陷入內核代碼的時候,該進程則進入了內核態,相比之下,進程在用戶空間執行本身的代碼的時候,則是處於用戶態函數

因爲進程 A 和進程 B 的虛擬地址不一樣,所以它們之間是相互透明的,都覺得本身獨享了系統的資源,固然也不能直接跟對方交互。可是,有些狀況下有些進程不免會須要跟其餘進程進行交互,這個交互過程就叫 IPC(Inter-Process Communication,進程間通訊)。IPC 的實質就是數據的交互,所以咱們這裏將進行 IPC 過程當中的通訊調用方和被調用放分別稱爲數據發送方和數據接收方,IPC 通訊的過程以下:post

  1. 數據發送方進程將數據放在內存緩存區,經過系統調用陷入內核態
  2. 內核程序在內核空間開闢一塊內核緩存區,經過 copy_from_user 函數將數據從數據發送方用戶空間的內存緩存區拷貝到內核空間的內核緩存區中
  3. 數據接收方進程在本身的用戶空間開闢一塊內存緩存區
  4. 內核程序將內核緩存區中經過 copy_to_user 函數將數據拷貝到數據接收方進程的內存緩存區

Linux IPC

經過以上過程,一次 IPC 就完成了,可是這種傳統的 IPC 機制有兩個問題:性能

  • 性能比較低:整個過程數據的傳遞須要經歷發送方內存緩存區——內核緩存區——接收方內存緩存區的過程
  • 接收方進程事先不知道須要開闢多大的內存用於存放數據,所以須要開闢儘量大的空間或者事先調用 API 來解決這個問題,這兩種方式不是浪費空間就是浪費時間。

Binder IPC 原理

爲了克服 Linux 傳統的 IPC 機制中的不足之處,Android 系統引入了 Binder 機制,從字面上看 Binder 是膠水的意思,在這裏,Binder 的職責是在不一樣的進程之間扮演一個橋樑的角色,讓它們之間可以相互通訊。從上一小節內容能夠了解到,進程間的通信少不了 Linux 內核的支持,而 Binder 並不屬於內核的一部分,可是,得益於 Linux 的 LKM(Loadable Kernel Module) 機制:學習

模塊是具備獨立功能的程序,它能夠被單獨編譯,但不能獨立運行。它在運行時被連接到內核做爲內核的一部分在內核空間運行網站

所以,Binder 做爲這種模塊存在於內核之中,也稱爲 Binder 驅動。回顧上一小節的內容,傳統 Linux IPC 的過程須要經歷兩次數據拷貝,Binder 藉助 Linux 的另外一個特性,只用一次數據拷貝,就能實現 IPC 過程,這就是內存映射

Binder IPC 機制中涉及到的內存映射經過 mmap() 來實現,mmap() 是操做系統中一種內存映射的方法。內存映射簡單的講就是將用戶空間的一塊內存區域映射到內核空間,映射關係創建後,用戶對這塊內存區域的修改能夠直接反應到內核空間;反以內核空間對這段區域的修改也能直接反應到用戶空間。

內存映射能減小數據拷貝次數,實現用戶空間和內核空間的高效互動。兩個空間各自的修改能直接反映在映射的內存區域,從而被對方空間及時感知。也正由於如此,內存映射可以提供對進程間通訊的支持。

Binder IPC 通訊過程以下:

  1. Binder 驅動在內核空間建立一個數據接收緩存區
  2. 而後在內核空間開闢一塊內存緩存區並與數據接收緩存區創建映射關係,同時,創建數據接收緩存區數據接收方的內存緩存區的映射關係
  3. 數據發送方經過系統調用 copy_from_user 函數將數據從內存緩存區拷貝到內核緩存區,因爲內核緩存區經過數據接收緩存區跟數據接收方的內存緩存區存在間接的映射關係,至關於將數據直接拷貝到了接收方的用戶空間,這樣便完成了一次 IPC 的過程。

Android IPC

Binder 通訊模型和通訊過程

在進行 Binder IPC 的時候,實際狀況比上面介紹的要複雜,Binder 通信模型是基於 C/S 架構的,通訊調用方進程稱爲 Client 進程,被調用方稱爲 Server 進程,除此以外還須要 ServiceManager 和 Binder 驅動的參與,它們都是經過 open/mmap/iotl 等系統調用來訪問設備文件 dev/binder 來實現 IPC 過程的。

Binder IPC module

其中,Client、Server 和 ServiceManager 運行在用戶空間,Binder Driver 運行在內核空間,Client 和 Server 需由用戶本身實現,ServiceManager 和 Binder Driver 則由系統提供。

Android Binder 設計與實現 文章中對 Client 和 Server 等角色有詳細的描述:

Binder 驅動: Binder 驅動就如同路由器同樣,是整個通訊的核心;驅動負責進程之間 Binder 通訊的創建,Binder 在進程之間的傳遞,Binder 引用計數管理,數據包在進程之間的傳遞和交互等一系列底層支持。

ServiceManager 與實名 Binder: ServiceManager 和 DNS 相似,做用是將字符形式的 Binder 名字轉化成 Client 中對該 Binder 的引用,使得 Client 可以經過 Binder 的名字得到對 Binder 實體的引用。註冊了名字的 Binder 叫實名 Binder,就像網站同樣除了除了有 IP 地址意外還有本身的網址。Server 建立了 Binder,併爲它起一個字符形式,可讀易記得名字,將這個 Binder 實體連同名字一塊兒以數據包的形式經過 Binder 驅動發送給 ServiceManager ,通知 ServiceManager 註冊一個名爲「張三」的 Binder,它位於某個 Server 中。驅動爲這個穿越進程邊界的 Binder 建立位於內核中的實體節點以及 ServiceManager 對實體的引用,將名字以及新建的引用打包傳給 ServiceManager。ServiceManger 收到數據後從中取出名字和引用填入查找表。

細心的讀者可能會發現,ServierManager 是一個進程,Server 是另外一個進程,Server 向 ServiceManager 中註冊 Binder 必然涉及到進程間通訊。當前實現進程間通訊又要用到進程間通訊,這就好像蛋能夠孵出雞的前提倒是要先找只雞下蛋!Binder 的實現比較巧妙,就是預先創造一隻雞來下蛋。ServiceManager 和其餘進程一樣採用 Bidner 通訊,ServiceManager 是 Server 端,有本身的 Binder 實體,其餘進程都是 Client,須要經過這個 Binder 的引用來實現 Binder 的註冊,查詢和獲取。ServiceManager 提供的 Binder 比較特殊,它沒有名字也不須要註冊。當一個進程使用 BINDERSETCONTEXT_MGR 命令將本身註冊成 ServiceManager 時 Binder 驅動會自動爲它建立 Binder 實體(這就是那隻預先造好的那隻雞)。其次這個 Binder 實體的引用在全部 Client 中都固定爲 0 而無需經過其它手段得到。也就是說,一個 Server 想要向 ServiceManager 註冊本身的 Binder 就必須經過這個 0 號引用和 ServiceManager 的 Binder 通訊。類比互聯網,0 號引用就比如是域名服務器的地址,你必須預先動態或者手工配置好。要注意的是,這裏說的 Client 是相對於 ServiceManager 而言的,一個進程或者應用程序多是提供服務的 Server,但對於 ServiceManager 來講它仍然是個 Client。

Client 得到實名 Binder 的引用: Server 向 ServiceManager 中註冊了 Binder 之後, Client 就能經過名字得到 Binder 的引用了。Client 也利用保留的 0 號引用向 ServiceManager 請求訪問某個 Binder: 我申請訪問名字叫張三的 Binder 引用。ServiceManager 收到這個請求後從請求數據包中取出 Binder 名稱,在查找表裏找到對應的條目,取出對應的 Binder 引用做爲回覆發送給發起請求的 Client。從面向對象的角度看,Server 中的 Binder 實體如今有兩個引用:一個位於 ServiceManager 中,一個位於發起請求的 Client 中。若是接下來有更多的 Client 請求該 Binder,系統中就會有更多的引用指向該 Binder ,就像 Java 中一個對象有多個引用同樣。

所以,ServiceManager 是 Binder IPC 通訊過程的核心,是上下文的管理者,Binder 服務端必須先向 ServerManager 註冊纔可以爲客戶端提供服務,Binder 客戶端在與服務端通訊以前須要從 ServerManager 中查找並獲取 Binder 服務端的引用。Binder IPC 過程能夠總結成如下步驟:

  1. 某個進程使用 BINDER_SET_CONTEXT_MGR 命令經過 Binder 驅動將本身註冊成 ServiceManager,負責管理全部的 Service
  2. 各個 Server 經過 Binder 驅動向 ServiceManager 註冊 Binder 實體,代表本身能夠對外提供服務,這時 Binder 驅動會爲這個 Binder 建立位於內核中的實體節點以及 ServiceManager 對該節點的引用,並將名字和該引用打包給 ServiceManager,ServiceManager 接收到數據包後將數據包中的名字和引用填入查找表中
  3. Client 經過上面 Server 的名字在 Binder 驅動的幫助下從 ServiceManager 中獲取到該 Server 對應的 Binder 引用對象,因爲該引用對象一樣具備 Server 的能力,所以 Client 能夠經過這個引用與真實的 Server 進行交互

仍是universus 老師的圖:

Binder Role

總結

進程隔離雖然使操做系統的安全性和應用程序的穩定性獲得了提高,但同時也給 IPC 帶來了必定的難度,Android 系統巧妙地應用了 Binder 機制,使得系統得於在存儲空間和硬件性能等有限的移動設備上可以流暢地運行。關於 Binder 在應用層的使用和分析,請看下一篇文章內容:藉助 AIDL 理解 Android Binder 機制——AIDL 的使用和原理分析

參考文章

寫給 Android 應用工程師的 Binder 原理剖析

Binder學習指南

若是你對文章內容有疑問或者有不一樣意見,歡迎留言,咱們一同探討。

相關文章
相關標籤/搜索