Android開發之——Binder

前言

最近沉迷於Android源碼中無法自拔,只是苦於內功淺薄,很多東西看得雲裏霧裏。起初對Android圖形系統比較感興趣,一頭扎進去,不到片刻,就再也找不到“線頭”。原因之一在於Android在啓動一個activity到從LCM中顯示出來圖像,使用了大量的Binder通訊,各種IXXX,BpXXX,BnXXX蜂擁而出,各種類派生,看得頭皮發麻。於是將圖形系統放在一邊,轉戰Binder通訊。

概述

Android系統中充滿各種各樣的進程,每個進程肩負着不同的任務。當某個進程處理不了的事情,則可以丟給其他能夠進行處理的進程進行處理,比如AudioTrack本身不具備播放音頻的能力,它就是向上給應用提供播放的接口,向下就讓AudioFlinger播放播放。由於AudioTrack和AudioFlinger處於不同的進程,所以這個過程就發生了跨進程通訊。

跨進程通訊需要採用特殊的通訊機制,也就是IPC(進程間通訊)。Android系統是基於Linux系統的,而Linux系統本身已經提供了共享內存,socket,管道等IPC機制。但Android卻沒有采納這些機制,而是自己開發了一個IPC機制:Binder通訊。Binder通訊性能好,穩定性和安全性高。

所有的IPC都是基於Linux內核實現的,Binder也不例外,Binder驅動就是整個Binder通訊的核心。那麼Binder驅動是如何實現進程間通訊的?看圖說話:

 

image

 

 

進程B作爲服務端,在進程啓動的時候,就會打開一個Binder設備,並調用mmap方法,將用戶空間和內核空間(會將此空間跟Binder驅動節點綁定)都映射到同一塊物理內存上。我們且先不管進程A調用什麼接口跟Binder驅動通訊,最終就是Binder驅動通過copy_from_user函數將A進程想要發給B進程的數據從用戶空間拷貝到了內核中Binder驅動的緩衝區。由於此時緩衝區的與物理內存有映射關係,而進程B的用戶空間也映射到了物理內存同樣的地址,所以此時進程B就能直接讀取到進程A發過來的數據了。這就是一次Binder通訊的大概框架。

如何找到目標binder節點

每個進程的用戶空間自成一體,進程之間想要進行通訊,就只能通過內核空間進行數據共享。從如上概述中,我們已經清楚,內核空間通過映射的方法達到數據共享的目的,但這裏有個前提,就是從用戶空間拷貝到內核間的數據,需要拷貝到服務端進程對應的Binder驅動對應的內核緩衝區,才能讓服務端進程的用戶空間訪問到數據。Binder驅動節點衆多,是如何將數據從用戶空間拷貝到內核空間中對應Binder節點的緩衝區內的?

Binder驅動會維護一張表,這張表由各個Binder節點組成,每當進程打開Binder設備,就會創建一個Binder節點,然後將該節點保存到這張表中。 作爲服務端的進程在啓動的時候,必須要打開Binder設備,創建一個Binder節點,然後往Binder大本營中添加該節點。並進行mmap操作,完成虛擬內存和物理內存的映射。當客戶端通過ioctl方法跟binder驅動通訊時,會封裝此次通訊的數據,目標handle就是數據之一,這個數據就是用來查找目標節點(服務端在binder驅動創建的binder節點)的關鍵。

 

image

 

 

handle的保存者--ServiceManager

既然handle如此重要,那它是從哪來的?答案在servicemanager(下面簡稱SM)中。這傢伙是Android系統服務的管家,保存着所有在SM中註冊的服務的handle。當客戶端想要調用到服務端某個方法時,首先得找到這個遠程服務,方法是通過servicemanager查詢對應服務的handle。然後客戶端再拿着這個handle(handle只是其中一個數據,會和其他的數據一起被封裝)去和binder驅動通訊。所有東西都很完美,但這時有個問題需要被解決,那就是客戶端跟SM的通訊也是跨進程,也就是說客戶端需要先有SM對應的handle,這樣才能進行通訊。

將SM對應的handle固定爲某個值,這樣客戶端默認就知道了SM的handle值。Android也正是這樣處理的,SM的handle值爲0,唯一且永不改變。

那SM上各個系統服務的handle是怎麼來的?總不能是憑空出現的。每個系統服務需要在SM中進行註冊,在註冊的過程中,會由驅動生成一個handle值,該handle保存到系統服務對應的binder節點上,且將handle通過svcinfo(包含服務名)的方式保存到SM的svclist列表中。這樣每當有客戶端向SM查詢服務時,SM就會通過服務名來獲取到相應的handle,再通過此handle去獲取具體的BpBinder(客戶端和服務端不在一個進程)或者BBinder(客戶端和服務端在同一個進程)

 

image

 

 

客戶端調用遠程服務的方法

Android淡化了進程這個概念,我們即使在使用遠程進程的時候,表面上就是獲取某個遠程對象,然後調用該對象的方法,實現我們的功能。但其實本質上是數據的打包發送和接收解析。所謂遠程服務的方法,其實也就是一個個的code編號。客戶端和服務端約定好編號的意義即可。

通過向SM查詢到的服務,其實是一個遠程代理,一般爲Bpxxx。Bpxxx有我們需要的遠程服務的接口,當我們調用了該接口的時候,實際上發生了Binder通訊,將打包好的數據通過IPCThread發送出去(調用ioctl,與Binder驅動通訊)。負責打包的是Parcel。服務端通過Binder驅動拿到客戶端發過來的數據,並進行解析,通過code編號,知道客戶端需要調用到自己的某個方法,於是開始執行該方法,並將結果通過Binder驅動發回給客戶端。

結語

在閱讀Binder的源碼過程中,如果能有意去將Binder業務層和通訊層區分開,那麼可能相對會好理解些。每次閱讀都有不同的理解,也都能夠糾錯以前的一些錯誤認知。

Android系統很龐大,很多東西都是一知半解。最近有個計劃,那就是分模塊來進行學習。學習的方法主要還是以問題爲驅動,然後針對這一塊閱讀不同的博客文章和書籍,再進行內化寫文章。

在這裏我也分享自己收錄整理的Android學習PDF,裏面對Binder有詳細的講解,希望可以幫助大家學習提升進階,也節省大家在網上搜索資料的時間來學習,可以分享給身邊好友一起學習

有需要的朋友可以私信我【666】獲取;

感興趣的可以加下粉絲裙哦:

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