簡介 Android是如何實現跨進程通信的,大家熟悉的Binder是什麼,怎麼設計的,進程間的數據如何發送接收的。本文將以及解析,並對Binder驅動實現、Native層實現、Java層實現三塊做一個總結分析。 Binder學習思路 1. Binder與傳統IPC的區別 2. Binder驅動的內部 ...
簡介
Android是如何實現跨進程通信的,大家熟悉的Binder是什麼,怎麼設計的,進程間的數據如何發送接收的。本文將以及解析,並對Binder驅動實現、Native層實現、Java層實現三塊做一個總結分析。
Binder學習思路
- Binder與傳統IPC的區別
- Binder驅動的內部設計、數據結構
- Binder驅動與應用程式進程(C/S)之間的通信過程
- Android應用程式通過Binder驅動進行通信的流程
- Android開發人員如何使用Binder通信(AIDL、Java層架構)
基礎知識理解
- Unix內核和應用程式進程所使用的物理記憶體是分開的,內核使用1G的物理記憶體,其他應用程式有各自的3G物理記憶體(32位操作系統)
- 因為內核和應用程式的物理記憶體是分開的,所以兩者之間傳遞數據需要進行數據拷貝
- 記憶體映射(mmap)可以將兩個虛擬記憶體地址空間(不同進程)映射到同一物理記憶體段上。實現多進程(或者內核與進程)之間公用一塊記憶體,減少數據拷貝次數
- Unix驅動程式是一個運行在內核態(使用內核對應的物理記憶體)的程式
- Binder也是一種IPC的實現方式,其與傳統的Unix IPC有一定的差別(使用了mmap)
理解Binder驅動的存在
因為要實現跨進程通信,那麼,數據是如何傳輸的,怎麼組織的。兩個進程之間是如何知道對方的標識(引用)的,這一系列問題,都由Binder驅動解決,每個進程需要為其他進程提供服務(API調用),都需要向Binder驅動註冊,其他進程才能知道自己的數據傳向哪裡。這裡大家先忽略ServiceManager的特殊身份。只討論Binder驅動的覺得定位即可。
這樣看來,其實Binder驅動就是一個多個進程之間的中樞神經,支撐起了Android中進程間通信,它內部的設計,與應用程式進程中的業務,不存在任何耦合關係,只負責實現進程間數據通信。可以用如下圖來理解Binder驅動與應用程式進程之間的關係。
當然,Android里的Binder架構應該還有ServiceManager這個系統服務。
ServiceManager的存在
ServiceManager下文簡稱SM,是一個Android操作系統提供的一個系統進程。那麼為什麼要單獨提他呢,因為這個進程里,記錄了所有Binder實體(提供服務的Binder實現對象)的信息。
也就是說,SM是用來給應用程式查找其他應用程式的數據中心與校驗中心,保障進程間通信的安全新,合法性。
SM是系統服務,在系統啟動後,SM便啟動,並執行以下事情:
- 打開Binder驅動
- 將自己註冊為Binder驅動的大管家(其他進程根據引用編號0可以找到SM對應的Binder實體)
- 進入迴圈,不斷從Binder驅動中讀取消息(無消息被阻塞)
- 讀取到消息之後處理消息
- 不斷迴圈,永不退出
SM處理的消息類型有:
- 註冊Binder實體對象的
- 查詢Binder實體對象,以引用編號的形式放回給查詢進程
註冊Binder實體信息到SM的時候,請求數據中需要寫到Binder實體的描述信息,之後進行查詢的時候就是根據描述信息來獲取到對應的Binder應用編號。
到這裡,我們可以看出,其實整個Binder架構就是一個Client,Server,DNS的結構,當然Binder驅動就扮演了一個路由器的角色。
這個結構的前提,就是DNS需要提前註冊。也就是說SM進程需要第一個註冊到Binder驅動中,而且,Client和Server都知道SM的引用編號(0),能夠直接通過SM獲取其他進程提供的Binder引用編號
Binder驅動啟動過程
打開
- 每個需要通過Binder通信的進程都需要打開/dev/binder驅動一次(至多一次)
- 打開Binder驅動之後,內核會調用驅動程式的binder_open方法,該方法內部將會創建binder_proc結構體,記憶體存儲了進程信息以及UID信息。
記憶體映射
- 使用mmap對/dev/binder進行記憶體映射操作
- 在mmap調用之後,內核會會調用驅動程式的binder_mmap方法,該方法內部會為進程創建binder_buffer結構體,也就是為進程創建緩衝區,用於接收數據。並且這塊內和緩衝區對應有兩個虛擬記憶體地址區間,一個是內核的虛擬空間,一個是進程用戶空間的虛擬空間。此塊緩衝區是一個只讀的區域,防止用戶空間對其進行修改。
動作執行者
對於應用程式進程來說,打開驅動和記憶體映射動作由Native類ProcessState完成,該類為單利,在構造方法中進行,先打開,再執行記憶體映射。
Binder與共用記憶體之間的區別
為什麼與共用記憶體進行對比(性能),是因為共用記憶體管是unix中最快的一種IPC機制。
共用記憶體為什麼快,是因為共用記憶體相當於是將兩個進程的虛擬地址空間指向了一塊物理記憶體,兩個進程對該記憶體區域的修改,能夠直接反應到對方進程中,也就是不需要對數據進行拷貝。
前面說到,Binder是通過mmap來實現的,理論上,mmap也可以讓兩個進程映射到同一段物理記憶體區域(文件)上。但是Binder沒有這樣實現,如果這樣的話,和共用記憶體就一樣了。那Binder又是如何實現的呢。
首先,Binder有驅動程式,所有數據傳輸和接收,都是通過Binder驅動來操作的。這就帶來一個問題,Binder驅動是運行在內核態的,那麼數據在使用Binder驅動傳輸時,是需要在內核記憶體空間與用戶記憶體空間進行拷貝操作的。
試想下,A進程與B進程進行通信,A進程給B進程發送數據data,按照上面的分析,數據data需要先從A進程的用戶空間拷貝到Binder驅動的內核空間,再通過Binder驅動寫入到(具體實現後面說)B進程的Binder驅動內核空間,最後從Binder驅動再拷貝的B進程的用戶空間。如此一來,數據進行了兩次拷貝。
其實,Binder驅動內部並不需要兩次數據的拷貝,原因在於Binder將內核記憶體空間與用戶記憶體空間進行了記憶體映射操作,具體如下圖
首先,我們從數據接收進程看,內核與用戶記憶體空間,通過mmap映射到了同一塊物理記憶體上。也就是說對該塊物理記憶體的修改,將會提現到數據接收進程的用戶空間和內核空間。
再看數據發送進程,左邊的數據發送進程,只是將內核的記憶體空間映射到了物理記憶體上。
接著,當數據發送進程需要向數據接收進程傳遞數據時,數據只需要從數據發送進程的用戶記憶體空間拷貝到數據發送進程的內核記憶體空間,此時,因為數據發送進程的內核記憶體空間與物理記憶體進行了映射,而數據接收進程的用戶記憶體空間與內核記憶體空間同時都映射到了同一塊物理記憶體上,所以此次拷貝,直接將數據發送進程的用戶空間數據,拷貝到了數據接收進程的用戶記憶體空間。
通過上面的分析,也就能理解,為什麼說Binder傳輸數據時需要拷貝1次數據,共用記憶體不需要拷貝數據
Binder的實現架構
完成對Binder跨進程通信底層IPC實現分析之後,需要思考,Android如何讓兩個進程建立聯繫(如何找到通信進程),那就需要一個系統進程,所有應用程式都知道它,並能聯繫到它,從這個系統進程那邊,能夠查找到(通過Service名字元串)需要通訊的進程。
最終,Android採用了Client、Server、ServiceManager的實現架構,其中Client需要從ServiceManager中找到Server,然後Client與Server之間即可進行通信
那麼什麼進程能夠在ServiceManager中註冊呢,就是在Android操作系統中註冊過(APP清單文件中的Service)的那部分服務才能註冊,到這,也就能理解Android為什麼採用這種架構模式了,在安全上又進一步約束。
Binder驅動
首先要知道Binder驅動是運行在內核態下,內核態的記憶體是所有進程共用的。
任務一:存儲所有進程的Binder信息(引用編號,Server端的虛擬記憶體地址)
任務二:進程間數據傳遞
Binder是什麼
Binder是什麼,需要從多方面解釋,不同環境中,其代表的是不一樣的東西。
Binder在Server中的表述
Binder在Server中代表的是具體的實現,簡稱Binder實體
Binder在Client中的表述
Binder的具體實現應該是在Server進程,也就是說Client進程是無法拿到該實現對象的地址信息的。
那麼Binder在Client中代表的僅僅是一個引用(驅動給的)編號,Client能夠通過該編號向遠端Server發送數據。
Binder在驅動中的表述
驅動,是Binder架構在最核心的一部分,驅動需要做的事情很多
- 所有Server端的Binder實體,需要在驅動中註冊
- Client端獲取Binder時,需要為Client創建Binder引用,並把引用編號信息記錄在驅動中
- 維護各個Client中的引用於Binder實體之間的映射關係
- 通過引用編號找到對應實體
- 創建Server端的Binder實體
- etc…
Binder實體(Server端)在驅動中的表述
Binder實體需要在驅動中進行註冊,註冊時,驅動需要在內核中為Binder實體創建一個結構體binder_node
該結構體中存儲的主要數據為
Server端Binder實體對象的記憶體地址
Server端Binder實體在所有實體鏈表中的節點結構體
說明:每個Server進程都對應有一個鏈表,用來存儲所有的Binder實體節點,以Binder實體對象的記憶體地址為索引進行查找。
Binder引用(Client端)在驅動中的表述
Binder引用在驅動中以binder_ref結構體的形式存在。改結構體中存儲的主要數據為:
- Binder實體在驅動中的結構體引用
- Binder實體在驅動中的引用號(編號)
- Binder引用在進程鏈表中的節點(以編號以及實體地址為索引的兩個鏈表節點)
說明:每個Client進程都對應有兩個鏈表,一個是以Binder實體在驅動中的結構體地址為索引建立的鏈表,一個是以Binder實體在驅動中的引用號為索引簡歷的鏈表。
Binder在傳輸數據中的表述
雖然Binder實體和Binder引用都在驅動中有不同的結構體來標識,但是Client和Server在於Binder進行通信時,並不是通過傳遞這兩個結構體來代表不同的Binder的,而是通過另一個統一的結構體flat_binder_object來代表本次通信對應的Binder。
既然使用的是同一個結構體,那麼這個結構體中應該有的內容:
- Binder類型(實體,引用)
- Binder實體的記憶體地址(類型為實體時用)
- Binder應用的編號(類型為引用時用)
其中Binder類型有以下幾種:
- BINDER_TYPE_BINDER:表示傳遞的是Binder實體,並且指向該實體的引用都是強類型;
- BINDER_TYPE_WEAK_BINDER:表示傳遞的是Binder實體,並且指向該實體的引用都是弱類型;
- BINDER_TYPE_HANDLE:表示傳遞的是Binder強類型的引用
- BINDER_TYPE_WEAK_HANDLE:表示傳遞的是Binder弱類型的引用
- BINDER_TYPE_FD:表示傳遞的是文件形式的Binder
那麼flat_binder_object里的內容填充方式具體是怎樣的呢,比如Server將Binder傳遞給Client,Server發送的flat_binder_object,類型應該是BINDER_TYPE_BINDER,此時,驅動將會在內核中為Server進程創建對應的binder_node結構,並且將flat_binder_object中的Binder實體的記憶體地址保存起來。接著驅動需要在內核中為Client進程創建一個binder_ref結構,因為Server穿過來的Binder實體的記憶體地址在Client進程是無效的,所以驅動需要為Client進程創建一個Binder對應的引用編號,並將此編號存入binder_ref結構中。同時,需要將flat_binder_object中的類型改成BINDER_TYPE_HANDLE,以及存儲引用編號。
當Client需要使用Server傳遞過來的Binder的時候,向驅動傳遞的數據包中,就需要用到Binder的引用編號,驅動將會對引用編號進行校驗,這樣就能在安全性上得到保障。
Binder表述總結
當一個Server進程創建了一個Binder實體,之後,這個實體在各個環境中的表述情況為
- Server進程中的Binder稱為Binder實體,其應該要繼承BBinder類(Native類)
- 其在Binder驅動中,以binder_node表述
- 當Server進程的Binder服務需要被Client進程所使用時,Binder驅動會創建一個binder_ref結構體,這也就是Server中創建的Binder實體在Client進程中的表述(存儲引用編號)
- 在Client的用戶空間中,需要創建一個Binder代理類,該類繼承BpBinder類,Client進程通過該代理類與Server端的Binder實體進行通信